3. 因子数据准备:数据源选择、数据清洗、缺失值处理、异常值截断

因子数据的质量直接决定分层回放验证的可靠性。本节将系统讲解从原始数据到可用因子矩阵的完整处理流程,涵盖数据源选型、清洗规则、缺失值填充策略以及异常值截断方法。

3.1 数据源选择与评估

选择数据源时需综合考虑覆盖范围、频率、历史长度与成本。下表对比了主流数据源的核心特征:

数据源类型 典型提供商 覆盖市场 频率 历史长度 成本
交易所直连 NYSE/NASDAQ/SSE 单市场 Tick/秒级 近3-5年 极高
金融数据终端 Wind/Bloomberg 全球多市场 日/分钟级 10-20年 高
量化数据平台 QuantConnect/JoinQuant 美股/A股 日/分钟级 5-15年 中
开源数据集 Yahoo Finance/Quandl 主要市场 日级 5-20年 低/免费
选型建议:因子回放验证对数据一致性要求极高,优先选择同一数据源完成全量历史数据获取,避免跨源拼接带来的口径偏差。对于A股因子研究,推荐使用Wind或聚宽数据;对于美股因子,可使用QuantConnect或CRSP数据库。

3.2 数据清洗流程

数据清洗是因子准备的核心环节,主要包含以下步骤:

  1. 格式统一:将日期、代码、价格等字段转换为标准格式(如日期统一为YYYY-MM-DD,股票代码统一为6位数字+后缀)。
  2. 重复值处理:按(日期, 股票代码)去重,保留最后一条记录或取均值。
  3. 逻辑校验:剔除开盘价、最高价、最低价、收盘价不满足最高价 ≥ 最低价且收盘价 ≥ 0的记录。
  4. 停牌/退市标记:对连续停牌超过阈值(如20个交易日)的股票,在回放期间标记为不可交易。
清洗规则示例(Python伪代码):
# 格式统一
df['date'] = pd.to_datetime(df['date'])
df['code'] = df['code'].astype(str).str.zfill(6)

# 重复值处理
df = df.drop_duplicates(subset=['date', 'code'], keep='last')

# 逻辑校验
df = df[(df['high'] >= df['low']) & (df['close'] > 0)]

# 停牌标记
df['suspended'] = df.groupby('code')['volume'].transform(
    lambda x: (x == 0).rolling(20).sum() >= 20
)

3.3 缺失值处理策略

因子数据中的缺失值需根据缺失比例和业务含义选择不同策略:

缺失比例 推荐策略 适用场景
< 5% 截面均值/中位数填充 流动性因子、波动率因子
5% - 20% 行业均值填充或时间序列前向填充 估值因子、成长因子
20% - 50% 模型预测填充(如KNN、线性回归) 复杂衍生因子
> 50% 剔除该因子或仅保留有效样本 数据质量过低的因子
注意:前向填充(forward fill)在因子回放中需谨慎使用,避免引入未来信息。正确的做法是使用截至当前截面可用的历史数据填充,而非使用未来数据。

3.4 异常值截断(Winsorization)

异常值会严重扭曲分层回放的结果,必须进行截断处理。常用的方法包括:

  • 百分位截断:将因子值限制在[1%, 99%]或[0.5%, 99.5%]区间内,超出部分替换为边界值。
  • 中位数绝对偏差(MAD)截断:以中位数为中心,剔除超过中位数 ± 5 × MAD的样本。
  • 标准差截断:剔除超过均值 ± 3 × 标准差的样本,适用于近似正态分布的因子。

下图展示了异常值截断的完整处理流程:

异常值截断处理流程 原始因子数据 选择截断方法 百分位截断 [1%, 99%] 边界替换 MAD截断 中位数 ± 5×MAD 标准差截断 均值 ± 3×标准差 截断后因子数据 截断后需检查因子分布形态,确保极端值已被有效抑制且样本量损失在可接受范围内

3.5 完整数据准备流程

将上述步骤整合为可执行的流水线:

def prepare_factor_data(raw_df, factor_col, method='percentile', limits=(0.01, 0.99)):
    """
    因子数据准备完整流程
    :param raw_df: 原始DataFrame,包含date, code, factor_col
    :param factor_col: 因子列名
    :param method: 截断方法 ('percentile', 'mad', 'std')
    :param limits: 百分位截断上下限
    :return: 清洗后的DataFrame
    """
    # 1. 格式统一
    df = raw_df.copy()
    df['date'] = pd.to_datetime(df['date'])
    
    # 2. 去重
    df = df.drop_duplicates(subset=['date', 'code'])
    
    # 3. 逻辑校验
    df = df[df[factor_col].notna()]
    
    # 4. 缺失值填充(截面中位数)
    df[factor_col] = df.groupby('date')[factor_col].transform(
        lambda x: x.fillna(x.median())
    )
    
    # 5. 异常值截断
    if method == 'percentile':
        lower = df[factor_col].quantile(limits[0])
        upper = df[factor_col].quantile(limits[1])
        df[factor_col] = df[factor_col].clip(lower, upper)
    elif method == 'mad':
        median = df[factor_col].median()
        mad = (df[factor_col] - median).abs().median()
        df[factor_col] = df[factor_col].clip(median - 5*mad, median + 5*mad)
    elif method == 'std':
        mean = df[factor_col].mean()
        std = df[factor_col].std()
        df[factor_col] = df[factor_col].clip(mean - 3*std, mean + 3*std)
    
    return df
关键检查点:数据准备完成后,建议对每个因子输出以下诊断信息:
• 缺失值比例(应接近0%)
• 截断前后因子分布对比(均值、标准差、偏度、峰度)
• 截面覆盖股票数量(确保每个日期有足够样本进行分层)

经过上述处理,因子数据将具备进入分层回放验证的基本条件。下一阶段将基于清洗后的因子值进行阈值分层与回放验证。