第五章 因子IC可视化:让数据自己说话
做因子挖掘,最怕什么?
最怕你辛辛苦苦算出一堆因子,结果不知道它到底有没有预测能力。我刚开始做量化那会儿,就吃过这个亏——算了几十个因子,回测曲线漂亮得不行,结果实盘一跑,直接翻车。
后来我才明白,因子 IC 可视化才是检验因子质量的照妖镜。今天咱们就把这四张图讲透:IC 折线图、IC 分布直方图、IC 热力图、IC 衰减图。
核心观点: IC(Information Coefficient)就是因子值和未来收益的相关系数。正 IC 意味着因子值越大,未来收益越高;负 IC 则相反。可视化就是把这些数字变成你能一眼看懂的图形。
知识体系总览
flowchart TD
Root[因子IC可视化] --> A[IC折线图<br/>时序稳定性]
Root --> B[IC分布直方图<br/>统计特征]
Root --> C[IC热力图<br/>分组表现]
Root --> D[IC衰减图<br/>预测时效]
5.1 IC折线图:看因子是否稳定
IC 折线图,说白了就是把每个时间点的 IC 值连成一条线。我习惯按月频来算,这样一年也就12个点,看起来清爽。
怎么读这张图?
- 如果线条大部分在0轴上方,说明因子有正向预测能力
- 如果线条上下乱窜,像心电图一样,那这因子基本废了
- 理想状态:稳定在0.05以上,偶尔负值但很快回正
我的经验: 别只看均值。我曾经遇到一个因子,平均 IC 有0.08,但折线图一看,前两年都是正的,第三年突然变成负的。这种因子你敢用吗?反正我不敢。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# 假设 factor_values 是因子值,forward_returns 是未来N日收益
def calc_ic_series(factor_values, forward_returns, dates):
"""
计算每个时间点的截面IC
"""
ic_list = []
for date in dates:
# 取当天的因子值和未来收益
fv = factor_values.loc[date]
ret = forward_returns.loc[date]
# 去掉缺失值
mask = fv.notna() & ret.notna()
if mask.sum() < 30: # 样本太少就不算
continue
ic = fv[mask].corr(ret[mask], method='spearman')
ic_list.append({'date': date, 'IC': ic})
ic_df = pd.DataFrame(ic_list)
# 画图
plt.figure(figsize=(12, 5))
plt.plot(ic_df['date'], ic_df['IC'], color='#4a90d9', linewidth=2)
plt.axhline(y=0, color='red', linestyle='--', alpha=0.5)
plt.axhline(y=ic_df['IC'].mean(), color='green', linestyle='--',
label=f'Mean IC: {ic_df["IC"].mean():.4f}')
plt.title('IC Time Series')
plt.legend()
plt.show()
return ic_df
5.2 IC分布直方图:看IC的统计特征
折线图看趋势,直方图看分布。我个人觉得,直方图能告诉你更多隐藏信息。
重点关注三个指标:
| 指标 | 含义 | 理想值 |
|---|---|---|
| 均值 | IC 的平均水平 | 绝对值 > 0.02 |
| 标准差 | IC 的波动程度 | 越小越好 |
| IR | IC 均值 / IC 标准差 | > 0.5 |
注意: IR(Information Ratio)比 IC 均值更重要。我曾经见过一个因子 IC 均值0.06,但标准差0.15,IR 只有0.4。这种因子在实盘中基本就是随机游走。
def plot_ic_distribution(ic_series):
"""
绘制IC分布直方图
"""
ic_mean = ic_series.mean()
ic_std = ic_series.std()
ic_ir = ic_mean / ic_std if ic_std != 0 else 0
plt.figure(figsize=(10, 6))
plt.hist(ic_series, bins=30, color='#4a90d9', edgecolor='white', alpha=0.7)
plt.axvline(x=ic_mean, color='red', linestyle='--',
label=f'Mean: {ic_mean:.4f}')
plt.axvline(x=0, color='gray', linestyle='-', alpha=0.5)
plt.title(f'IC Distribution (IR={ic_ir:.3f})')
plt.legend()
plt.show()
return {'mean': ic_mean, 'std': ic_std, 'ir': ic_ir}
5.3 IC热力图(分组):看因子是否单调
这个图是我最看重的。它把股票按因子值分成 N 组(通常5组或10组),然后看每组未来的平均收益。
理想情况: 第1组收益最低,第5组收益最高,中间组依次排列。这就是所谓的单调性。
记住: 单调性比 IC 绝对值更重要。一个 IC 只有0.03但单调性完美的因子,比一个 IC 有0.08但单调性混乱的因子靠谱得多。
import seaborn as sns
def plot_ic_heatmap(factor_values, forward_returns, n_groups=5):
"""
分组IC热力图
"""
# 按因子值分组
factor_values['group'] = pd.qcut(factor_values['factor'],
q=n_groups, labels=False)
# 计算每组平均收益
group_returns = factor_values.groupby('group')['forward_return'].mean()
# 画热力图
plt.figure(figsize=(8, 6))
sns.heatmap(group_returns.values.reshape(1, -1),
annot=True, fmt='.4f', cmap='RdYlGn',
xticklabels=[f'G{i+1}' for i in range(n_groups)],
yticklabels=['Return'])
plt.title('Group-wise Forward Returns')
plt.show()
# 检查单调性
diffs = np.diff(group_returns.values)
monotonic = all(diffs > 0) or all(diffs < 0)
print(f'单调性: {"完美" if monotonic else "有问题"}')
return group_returns
5.4 IC衰减图:看因子的预测时效
这个图很多人会忽略,但它特别重要。它回答一个问题:你的因子能预测多久?
做法很简单:算 T 日的因子值,分别和 T+1、T+2、...、T+N 日的收益算 IC,然后画成一条线。
怎么读?
- 快速衰减:IC 在第1天很高,第2天就掉一半,第3天接近0。说明因子只能做短线
- 缓慢衰减:IC 能维持5天以上。这种因子适合做中长线
- 先升后降:有时候 IC 在第2天反而比第1天高。嗯,这种情况我遇到过,通常是数据泄露了
避坑指南: 我曾经遇到一个因子,衰减图显示 IC 在第10天突然又变高了。一开始我还以为发现了什么神奇规律,后来一查,原来是未来函数没处理好。所以看到异常衰减曲线,先检查数据。
def plot_ic_decay(factor_values, forward_returns_dict, max_lag=20):
"""
IC衰减图
forward_returns_dict: {lag: forward_returns_df}
"""
ic_decay = []
for lag in range(1, max_lag + 1):
if lag not in forward_returns_dict:
continue
ret = forward_returns_dict[lag]
# 计算截面IC
ic = factor_values.corrwith(ret, axis=1).mean()
ic_decay.append({'lag': lag, 'IC': ic})
decay_df = pd.DataFrame(ic_decay)
plt.figure(figsize=(10, 5))
plt.plot(decay_df['lag'], decay_df['IC'], 'o-', color='#4a90d9')
plt.axhline(y=0, color='red', linestyle='--', alpha=0.5)
plt.xlabel('Prediction Horizon (days)')
plt.ylabel('IC')
plt.title('IC Decay Curve')
plt.grid(True, alpha=0.3)
plt.show()
# 计算半衰期
half_life = None
initial_ic = decay_df['IC'].iloc[0]
for _, row in decay_df.iterrows():
if row['IC'] <= initial_ic / 2:
half_life = row['lag']
break
print(f'IC半衰期: {half_life} 天')
return decay_df
5.5 实战中的组合拳
这四张图,我建议你每次挖因子都跑一遍。顺序也很重要:
- 先看 IC 折线图——因子有没有持续预测能力?
- 再看 IC 分布直方图——IR 够不够高?
- 然后看 IC 热力图——单调性好不好?
- 最后看 IC 衰减图——能预测多久?
四张图都过关的因子,才值得你花时间去做回测。否则,趁早放弃,别浪费时间。
我的经验总结: 挖了这么多年因子,我发现一个规律——好的因子往往在四张图上都表现不错,但不会在某一项上特别突出。那些 IC 均值0.15以上的因子,要么是过拟合,要么是数据泄露。记住,稳健比惊艳更重要。
好了,IC 可视化的内容就这些。你想想看,如果每次挖因子都按这个流程走一遍,是不是能少踩很多坑?至少我是这么过来的。