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 数据清洗流程
数据清洗是因子准备的核心环节,主要包含以下步骤:
- 格式统一:将日期、代码、价格等字段转换为标准格式(如日期统一为YYYY-MM-DD,股票代码统一为6位数字+后缀)。
- 重复值处理:按(日期, 股票代码)去重,保留最后一条记录或取均值。
- 逻辑校验:剔除开盘价、最高价、最低价、收盘价不满足
最高价 ≥ 最低价且收盘价 ≥ 0的记录。 - 停牌/退市标记:对连续停牌超过阈值(如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 × 标准差的样本,适用于近似正态分布的因子。
下图展示了异常值截断的完整处理流程:
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%)
• 截断前后因子分布对比(均值、标准差、偏度、峰度)
• 截面覆盖股票数量(确保每个日期有足够样本进行分层)
• 缺失值比例(应接近0%)
• 截断前后因子分布对比(均值、标准差、偏度、峰度)
• 截面覆盖股票数量(确保每个日期有足够样本进行分层)
经过上述处理,因子数据将具备进入分层回放验证的基本条件。下一阶段将基于清洗后的因子值进行阈值分层与回放验证。