第三章:数据获取与清洗——数据源选择、字段定义与预处理
做量化回测,第一步就是搞数据。这事儿听起来简单,但坑特别多。
我见过不少新手,策略写得漂漂亮亮,一跑回测发现结果不对。查来查去,最后发现是数据源的问题。嗯,数据质量直接决定你的回测是否可信。今天我们就来聊聊数据获取与清洗的那些事儿。
3.1 数据源选择:Tushare / Wind / 聚宽
选数据源,说白了就是选「食材」。食材不好,再好的厨艺也白搭。
Tushare:我个人比较喜欢它。免费版够用,Pro版数据更全。适合个人研究和小团队。缺点嘛,免费版有调用频率限制,我曾在项目中因为没注意限流,被ban了半小时。
Wind:机构标配。数据全、更新快、接口稳定。但价格不便宜,一年几万块。如果你在机构工作,直接用Wind就好。个人用户嘛,性价比不高。
聚宽:平台自带数据,省心。适合在聚宽平台上直接做研究。但如果你想把数据拉下来本地跑,就得走API,稍微麻烦点。
我的建议:个人研究用Tushare Pro,机构用Wind。聚宽适合不想折腾数据的人。
3.2 数据字段定义
拿到数据后,第一件事就是搞清楚每个字段的含义。别小看这一步,我见过有人把「复权因子」当「收盘价」用,回测结果直接翻车。
常用的字段有这些:
| 字段名 | 含义 | 注意事项 |
|---|---|---|
| trade_date | 交易日期 | 注意格式统一,别混用 |
| open | 开盘价 | 未复权,需自行处理 |
| high | 最高价 | 同上 |
| low | 最低价 | 同上 |
| close | 收盘价 | 回测中最常用的价格 |
| volume | 成交量 | 注意单位(股还是手) |
| adj_factor | 复权因子 | 用于计算复权价格 |
你想想看,如果字段定义不清晰,后面所有计算都是错的。所以拿到数据后,先打印几行看看,确认字段名和含义。
3.3 缺失值处理
数据里总会有缺失值。停牌、节假日、数据源本身的问题,都可能导致某天没数据。
常见的处理方法:
- 向前填充:用上一个交易日的数据填充。适合价格类数据。
- 向后填充:用下一个交易日的数据填充。适合某些指标。
- 直接删除:如果缺失值不多,直接删掉那行。
- 插值法:用前后数据做线性插值。适合连续型数据。
小技巧:我个人习惯先看缺失比例。如果某只股票缺失超过20%,直接放弃。别在垃圾数据上浪费时间。
代码示例:
import pandas as pd
# 假设df是DataFrame,包含股票数据
# 向前填充缺失值
df['close'].fillna(method='ffill', inplace=True)
# 如果还是缺失,用向后填充
df['close'].fillna(method='bfill', inplace=True)
# 检查是否还有缺失
print(df.isnull().sum())
3.4 复权处理
复权是个大坑。我刚开始做回测时,直接用原始价格算收益率,结果发现分红除权后,价格突然跳空,收益率算出来完全不对。
为什么会这样?因为股票分红、送股后,价格会调整。如果不复权,你的回测会看到「假跌」或「假涨」。
复权有两种方式:
- 前复权:调整历史价格,让价格连续。适合看历史走势。
- 后复权:调整当前价格,适合计算真实收益率。
我个人习惯用前复权。因为回测时,我们关心的是「如果我在过去买入,现在卖出能赚多少」。前复权能更直观地反映价格变化。
注意:不同数据源的复权方式可能不同。Tushare的复权因子是乘数,Wind的复权因子是除数。千万别搞混。
代码示例:
# 假设df包含close和adj_factor字段
# 前复权价格 = 原始价格 * 复权因子
df['adj_close'] = df['close'] * df['adj_factor']
# 计算收益率
df['return'] = df['adj_close'].pct_change()
3.5 异常值检测
数据里总会有「脏数据」。比如某天价格突然涨了100倍,或者成交量突然为0。这些异常值会严重干扰你的回测。
常用的检测方法:
- 3σ原则:超过均值±3倍标准差的数据,视为异常。
- IQR方法:四分位距法,适合非正态分布的数据。
- 业务规则:比如涨跌幅超过20%,或者成交量突然为0。
我曾经在项目中遇到过一个奇葩情况:某只股票某天的收盘价是前一天收盘价的10倍。查了半天,发现是数据源录入错误。嗯,这种异常值如果不处理,回测结果会非常离谱。
代码示例:
# 使用3σ原则检测异常值
mean = df['return'].mean()
std = df['return'].std()
df['is_outlier'] = (df['return'] > mean + 3*std) | (df['return'] < mean - 3*std)
# 删除异常值
df_clean = df[~df['is_outlier']]
3.6 知识体系图
下面这张图展示了本章的核心逻辑:
3.7 总结
数据获取与清洗,说白了就是「把食材洗干净、切好」。这一步做扎实了,后面的回测才能放心跑。
我个人习惯是:先选好数据源,然后定义字段,接着处理缺失值和复权,最后检测异常值。每一步都写个函数,方便复用。
记住一句话:垃圾数据进,垃圾结果出。别偷懒,数据清洗值得你花时间。