4. 因子构建与处理:从原始数据到有效信号
好,咱们进入第四章。这一章,说白了就是量化选股的「炼金术」——怎么把一堆原始数据,变成能赚钱的信号。
我个人习惯把因子处理看作「食材准备」。你想想看,再好的厨师,拿到发臭的鱼肉也做不出好菜。因子处理也是一样,原始数据里全是噪音、缺失值、极端值,不处理干净,后面模型再牛也白搭。
4.1 因子定义与分类
先说说因子是什么。因子就是能解释股票收益差异的特征。我刚开始做量化时,以为因子越多越好,堆了上百个因子进去,结果过拟合得一塌糊涂。后来才明白,因子的质量远比数量重要。
常见的因子分类,我一般分成四大类:
| 因子类别 | 典型因子 | 逻辑解释 |
|---|---|---|
| 估值因子 | PE、PB、PS、PCF | 便宜才是硬道理?低估值股票长期有超额收益 |
| 动量因子 | 过去1个月收益、过去12个月收益(剔除最近1个月) | 强者恒强,还是均值回归?不同周期表现不同 |
| 质量因子 | ROE、毛利率、资产负债率、盈利稳定性 | 好公司才能持续赚钱,但好公司不一定有好股价 |
| ESG因子 | E评分、S评分、G评分、ESG综合得分 | 负责任的公司,长期风险更低,但短期可能拖累收益 |
关键认知:没有哪个因子永远有效。估值因子在牛市跑不赢动量,质量因子在熊市更抗跌。ESG因子呢?我做过回测,它在市场下跌时确实能减少回撤,但上涨时弹性不足。
4.2 因子计算与标准化
因子计算这块,我踩过不少坑。举个例子,计算动量因子时,很多人直接用「过去12个月收益率」,但这样会引入「1个月反转效应」的干扰。我建议用12-1个月动量,即剔除最近1个月的数据。
# 计算动量因子(12-1个月动量)
import pandas as pd
import numpy as np
def calc_momentum(price_df, months=12, skip_months=1):
"""
计算动量因子
price_df: 日度价格数据,index为日期,columns为股票代码
"""
# 计算月收益率
monthly_ret = price_df.resample('M').last().pct_change()
# 滚动求和,跳过最近skip_months个月
momentum = monthly_ret.rolling(window=months).sum() - \
monthly_ret.rolling(window=skip_months).sum()
return momentum.shift(1) # 避免未来信息
避坑指南:我曾经在计算因子时忘记shift(1),结果用到了当天的数据来预测当天的收益——这在回测里表现完美,实盘却一塌糊涂。记住:所有因子必须用历史数据计算,不能包含未来信息。
标准化处理,我一般分三步走:
- 去极值:用MAD(中位数绝对偏差)或百分位法,把极端值拉回来。我习惯用3倍MAD。
- 缺失值处理:行业均值填充,或者直接剔除。如果某个因子缺失率超过30%,我建议直接放弃这个因子。
- 标准化:Z-score或Rank标准化。Rank标准化对异常值更鲁棒,我比较常用。
def standardize_factor(factor_series, method='rank'):
"""
因子标准化
method: 'zscore' 或 'rank'
"""
if method == 'zscore':
return (factor_series - factor_series.mean()) / factor_series.std()
elif method == 'rank':
# Rank标准化:将因子值排序后映射到[0,1]
ranks = factor_series.rank()
return (ranks - 1) / (len(ranks) - 1)
4.3 因子相关性分析
因子之间如果高度相关,就会带来多重共线性问题。我记得有一次,我同时用了PE和PB两个估值因子,结果模型训练出来,两个因子的系数一正一负,互相抵消——这就是典型的「因子拥挤」。
我一般会计算因子间的相关系数矩阵,设定一个阈值(比如0.7),超过这个阈值的因子只保留一个。
# 计算因子相关性矩阵
factor_corr = factor_df.corr()
# 找出高度相关的因子对
high_corr_pairs = []
for i in range(len(factor_corr.columns)):
for j in range(i+1, len(factor_corr.columns)):
if abs(factor_corr.iloc[i, j]) > 0.7:
high_corr_pairs.append(
(factor_corr.columns[i], factor_corr.columns[j],
factor_corr.iloc[i, j])
)
注意:相关性高不代表因子无效。比如ESG中的E和S评分,天然就有相关性(好公司通常各方面都好)。这时候可以用正交化处理,把共同部分剥离掉,保留各自独特的信息。
4.4 因子IC/IR分析
IC(信息系数)和IR(信息比率)是衡量因子有效性的核心指标。说白了,IC就是因子值和未来收益的相关系数,IR则是IC的均值除以标准差——衡量因子表现的稳定性。
我一般这样计算:
def calc_ic(factor_series, forward_return, method='spearman'):
"""
计算IC值
factor_series: 因子值
forward_return: 未来N日收益
method: 'spearman'(秩相关)或 'pearson'(线性相关)
"""
if method == 'spearman':
from scipy.stats import spearmanr
ic, p_value = spearmanr(factor_series, forward_return)
else:
ic = factor_series.corr(forward_return)
return ic
def calc_ir(ic_series):
"""
计算IR值
ic_series: 时间序列上的IC值
"""
return ic_series.mean() / ic_series.std()
在实际项目中,我会按月计算IC,然后看IC的均值、标准差、正负比例。一个合格的因子,IC均值应该在0.02以上,IR大于0.5。如果IC经常正负交替,说明这个因子不稳定,需要谨慎使用。
我的经验:ESG因子的IC通常不高,可能在0.01-0.03之间,但它的IR往往不错——因为ESG因子的表现比较稳定,不会大起大落。所以ESG更适合作为「风控因子」而非「进攻因子」。
4.5 本章知识体系
下面这张图,是我梳理的因子构建与处理的核心流程。你可以把它当作操作手册:
嗯,到这里,因子构建与处理的核心内容就讲完了。记住一句话:因子处理不是技术活,而是良心活——你偷的每一个懒,都会在回测结果里加倍还给你。