第二章:数据准备与预处理——金融数据的“洗菜”功夫
做量化分析,有个老生常谈的道理:垃圾进,垃圾出。你模型再牛,策略再花哨,数据一塌糊涂,结果就是白忙活。我刚开始做量化那会儿,就吃过这个亏——拿了一堆没清洗的数据跑回测,收益曲线漂亮得不行,结果实盘一跑,直接傻眼。后来才发现,是数据里藏着几个异常值,把整个策略都给骗了。
所以,今天咱们就来聊聊金融数据预处理这档子事。说白了,就是怎么把“带泥的菜”洗干净,让它能下锅。
核心要点:金融数据预处理 = 获取 + 清洗 + 填充 + 对齐。一步不到位,后面全白费。
2.1 数据获取:从Yahoo Finance“捞”数据
我个人习惯用 yfinance 这个库,它是对 Yahoo Finance API 的 Python 封装,用起来特别顺手。你想想看,一行代码就能把股票历史数据拉下来,多方便。
import yfinance as yf
import pandas as pd
import numpy as np
# 下载苹果公司股票数据
aapl = yf.download('AAPL', start='2020-01-01', end='2023-12-31')
print(aapl.head())
嗯,这里要注意:yfinance 返回的是 pandas DataFrame,列名包括 Open、High、Low、Close、Volume、Adj Close。其中 Adj Close 是复权收盘价,做回测时我建议用这个,因为它考虑了分红、拆股等因素。
yf.download(['AAPL', 'MSFT', 'GOOGL'], start='...')。返回的 DataFrame 会带有多层索引,处理起来稍微麻烦点,但习惯就好。
2.2 数据清洗:把“脏东西”筛出去
数据拉下来之后,第一件事就是看看有没有脏数据。我在项目中遇到过好几次,明明看着好好的数据,一检查发现全是坑。
常见的脏数据包括:
- 重复行:同一个交易日出现两次
- 零值或负值:价格不可能为0或负数(除非是某些衍生品)
- 跳空或异常波动:比如某天突然涨了1000%,多半是数据错误
# 检查重复行
print(f"重复行数量: {aapl.duplicated().sum()}")
# 删除重复行
aapl = aapl.drop_duplicates()
# 检查零值或负值
print(f"零值数量: {(aapl['Close'] == 0).sum()}")
print(f"负值数量: {(aapl['Close'] < 0).sum()}")
# 过滤掉异常值(比如单日涨跌幅超过20%)
daily_returns = aapl['Close'].pct_change()
aapl = aapl[abs(daily_returns) < 0.2]
duplicated()。
2.3 缺失值处理:别让“空洞”毁了你的模型
金融数据里,缺失值太常见了。节假日、停牌、数据源故障,都会导致某天没有数据。你想想看,如果模型遇到缺失值直接报错,那还怎么玩?
处理缺失值,我一般分三步走:
- 先看看缺失情况:用
isnull().sum()统计每列的缺失数量 - 判断缺失原因:是随机缺失,还是结构性缺失(比如某只股票退市了)
- 选择填充方法:常用的有向前填充、插值法、或者直接用均值/中位数填充
# 查看缺失值
print(aapl.isnull().sum())
# 向前填充(用上一个交易日的数据填充)
aapl.fillna(method='ffill', inplace=True)
# 或者用线性插值
aapl.interpolate(method='linear', inplace=True)
ffill(向前填充)。为什么呢?因为金融数据有很强的连续性,今天的缺失值用昨天的数据来补,比用均值更合理。比如某天停牌,复牌后的价格通常和停牌前有延续性。
2.4 重采样与对齐:让不同频率的数据“步调一致”
做量化分析,经常会遇到不同频率的数据。比如股票是日频数据,宏观经济指标是月频数据,你要把它们放在一起分析,就得先对齐。
重采样,说白了就是把数据从一种频率转换成另一种频率。比如把日频数据转成周频:
# 将日频数据重采样为周频(取每周最后一个交易日)
weekly_data = aapl.resample('W').last()
# 或者取每周的平均值
weekly_mean = aapl.resample('W').mean()
对齐就更简单了,直接用 pd.concat 或者 merge 就行。但要注意,不同数据源的日期格式可能不一样,得先统一成 datetime 类型。
# 假设我们有两只股票的数据
aapl = yf.download('AAPL', start='2020-01-01', end='2023-12-31')
msft = yf.download('MSFT', start='2020-01-01', end='2023-12-31')
# 只保留两者都有的交易日
combined = pd.concat({'AAPL': aapl['Close'], 'MSFT': msft['Close']}, axis=1)
combined.dropna(inplace=True) # 去掉缺失值,实现对齐
fillna 硬填。如果某只股票在某个时间段根本没有数据(比如还没上市),硬填只会引入噪声。我建议直接 dropna,或者用 reindex 指定一个共同的日期范围。
2.5 本章知识体系图
下面这张图,把数据预处理的整个流程串起来了。你可以把它当作一个检查清单,每次做数据预处理时对照着来。
2.6 本章小结
数据预处理,听起来枯燥,但它是整个量化分析的地基。地基不稳,楼盖得再高也得塌。我个人觉得,花在数据清洗上的时间,至少应该占整个项目时间的30%。别嫌多,这钱花得值。
最后送大家一句话:好的数据,是成功策略的一半。 下次咱们聊特征工程,看看怎么从干净的数据里挖出有用的信号。