4、因子数据处理:数据对齐与重采样、缺失值处理、去极值与标准化、中性化处理

各位好,我是老K。今天咱们聊聊因子数据处理。说实话,这步才是量化策略的「分水岭」。你因子算得再漂亮,数据没处理好,回测结果就是一堆垃圾。我在Qlib项目里踩过不少坑,今天把这些经验掰开揉碎讲给你听。

4.1 数据对齐与重采样:别让时间戳骗了你

先问个问题:你拿到日频数据,是不是直接拿来就用?

我刚开始做因子时也这样。结果回测收益曲线漂亮得不行,实盘一跑就崩。后来发现,问题出在数据对齐上。

举个例子。你算了个「昨日收盘价/20日均线」因子。但你的价格数据是16:00收盘,而你的因子计算脚本是17:00跑的。中间这1小时,数据源可能已经更新了。你算出来的因子,其实包含了未来信息。

数据对齐的核心原则:因子值必须基于「已知信息」,不能偷看未来。

在Qlib里,数据对齐靠的是D(日频)和M(分钟频)这些频率标识。我个人习惯用D做日频因子,然后用.resample()做重采样。

重采样场景:把5分钟数据重采样成日数据。注意,不是简单取平均。我一般用「最后一条数据」作为日频值,因为那才是收盘时刻的真实状态。

# Qlib风格的数据对齐示例
import qlib
from qlib.data import D

# 加载日频数据
daily_data = D.features(["close", "volume"], ["SH600519"], start_time="2020-01-01", end_time="2020-12-31")

# 重采样:从分钟到日
# 注意:这里用last()而不是mean()
daily_resampled = daily_data.resample("1D").last()

我曾经踩过的坑:用mean()做重采样,结果把开盘价、盘中价、收盘价混在一起。因子信号完全失真。记住:金融数据重采样,默认用last()ohlc(),别偷懒。

4.2 缺失值处理:不是填个0就完事了

缺失值处理,说白了就是「数据里有个洞,你怎么补」。但补法不对,因子就废了。

我见过最粗暴的做法:df.fillna(0)。嗯,填0。你想想看,一个市盈率因子,缺失值填0,那这只股票瞬间变成「估值极低」的香饽饽。回测里它被疯狂买入,实盘里你根本买不到——因为数据源根本没这只股票。

正确的处理思路

  • 时间序列缺失:用前向填充(ffill)或插值。比如某天停牌,用前一天数据。
  • 截面缺失:用行业均值填充。比如某只股票没算出来ROE,用同行业其他股票的均值代替。
  • 大量缺失:直接删除。如果某只股票有80%的数据都缺失,别补了,删掉它。
# 缺失值处理示例
import pandas as pd

# 前向填充:适合时间序列
df["factor"] = df["factor"].ffill()

# 行业均值填充:适合截面数据
df["factor"] = df.groupby("industry")["factor"].transform(lambda x: x.fillna(x.mean()))

# 删除缺失过多的股票
df = df.dropna(thresh=0.8 * len(df.columns))

我的小技巧:处理缺失值时,先看看缺失率。如果某只股票缺失率超过50%,我直接扔掉。别心疼数据,垃圾进垃圾出。

4.3 去极值与标准化:让因子站在同一起跑线

去极值和标准化,这俩经常一起做。为什么?

你想想看,一个市盈率因子,茅台可能50倍,某ST股可能-100倍。这俩放在一起,ST股直接把均值拉偏了。去极值就是把这些「离谱值」拉回来。

去极值的方法

  • MAD法:中位数绝对偏差。稳健,不受极端值影响。
  • 百分位法:比如把1%和99%以外的值截断。
  • 3σ法:超过3倍标准差的值拉回边界。

我个人偏爱MAD法。为什么?因为它对异常值不敏感。3σ法容易被一个极端值带偏。

# MAD法去极值
import numpy as np

def mad_winsorize(series, n=5):
    median = series.median()
    mad = np.abs(series - median).median()
    upper = median + n * mad
    lower = median - n * mad
    return series.clip(lower, upper)

# 标准化:Z-score
def standardize(series):
    return (series - series.mean()) / series.std()

标准化后的因子:均值为0,标准差为1。这样不同量纲的因子才能放在一起比较。比如市盈率和市净率,一个几十倍,一个几倍,不标准化没法合并。

4.4 中性化处理:剔除你不想要的「偏见」

中性化,说白了就是「把因子里的杂质去掉」。最常见的杂质是行业和市值。

举个例子。你算了个「动量因子」,发现银行股普遍动量强。但这不是因为银行股本身动量好,而是因为银行股盘子大、波动小。你真正想捕捉的是「个股自身的动量」,而不是「行业带来的动量」。

中性化的做法:用回归把行业和市值的影响剔除。

# 行业市值中性化示例
import statsmodels.api as sm

def neutralize(factor, industry_dummies, market_cap):
    # 构建回归模型
    X = pd.concat([industry_dummies, np.log(market_cap)], axis=1)
    X = sm.add_constant(X)
    y = factor
    
    # 回归,取残差
    model = sm.OLS(y, X).fit()
    residual = model.resid
    
    return residual

我曾经犯过的错:中性化时忘了取对数市值。结果市值大的股票残差依然很大,中性化等于没做。记住:市值分布是偏态的,取对数后才接近正态。

4.5 本章知识体系

下面这张图,是我自己总结的因子数据处理流程。你照着这个顺序做,基本不会出错。

因子数据处理流程 原始因子数据 数据对齐与重采样 缺失值处理 去极值处理 标准化 中性化处理 关键要点 • 对齐:用last()重采样 • 缺失:ffill或行业均值 • 去极值:推荐MAD法 • 标准化:Z-score • 中性化:回归取残差 • 顺序不能乱! • 先对齐,再补缺失 • 再去极值,再标准化 • 最后中性化

嗯,这套流程我用了好几年。每次做因子研究,都是这个顺序。你照着来,至少能保证数据层面不出大问题。

最后提醒一句:因子数据处理不是一次性工作。每次回测前,都要重新做一遍。别想着「上次处理过了,这次直接用」。市场在变,数据在变,你的处理逻辑也得跟着变。


公众号:蓝海数据掘金营,微信deep3321