第四章 因子评价体系:IC/IR分析、分层回测、多空组合收益计算
因子挖出来,不能直接扔到模型里。你得先问问它:这玩意儿到底有没有预测能力?
我见过太多人,挖了一堆因子,回测曲线漂亮得不行,一上实盘就崩。为什么?因为评价体系没搭好。今天我们就来聊聊,怎么用Python搭一套靠谱的因子评价框架。
4.1 因子评价的核心逻辑
说白了,因子评价就是回答三个问题:
- 这个因子和未来收益有没有关系?——IC分析
- 这个关系稳不稳定?——IR分析
- 按这个因子选股,能不能赚钱?——分层回测 & 多空组合
我个人习惯,先跑IC,再看分层回测。IC不过关的因子,后面就不用看了,省时间。
核心公式:
IC = corr(因子值, 未来一期收益)
IR = mean(IC) / std(IC)
嗯,这里要注意:IC算的是截面相关性,不是时间序列。你想想看,每个时间点,所有股票的因子值和未来收益算一个相关系数,这才叫截面IC。
4.2 用Python搭建IC/IR分析模块
先上代码。我习惯用pandas和scipy,干净利落。
import pandas as pd
import numpy as np
from scipy.stats import spearmanr
def calc_ic(factor_df, return_df, method='spearman'):
"""
计算每日截面IC
factor_df: index=日期, columns=股票代码, values=因子值
return_df: index=日期, columns=股票代码, values=未来一期收益
"""
dates = factor_df.index
ic_series = pd.Series(index=dates, dtype=float)
for dt in dates:
f = factor_df.loc[dt].dropna()
r = return_df.loc[dt].dropna()
common = f.index.intersection(r.index)
if len(common) < 30:
continue
if method == 'spearman':
ic, _ = spearmanr(f[common], r[common])
else:
ic = f[common].corr(r[common])
ic_series[dt] = ic
return ic_series
def calc_ir(ic_series):
"""计算IR = mean(IC) / std(IC)"""
return ic_series.mean() / ic_series.std()
我在项目中遇到过一个问题:有些日期股票数量太少,算出来的IC噪音很大。所以我加了个阈值,少于30只股票就不算。这个经验值你可以根据自己数据调整。
小技巧: IC的绝对值在0.05以上就算不错了。如果平均IC能到0.1,那这个因子相当强。IR大于1.0,说明因子稳定性可以接受。
4.3 分层回测:看看因子怎么分组
IC告诉你相关性,但实际交易中,我们更关心:按因子排序,买前10%卖后10%,能赚多少?
分层回测就是干这个的。每个月(或每天)按因子值把股票分成N组,分别计算每组未来的收益。
def layer_backtest(factor_df, return_df, n_layers=10):
"""
分层回测
返回每组的累计收益序列
"""
dates = factor_df.index
layer_returns = {i: [] for i in range(n_layers)}
for dt in dates:
f = factor_df.loc[dt].dropna()
r = return_df.loc[dt].dropna()
common = f.index.intersection(r.index)
if len(common) < n_layers * 10:
continue
# 按因子值排序,分成n_layers组
sorted_stocks = f[common].sort_values()
group_size = len(sorted_stocks) // n_layers
for i in range(n_layers):
start = i * group_size
end = (i + 1) * group_size if i < n_layers - 1 else len(sorted_stocks)
group_stocks = sorted_stocks.index[start:end]
group_return = r[group_stocks].mean()
layer_returns[i].append(group_return)
# 转成DataFrame
result = pd.DataFrame(layer_returns, index=dates[:len(layer_returns[0])])
result.columns = [f'Layer_{i+1}' for i in range(n_layers)]
return result
我曾经犯过一个错:分层时没考虑停牌股票。结果回测收益虚高,因为停牌股在复牌后往往有补涨。后来我加了过滤条件,停牌期间不纳入分组。
避坑指南: 分层回测一定要考虑交易成本。我见过一个因子,分层收益单调性很好,但加上千分之一的印花税和滑点,收益直接腰斩。所以,别只看毛收益。
4.4 多空组合收益计算
多空组合,说白了就是做多第一层,做空最后一层。这是检验因子选股能力的黄金标准。
def long_short_return(layer_returns, long_layer=0, short_layer=-1):
"""
计算多空组合收益
long_layer: 做多的层索引(默认第1层)
short_layer: 做空的层索引(默认最后一层)
"""
long_ret = layer_returns.iloc[:, long_layer]
short_ret = layer_returns.iloc[:, short_layer]
# 多空收益 = 多头收益 - 空头收益
ls_ret = long_ret - short_ret
# 累计收益
cum_ret = (1 + ls_ret).cumprod()
# 年化收益率
annual_ret = (cum_ret.iloc[-1] ** (252 / len(cum_ret))) - 1
# 夏普比率
sharpe = ls_ret.mean() / ls_ret.std() * np.sqrt(252)
return {
'cumulative_return': cum_ret,
'annual_return': annual_ret,
'sharpe_ratio': sharpe,
'max_drawdown': (cum_ret / cum_ret.cummax() - 1).min()
}
你想想看,如果多空组合的夏普比率能到1.5以上,这个因子基本可以实盘了。当然,前提是样本外测试也过得了关。
4.5 因子评价框架图
下面这张图,是我自己总结的因子评价流程。每次开发新因子,我都按这个走一遍。
4.6 综合评价报告
最后,我把所有指标汇总成一个评价报告。这样一眼就能看出因子好坏。
def factor_evaluation_report(factor_df, return_df, n_layers=10):
"""生成因子综合评价报告"""
# 1. IC分析
ic_series = calc_ic(factor_df, return_df)
ir = calc_ir(ic_series)
# 2. 分层回测
layer_ret = layer_backtest(factor_df, return_df, n_layers)
# 3. 多空组合
ls_result = long_short_return(layer_ret)
# 4. 输出报告
report = {
'IC_mean': ic_series.mean(),
'IC_std': ic_series.std(),
'IR': ir,
'IC_positive_ratio': (ic_series > 0).mean(),
'Layer_returns': layer_ret,
'Long_short_annual_return': ls_result['annual_return'],
'Long_short_sharpe': ls_result['sharpe_ratio'],
'Long_short_max_drawdown': ls_result['max_drawdown']
}
return report
实战经验: 我一般要求因子满足以下条件才考虑实盘:
- IC均值绝对值 > 0.03
- IR > 0.8
- 分层收益单调性明显(前几层收益高于后几层)
- 多空组合年化收益 > 8%
- 多空组合最大回撤 < 15%
记住,因子评价不是一次性工作。随着市场变化,因子的表现也会变。我建议每个月重新跑一次评价,看看因子有没有衰减。如果IC持续走低,就该考虑替换了。
好了,这套框架你拿去用。代码可以直接跑,但参数要根据你的数据调整。有什么问题,欢迎交流。
公众号:蓝海资料掘金营,微信deep3321