单因子测试框架:回测框架搭建、IC/IR分析、分组收益分析、多空组合收益计算
做量化投资的朋友都知道,因子研究是策略的基石。但光有因子不行,你得知道它到底有没有用。怎么判断?靠的就是单因子测试框架。今天我就把这套框架的核心模块拆开来讲,包括回测框架怎么搭、IC/IR怎么算、分组收益怎么看、多空组合怎么搞。
说实话,我入行头两年踩过不少坑。最惨的一次,一个看起来年化收益15%的因子,实盘跑下来直接亏了3个月。后来复盘才发现,是回测框架里有个不起眼的未来函数bug。嗯,从那以后我对框架的每个细节都格外较真。
一、回测框架搭建:地基要稳
回测框架说白了就是模拟历史交易。你拿着因子信号,在历史数据上跑一遍,看看如果当时按这个信号买卖,能赚多少钱。框架的核心就三个东西:数据、信号、交易逻辑。
数据层:至少需要行情数据(开盘价、收盘价、成交量等)和财务数据(PE、PB、ROE等)。我习惯用日频数据做回测,频率太高容易过拟合,太低又丢失信号。
信号层:因子值就是信号。比如你算出一个“低波动因子”,那么每个月末按波动率排序,选波动最小的30只股票,这就是信号。
交易层:什么时候买、什么时候卖、手续费怎么扣、涨跌停怎么办。这些细节决定了回测的逼真程度。
下面是一个极简的回测框架代码骨架:
import pandas as pd
import numpy as np
def simple_backtest(factor_df, price_df, period=20):
"""
factor_df: 因子值,index为日期,columns为股票代码
price_df: 价格数据,同上
period: 调仓周期(交易日)
"""
# 每月末调仓
rebalance_dates = factor_df.index[::period]
# 存储每日收益
daily_returns = []
for i in range(len(rebalance_dates)-1):
start = rebalance_dates[i]
end = rebalance_dates[i+1]
# 获取当期因子值
factor_slice = factor_df.loc[start]
# 选前20%的股票
n_stocks = int(len(factor_slice.dropna()) * 0.2)
selected = factor_slice.nlargest(n_stocks).index
# 计算持有期收益
ret = price_df.loc[end] / price_df.loc[start] - 1
portfolio_ret = ret[selected].mean()
daily_returns.append(portfolio_ret)
return pd.Series(daily_returns, index=rebalance_dates[:-1])
这个框架虽然简单,但核心逻辑都有了。实际项目中,你还要处理停牌、退市、新股等特殊情况。我曾经因为没处理停牌,回测收益虚高了8个百分点,教训深刻。
二、IC/IR分析:因子的预测能力有多强
IC(信息系数)衡量的是因子值和未来收益之间的相关性。简单说,因子值越高,未来收益是不是也越高?如果是,IC就是正的。
IC的计算方式有两种:
- Spearman秩相关系数:对因子值和收益分别排序,算排序后的相关性。这是最常用的,因为它对异常值不敏感。
- Pearson相关系数:直接算因子值和收益的线性相关性。容易受极端值影响,我一般不用。
IR(信息比率)是IC的均值除以IC的标准差。它衡量的是因子预测能力的稳定性。IR越高,说明因子不仅有效,而且稳定有效。
来看代码实现:
def calc_ic_ir(factor_df, ret_df):
"""
计算每日IC和IR
factor_df: 因子值
ret_df: 未来一期收益
"""
ic_series = []
for date in factor_df.index:
f = factor_df.loc[date].dropna()
r = ret_df.loc[date].dropna()
# 取交集
common = f.index.intersection(r.index)
if len(common) < 30: # 样本太少不计算
continue
f_common = f[common]
r_common = r[common]
# Spearman秩相关
ic = f_common.corr(r_common, method='spearman')
ic_series.append(ic)
ic_series = pd.Series(ic_series, index=factor_df.index[:len(ic_series)])
# IR = IC均值 / IC标准差
ir = ic_series.mean() / ic_series.std()
return ic_series, ir
我个人习惯把IC的时序图画出来,看看它是不是稳定在0附近波动。如果IC忽正忽负,说明因子不稳定,IR会很低。一般IR大于0.5就算不错了,大于1.0就是很优秀的因子。
三、分组收益分析:因子能区分好坏股票吗
分组收益分析是最直观的因子检验方法。你把股票按因子值从大到小分成10组(或5组),然后看每组未来的平均收益。如果第1组(因子值最大)的收益明显高于第10组(因子值最小),说明因子有效。
分组分析能回答三个问题:
- 单调性:收益是不是从第1组到第10组单调递减?如果是,说明因子有很好的区分度。
- 区分度:第1组和第10组的收益差距有多大?差距越大,因子越强。
- 稳定性:这种分组效果在不同时间段是否一致?
代码实现如下:
def group_analysis(factor_df, ret_df, n_groups=10):
"""
分组收益分析
返回每组在每个调仓期的收益
"""
results = {}
for date in factor_df.index:
f = factor_df.loc[date].dropna()
r = ret_df.loc[date].dropna()
common = f.index.intersection(r.index)
if len(common) < n_groups * 10:
continue
f_common = f[common]
r_common = r[common]
# 按因子值分组
f_sorted = f_common.sort_values()
group_size = len(f_sorted) // n_groups
for g in range(n_groups):
start = g * group_size
end = (g+1) * group_size if g < n_groups-1 else len(f_sorted)
group_stocks = f_sorted.index[start:end]
group_ret = r_common[group_stocks].mean()
if g not in results:
results[g] = []
results[g].append(group_ret)
# 转成DataFrame
result_df = pd.DataFrame(results)
result_df.index = factor_df.index[:len(result_df)]
return result_df
你想想看,如果第1组年化收益20%,第10组年化收益-5%,多空组合(做多第1组、做空第10组)的年化收益就是25%。这就是我们下一步要讲的多空组合。
四、多空组合收益计算:真正的alpha来源
多空组合是因子测试的终极检验。它做多因子值最高的组,做空因子值最低的组,把市场风险对冲掉,剩下的就是因子的纯alpha收益。
计算多空组合收益很简单:
def long_short_return(group_returns, top_pct=0.1, bottom_pct=0.1):
"""
计算多空组合收益
group_returns: 分组收益DataFrame,每列是一组
top_pct: 做多的组比例(前10%)
bottom_pct: 做空的组比例(后10%)
"""
n_groups = group_returns.shape[1]
top_n = int(n_groups * top_pct)
bottom_n = int(n_groups * bottom_pct)
# 做多前top_n组,做空后bottom_n组
long_ret = group_returns.iloc[:, :top_n].mean(axis=1)
short_ret = group_returns.iloc[:, -bottom_n:].mean(axis=1)
ls_ret = long_ret - short_ret
return ls_ret
多空组合的年化收益、夏普比率、最大回撤,是衡量因子质量的核心指标。我一般要求多空组合的年化收益至少5%以上,夏普比率大于0.5,最大回撤不超过15%。
这里有个细节要注意:多空组合的收益计算要考虑做空成本。A股融券成本不低,而且很多股票根本融不到。所以实际做多空组合时,我通常用“多空收益差”来评估因子,而不是真的去做空。
五、知识体系总览
说了这么多,我把单因子测试框架的核心逻辑画成了一张图,方便你理解各个模块之间的关系:
从这张图可以看得很清楚:数据层、信号层、交易层构成了回测引擎的基础。回测跑完之后,我们通过IC/IR分析、分组收益分析、多空组合收益计算三个维度来评估因子。三者相互印证,缺一不可。
我个人习惯是先看分组收益的单调性,如果单调性好,再看IC/IR是否稳定,最后用多空组合算算实际能赚多少钱。这个顺序能帮你快速过滤掉大部分无效因子。
好了,单因子测试框架的核心内容就这些。框架搭好了,后面的事情就顺了。记住一句话:回测是工具,不是目的。真正重要的是你从回测中理解因子的行为逻辑。