单因子测试框架:IC分析、IR分析、分层回测、多空组合收益
做量化投资这些年,我见过太多人拿着一个因子就往策略里塞。结果呢?回测曲线漂亮得不行,实盘一跑就崩。说白了,就是没做好单因子测试。
单因子测试,是因子研究的基石。你想想看,连一个因子都测不明白,怎么敢把它放进组合里?今天我就把这块硬骨头啃下来,带你走一遍完整的测试流程。
IC分析:因子与收益的相关性
IC,全称是Information Coefficient,信息系数。它衡量的是因子值与未来收益之间的相关性。我习惯用Spearman秩相关系数,因为它对极端值不敏感。
具体怎么算?很简单。在每个截面期(比如每个月末),计算所有股票的因子排名和未来收益排名,然后求这两个排名的相关系数。
import pandas as pd
import numpy as np
from scipy.stats import spearmanr
def calc_ic(factor_series, return_series):
"""
计算截面IC值
factor_series: 因子值序列
return_series: 未来收益序列
"""
# 去掉缺失值
valid = pd.concat([factor_series, return_series], axis=1).dropna()
if len(valid) < 30:
return np.nan
ic, p_value = spearmanr(valid.iloc[:, 0], valid.iloc[:, 1])
return ic
嗯,这里要注意:IC值在-1到1之间。正IC说明因子值越高,未来收益越好。负IC则相反。绝对值越大,预测能力越强。
核心要点:
- IC > 0.05:有正向预测能力
- IC < -0.05:有负向预测能力
- |IC| > 0.1:预测能力较强
- IC接近0:基本没有预测能力
我在项目中遇到过一个问题:IC值忽高忽低,今天0.08,明天-0.03。后来发现是数据没做中性化处理。行业和市值的影响太大了,必须剔除掉。
IR分析:IC的稳定性
IC看的是预测能力,IR看的是这个能力稳不稳定。IR = IC的均值 / IC的标准差。说白了,就是信噪比。
你想想看,一个因子IC均值0.06,但标准差0.12,IR只有0.5。另一个因子IC均值0.04,但标准差0.02,IR有2.0。哪个更靠谱?显然是后者。稳定的微弱信号,比忽高忽强的信号更有价值。
def calc_ir(ic_series):
"""
计算IR值
ic_series: 时间序列的IC值
"""
mean_ic = ic_series.mean()
std_ic = ic_series.std()
if std_ic == 0 or np.isnan(std_ic):
return np.nan
ir = mean_ic / std_ic
return ir
实战经验:
我个人习惯把IR大于0.5作为筛选门槛。低于这个值,因子基本没法用。当然,高频因子可以放宽到0.3,但低频因子我建议至少0.8。
IR还有一个妙用:可以用来做因子择时。当IR持续走低时,说明因子失效了,该减仓了。我曾经靠这个躲过了一次因子集体回撤,少亏了十几个点。
分层回测:因子单调性检验
IC和IR看的是整体相关性,但还不够。我们需要知道:因子值从低到高,收益是不是单调递增或递减?这就是分层回测要解决的问题。
做法很简单:在每个截面期,把所有股票按因子值分成10组(或者5组)。然后计算每组在未来一段时间的平均收益。最后把所有截面期的收益汇总,画出净值曲线。
def layer_backtest(factor_df, return_df, n_groups=10):
"""
分层回测
factor_df: 因子值矩阵,行是时间,列是股票
return_df: 收益矩阵
"""
results = {}
for date in factor_df.index:
# 获取当期的因子值和收益
factors = factor_df.loc[date].dropna()
returns = return_df.loc[date]
# 按因子值分组
sorted_factors = factors.sort_values()
group_size = len(sorted_factors) // n_groups
for i in range(n_groups):
start = i * group_size
end = (i + 1) * group_size if i < n_groups - 1 else len(sorted_factors)
group_stocks = sorted_factors.index[start:end]
group_return = returns[group_stocks].mean()
results.setdefault(f'Group_{i+1}', []).append(group_return)
return pd.DataFrame(results)
避坑指南:
我曾经犯过一个错误:分层回测时没考虑停牌股票。结果某组里全是停牌股,收益曲线一条直线,差点误导了我的判断。记住,回测前一定要做数据清洗,剔除停牌、ST、新股。
好的分层结果应该是:Group_1到Group_10的收益单调递增或递减。如果中间有交叉,说明因子在某些区间失效了。我见过最离谱的是U型分布,两头高中间低,这种因子基本没法用。
多空组合收益:因子价值的终极检验
前面那些都是铺垫,多空组合收益才是真刀真枪的检验。它的逻辑是:做多因子值最高的那组,做空因子值最低的那组,看看能不能稳定赚钱。
为什么说这是终极检验?因为它剔除了市场整体走势的影响。不管大盘涨跌,只要因子有效,多空组合就应该有正收益。
def long_short_portfolio(factor_df, return_df, top_pct=0.1, bottom_pct=0.1):
"""
多空组合收益计算
"""
long_returns = []
short_returns = []
for date in factor_df.index:
factors = factor_df.loc[date].dropna()
returns = return_df.loc[date]
# 取因子值最高和最低的股票
n_stocks = len(factors)
n_long = int(n_stocks * top_pct)
n_short = int(n_stocks * bottom_pct)
sorted_factors = factors.sort_values()
long_stocks = sorted_factors.index[-n_long:]
short_stocks = sorted_factors.index[:n_short]
# 计算多空收益
long_ret = returns[long_stocks].mean()
short_ret = returns[short_stocks].mean()
long_returns.append(long_ret)
short_returns.append(short_ret)
# 多空组合收益 = 多头收益 - 空头收益
long_short = pd.Series(long_returns) - pd.Series(short_returns)
return long_short
多空组合的关键指标:
- 年化收益率:至少5%以上才算有效
- 夏普比率:大于1是及格线
- 最大回撤:控制在10%以内
- 胜率:大于60%比较理想
我见过最经典的一个案例:某个动量因子,IC只有0.03,IR不到0.5,但多空组合年化收益做到了8%,夏普1.2。为什么?因为它的IC虽然低,但非常稳定,而且分层单调性极好。所以你看,单一指标不能说明全部问题,要综合来看。
知识体系总览
下面这张图,是我做单因子测试的完整流程。你可以把它当作一个检查清单,每次测试都走一遍。
这套框架我用了好几年,踩过不少坑,也积累了一些心得。核心就一句话:IC看方向,IR看稳定,分层看单调,多空看收益。四个维度都过了,这个因子才敢放心用。
我的个人习惯:
每次测试完,我会把结果整理成一个表格,包含IC均值、IR、分层收益差、多空夏普等指标。然后跟历史数据对比,看看有没有异常。如果某个指标突然变差,我会深入排查原因,而不是直接放弃因子。
好了,单因子测试这块就讲到这里。记住,测试不是目的,目的是找到真正有效的因子。下一章我们会聊因子组合优化,到时候这些测试结果就是你的原材料。