模型评估与特征重要性
直觉
训练完一个模型,不能只看「夏普多高」。对横截面选股模型,更该问三件事:(1) 预测力稳不稳——每月 IC 是否时好时坏?(2) 信号能活多久——这个月预测的排序,下个月、下下个月还有效吗(IC 衰减)?(3) 模型到底靠谁——是依赖一两个稳健特征,还是被噪声特征带跑?回答这三个问题,分别用 IC 时序与 ICIR、IC 衰减曲线、置换重要性。
IC 时序、ICIR 与衰减
- ICIR(信息比):衡量 IC 的稳定性,比单一均值更关键——ICIR 已属优秀;
- IC 衰减:在决策日 给出排序后,计算它与第 个月实现的收益的 Rank IC,画出 的曲线,看信号半衰期。
「人话」解释:为什么 ICIR 比 IC 均值更重要?
IC 均值 0.05 看着不错,但如果某几个月是 +0.20、其他月份是 −0.10,说明信号极不稳定——你不知道下个月是赚是亏。ICIR 把「波动」也除掉了:ICIR 高,意味着「每个月都稳定地有一点点预测力」,这才是能靠复利赚到的钱。衰减快( 就归零)的信号必须高频换手,交易成本会吃掉利润。
可运行案例:IC 时序、衰减与置换重要性
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.ensemble import RandomForestRegressor
prices = pd.read_csv('/data/sp500_universe_daily.csv', parse_dates=['date']).set_index('date')
meta = pd.read_csv('/data/sp500_metadata.csv').set_index('ticker').loc[prices.columns]
dret = np.log(prices / prices.shift(1)); monthly = prices.resample('ME').last()
mom = lambda n: np.log(monthly / monthly.shift(n)).stack()
feats = {'mom_1': mom(1), 'mom_3': mom(3), 'mom_6': mom(6), 'mom_12': mom(12),
'vol_3': (dret.rolling(63).std() * np.sqrt(252)).resample('ME').last().stack()}
F = pd.DataFrame(feats).join(meta['quality'], on='ticker')
y = monthly.pct_change().shift(-1).stack()
data = F.join(y.rename('y')).dropna(); cols = list(F.columns)
dates = data.index.get_level_values('date').unique().sort_values()
cut = dates[int(len(dates) * 0.7)]
is_m = data.index.get_level_values('date') <= cut
rf = RandomForestRegressor(n_estimators=200, max_depth=4, random_state=0).fit(
data.loc[is_m, cols], data.loc[is_m, 'y'])
Xte = data.loc[~is_m, cols]; yte = data.loc[~is_m, 'y']
pred = pd.Series(rf.predict(Xte), index=Xte.index)
# 1) 月度 IC 时序 + ICIR
ic = pred.groupby('date').apply(lambda s: s.rank().corr(yte.loc[s.index].rank()))
print(f"OOS 月均 IC = {ic.mean():+.3f} ICIR = {ic.mean()/ic.std():.2f}")
# 2) IC 衰减: 预测排序与第 j 个月实现的收益的 IC
rm = monthly.pct_change()
decay = []
for j in range(1, 7):
fj = rm.shift(-j).stack()
a = pred.rename('p').to_frame().join(fj.rename('r'), how='inner')
decay.append(a.groupby('date').apply(lambda g: g['p'].rank().corr(g['r'].rank())).mean())
print("IC 衰减 j=1..6:", [round(x, 3) for x in decay])
# 3) 置换重要性(IC 口径): 打乱某特征后 IC 掉多少
base = ic.mean()
perm = {}
for c in cols:
Xp = Xte.copy(); col = Xp[c].to_numpy().copy()
np.random.default_rng(1).shuffle(col); Xp[c] = col
pp = pd.Series(rf.predict(Xp), index=Xte.index)
perm[c] = base - pp.groupby('date').apply(lambda s: s.rank().corr(yte.loc[s.index].rank())).mean()
perm = pd.Series(perm).sort_values()
print("\n置换重要性(IC 掉幅):", perm.round(3).to_dict())
fig, ax = plt.subplots(1, 2, figsize=(11, 3.4))
ax[0].bar(range(1, 7), decay, color='seagreen'); ax[0].set_title('IC 衰减'); ax[0].set_xlabel('j 月')
ax[1].barh(range(len(perm)), perm.values, color='indianred'); ax[1].set_yticks(range(len(perm)))
ax[1].set_yticklabels(perm.index); ax[1].set_title('置换重要性'); plt.tight_layout(); plt.show()
小结
- ICIR(IC 均值/标准差)比 IC 均值更能说明信号稳定性;
- IC 衰减曲线告诉你信号有效期,决定换手频率与成本预算;
- 置换重要性比 impurity 重要性更可信——它直接衡量「打乱这个特征,IC 掉多少」。