4、IC分析实战:从序列到检验,一步步拆解
好,咱们进入实战环节。IC分析,说白了就是看因子和未来收益之间的“默契程度”。我做了这么多年量化,见过太多因子在回测里漂亮得像仙女,一上实盘就原形毕露。为什么?IC分析没做透。
今天咱们就把IC分析拆成五个步骤:序列计算、均值与标准差、ICIR、衰减分析、分布检验。每一步我都会告诉你,我在项目中踩过哪些坑。
4.1 IC序列计算:别让数据骗了你
IC序列,就是每个时间截面上,因子值和未来收益的相关系数。最常见的做法是Spearman秩相关,因为它对极端值不敏感。
我习惯用Python的scipy.stats.spearmanr,但要注意一点:一定要做行业市值中性化处理。否则你算出来的IC,可能只是行业暴露带来的假象。
import pandas as pd
import numpy as np
from scipy.stats import spearmanr
def calc_ic_series(factor_df, return_df, group_col='date'):
"""
计算IC序列
factor_df: 因子值,index为股票代码,columns为日期
return_df: 未来收益,同上
"""
ic_series = pd.Series(dtype=float)
for date in factor_df.columns:
# 对齐数据,去掉缺失值
valid = factor_df[date].notna() & return_df[date].notna()
f = factor_df[date][valid]
r = return_df[date][valid]
if len(f) < 30: # 样本太少时跳过
continue
ic, _ = spearmanr(f, r)
ic_series[date] = ic
return ic_series
# 使用示例
ic_series = calc_ic_series(factor_values, forward_returns)
print(f"IC序列长度: {len(ic_series)}")
print(f"IC序列前5个值:\n{ic_series.head()}")
4.2 IC均值与标准差:因子的“平均表现”和“稳定性”
IC均值,就是IC序列的平均值。正数说明因子和收益正相关,负数就是负相关。绝对值越大,预测能力越强。
但光看均值不够。你想想看,一个因子均值0.05,但标准差0.3,说明它忽正忽负,不稳定。另一个因子均值0.03,标准差0.05,虽然平均效果弱一点,但稳定啊。
我个人习惯用这两个指标来初步筛选因子:
- IC均值 > 0.02(或 < -0.02)
- IC标准差 < 0.15
ic_mean = ic_series.mean()
ic_std = ic_series.std()
print(f"IC均值: {ic_mean:.4f}")
print(f"IC标准差: {ic_std:.4f}")
print(f"IC均值/标准差: {ic_mean/ic_std:.4f}")
4.3 ICIR(信息比率):因子的“性价比”
ICIR,就是IC均值除以IC标准差。它衡量的是因子每承担一单位波动,能带来多少超额收益。
公式很简单:
ICIR = mean(IC) / std(IC)
但这里有个细节:ICIR需要年化。如果IC序列是日频的,要乘以根号252;周频乘以根号52;月频乘以根号12。
def calc_icir(ic_series, freq='daily'):
"""
计算年化ICIR
freq: 'daily', 'weekly', 'monthly'
"""
ic_mean = ic_series.mean()
ic_std = ic_series.std()
# 年化因子
if freq == 'daily':
annual_factor = np.sqrt(252)
elif freq == 'weekly':
annual_factor = np.sqrt(52)
elif freq == 'monthly':
annual_factor = np.sqrt(12)
else:
annual_factor = 1
icir = (ic_mean / ic_std) * annual_factor
return icir
icir = calc_icir(ic_series, freq='daily')
print(f"年化ICIR: {icir:.4f}")
4.4 IC衰减分析:因子的“保质期”有多长?
IC衰减分析,就是看因子对未来不同期限收益的预测能力。比如,因子对T+1日收益的IC是0.05,对T+5日收益的IC是0.03,对T+20日收益的IC是0.01。这说明因子的预测能力随时间衰减。
我一般会算未来1、5、10、20、60个交易日的IC,然后画一条衰减曲线。
def calc_ic_decay(factor_df, return_dict, horizons=[1, 5, 10, 20, 60]):
"""
计算IC衰减
return_dict: 字典,key为持有期,value为收益DataFrame
"""
decay_results = {}
for h in horizons:
ret_df = return_dict[h]
ic_series = calc_ic_series(factor_df, ret_df)
decay_results[h] = ic_series.mean()
return decay_results
decay = calc_ic_decay(factor_values, forward_returns_dict)
for h, ic in decay.items():
print(f"持有期 {h:3d} 天: IC = {ic:.4f}")
4.5 IC分布检验:因子是“真功夫”还是“运气好”?
最后一步,检验IC序列的分布。如果IC序列服从正态分布,均值显著不为0,那说明因子确实有预测能力。否则,可能就是随机波动。
我常用的检验方法:
- t检验:检验IC均值是否显著不为0
- 正态性检验:检验IC序列是否服从正态分布
- 自相关检验:检验IC序列是否存在序列相关性
from scipy import stats
def ic_distribution_test(ic_series):
"""
IC分布检验
"""
# 1. t检验:均值是否显著不为0
t_stat, p_value = stats.ttest_1samp(ic_series, 0)
# 2. 正态性检验
_, norm_p = stats.normaltest(ic_series)
# 3. 自相关检验(Ljung-Box)
from statsmodels.stats.diagnostic import acorr_ljungbox
lb_result = acorr_ljungbox(ic_series, lags=[10], return_df=True)
return {
't_stat': t_stat,
't_pvalue': p_value,
'norm_pvalue': norm_p,
'lb_pvalue': lb_result['lb_pvalue'].values[0]
}
test_results = ic_distribution_test(ic_series)
print(f"t检验p值: {test_results['t_pvalue']:.4f}")
print(f"正态性检验p值: {test_results['norm_pvalue']:.4f}")
print(f"自相关检验p值: {test_results['lb_pvalue']:.4f}")
- t检验p值 < 0.05:IC均值显著不为0,因子有效
- 正态性检验p值 > 0.05:IC序列近似正态,可以用参数检验
- 自相关检验p值 > 0.05:IC序列无显著自相关,ICIR公式更可靠
4.6 实战框架图:IC分析全流程
下面这张图,是我做IC分析的标准流程。每次拿到一个新因子,我都会走一遍这个流程。
嗯,这套流程走下来,基本能判断一个因子是不是“真功夫”。但记住,IC分析只是第一步。真正的好因子,还要经过分组回测、多因子模型、压力测试等环节。咱们后面慢慢聊。
- 先算IC序列,看看因子有没有“方向感”
- 再看IC均值和标准差,判断“平均表现”和“稳定性”
- 算ICIR,评估“性价比”
- 做衰减分析,了解“保质期”
- 最后做分布检验,排除“运气成分”
好了,IC分析实战就讲到这里。代码我都贴出来了,建议你拿自己的因子跑一遍。遇到问题别慌,回头看看我提到的那些坑,大概率能解决。