3、因子构建与数据清洗:如何从原始数据中构建有效因子,处理缺失值与极端值
好,咱们进入正题。前面聊了因子模型的理论框架,现在该动手了。说实话,因子构建和数据清洗这块,是量化研究里最「脏」的活。我见过太多人,模型理论讲得头头是道,一到处理数据就翻车。嗯,今天咱们就把这块硬骨头啃下来。
3.1 从原始数据到候选因子:第一步怎么走?
原始数据长什么样?说白了就是一张大宽表:股票代码、时间、开盘价、收盘价、成交量、财务指标……一堆字段堆在那。你要做的,是从里面提炼出有预测能力的信息。
我个人习惯把因子分成三类:
- 价量因子:基于价格和成交量计算,比如动量、反转、波动率
- 基本面因子:基于财务报表,比如市盈率、市净率、ROE
- 另类因子:比如舆情、供应链数据、卫星图像(这个门槛高一些)
举个例子,你想构建一个「过去20日动量因子」。代码怎么写?
import pandas as pd
import numpy as np
# 假设df是日度数据,包含'stock_id', 'date', 'close'
def calc_momentum(df, window=20):
df = df.sort_values(['stock_id', 'date'])
df['momentum'] = df.groupby('stock_id')['close'].pct_change(window)
return df
# 调用
df = calc_momentum(df, 20)
你看,核心逻辑就一行:用过去20天的收益率作为因子值。但这里有个坑——未来信息泄露。我在项目中遇到过,有人用当天的收盘价和当天的因子值做回归,结果回测曲线漂亮得不像话,实盘直接崩。为什么?因为因子值里包含了未来信息。所以一定要用shift(1)把数据对齐到前一天。
3.2 缺失值处理:不是简单删掉就完事
原始数据里缺失值太常见了。停牌、新股上市、财务数据未更新……都会导致缺失。怎么处理?
我的经验是分情况讨论:
| 缺失类型 | 推荐处理方式 | 适用场景 |
|---|---|---|
| 随机缺失 | 用截面均值/中位数填充 | 因子值分布较稳定时 |
| 连续缺失 | 向前填充(ffill) | 停牌导致的价格缺失 |
| 大量缺失(>50%) | 直接剔除该因子 | 因子本身数据质量差 |
| 新股/退市 | 设置最小上市天数门槛 | 避免次新股噪音 |
你想想看,如果一只股票停牌了5天,它的价格数据是缺失的。这时候用均值填充合理吗?不合理。因为停牌期间价格理论上不变,应该用停牌前的价格填充。所以向前填充(ffill)才是正解。
# 向前填充缺失值
df['close'] = df.groupby('stock_id')['close'].ffill()
# 如果还是缺失,用截面中位数填充
df['factor'] = df.groupby('date')['factor'].transform(
lambda x: x.fillna(x.median())
)
3.3 极端值处理:别让一颗老鼠屎坏了一锅粥
极端值,说白了就是那些离谱的数据。比如某只股票一天涨了100倍(别笑,A股历史上真有过),或者市盈率突然变成负数。这些值会严重扭曲你的因子模型。
处理极端值,我常用的方法有三种:
- MAD法(中位数绝对偏差):稳健性最好,适合大多数场景
- 百分位截断法:简单粗暴,直接砍掉上下1%或5%
- Winsorize(缩尾处理):把极端值拉到边界值,不删除样本
我个人最推荐MAD法。为什么?因为它不受极端值本身的影响。你看均值,一个极端值就能把均值拉偏,但中位数不会。
def winsorize_mad(series, n=5):
"""
基于MAD的缩尾处理
n: 中位数上下n倍MAD
"""
median = series.median()
mad = (series - median).abs().median()
upper = median + n * mad
lower = median - n * mad
return series.clip(lower, upper)
# 应用
df['factor_winsor'] = df.groupby('date')['factor'].transform(winsorize_mad)
3.4 知识体系总览
说了这么多,咱们用一张图把整个流程串起来。从原始数据到最终可用的因子,中间经历了什么?
你看,从原始数据到最终可用的因子,中间要过好几道关卡。每一步处理不当,都会影响最终模型的表现。我刚开始做量化的时候,总觉得数据清洗是浪费时间,恨不得直接拿原始数据跑模型。结果呢?回测看起来不错,实盘一塌糊涂。后来才明白,数据质量决定了模型的上限。
1. 时间对齐,避免未来信息
2. 截面处理,保持分布一致性
3. 稳健优先,别让极端值牵着鼻子走
嗯,这一章的内容就到这。数据清洗这块,说白了就是「慢工出细活」。你花在数据上的时间,最终都会在模型表现上体现出来。