4、因子构建方法论:从原始数据到因子值的标准化流程
做量化投资这些年,我最大的体会就是:因子构建的成败,往往不在选什么因子,而在数据处理。你想想看,原始数据就像刚从矿里挖出来的矿石,杂质多、分布乱、量纲不统一。直接拿去做回测,结果基本是废的。
这一章,我就带你走一遍因子构建的标准化流程。说白了就三步:去极值 → 标准化 → 中性化。每一步都有坑,我踩过不少,今天一并告诉你。
4.1 去极值:别让异常值毁了你的因子
先说说去极值。为什么非做不可?
我举个例子你就明白了。假设你在算市盈率因子,大部分股票PE在10到40之间。突然来了一只亏损股,PE是-5000。如果你不做处理,这只股票会把整个因子的均值和标准差拉到离谱的位置。后面做标准化、做回归,全都会变形。
我个人习惯用两种方法:
方法一:MAD法(中位数绝对偏差)
这个方法比传统的3σ法稳健得多。为什么?因为均值本身会被极端值影响,而中位数不会。
def winsorize_mad(series, n=5):
"""
基于MAD的去极值
n: 阈值倍数,我一般取5
"""
median = series.median()
mad = (series - median).abs().median()
upper = median + n * mad
lower = median - n * mad
return series.clip(lower, upper)
方法二:百分位法
这个方法更直观。直接切掉头尾1%或5%的数据。
def winsorize_percentile(series, lower=0.01, upper=0.99):
"""
百分位去极值
"""
q_low = series.quantile(lower)
q_high = series.quantile(upper)
return series.clip(q_low, q_high)
4.2 标准化:让因子站在同一起跑线上
去完极值,接下来就是标准化。这一步的目的是让不同量纲的因子可以互相比较。
你想想看,市值因子的数值是几十亿到几千亿,换手率因子是0.1%到10%。如果不标准化,你根本没法把它们放在同一个模型里。
常用的标准化方法有两种:
| 方法 | 公式 | 适用场景 | 我的评价 |
|---|---|---|---|
| Z-score标准化 | (x - μ) / σ | 因子近似正态分布 | 最常用,但受极端值影响大 |
| 排序分标准化 | rank(x) / N | 因子分布未知或偏态 | 稳健,但丢失了幅度信息 |
def standardize_zscore(series):
"""Z-score标准化"""
return (series - series.mean()) / series.std()
def standardize_rank(series):
"""排序分标准化,结果在[0,1]之间"""
return series.rank(pct=True)
我个人更倾向于排序分标准化。为什么?因为金融数据很少有严格正态分布的。你去做个偏度检验就知道了,大部分因子都有明显的偏态。排序分虽然丢失了数值间的距离信息,但换来了稳健性——这在多因子模型里特别重要。
4.3 中性化:剥离你不想要的风格暴露
这一步是最容易被忽视的,但恰恰是最关键的。
什么叫中性化?说白了就是:把因子中与某些已知风格(如市值、行业)相关的部分剥离掉。
举个例子。你构建了一个"营收增长率"因子。但你发现这个因子和市值高度相关——小市值公司的增长率普遍更高。如果你不做中性化,你的因子其实是在"做多小市值、做空大市值"。这到底是增长因子的效果,还是市值因子的效果?说不清楚。
我常用的方法是回归残差法:
import statsmodels.api as sm
def neutralize_factor(factor, market_cap, industry_dummies):
"""
对因子做市值和行业中性化
factor: 待中性化的因子值
market_cap: 对数市值
industry_dummies: 行业哑变量矩阵
"""
X = pd.concat([market_cap, industry_dummies], axis=1)
X = sm.add_constant(X)
model = sm.OLS(factor, X).fit()
residual = model.resid
return residual
回归之后,残差就是剥离了市值和行业影响后的"纯净因子"。这个纯净因子才是你真正想要的信号。
4.4 完整的因子处理流水线
把上面三步串起来,就是一个完整的因子处理流程:
def factor_pipeline(factor_series, market_cap, industry_dummies):
"""
因子处理全流程
"""
# 第一步:去极值
cleaned = winsorize_mad(factor_series, n=5)
# 第二步:标准化
standardized = standardize_rank(cleaned)
# 第三步:中性化
neutralized = neutralize_factor(standardized, market_cap, industry_dummies)
# 最后再标准化一次(中性化后分布可能变化)
final_factor = standardize_zscore(neutralized)
return final_factor
嗯,到这里因子构建的标准化流程就讲完了。总结下来就三句话:去极值保稳健,标准化消量纲,中性化提纯度。每一步都有它的道理,少一步都不行。
我在实际项目中,会把这套流程封装成一个类,每次新因子进来,自动走一遍流水线。这样既保证了处理的一致性,也方便后续的因子监控和归因分析。