4. 因子数据获取与清洗:数据源选择、缺失值处理、异常值处理、标准化与中性化
各位同学,咱们今天聊点实在的。
因子数据,说白了就是量化模型的「原材料」。原材料不行,再牛的模型也是白搭。我在早期做策略的时候,就吃过这个亏——数据没洗干净,回测曲线漂亮得不行,一上实盘就崩。嗯,从那以后,我对数据清洗这件事,再也不敢马虎。
4.1 数据源选择:你的「米」从哪来?
我个人习惯,把数据源分成三类:
- 免费公开数据:比如Tushare、AKShare。适合个人研究、快速验证想法。但要注意,免费数据偶尔会有延迟或缺失。
- 商业金融终端:Wind、Choice、Bloomberg。机构标配,数据质量高,字段全。缺点是贵,一年几十万起步。
- 自建数据库:从交易所原始数据开始,自己清洗入库。精度最高,但维护成本也最高。
我的建议:初期用免费数据跑通流程,策略稳定后再切换到商业数据。别一上来就砸钱买Wind,万一策略不行,钱就白花了。
你可能会问:「那我该选哪个?」
其实没有标准答案。我见过有人用AKShare跑出年化30%的策略,也见过有人用Wind数据照样亏钱。关键不是数据源多「高级」,而是你是否了解它的局限性。
4.2 缺失值处理:那些「消失」的数据
现实中的数据,很少是完整的。停牌、退市、节假日、数据推送失败……都会造成缺失。
我在项目中遇到过最头疼的情况:某只小盘股因为重大资产重组停牌了半年,复牌后直接一字板涨停。如果你简单地把缺失值填充为前一天的收盘价,那你的因子值会完全失真。
常见的处理方法有这几种:
| 方法 | 适用场景 | 注意事项 |
|---|---|---|
| 删除缺失行 | 缺失比例很小(<5%) | 会损失样本量,小心幸存者偏差 |
| 前向填充 | 时间序列数据,如价格 | 停牌期间用此方法,复牌后要修正 |
| 均值/中位数填充 | 截面数据,如财务指标 | 会压缩方差,影响因子区分度 |
| 行业均值填充 | 有行业属性的因子 | 比全局均值更合理,我常用这个 |
| 模型预测填充 | 缺失模式复杂时 | 计算量大,容易过拟合 |
我的小技巧:对于财务类因子(如PE、ROE),我习惯用「行业+市值」分组后的中位数填充。这样既保留了行业特性,又考虑了公司规模的影响。效果比单纯用行业均值好不少。
4.3 异常值处理:那些「离谱」的数字
什么叫异常值?举个例子:某只股票的市盈率突然变成10000倍,或者日收益率出现±50%的极端值。这些数据,十有八九是错的。
我曾经在回测中发现一个「神奇」的因子——「前一日收盘价与当日开盘价的差值」。回测夏普比率高达5.0,我当时兴奋得不行。后来一查,原来是某天数据推送错误,把开盘价写成了前收盘价的10倍。嗯,白高兴一场。
处理异常值,我一般分三步走:
- 识别:用3σ原则或IQR(四分位距)法。我个人更偏好IQR,因为它不受极端值本身的影响。
- 处理:要么截断(winsorize),要么删除。我通常用1%和99%分位数做截断。
- 验证:处理完后,看一眼分布图。如果还是「奇形怪状」,说明数据本身可能有问题。
注意:千万不要在不知道数据含义的情况下,机械地删除异常值。比如「涨停板」的股票,收益率就是10%,这不是异常,这是市场规则。我曾经见过有人把涨停数据当异常删了,结果因子信号全乱了。
4.4 标准化:让因子「站在同一起跑线」
不同因子的量纲差异很大。比如市值因子,数值动辄几十亿;而换手率因子,可能只有个位数。如果不做标准化,模型会天然「偏爱」数值大的因子。
常用的标准化方法:
- Z-score标准化:(x - μ) / σ。假设数据服从正态分布。我最常用这个。
- Min-Max标准化:(x - min) / (max - min)。把数据映射到[0,1]区间。适合有明确上下界的因子。
- 排序标准化:把数值转为排名,再映射到[-1,1]或[0,1]。对异常值不敏感,但会丢失数值间的距离信息。
我个人习惯:对于截面因子(比如同一时刻所有股票的PE),用Z-score;对于时间序列因子(比如某只股票过去60天的波动率),用排序标准化。
4.5 中性化:剔除「噪音」干扰
中性化,说白了就是「把因子中你不想要的部分去掉」。最常见的是市值中性化和行业中性化。
举个例子:你发现「小市值因子」能赚钱。但问题是,小市值股票往往也是「高波动」的。你怎么知道赚钱的是「小市值」还是「高波动」?这时候就需要中性化——把市值因子中与波动率相关的部分剔除掉。
具体做法:
- 用因子值对市值(或行业虚拟变量)做回归。
- 取回归的残差作为「中性化后的因子」。
代码实现很简单:
import statsmodels.api as sm
# 假设 factor 是原始因子,market_cap 是市值
X = sm.add_constant(market_cap)
model = sm.OLS(factor, X).fit()
residual = model.resid # 这就是中性化后的因子
我的经验:中性化不是「做得越多越好」。过度中性化会抹掉因子的有效信息。我一般只做行业中性化+市值中性化,最多再加一个波动率中性化。再多,因子就「死」了。
4.6 本章知识体系总览
下面这张图,是我自己梳理的因子数据清洗流程。你可以把它当作一个「检查清单」:
这张图,你每次做因子清洗的时候都可以拿出来对照一下。我自己就把它贴在工位上,省得漏掉哪一步。
好了,关于因子数据获取与清洗,今天就聊到这儿。记住一句话:数据清洗花的时间,永远值得。你省掉的每一步,都会在实盘的时候加倍还给你。
公众号:蓝海资料掘金营,微信deep3321