因子IC分析:从定义到实战
因子IC分析,说白了就是回答一个问题:我这个因子,到底有没有预测能力?
我刚开始做量化那会儿,经常遇到这种情况——回测曲线漂亮得不行,一上实盘就拉胯。后来才明白,问题就出在IC分析没做透。今天咱们就把这块硬骨头啃下来。
IC(信息系数)定义与计算
IC的全称是Information Coefficient,信息系数。它衡量的是因子值与未来收益之间的相关性。
数学上,IC就是Spearman秩相关系数。为什么用秩相关?因为我们要的是排序能力,不是线性关系。你想想看,选股本质上就是排序——因子值排名靠前的股票,未来收益排名也应该靠前。
IC的核心公式:
IC_t = corr(rank(Factor_t), rank(Return_{t+1}))
其中,Factor_t是t时刻的因子值,Return_{t+1}是t+1时刻的收益
计算IC的步骤其实很简单:
- 在每个时间截面上,对所有股票的因子值排序
- 对同一时间截面的未来收益排序
- 计算两个排序序列的相关系数
来看一段Python代码,这是我实际项目中一直在用的计算方式:
import pandas as pd
import numpy as np
from scipy.stats import spearmanr
def calc_ic(factor_df, return_df):
"""
计算截面IC序列
factor_df: 因子值,index为股票,columns为时间
return_df: 未来收益,index为股票,columns为时间
"""
ic_series = pd.Series(dtype=float)
for date in factor_df.columns:
# 获取当前截面的因子值和收益
factor = factor_df[date].dropna()
ret = return_df[date].reindex(factor.index).dropna()
# 取交集
common = factor.index.intersection(ret.index)
if len(common) < 30: # 样本太少时跳过
continue
# 计算秩相关系数
ic, _ = spearmanr(factor[common], ret[common])
ic_series[date] = ic
return ic_series
个人经验:我习惯在计算IC前先做一次极值处理。曾经有个因子,因为某只股票的数据异常,IC直接从0.05跳到了-0.12。后来加了MAD去极值,IC序列就稳定多了。
IC序列的统计检验
拿到IC序列之后,别急着高兴。我们需要回答三个问题:
- 均值:IC的平均水平是多少?正还是负?
- 标准差:IC的波动大不大?
- t统计量:IC显著不为零吗?
我见过太多人只看均值。均值0.05就觉得是好因子,结果标准差0.15,t值才1.2——根本不显著。说白了,就是运气成分太大。
统计检验的公式:
def ic_statistics(ic_series):
"""
IC序列的统计检验
"""
mean_ic = ic_series.mean()
std_ic = ic_series.std()
t_stat = mean_ic / (std_ic / np.sqrt(len(ic_series)))
p_value = 2 * (1 - stats.t.cdf(abs(t_stat), df=len(ic_series)-1))
return {
'mean_ic': mean_ic,
'std_ic': std_ic,
't_stat': t_stat,
'p_value': p_value,
'obs': len(ic_series)
}
避坑指南:我曾经遇到一个因子,全样本IC均值0.03,t值2.1,看起来挺不错。但分年度一看,2018年IC是-0.02,2019年是0.08,2020年又变成-0.01。这种不稳定的因子,实盘大概率要翻车。
ICIR(信息比率)计算
ICIR = mean(IC) / std(IC)
这个指标衡量的是单位风险下的IC收益。ICIR越高,说明因子的预测能力越稳定。
我个人习惯用ICIR来筛选因子:
| ICIR范围 | 因子质量 | 我的建议 |
|---|---|---|
| < 0.5 | 较差 | 直接放弃,别浪费时间 |
| 0.5 - 1.0 | 一般 | 可以继续观察,但别重仓 |
| 1.0 - 1.5 | 良好 | 值得深入研究的候选因子 |
| > 1.5 | 优秀 | 赶紧纳入组合,但注意过拟合 |
嗯,这里要注意:ICIR的计算周期要和你的交易频率匹配。做日频交易的,就看日度ICIR;做周频的,就看周度ICIR。混着用会出问题。
IC衰减与换手率分析
这部分是我觉得最容易被忽视的。因子不是永远有效的,它会衰减。
IC衰减指的是:随着持有期的延长,因子的预测能力逐渐下降。我们通常用分层IC衰减曲线来观察这个现象。
计算方式:
def ic_decay_analysis(factor_df, return_df, max_lag=20):
"""
IC衰减分析
max_lag: 最大滞后天数
"""
decay_results = []
for lag in range(1, max_lag + 1):
# 将收益向前移动lag期
shifted_ret = return_df.shift(-lag)
ic = calc_ic(factor_df, shifted_ret)
decay_results.append({
'lag': lag,
'mean_ic': ic.mean(),
'icir': ic.mean() / ic.std()
})
return pd.DataFrame(decay_results)
实战经验:我记得有个动量因子,1日IC是0.04,5日IC是0.03,10日IC就变成0.01了。这说明它的预测能力只能维持一周左右。如果我用月频调仓,这个因子基本没用。
换手率分析和IC衰减是孪生兄弟。换手率越高,交易成本越大,因子收益被侵蚀得越厉害。
我一般这样算换手率:
def turnover_analysis(factor_df, top_pct=0.2):
"""
分析因子多空组合的换手率
top_pct: 多头/空头比例
"""
turnover_list = []
for i in range(1, len(factor_df.columns)):
prev_date = factor_df.columns[i-1]
curr_date = factor_df.columns[i]
# 获取前后两期的多头股票
prev_long = factor_df[prev_date].nlargest(
int(len(factor_df) * top_pct)
).index
curr_long = factor_df[curr_date].nlargest(
int(len(factor_df) * top_pct)
).index
# 计算换手率
turnover = 1 - len(
prev_long.intersection(curr_long)
) / len(prev_long)
turnover_list.append(turnover)
return np.mean(turnover_list)
我曾经踩过的坑:有个因子ICIR高达1.8,但换手率每周80%。算上双边千三的交易成本,年化收益直接从15%掉到6%。后来我加了换手率约束,虽然ICIR降到了1.2,但实盘收益反而更好了。
本章知识体系
下面这张图总结了IC分析的核心逻辑,我建议你保存下来:
最后说一句:IC分析不是一次性工作。因子会随着市场环境变化而失效,我建议你每月至少做一次IC回顾。如果发现IC均值持续下降或者波动率变大,就要考虑是不是该换因子了。
我的习惯:每次新因子入库前,我会跑一套完整的IC分析流程——全样本IC、滚动IC、分行业IC、分年度IC。全部通过之后,才敢放到实盘组合里。这套流程帮我过滤掉了至少60%的伪因子。
公众号:蓝海资料掘金营,微信deep3321