4. 因子IC分析:IC的定义与计算、Rank IC与Pearson IC、IC序列的统计检验、IC衰减分析
各位同学,欢迎来到因子实战的第四讲。今天咱们聊一个核心话题——IC分析。
说实话,我见过不少做量化的人,因子库堆了一大堆,回测曲线也漂亮,但一到实盘就拉胯。为什么?说白了,就是没搞清楚因子和未来收益之间到底有没有稳定的关系。而IC,就是用来量化这个关系的。
4.1 IC的定义与计算
IC,全称是Information Coefficient,信息系数。它的定义很简单:因子在第t期的取值,与第t+1期股票收益之间的相关性。
你想想看,如果因子值高的股票,下一期收益也高,那IC就是正的。反之就是负的。IC的绝对值越大,说明因子的预测能力越强。
核心公式:
IC_t = Corr(Factor_t, Return_{t+1})
其中,Factor_t是t时刻所有股票的因子值向量,Return_{t+1}是t+1时刻的收益向量。
我在项目中遇到过一个问题:有些同学直接用当期的因子值和当期的收益算IC。这其实是个坑。因子是用来预测未来的,不是解释过去的。所以一定要用t期的因子对t+1期的收益。
4.2 Pearson IC与Rank IC
IC有两种常见的计算方式,咱们得区分清楚。
4.2.1 Pearson IC
Pearson IC就是普通的皮尔逊相关系数。它衡量的是因子值和收益之间的线性关系。
import pandas as pd
import numpy as np
from scipy.stats import pearsonr
def calc_pearson_ic(factor_series, return_series):
"""
计算Pearson IC
factor_series: 某期所有股票的因子值
return_series: 下一期所有股票的收益
"""
# 去除缺失值
mask = ~(factor_series.isna() | return_series.isna())
f = factor_series[mask]
r = return_series[mask]
ic, p_value = pearsonr(f, r)
return ic, p_value
Pearson IC有个问题:它对异常值非常敏感。我记得有一次,某个股票因子值突然爆了个大数,结果那期的IC直接从0.05跳到了0.3。后来一查,是数据录入错误。所以,我个人习惯在算Pearson IC之前,先做一下极值处理。
4.2.2 Rank IC
Rank IC用的是秩相关系数,也就是Spearman相关系数。它不关心因子值具体是多少,只关心排序。
from scipy.stats import spearmanr
def calc_rank_ic(factor_series, return_series):
"""
计算Rank IC
"""
mask = ~(factor_series.isna() | return_series.isna())
f = factor_series[mask]
r = return_series[mask]
ic, p_value = spearmanr(f, r)
return ic, p_value
Rank IC的好处是稳健。你想想看,因子值被一个极端值污染了,排序可能只变化一两位,但Pearson IC可能就崩了。所以,业界更常用Rank IC。
| 对比维度 | Pearson IC | Rank IC |
|---|---|---|
| 衡量关系 | 线性关系 | 单调关系 |
| 对异常值敏感度 | 高 | 低 |
| 计算复杂度 | 低 | 中 |
| 业界使用频率 | 较低 | 较高 |
我的建议:日常分析用Rank IC,做学术报告或写论文时,可以两个都展示。如果两者差异很大,说明因子可能存在非线性关系或者被异常值干扰了。
4.3 IC序列的统计检验
算出一堆IC值之后,怎么判断这个因子是不是真的有效?光看平均值是不够的。我们需要做统计检验。
4.3.1 均值检验
最基础的是检验IC序列的均值是否显著不为零。假设我们有一个IC序列,长度是T。
from scipy import stats
def ic_mean_test(ic_series):
"""
对IC序列做t检验,看均值是否显著不为零
"""
t_stat, p_value = stats.ttest_1samp(ic_series, 0)
mean_ic = ic_series.mean()
std_ic = ic_series.std()
ir = mean_ic / std_ic # 信息比率
return {
'mean_ic': mean_ic,
'std_ic': std_ic,
't_stat': t_stat,
'p_value': p_value,
'ir': ir
}
这里有个概念叫IR(Information Ratio),就是IC的均值除以标准差。IR越高,说明因子越稳定。我个人习惯看IR大于0.5的因子,低于这个数,实盘大概率会出问题。
4.3.2 胜率与累积IC
除了均值检验,我还会看两个指标:
- IC胜率:IC大于0的期数占总期数的比例。如果胜率超过60%,说明因子正向预测的稳定性不错。
- 累积IC曲线:把IC序列累加起来画个图。如果曲线是稳步向上的,说明因子持续有效。如果曲线像过山车,那就要小心了。
注意:我曾经踩过一个坑。某个因子的均值IC是0.03,t检验p值也小于0.05,看起来挺不错。但一画累积IC曲线,发现前两年涨得挺好,后两年一路向下。这说明因子已经失效了。所以,只看均值不看时序趋势,是新手最容易犯的错误。
4.4 IC衰减分析
因子预测能力能持续多久?这就是IC衰减要回答的问题。
4.4.1 多期IC
我们不仅要看t期因子对t+1期收益的IC,还要看对t+2、t+3...期的IC。这叫多期IC。
def calc_multi_period_ic(factor_df, return_df, max_lag=10):
"""
计算多期IC
factor_df: 因子值DataFrame,行是时间,列是股票
return_df: 收益DataFrame,行是时间,列是股票
max_lag: 最大滞后天数
"""
results = []
for lag in range(1, max_lag + 1):
# 对齐数据:t期的因子对t+lag期的收益
aligned_factor = factor_df.iloc[:-lag]
aligned_return = return_df.iloc[lag:]
# 计算每期的IC
ic_list = []
for i in range(len(aligned_factor)):
f = aligned_factor.iloc[i]
r = aligned_return.iloc[i]
ic, _ = spearmanr(f, r)
ic_list.append(ic)
results.append({
'lag': lag,
'mean_ic': np.mean(ic_list),
'std_ic': np.std(ic_list)
})
return pd.DataFrame(results)
4.4.2 衰减曲线
把不同滞后期对应的IC均值画出来,就是衰减曲线。一个理想的因子,应该是IC随着滞后期增加而逐渐衰减,而不是突然消失或者变成负的。
我见过一些高频因子,IC在滞后1期的时候有0.08,但滞后2期就掉到0.01了。这种因子只能做T+0交易,持仓稍微长一点就失效。而一些基本面因子,比如ROE,IC衰减就很慢,滞后10期还能保持0.02以上。
实战经验:
我个人习惯把衰减曲线分成三类:
- 快速衰减型(滞后3期IC减半):适合高频交易,持仓周期短
- 中等衰减型(滞后5-10期IC减半):适合中频交易,持仓几天到几周
- 慢速衰减型(滞后20期以上IC仍为正):适合低频或基本面投资
4.4.3 半衰期
半衰期是指IC衰减到初始值一半所需的时间。这是一个很直观的指标。
def calc_half_life(ic_decay_series):
"""
计算IC半衰期
ic_decay_series: 不同滞后期对应的IC均值
"""
initial_ic = ic_decay_series.iloc[0]
half_ic = initial_ic / 2
# 找到IC首次低于半值的位置
for i, ic in enumerate(ic_decay_series):
if ic < half_ic:
return i + 1 # 返回滞后期数
return len(ic_decay_series) # 如果一直没低于半值,返回最大滞后期
嗯,这里要注意:半衰期只是一个参考指标,不能完全依赖它。因为有些因子的衰减不是线性的,可能前几期衰减很快,后面又稳住了。
4.5 本章知识体系
为了让大家更直观地理解本章的内容结构,我画了一张图:
好了,关于因子IC分析的核心内容就这些。从定义到计算,从统计检验到衰减分析,每一步都有坑,也都有技巧。我个人觉得,IC分析是因子研究中最基础也最重要的一环。你花一周时间把IC分析做扎实了,后面选因子、配权重都会轻松很多。
一个小技巧:我每次开发新因子,都会先跑一遍完整的IC分析流程。如果Rank IC的均值低于0.02,或者IR低于0.3,我会直接放弃这个因子,不再浪费时间做回测。这帮我省了不少精力。