3、数据准备与清洗:数据源选择、数据对齐、缺失值处理、异常值处理、行业分类标准化

数据准备,说白了就是给因子做「体检」。

我见过太多人,因子逻辑漂亮得不行,回测曲线也好看,结果一上实盘就崩。为什么?数据没洗干净。你想想看,垃圾数据进去,出来的因子能靠谱吗?

这一章,我就把数据清洗的四个核心环节掰开揉碎讲清楚。嗯,都是我在项目里踩过的坑。

3.1 数据源选择:别在源头就输了

我个人习惯,数据源至少要备两个。一个做主库,一个做交叉验证。

国内常用的数据源,我列个表给你看:

数据源 优点 缺点 我的建议
Wind 覆盖全,机构认可度高 贵,接口慢 做主力数据源
聚宽/JQData 便宜,Python友好 历史数据深度不够 做快速验证
Tushare 免费,社区活跃 稳定性一般 做辅助校验
自己爬的 定制化强 维护成本高 慎用,除非你有人力
注意:千万不要只用单一数据源。我曾经在某个项目里,因子收益一直很稳,后来换了个数据源一对比,发现原始行情里有一整年的复权数据算错了。要不是交叉验证,这个因子就废了。

3.2 数据对齐:时间轴必须对齐

数据对齐,是新手最容易忽略的环节。

你想想看,因子数据是日频的,收益率数据也是日频的,但它们的交易日历可能不一样。A股有调休、有停牌,港股有台风休市,美股有节假日。如果不做对齐,因子和收益之间就存在「时间错位」。

我一般这样做:

  1. 统一交易日历:以交易所公布的交易日为准,剔除所有非交易日
  2. 前向填充:因子数据遇到缺失的交易日,用上一个有效值填充
  3. 后向对齐:因子T日的数据,对齐到T+1日的收益(防止未来信息)
核心原则:因子数据的时间戳,必须严格早于收益数据的时间戳。哪怕早1秒都行,但不能晚。

3.3 缺失值处理:别一股脑全删了

缺失值怎么处理?很多人上来就dropna(),这其实很危险。

为什么?因为缺失值本身可能包含信息。比如某只股票突然停牌了,它的因子数据缺失,这恰恰说明这只股票处于「异常状态」。你把它删了,反而会引入幸存者偏差。

我的处理策略分三步:

  1. 先分类:看缺失是随机的,还是系统性的
  2. 再处理
    • 随机缺失(比如某天数据没传上来):用行业均值填充
    • 系统性缺失(比如新股上市前没有数据):直接剔除该时间段
    • 停牌导致的缺失:保留NaN,单独作为一个类别
  3. 最后检查:填充后的数据分布是否发生明显偏移
# 我常用的缺失值处理代码
import pandas as pd
import numpy as np

def handle_missing(df, factor_col, group_col='industry'):
    """
    df: 包含因子值和行业分类的DataFrame
    factor_col: 因子列名
    group_col: 行业列名
    """
    # 1. 计算行业均值
    industry_mean = df.groupby(group_col)[factor_col].transform('mean')
    
    # 2. 用行业均值填充
    df[factor_col] = df[factor_col].fillna(industry_mean)
    
    # 3. 如果填充后还有缺失(比如整个行业都缺失),用全局均值
    global_mean = df[factor_col].mean()
    df[factor_col] = df[factor_col].fillna(global_mean)
    
    return df
小技巧:填充后,记得画一下分布图。如果填充前后的分布差异很大,说明你的填充策略有问题。

3.4 异常值处理:别把金子当垃圾扔了

异常值处理,是最考验经验的地方。

我记得有一次,一个因子在某个行业里表现特别好,我以为是发现了新规律。后来一查,原来是那个行业里有一家公司的数据录入错了,多了一个零。嗯,这种异常值必须处理。

但有些异常值,其实是真正的alpha信号。比如某只股票突然放量暴跌,它的因子值可能偏离正常范围,但这恰恰是交易机会。

我的处理原则:

  • 先识别,再判断:用3-sigma或IQR方法找出异常值
  • 不要直接删除:先看看这些异常值对应的股票,是不是有合理的解释
  • 缩尾处理(Winsorize):把极端值拉到某个分位数上,而不是直接删掉
# 缩尾处理示例
def winsorize_series(s, lower=0.01, upper=0.99):
    """
    对序列进行缩尾处理
    """
    q_low = s.quantile(lower)
    q_high = s.quantile(upper)
    return s.clip(lower=q_low, upper=q_high)
避坑指南:我曾经在某个多因子模型里,对所有因子都做了3-sigma截断。结果发现,有个因子在极端行情下的预测能力特别强,但被我硬生生截掉了。从那以后,我都是先分组看异常值的表现,再决定怎么处理。

3.5 行业分类标准化:统一口径才能比较

行业分类,看着简单,其实坑最多。

国内有申万行业、中信行业、证监会行业,国外有GICS、ICB。不同分类标准,同一只股票可能被分到不同行业。你想想看,如果因子在行业内部做中性化处理,行业分类不一致,结果能对吗?

我的做法:

  1. 统一到一套标准:我个人习惯用申万一级行业,因为覆盖面广,更新及时
  2. 建立映射表:把其他分类标准映射到申万行业上
  3. 处理综合类公司:有些公司业务多元,会被分到「综合」行业。这类股票我一般单独处理,不参与行业中性化
重要:行业分类不是一成不变的。每年都会有公司被重新分类。如果你做的是长期回测,一定要用「当时」的行业分类,而不是现在的。否则就引入了未来信息。

3.6 数据清洗的整体流程

说了这么多,我画个流程图,把整个数据清洗的逻辑串起来:

数据清洗完整流程 数据源选择 数据对齐 缺失值处理 异常值处理 行业分类标准化 干净可用的因子数据 多源交叉验证 统一交易日历 行业均值填充 缩尾处理 统一到申万行业

这个流程,我建议你每次做因子研究之前都走一遍。别嫌麻烦,数据清洗花的时间,会在后面的建模和回测阶段十倍百倍地省回来。

最后说一句:数据清洗没有标准答案。每个因子、每个市场、每个时间周期,都可能需要不同的处理方式。关键是要理解每一步在做什么,为什么要这么做。而不是机械地复制代码。

公众号:蓝海资料掘金营,微信deep3321