3. 单因子分析:IC分析、IR分析、分组回测、因子收益率计算
单因子分析,说白了就是给每个因子做「体检」。
我刚开始做量化那会儿,总觉得因子越多越好,恨不得把几百个因子全塞进模型里。结果呢?过拟合得一塌糊涂,实盘跑起来跟过山车似的。后来我才明白——因子不在多,在于精。而精不精,就得靠单因子分析来把关。
3.1 因子分析的核心逻辑
先看一张图,帮你快速建立整体认知:
这张图把单因子分析的四个维度串起来了。接下来我们一个一个拆开讲。
3.2 IC分析:因子与收益的相关性
IC(Information Coefficient),就是因子值与未来收益的相关系数。它回答一个核心问题:这个因子到底能不能预测涨跌?
我个人习惯用两种IC:
- Spearman秩相关系数:只看排序关系,对极端值不敏感。我一般用这个做主力。
- Pearson相关系数:看线性关系,但容易被极端值带偏。偶尔用来做交叉验证。
IC的行业标准
- |IC| > 0.05:有预测能力
- |IC| > 0.10:预测能力较强
- |IC| > 0.15:非常优秀(但少见)
来看代码实现:
import pandas as pd
import numpy as np
from scipy.stats import spearmanr
def calc_ic(factor_series, return_series, method='spearman'):
"""
计算IC值
factor_series: 因子值(某期截面)
return_series: 未来N期收益
"""
# 去空值
mask = factor_series.notna() & return_series.notna()
f = factor_series[mask]
r = return_series[mask]
if method == 'spearman':
ic, p_value = spearmanr(f, r)
else:
ic = f.corr(r, method='pearson')
p_value = None
return ic, p_value
# 滚动计算IC序列
def rolling_ic(factor_df, return_df, window=12):
"""
计算滚动IC序列
factor_df: 因子值矩阵(日期×股票)
return_df: 收益矩阵(日期×股票)
"""
dates = factor_df.index
ic_series = []
for t in range(len(dates)):
ic, _ = calc_ic(factor_df.iloc[t], return_df.iloc[t])
ic_series.append(ic)
return pd.Series(ic_series, index=dates)
我的经验:IC序列比单期IC重要得多。我曾经遇到一个因子,单期IC高达0.12,但IC序列正负交替,实盘直接翻车。后来我养成了习惯——先看IC序列的稳定性,再看IC的绝对值。
3.3 IR分析:IC的稳定性
IR(Information Ratio),就是IC的均值除以IC的标准差。它衡量的是因子预测能力的稳定性。
公式很简单:
IR = mean(IC) / std(IC)
IR越高,说明因子的预测能力越稳定。我个人认为:
- IR > 0.5:合格
- IR > 1.0:优秀
- IR > 1.5:非常罕见
注意:IR对IC序列的长度很敏感。我建议至少用12个月以上的IC序列来计算IR,太短的话统计意义不大。
代码实现:
def calc_ir(ic_series):
"""
计算IR
ic_series: IC时间序列
"""
mean_ic = ic_series.mean()
std_ic = ic_series.std()
if std_ic == 0:
return 0
ir = mean_ic / std_ic
return ir
# 实战用法
ic_series = rolling_ic(factor_df, return_df)
ir_value = calc_ir(ic_series)
print(f"IR = {ir_value:.3f}")
嗯,这里要注意——IR高不代表因子一定好。如果一个因子IC均值0.02,标准差0.01,IR=2.0,但IC绝对值太小,实际预测能力有限。所以IC和IR要结合起来看。
3.4 分组回测:最直观的验证
分组回测,就是把股票按因子值分成N组(通常是5组或10组),然后看每组未来的收益表现。
为什么做分组回测?因为IC只是一个相关系数,它不能告诉你:因子值最高的股票到底赚了多少?最低的又亏了多少?
分组回测的步骤:
- 每个月末,按因子值排序,分成5组(Q1~Q5)
- 每组等权或市值加权构建组合
- 持有到下个月末,计算组合收益
- 滚动重复,得到各组收益序列
def group_backtest(factor_df, return_df, n_groups=5):
"""
分组回测
factor_df: 因子值矩阵
return_df: 收益矩阵
"""
dates = factor_df.index
group_returns = {i: [] for i in range(n_groups)}
for t in range(len(dates)):
# 获取当期因子和收益
f = factor_df.iloc[t].dropna()
r = return_df.iloc[t]
# 分组
sorted_f = f.sort_values()
group_size = len(sorted_f) // n_groups
for g in range(n_groups):
start = g * group_size
end = (g + 1) * group_size if g < n_groups - 1 else len(sorted_f)
stocks = sorted_f.index[start:end]
# 等权收益
group_return = r[stocks].mean()
group_returns[g].append(group_return)
# 转换为DataFrame
result = pd.DataFrame(group_returns, index=dates)
result.columns = [f'Q{i+1}' for i in range(n_groups)]
# 计算多空收益(Q1 - Q5)
result['Long-Short'] = result['Q1'] - result['Q5']
return result
分组回测的评判标准
- 单调性:Q1到Q5的收益应该单调递减(或递增)
- 区分度:Q1和Q5的收益差距要显著
- 稳定性:多空组合的收益序列要稳定,不能大起大落
我在项目中遇到过这样的情况:一个因子IC很高,但分组回测发现Q2和Q3的收益几乎一样,没有单调性。这说明因子只在极端值上有区分度,中间区域完全失效。这种因子我一般会直接淘汰。
3.5 因子收益率计算
因子收益率,就是做多因子值最高的股票、做空因子值最低的股票所获得的收益。它本质上就是分组回测中「多空组合」的收益。
计算方式有两种:
| 方法 | 说明 | 适用场景 |
|---|---|---|
| 简单多空 | 做多Q1,做空Q5,等权 | 快速验证因子有效性 |
| 市值加权多空 | 按市值加权构建多空组合 | 更贴近实盘 |
def factor_return(group_returns):
"""
计算因子收益率(多空收益)
group_returns: 分组回测结果
"""
# 简单多空
long_short = group_returns['Q1'] - group_returns['Q5']
# 累计收益
cumulative = (1 + long_short).cumprod()
# 年化收益率
annual_return = (cumulative.iloc[-1] ** (252 / len(cumulative))) - 1
# 年化波动率
annual_vol = long_short.std() * np.sqrt(252)
# 夏普比率
sharpe = annual_return / annual_vol if annual_vol > 0 else 0
return {
'annual_return': annual_return,
'annual_vol': annual_vol,
'sharpe': sharpe,
'cumulative': cumulative
}
避坑指南:我曾经犯过一个错误——直接用因子收益率代替分组回测。结果发现因子收益率很高,但分组回测的单调性很差。后来我明白了:因子收益率只看两端,分组回测看全局。两者缺一不可。
3.6 综合评判:如何判断一个因子是否有效?
把四个维度串起来,我一般这样判断:
- IC均值 > 0.05:有预测能力
- IR > 0.5:预测能力稳定
- 分组单调性好:Q1到Q5收益严格递减
- 多空收益显著:年化收益 > 5%,夏普 > 0.5
四个条件都满足,这个因子才算「合格」。如果只满足两三个,我会再观察一段时间,或者做更深入的分析。
你想想看,如果连单因子分析这关都过不了,那放进多因子模型里只会添乱。所以,单因子分析不是可选项,而是必选项。