第二章:数据准备与清洗——因子研究的基石
做量化研究这么多年,我越来越觉得一个道理:因子研究,三分靠模型,七分靠数据。你想想看,数据要是脏的,再牛的算法也白搭。这一章,我就把数据准备与清洗的那些坑,一个一个给你讲清楚。
核心观点:数据清洗不是体力活,是决定因子有效性的第一道门槛。跳过这一步,后面全是幻觉。
2.1 数据源选择——选对源头,事半功倍
数据源的选择,说白了就是决定你研究的「原材料」质量。我个人习惯把数据源分成三类:
- 交易所官方数据:最权威,但获取成本高,更新频率可能不够快。
- 金融数据服务商:比如Wind、聚宽、Tushare等,覆盖广,但要注意数据清洗程度。
- 第三方爬虫数据:灵活,但质量参差不齐,需要大量校验。
我在项目中遇到过一个问题:用某家数据商的日频数据做回测,结果发现复权因子算错了,导致回测收益虚高20%。嗯,从那以后,我每次换数据源都会做一次「交叉验证」——拿两个不同来源的数据对比一下,看看关键指标是否一致。
我的建议:至少准备两个独立数据源。一个做主数据,一个做校验。别嫌麻烦,这能救你命。
2.2 缺失值处理——别让「空」骗了你
缺失值,是数据清洗里最常见的坑。很多人上来就删,或者直接填0,这其实很危险。
为什么会这样?因为缺失值背后可能有信息。比如某只股票停牌了,那缺失值代表的是「流动性缺失」,而不是「数据丢了」。
我一般把缺失值分成三类:
| 类型 | 含义 | 处理方法 |
|---|---|---|
| 随机缺失 | 数据偶然丢失,无规律 | 插值法(线性/时间序列插值) |
| 结构性缺失 | 因停牌、退市等原因缺失 | 保留缺失,或标记为特殊值 |
| 完全缺失 | 该股票从未有数据 | 直接剔除该股票 |
举个例子,处理日频收益率数据时,我习惯这样做:
# 伪代码示例:缺失值处理逻辑
if 缺失比例 < 5%:
使用前向填充 + 线性插值
elif 缺失比例 < 20%:
使用行业均值填充
else:
剔除该股票或该时间段
避坑指南:我曾经用「0填充」处理过收益率缺失值,结果回测时发现策略在停牌股上「凭空赚钱」。后来才意识到,停牌期间收益率应该是NaN,而不是0。切记:不要随意填充你不理解的数据。
2.3 异常值识别与处理——别让「极端值」带偏你
异常值,说白了就是那些「离谱」的数据点。比如某只股票一天涨了1000%,这明显是数据错误。
我常用的异常值识别方法有几种:
- 3σ原则:超过均值±3倍标准差的数据,视为异常。适合正态分布的数据。
- IQR方法:用四分位距来识别。更稳健,不受极端值影响。
- MAD方法:中位数绝对偏差。对异常值最不敏感,我比较喜欢用这个。
处理方式上,我一般分两步走:
- 先识别:用MAD方法找出异常点。
- 再处理:不是直接删除,而是「缩尾处理」(Winsorize)——把异常值拉到边界值。
# 缩尾处理示例(Python风格)
def winsorize(series, limits=[0.01, 0.01]):
lower = series.quantile(limits[0])
upper = series.quantile(1 - limits[1])
return series.clip(lower, upper)
个人经验:我一般缩尾到1%和99%分位数。但如果是高频数据,我会缩得更狠一点,比如0.5%和99.5%。因为高频数据里的异常值往往更「离谱」。
2.4 数据标准化——让因子站在同一起跑线
标准化,说白了就是把不同量纲的因子拉到同一个尺度上。你想想看,市盈率可能是几十,换手率可能是百分之几,不标准化怎么比较?
常用的标准化方法:
| 方法 | 公式 | 适用场景 |
|---|---|---|
| Z-score | (x - μ) / σ | 数据近似正态分布 |
| Min-Max | (x - min) / (max - min) | 数据有明确边界 |
| Rank标准化 | 排序后映射到[0,1] | 数据分布未知,或存在异常值 |
我个人最常用的是Rank标准化。为什么?因为它对异常值不敏感。你想想看,如果某只股票的市盈率是1000,用Z-score会把它变成一个巨大的异常值,但用Rank标准化,它只是排最后一名而已。
重要提醒:标准化一定要在「截面」上做,也就是每个时间点单独做。千万别把所有时间的数据混在一起标准化,那会引入未来信息,造成「前视偏差」。
嗯,这里要注意一点:标准化之后,因子值就变成了相对值,不再保留原始量纲。所以你在解释因子含义时,要小心别把标准化后的值当成原始值去理解。
本章知识体系
下面这张图,是我梳理的数据准备与清洗的完整流程。你可以把它当成一个检查清单,每次做数据清洗时对照着来。
这张图里,我特意画了一个循环箭头。为什么?因为数据清洗不是一次性的工作。你做完标准化之后,最好回头看看原始数据分布有没有变化,异常值处理得是否合理。我经常要迭代两三轮,才能得到一份「干净」的数据。
最后一个小技巧:每次清洗完数据,我都会保存一份「清洗日志」。记录每一步做了什么、删了多少数据、填充了什么值。这样万一回测结果不对,还能回溯排查。别问我怎么知道的——都是血泪教训。