评估指标详解
目录
- 1. IC(Pearson 相关系数)
- 2. Rank IC(Spearman 秩相关)
- 3. ICIR(信息比率)
- 4. IC 统计显著性
- 5. 滚动 IC 分析
- 6. 分层回测
- 7. IC 衰减分析
- 8. 评估标准总结
1. IC(Pearson 相关系数)
1.1 什么是 IC
IC(Information Coefficient,信息系数)衡量预测值与真实值之间的线性相关程度。
定义: 预测值和真实收益率的皮尔逊相关系数。
取值范围:
| IC 值 | 含义 |
|---|---|
| 1 | 完美正相关 |
| 0 | 无相关性 |
| -1 | 完美负相关 |
1.2 为什么用 IC
在量化投资中,我们关心的是排序能力而非绝对预测精度。
场景:预测股票收益率
预测值: [0.05, 0.03, 0.01, -0.01, -0.03]
真实值: [0.06, 0.04, 0.02, -0.02, -0.04]
虽然绝对值有误差,但排序完全正确!
IC 会捕捉这种排序关系。
1.3 IC 计算
import numpy as np
import pandas as pd
def calculate_ic(predictions, returns):
"""
计算 IC(Pearson 相关系数)
参数:
predictions: 预测值,shape (n_samples,)
returns: 真实收益率,shape (n_samples,)
返回:
ic: 相关系数
p_value: 显著性检验的 p 值
"""
# 移除 NaN
mask = ~(np.isnan(predictions) | np.isnan(returns))
pred_clean = predictions[mask]
ret_clean = returns[mask]
if len(pred_clean) < 2:
return np.nan, np.nan
# 计算相关系数
ic = np.corrcoef(pred_clean, ret_clean)[0, 1]
# 计算 p 值
from scipy import stats
n = len(pred_clean)
t_stat = ic * np.sqrt(n - 2) / np.sqrt(1 - ic**2) if ic != 1 else np.inf
p_value = 2 * (1 - stats.t.cdf(abs(t_stat), n - 2))
return ic, p_value
# 生成示例数据
np.random.seed(42)
n = 1000
# 预测值:有信号 + 噪声
signal = np.random.randn(n)
predictions = 0.3 * signal + np.random.randn(n) * 0.7
# 真实收益:与信号相关
returns = 0.5 * signal + np.random.randn(n) * 0.5
ic, p_value = calculate_ic(predictions, returns)
print(f"IC: {ic:.4f}")
print(f"P-value: {p_value:.4f}")
print(f"统计显著: {p_value < 0.05}")1.4 截面 IC vs 时序 IC
# 截面 IC:某一时点,不同股票之间的相关性
def calculate_cross_sectional_ic(df, date_col='date', pred_col='prediction', return_col='return'):
"""
计算截面 IC
参数:
df: 包含 date, prediction, return 列的 DataFrame
date_col: 日期列名
pred_col: 预测列名
return_col: 收益列名
返回:
ic_series: 每日的 IC 值
"""
ic_by_date = []
for date, group in df.groupby(date_col):
pred = group[pred_col].values
ret = group[return_col].values
ic, _ = calculate_ic(pred, ret)
ic_by_date.append({'date': date, 'ic': ic})
return pd.DataFrame(ic_by_date).set_index('date')
# 模拟多股票数据
dates = pd.date_range('2020-01-01', '2023-12-31', freq='M')
stocks = [f'STOCK{i:03d}' for i in range(100)]
data = []
for date in dates:
for stock in stocks:
pred = np.random.randn() * 0.1
ret = 0.3 * pred + np.random.randn() * 0.05
data.append({'date': date, 'stock': stock, 'prediction': pred, 'return': ret})
df_cs = pd.DataFrame(data)
# 计算截面 IC
ic_series = calculate_cross_sectional_ic(df_cs)
print(f"截面 IC 均值: {ic_series['ic'].mean():.4f}")
print(f"截面 IC 标准差: {ic_series['ic'].std():.4f}")2. Rank IC(Spearman 秩相关)
2.1 什么是 Rank IC
Rank IC 使用 Spearman 秩相关系数,只关注排序关系,不关心绝对值大小。
优势: 对异常值更稳健。
2.2 Rank IC vs IC
| 特性 | IC (Pearson) | Rank IC (Spearman) |
|---|---|---|
| 关注点 | 线性关系 | 单调关系 |
| 异常值敏感 | 是 | 否 |
| 量化场景 | 标准指标 | 更稳健的指标 |
| 计算复杂度 | 低 | 稍高(需要排序) |
def calculate_rank_ic(predictions, returns):
"""
计算 Rank IC(Spearman 秩相关系数)
参数:
predictions: 预测值
returns: 真实收益率
返回:
rank_ic: 秩相关系数
p_value: 显著性 p 值
"""
from scipy.stats import spearmanr
mask = ~(np.isnan(predictions) | np.isnan(returns))
pred_clean = predictions[mask]
ret_clean = returns[mask]
if len(pred_clean) < 2:
return np.nan, np.nan
rank_ic, p_value = spearmanr(pred_clean, ret_clean)
return rank_ic, p_value
# 对比 IC 和 Rank IC
print("IC vs Rank IC 对比:")
print(f"IC: {ic:.4f}")
print(f"Rank IC: {calculate_rank_ic(predictions, returns)[0]:.4f}")
# 演示异常值的影响
print("\n异常值影响测试:")
pred_with_outlier = predictions.copy()
pred_with_outlier[0] = 100 # 极端异常值
ic_normal, _ = calculate_ic(predictions, returns)
ic_outlier, _ = calculate_ic(pred_with_outlier, returns)
rank_ic_normal, _ = calculate_rank_ic(predictions, returns)
rank_ic_outlier, _ = calculate_rank_ic(pred_with_outlier, returns)
print(f"IC (无异常): {ic_normal:.4f}")
print(f"IC (有异常): {ic_outlier:.4f} (变化: {abs(ic_outlier - ic_normal):.4f})")
print(f"Rank IC (无异常): {rank_ic_normal:.4f}")
print(f"Rank IC (有异常): {rank_ic_outlier:.4f} (变化: {abs(rank_ic_outlier - rank_ic_normal):.4f})")3. ICIR(信息比率)
3.1 什么是 ICIR
ICIR(Information Coefficient Information Ratio)衡量 IC 的稳定性。
含义: 单位风险下的 IC 水平。
3.2 ICIR 的意义
| ICIR 水平 | 评价 | 说明 |
|---|---|---|
| < 0.5 | 较差 | IC 不稳定 |
| 0.5 - 1.0 | 一般 | 有一定稳定性 |
| > 1.0 | 良好 | IC 稳定 |
| > 1.5 | 优秀 | 非常稳定 |
def calculate_icir(ic_series):
"""
计算 ICIR
参数:
ic_series: 时间序列 IC 值
返回:
icir: 信息比率
ic_mean: IC 均值
ic_std: IC 标准差
"""
ic_mean = np.mean(ic_series)
ic_std = np.std(ic_series, ddof=1)
if ic_std == 0:
return np.nan, ic_mean, ic_std
icir = ic_mean / ic_std
return icir, ic_mean, ic_std
# 计算示例
ic_values = ic_series['ic'].dropna().values
icir, ic_mean, ic_std = calculate_icir(ic_values)
print(f"IC 均值: {ic_mean:.4f}")
print(f"IC 标准差: {ic_std:.4f}")
print(f"ICIR: {icir:.4f}")3.3 ICIR 与 IC 的权衡
高 IC + 低 ICIR → 潜力大但风险大(可能过拟合)
低 IC + 高 ICIR → 稳定但收益有限
高 IC + 高 ICIR → 理想状态
低 IC + 低 ICIR → 无使用价值
# 可视化 IC 稳定性
import matplotlib.pyplot as plt
def plot_ic_stability(ic_series, window=20):
"""绘制 IC 及其滚动统计"""
fig, axes = plt.subplots(2, 1, figsize=(12, 6), sharex=True)
# IC 时间序列
axes[0].plot(ic_series.index, ic_series['ic'], marker='o', linestyle='-', alpha=0.6)
axes[0].axhline(y=0, color='black', linestyle='--', linewidth=0.5)
axes[0].set_ylabel('IC')
axes[0].set_title('IC Time Series')
axes[0].grid(True, alpha=0.3)
# 滚动 ICIR
rolling_icir = ic_series['ic'].rolling(window).mean() / ic_series['ic'].rolling(window).std()
axes[1].plot(ic_series.index, rolling_icir, color='darkorange', linewidth=2)
axes[1].axhline(y=1, color='green', linestyle='--', label='ICIR = 1')
axes[1].set_ylabel('Rolling ICIR')
axes[1].set_xlabel('Date')
axes[1].set_title(f'Rolling ICIR (Window={window})')
axes[1].legend()
axes[1].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
plot_ic_stability(ic_series, window=10)4. IC 统计显著性
4.1 t 检验
检验 IC 是否显著不为 0。
假设:
- H0: IC = 0(无相关性)
- H1: IC ≠ 0(有相关性)
t 统计量:
def ic_significance_test(ic_series, alpha=0.05):
"""
IC 的统计显著性检验
参数:
ic_series: 时间序列 IC 值
alpha: 显著性水平
返回:
result: 包含统计量的字典
"""
from scipy import stats
ic_values = ic_series.dropna()
n = len(ic_values)
mean_ic = ic_values.mean()
# t 检验
se = ic_values.std(ddof=1) / np.sqrt(n)
t_stat = mean_ic / se if se > 0 else 0
p_value = 2 * (1 - stats.t.cdf(abs(t_stat), n - 1))
# 置信区间
from scipy.stats import t
t_crit = t.ppf(1 - alpha/2, n - 1)
ci_lower = mean_ic - t_crit * se
ci_upper = mean_ic + t_crit * se
return {
'mean_ic': mean_ic,
'std_ic': ic_values.std(),
'n': n,
't_statistic': t_stat,
'p_value': p_value,
'ci_lower': ci_lower,
'ci_upper': ci_upper,
'is_significant': p_value < alpha
}
# 检验示例
result = ic_significance_test(ic_series['ic'])
print("IC 统计显著性检验:")
print(f" 样本数: {result['n']}")
print(f" IC 均值: {result['mean_ic']:.4f}")
print(f" t 统计量: {result['t_statistic']:.4f}")
print(f" p 值: {result['p_value']:.4f}")
print(f" {100*(1-0.05):.0f}% 置信区间: [{result['ci_lower']:.4f}, {result['ci_upper']:.4f}]")
print(f" 统计显著: {'是' if result['is_significant'] else '否'}")4.2 IC 为正的概率
def ic_positive_ratio(ic_series):
"""IC 为正的比例"""
positive_count = (ic_series > 0).sum()
total_count = len(ic_series.dropna())
ratio = positive_count / total_count if total_count > 0 else 0
return ratio, positive_count, total_count
ratio, pos, total = ic_positive_ratio(ic_series['ic'])
print(f"IC 为正的比例: {ratio:.2%} ({pos}/{total})")5. 滚动 IC 分析
5.1 滚动 IC 计算
def rolling_ic(predictions, returns, window=20):
"""
计算滚动 IC
参数:
predictions: 预测值序列
returns: 收益率序列
window: 滚动窗口大小
返回:
rolling_ic: 滚动 IC 序列
"""
if len(predictions) < window:
raise ValueError(f"数据长度 ({len(predictions)}) 小于窗口大小 ({window})")
rolling_ic_values = []
for i in range(window - 1, len(predictions)):
pred_window = predictions[i - window + 1:i + 1]
ret_window = returns[i - window + 1:i + 1]
ic, _ = calculate_ic(pred_window, ret_window)
rolling_ic_values.append(ic)
return np.array(rolling_ic_values)
# 生成时序数据
n = 500
predictions_ts = np.cumsum(np.random.randn(n) * 0.1)
returns_ts = 0.3 * predictions_ts + np.random.randn(n) * 0.5
# 计算滚动 IC
ric = rolling_ic(predictions_ts, returns_ts, window=60)
print(f"滚动 IC 均值: {np.nanmean(ric):.4f}")
print(f"滚动 IC 标准差: {np.nanstd(ric):.4f}")5.2 滚动 IC 可视化
def plot_rolling_ic(returns, predictions, window=60):
"""绘制滚动 IC"""
ric = rolling_ic(predictions, returns, window)
fig, ax = plt.subplots(figsize=(14, 4))
# 绘制滚动 IC
ax.plot(range(len(ric)), ric, color='steelblue', linewidth=1.5, label='Rolling IC')
ax.axhline(y=0, color='black', linestyle='--', linewidth=0.5)
ax.axhline(y=np.nanmean(ric), color='red', linestyle='--', label=f'Mean: {np.nanmean(ric):.4f}')
# 填充正负区域
ax.fill_between(range(len(ric)), ric, 0, where=(ric > 0),
color='green', alpha=0.3, label='IC > 0')
ax.fill_between(range(len(ric)), ric, 0, where=(ric < 0),
color='red', alpha=0.3, label='IC < 0')
ax.set_xlabel('Time')
ax.set_ylabel('IC')
ax.set_title(f'Rolling IC (Window={window})')
ax.legend(loc='upper left')
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
plot_rolling_ic(returns_ts, predictions_ts, window=60)6. 分层回测
6.1 什么是分层回测
将股票按预测值分为若干组,做多表现好的一组,做空表现差的一组。
目的: 验证模型的实际交易价值。
6.2 分组收益计算
def quantile_analysis(predictions, returns, n_quantiles=5):
"""
分层回测分析
参数:
predictions: 预测值,shape (n_samples,)
returns: 真实收益率,shape (n_samples,)
n_quantiles: 分组数量
返回:
result: 包含各组统计信息的字典
"""
# 移除 NaN
mask = ~(np.isnan(predictions) | np.isnan(returns))
pred = predictions[mask]
ret = returns[mask]
# 分组
quantiles = pd.qcut(pred, n_quantiles, labels=False, duplicates='drop')
groups = pd.DataFrame({
'prediction': pred,
'return': ret,
'group': quantiles
})
# 计算各组统计
group_stats = []
for i in range(n_quantiles):
group_data = groups[groups['group'] == i]['return']
stats_i = {
'group': i + 1,
'n_samples': len(group_data),
'mean_return': group_data.mean(),
'std_return': group_data.std(),
'median_return': group_data.median(),
'mean_prediction': groups[groups['group'] == i]['prediction'].mean()
}
group_stats.append(stats_i)
group_stats_df = pd.DataFrame(group_stats)
# 多空收益
long_return = groups[groups['group'] == n_quantiles - 1]['return'].mean()
short_return = groups[groups['group'] == 0]['return'].mean()
long_short_return = long_return - short_return
# 胜率
long_win_rate = (groups[groups['group'] == n_quantiles - 1]['return'] > 0).mean()
short_win_rate = (groups[groups['group'] == 0]['return'] < 0).mean()
return {
'group_stats': group_stats_df,
'long_return': long_return,
'short_return': short_return,
'long_short_return': long_short_return,
'long_win_rate': long_win_rate,
'short_win_rate': short_win_rate
}
# 使用示例
np.random.seed(42)
n = 5000
pred_quantile = np.random.randn(n)
ret_quantile = 0.05 * pred_quantile + np.random.randn(n) * 0.5
result = quantile_analysis(pred_quantile, ret_quantile, n_quantiles=5)
print("=== 分层回测结果 ===\n")
print("各组统计:")
print(result['group_stats'][['group', 'n_samples', 'mean_prediction', 'mean_return', 'std_return']])
print(f"\n多头收益 (Group 5): {result['long_return']:.4f}")
print(f"空头收益 (Group 1): {result['short_return']:.4f}")
print(f"多空收益: {result['long_short_return']:.4f}")
print(f"多头胜率: {result['long_win_rate']:.2%}")
print(f"空头胜率: {result['short_win_rate']:.2%}")6.3 分层回测可视化
def plot_quantile_analysis(result):
"""绘制分层回测结果"""
group_stats = result['group_stats']
fig, axes = plt.subplots(1, 2, figsize=(14, 4))
# 左图:各组平均收益
ax1 = axes[0]
colors = ['red', 'orange', 'yellow', 'lightgreen', 'green']
ax1.bar(group_stats['group'], group_stats['mean_return'], color=colors, alpha=0.7)
ax1.axhline(y=0, color='black', linestyle='--', linewidth=0.5)
ax1.set_xlabel('Quantile Group')
ax1.set_ylabel('Mean Return')
ax1.set_title('Mean Return by Quantile Group')
ax1.grid(True, alpha=0.3, axis='y')
# 添加数值标签
for i, row in group_stats.iterrows():
ax1.text(row['group'], row['mean_return'], f"{row['mean_return']:.4f}",
ha='center', va='bottom' if row['mean_return'] > 0 else 'top')
# 右图:各组预测值与收益
ax2 = axes[1]
ax2.scatter(group_stats['mean_prediction'], group_stats['mean_return'],
s=group_stats['n_samples']/10, alpha=0.6, c=range(5), cmap='RdYlGn')
ax2.set_xlabel('Mean Prediction')
ax2.set_ylabel('Mean Return')
ax2.set_title('Prediction vs Return by Group')
ax2.grid(True, alpha=0.3)
# 添加回归线
z = np.polyfit(group_stats['mean_prediction'], group_stats['mean_return'], 1)
p = np.poly1d(z)
ax2.plot(group_stats['mean_prediction'], p(group_stats['mean_prediction']),
"r--", alpha=0.8, linewidth=2)
plt.tight_layout()
plt.show()
plot_quantile_analysis(result)7. IC 衰减分析
7.1 不同持仓期的 IC
预测不同持有期的收益能力。
def ic_decay_analysis(predictions, returns_dict, max_horizon=20):
"""
IC 衰减分析
参数:
predictions: 预测值
returns_dict: 字典,key 为持仓期,value 为对应收益率
max_horizon: 最大分析周期
返回:
decay_df: IC 衰减数据表
"""
results = []
for horizon in range(1, max_horizon + 1):
if horizon in returns_dict:
ret = returns_dict[horizon]
ic, p_value = calculate_ic(predictions, ret)
rank_ic, _ = calculate_rank_ic(predictions, ret)
results.append({
'horizon': horizon,
'ic': ic,
'rank_ic': rank_ic,
'p_value': p_value
})
return pd.DataFrame(results)
# 生成不同持仓期的收益
n = 1000
pred_decay = np.random.randn(n)
returns_dict = {}
for h in range(1, 21):
# 持仓期越长,预测能力越弱
signal_decay = 1 / np.sqrt(h)
returns_dict[h] = signal_decay * 0.3 * pred_decay + np.random.randn(n) * 0.5
# 分析
decay_df = ic_decay_analysis(pred_decay, returns_dict, max_horizon=20)
print("IC 衰减分析:")
print(decay_df.head(10))7.2 IC 衰减可视化
def plot_ic_decay(decay_df):
"""绘制 IC 衰减曲线"""
fig, ax = plt.subplots(figsize=(12, 5))
# IC 和 Rank IC
ax.plot(decay_df['horizon'], decay_df['ic'], marker='o', label='IC', linewidth=2)
ax.plot(decay_df['horizon'], decay_df['rank_ic'], marker='s', label='Rank IC', linewidth=2)
# 零线
ax.axhline(y=0, color='black', linestyle='--', linewidth=0.5)
# 显著性标记
sig_points = decay_df[decay_df['p_value'] < 0.05]
ax.scatter(sig_points['horizon'], sig_points['ic'], marker='o', s=100,
facecolors='none', edgecolors='green', linewidths=2, label='Significant (p<0.05)')
ax.set_xlabel('Holding Period (Days)')
ax.set_ylabel('IC')
ax.set_title('IC Decay Analysis')
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
plot_ic_decay(decay_df)8. 评估标准总结
8.1 IC 评估阈值
| IC 水平 | 评价 | 可交易性 | 说明 |
|---|---|---|---|
| < 0.01 | 基本无效 | 不可交易 | 几乎无预测能力 |
| 0.01 - 0.02 | 很弱 | 不可交易 | 难以覆盖成本 |
| 0.02 - 0.03 | 弱 | 勉强 | 需要精细风控 |
| 0.03 - 0.05 | 中等 | 可考虑 | 有一定价值 |
| 0.05 - 0.08 | 良好 | 可交易 | 值得开发 |
| > 0.08 | 优秀 | 强烈推荐 | 稀有信号 |
8.2 ICIR 评估阈值
| ICIR 水平 | 评价 | 稳定性 |
|---|---|---|
| < 0.5 | 较差 | 不稳定 |
| 0.5 - 1.0 | 一般 | 轻度稳定 |
| 1.0 - 1.5 | 良好 | 稳定 |
| > 1.5 | 优秀 | 非常稳定 |
8.3 分层回测评估
| 指标 | 可交易阈值 | 良好阈值 |
|---|---|---|
| 多空收益 (日频) | > 0.05% | > 0.10% |
| 多头胜率 | > 52% | > 55% |
| 空头胜率 | > 52% | > 55% |
| Group 单调性 | 满足 | 满足 |
8.4 综合评估框架
def comprehensive_evaluation(predictions, returns, n_quantiles=5):
"""
综合评估模型
返回评估报告
"""
report = {}
# 1. IC 指标
ic, ic_p = calculate_ic(predictions, returns)
rank_ic, _ = calculate_rank_ic(predictions, returns)
report['ic'] = ic
report['rank_ic'] = rank_ic
report['ic_significant'] = ic_p < 0.05
# 2. 分层回测
quant_result = quantile_analysis(predictions, returns, n_quantiles)
report['long_short_return'] = quant_result['long_short_return']
report['long_win_rate'] = quant_result['long_win_rate']
# 3. 评估结论
conclusions = []
# IC 评估
if ic > 0.08:
conclusions.append("✅ IC 优秀")
elif ic > 0.05:
conclusions.append("✅ IC 良好")
elif ic > 0.03:
conclusions.append("⚠️ IC 中等")
elif ic > 0.02:
conclusions.append("⚠️ IC 偏低")
else:
conclusions.append("❌ IC 不足")
# Rank IC 评估
if rank_ic > 0.05:
conclusions.append("✅ Rank IC 良好")
else:
conclusions.append("⚠️ Rank IC 偏低")
# 多空收益评估
ls_return = quant_result['long_short_return']
if ls_return > 0.001:
conclusions.append("✅ 多空收益高")
elif ls_return > 0.0005:
conclusions.append("✅ 多空收益尚可")
else:
conclusions.append("⚠️ 多空收益不足")
report['conclusions'] = conclusions
# 打印报告
print("=" * 50)
print(" 模型综合评估报告")
print("=" * 50)
print(f"\n【IC 指标】")
print(f" IC: {ic:.4f}")
print(f" Rank IC: {rank_ic:.4f}")
print(f" 显著性: {'是' if ic_p < 0.05 else '否'} (p={ic_p:.4f})")
print(f"\n【分层回测】")
print(f" 多空收益: {ls_return:.4f}")
print(f" 多头胜率: {quant_result['long_win_rate']:.2%}")
print(f" 空头胜率: {quant_result['short_win_rate']:.2%}")
print(f"\n【评估结论】")
for conclusion in conclusions:
print(f" {conclusion}")
print("=" * 50)
return report
# 使用示例
np.random.seed(42)
n_eval = 5000
pred_eval = np.random.randn(n_eval)
ret_eval = 0.08 * pred_eval + np.random.randn(n_eval) * 0.5
report = comprehensive_evaluation(pred_eval, ret_eval, n_quantiles=5)核心知识点总结
评估指标体系
一级指标:
├── IC: 预测相关性
├── Rank IC: 排序相关性(更稳健)
├── ICIR: IC 稳定性
└── 分层回测: 实际交易价值
二级指标:
├── IC 统计显著性: p 值
├── IC 正向比例: P(IC > 0)
├── 滚动 IC: 时序稳定性
└── IC 衰减: 不同持仓期表现
评估标准
| 场景 | IC 要求 | Rank IC 要求 | ICIR 要求 |
|---|---|---|---|
| 研究探索 | > 0.03 | > 0.03 | > 0.5 |
| 实战候选 | > 0.05 | > 0.05 | > 1.0 |
| 优秀策略 | > 0.08 | > 0.08 | > 1.5 |
常见陷阱
- 只看 IC,不看 ICIR → 高 IC 可能不稳定
- 只看单点 IC → 需要看滚动 IC 和时序稳定性
- 忽略统计显著性 → 小样本的 IC 可能是随机噪声
- 不做分层回测 → IC 高不一定有交易价值
- 过度拟合测试集 → OOS IC 才是真实表现
下一节: 05-特征重要性分析.md - 学习如何分析和解释特征重要性。