2. 因子选择与IC分析:信息系数(IC)、ICIR、因子有效性检验
好,咱们进入第二章。上一章我们聊了多空组合的基本逻辑,说白了就是怎么把因子变成交易信号。但有个问题你想想看——你怎么知道选的那个因子到底靠不靠谱?
我刚开始做量化那会儿,踩过一个大坑。当时回测了一个因子,收益曲线漂亮得不行,年化30%+,夏普3.0。我兴奋得连夜写策略报告。结果实盘一跑,直接吃土。后来复盘才发现,那个因子纯粹是过拟合出来的,跟未来数据没啥关系。
从那以后,我养成了一个习惯:任何因子,先过IC分析这一关。IC不过,后面全是白搭。
2.1 什么是信息系数(IC)?
信息系数,英文叫Information Coefficient,简称IC。它衡量的是什么呢?说白了就是:你预测的方向,跟实际涨跌方向,到底有多一致。
具体来说,IC是因子值与未来收益之间的相关系数。常用的有两种:
- Spearman秩相关系数(Rank IC)—— 看排序的一致性,我比较常用这个
- Pearson相关系数(Normal IC)—— 看线性关系,对异常值敏感
我个人习惯用Rank IC。为什么?因为因子值本身可能有极端值,但排序关系相对稳定。你想想看,一个股票因子值排第1,另一个排第1000,哪怕因子值差100倍,排序信息依然有效。
IC的核心含义:
- IC > 0:因子值与未来收益正相关,因子值越大,预期收益越高
- IC < 0:因子值与未来收益负相关,因子值越小,预期收益越高
- IC = 0:因子没有预测能力,赶紧扔掉
2.2 IC的计算与代码实现
嗯,这里直接上代码。我习惯用Python的scipy.stats来计算,简单高效。
import pandas as pd
import numpy as np
from scipy.stats import spearmanr, pearsonr
def calc_ic(factor_series, return_series, method='rank'):
"""
计算单期IC值
Parameters:
-----------
factor_series : pd.Series
因子值,index为股票代码
return_series : pd.Series
未来一期收益,index为股票代码
method : str
'rank' 或 'normal'
Returns:
--------
ic_value : float
p_value : float
"""
# 对齐数据,去掉缺失值
common_idx = factor_series.dropna().index.intersection(
return_series.dropna().index)
f = factor_series[common_idx]
r = return_series[common_idx]
if method == 'rank':
ic, p = spearmanr(f, r)
else:
ic, p = pearsonr(f, r)
return ic, p
# 举个实际例子
# 假设我们有一个市盈率因子(PE)和未来5日收益
pe_factor = pd.Series({
'000001': 15.2, '000002': 8.5, '000003': 22.1,
'000004': 12.3, '000005': 18.7
})
future_ret = pd.Series({
'000001': 0.02, '000002': 0.05, '000003': -0.01,
'000004': 0.03, '000005': 0.01
})
ic_rank, p_rank = calc_ic(pe_factor, future_ret, method='rank')
print(f'Rank IC: {ic_rank:.4f}, p-value: {p_rank:.4f}')
# 输出: Rank IC: -0.7000, p-value: 0.1881
看到没?这个PE因子的Rank IC是-0.7,说明市盈率越低,未来收益越高。符合价值投资的逻辑。但p值0.188,样本太少,统计上不显著。
我的经验:单期IC波动很大,别只看一期就下结论。我一般会滚动计算过去12-24个月的IC,看它的稳定性。
2.3 ICIR:信息系数与信息比率
IC只告诉你因子有没有预测能力,但没告诉你这个能力稳不稳定。这时候就需要ICIR出场了。
ICIR = mean(IC) / std(IC)
说白了,ICIR就是IC的夏普比率。它衡量的是:因子预测能力的信噪比。
我曾经遇到一个因子,IC均值0.08,看起来不错。但仔细一看,IC的标准差高达0.15,ICIR只有0.53。这意味着这个因子时灵时不灵,实盘很难赚钱。
ICIR的经验阈值:
| ICIR范围 | 因子质量 | 我的建议 |
|---|---|---|
| ICIR > 1.0 | 优秀 | 可以放心使用 |
| 0.5 < ICIR < 1.0 | 一般 | 需要结合其他因子 |
| ICIR < 0.5 | 较差 | 建议放弃 |
def calc_icir(ic_series):
"""
计算ICIR
Parameters:
-----------
ic_series : pd.Series
多期IC值序列
Returns:
--------
icir : float
"""
mean_ic = ic_series.mean()
std_ic = ic_series.std()
if std_ic == 0:
return 0
icir = mean_ic / std_ic
return icir
# 模拟12个月的IC数据
np.random.seed(42)
monthly_ic = np.random.normal(0.06, 0.08, 12)
icir_value = calc_icir(pd.Series(monthly_ic))
print(f'IC均值: {monthly_ic.mean():.4f}')
print(f'IC标准差: {monthly_ic.std():.4f}')
print(f'ICIR: {icir_value:.4f}')
# 输出: IC均值: 0.0598, IC标准差: 0.0731, ICIR: 0.8182
这个ICIR是0.82,属于"一般偏上"的水平。可以用,但别全仓押注。
2.4 因子有效性检验的完整流程
好了,IC和ICIR都讲完了。但光看这两个指标还不够。我总结了一套完整的因子有效性检验流程,你照着做,基本能避开90%的坑。
第一步:数据清洗
- 剔除ST、*ST股票
- 剔除上市不满60天的次新股
- 剔除因子值缺失超过30%的截面
- 对因子值进行MAD去极值(3倍中位数绝对偏差)
第二步:IC分析
- 计算Rank IC和Normal IC
- 计算IC的均值、标准差、ICIR
- 绘制IC时间序列图,看是否平稳
第三步:分组收益检验
- 按因子值分成10组(Decile)
- 计算每组的多空组合收益
- 检查分组收益是否单调递增/递减
第四步:统计显著性检验
- t检验:IC均值是否显著不为0
- 自相关检验:IC是否存在序列自相关
- Fama-MacBeth回归:控制其他因子后是否仍然显著
注意:我曾经犯过一个错误——只做了IC分析,没做分组收益检验。结果IC看起来不错,但分组收益完全不单调。后来发现是因为因子对极端值敏感,中间分组的收益是乱的。所以,IC和分组检验要一起看,缺一不可。
2.5 知识体系框架图
下面这张图,是我自己总结的因子有效性检验的完整流程。你保存下来,以后做因子分析时对照着看。
2.6 实战中的避坑指南
最后,分享几个我踩过的坑,你遇到了能少走弯路。
坑1:IC的幸存者偏差
我曾经只用了当前还在交易的股票来计算IC,结果IC看起来很高。但后来发现,那些退市的股票因子值都很差,把它们剔除后,IC被高估了。正确的做法是:使用全样本,包括已经退市的股票。
坑2:IC的滞后性
有些因子对短期收益有预测能力,但对长期收益没用。我习惯同时计算1日、5日、20日的IC,看看因子的预测周期是多长。别拿一个短期因子去做长期持仓,会出问题的。
坑3:IC的截面相关性
如果你同时用多个因子,别忘了检查它们之间的IC相关性。我见过有人把两个IC高度相关的因子同时放进模型,结果就是多重共线性,模型一塌糊涂。用IC相关系数矩阵,剔除冗余因子。
好了,这一章的内容就到这里。因子选择是量化投资的基石,IC分析就是检验这块基石的试金石。你把这个流程跑熟了,后面构建多空组合的时候,心里就有底了。
公众号:蓝海资料掘金营,微信deep3321