4. 单因子分析框架:因子计算、因子预处理、因子IC分析、因子IR分析
各位同学,今天我们来聊聊单因子分析框架。说实话,这是整个Alpha研究里最基础也最关键的一环。我见过太多人,因子算出来就直接扔进模型,结果回测漂亮得一塌糊涂,实盘却亏得亲妈都不认识。为什么?因为因子没洗干净。
单因子分析,说白了就是回答三个问题:这个因子能不能赚钱?赚的钱稳不稳?会不会是运气?我们一步步拆开看。
4.1 因子计算:从原始数据到因子值
因子计算是第一步。我个人习惯把因子分成三类:价量因子、基本面因子、另类因子。不管哪一类,核心逻辑都一样——用原始数据算出一个数值,代表某只股票在某个时间点的某种特征。
举个例子,最简单的动量因子:
# 计算过去20个交易日的累计收益率
def calc_momentum_factor(df, window=20):
"""
df: 包含'close'列的DataFrame,索引为日期,列为股票代码
返回:动量因子值
"""
# 计算每日收益率
returns = df['close'].pct_change()
# 滚动求和
momentum = returns.rolling(window=window).sum()
return momentum
嗯,这里要注意。因子计算最容易被忽视的问题是前瞻偏差。我刚开始做研究时,有一次算了一个"明日收益率预测因子",回测IC高达0.15,兴奋得差点直接上实盘。后来发现,我用的数据里包含了未来信息——说白了就是作弊。
4.2 因子预处理:去极值、标准化、中性化
因子算出来了,但直接能用吗?不能。原始因子值通常有三个问题:极端值、量纲不统一、与其他因子高度相关。所以我们需要三步预处理。
4.2.1 去极值
你想想看,一只股票突然涨停,收益率20%,这个极端值会严重扭曲后续分析。去极值的方法有很多,我个人最常用的是MAD法(中位数绝对偏差法)。
def winsorize_mad(factor, n=5):
"""
使用MAD法去极值
factor: 因子值序列
n: 阈值倍数,默认5倍MAD
"""
median = factor.median()
mad = (factor - median).abs().median()
upper = median + n * mad
lower = median - n * mad
return factor.clip(lower, upper)
为什么用MAD而不是标准差?因为标准差对极端值本身就很敏感,你想想看,一个极端值会把标准差拉得很大,导致去极值效果变差。MAD更稳健。
4.2.2 标准化
不同因子的量纲不一样。比如市盈率可能是几十,换手率可能是百分之几。如果不标准化,后续分析时量纲大的因子会主导结果。
标准化的方法很简单:减去均值,除以标准差。但要注意,我建议用截面标准化,也就是在每个时间截面上单独做标准化,而不是用全历史数据。
def standardize_cross_sectional(factor, date_col='date'):
"""
截面标准化:每个日期单独做Z-score标准化
"""
def _zscore(x):
return (x - x.mean()) / x.std()
return factor.groupby(date_col).transform(_zscore)
4.2.3 中性化
中性化是预处理里最容易被忽视的一步。为什么要中性化?因为很多因子其实是在"搭便车"。比如市值因子,你算出来的因子可能只是因为它持有了小盘股,而不是因子本身有效。
中性化的做法:把因子值对需要中性化的变量(比如市值、行业)做回归,取残差作为新的因子值。
def neutralize(factor, exposure_vars):
"""
对因子做中性化处理
factor: 原始因子值
exposure_vars: 需要中性化的变量列表,如['market_cap', 'industry']
返回:中性化后的因子值(残差)
"""
import statsmodels.api as sm
X = sm.add_constant(exposure_vars)
model = sm.OLS(factor, X).fit()
residual = model.resid
return residual
我记得有一次,一个同学兴奋地跟我说发现了一个IC高达0.12的因子。我一看,这个因子和市值因子的相关系数0.95。中性化之后,IC直接掉到0.02。说白了,这个因子就是市值因子的"影子"。
4.3 因子IC分析:因子有效性的核心指标
因子预处理完了,接下来要回答:这个因子到底有没有预测能力?IC(Information Coefficient)就是干这个的。
IC的定义很简单:因子值和未来收益的相关系数。常用的有两种:
- Pearson IC:线性相关系数,假设因子和收益是线性关系
- Rank IC:秩相关系数(Spearman),只关心排序关系,更稳健
我个人更倾向于Rank IC。为什么?因为因子值和收益之间往往不是严格的线性关系。你想想看,因子值从1到2带来的收益增量,和从100到101带来的收益增量,能一样吗?Rank IC只关心排序,更符合实际。
def calc_rank_ic(factor, forward_return):
"""
计算Rank IC
factor: 当前因子值
forward_return: 未来N期的收益率
返回:Rank IC值
"""
from scipy.stats import spearmanr
ic, p_value = spearmanr(factor, forward_return)
return ic, p_value
IC分析不只是算一个数值。我通常会看几个维度:
| 指标 | 含义 | 经验阈值 |
|---|---|---|
| IC均值 | 因子整体预测能力 | 绝对值 > 0.02 算有效 |
| IC标准差 | 因子预测能力的稳定性 | 越小越好 |
| ICIR | IC均值 / IC标准差 | > 0.5 算不错,> 1.0 算优秀 |
| IC正值比例 | 因子方向一致性的比例 | > 55% 算稳定 |
4.4 因子IR分析:信息比率的实战意义
IR(Information Ratio),也就是信息比率,是IC的"升级版"。它衡量的是单位风险带来的超额收益。
IR的计算公式很简单:
IR = IC均值 / IC标准差
但这里有个坑。很多教材直接拿全样本的IC均值和标准差来算IR,但这样做忽略了IC的时间序列自相关性。我建议用滚动窗口来计算:
def calc_rolling_ir(factor, forward_return, window=12):
"""
计算滚动IR
window: 滚动窗口大小,通常用12个月
"""
ic_series = []
for i in range(window, len(factor)):
ic, _ = calc_rank_ic(factor[i-window:i], forward_return[i-window:i])
ic_series.append(ic)
ir = np.mean(ic_series) / np.std(ic_series)
return ir, ic_series
IR的实战意义在哪里?我举个例子。假设你有两个因子:
- 因子A:IC均值0.05,IC标准差0.10,IR=0.5
- 因子B:IC均值0.03,IC标准差0.02,IR=1.5
因子A的IC均值更高,但因子B的IR更高。在实际组合中,因子B带来的风险调整后收益更好。为什么?因为因子A虽然平均预测能力强,但波动太大,你很难坚持持有。
4.5 单因子分析框架总览
好了,我们把整个流程串起来。下面这张图展示了单因子分析的完整框架:
整个流程走下来,你就能对一个因子做出全面评估。我个人建议,每个因子至少要看12个月以上的IC和IR数据,才能初步判断它是否有效。少于12个月的数据,统计意义不大。
好了,单因子分析框架就讲到这里。记住:因子计算是基础,预处理是保障,IC和IR是核心判断依据。这四个环节缺一不可。下次我们聊聊多因子组合,看看怎么把多个有效的因子组合成一个更强大的模型。
公众号:蓝海数据掘金营,微信deep3321