单因子测试框架:IC分析、分组回测、多空组合收益计算
做可转债量化,最核心的一步是什么?
我个人觉得,就是单因子测试。你想想看,你辛辛苦苦想出一个因子,比如「转股溢价率」或者「双低值」,它到底有没有预测能力?能不能帮你赚钱?
嗯,光靠拍脑袋可不行。我们需要一套科学的框架来验证。这套框架,说白了就是三个步骤:IC分析、分组回测、多空组合收益计算。
我在项目中遇到过不少新手,上来就搞复杂的机器学习模型,结果因子本身就不靠谱,模型再花哨也是白搭。所以,先把单因子测试这关过了,后面的事才谈得上。
一、IC分析:因子的「信号强度」检测
IC,全称是Information Coefficient,信息系数。它衡量的是因子值与未来收益率之间的相关性。
说白了,就是看你的因子能不能「预测」未来。如果因子值高的转债,未来涨得好;因子值低的,未来跌得多,那这个因子就有正IC。
1.1 计算方式
我习惯用Spearman秩相关系数,而不是普通的Pearson相关系数。为什么?因为秩相关对极端值不敏感,更稳健。可转债市场经常有妖债,一个极端值就能把Pearson相关系数带偏。
核心公式:
IC_t = corr(rank(Factor_t), rank(Return_{t+1}))
其中,Factor_t 是 t 时刻的因子值,Return_{t+1} 是 t+1 时刻的收益率。
代码实现其实很简单,我贴一段我常用的Python代码:
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_mask = factor_series.notna() & return_series.notna()
factor = factor_series[valid_mask]
ret = return_series[valid_mask]
# 计算秩相关系数
ic, p_value = spearmanr(factor, ret)
return ic, p_value
# 示例:计算某一天的IC
daily_ic, p_val = calc_ic(df['双低值'], df['next_day_return'])
print(f'IC值: {daily_ic:.4f}, P值: {p_val:.4f}')
1.2 IC的统计指标
单期IC有噪音,我们需要看一段时间的IC表现。我通常会关注以下几个指标:
| 指标 | 含义 | 合格标准 |
|---|---|---|
| IC均值 | 一段时间内IC的平均值 | 绝对值 > 0.02 |
| IC标准差 | IC的波动程度 | 越小越好 |
| IR (IC_Ratio) | IC均值 / IC标准差 | 绝对值 > 0.5 |
| IC胜率 | IC为正的天数占比 | > 55% |
我的经验:IR比IC均值更重要。一个因子IC均值0.03但标准差很大,说明时灵时不灵,不太靠谱。我一般要求IR至少0.5以上,才考虑进一步测试。
二、分组回测:看因子「分层」效果
IC分析告诉你因子有没有预测能力,但没告诉你这个能力有多强、是否稳定。分组回测就是解决这个问题的。
我曾经犯过一个错误:一个因子IC看起来不错,但分组回测发现,只有第一组和最后一组有区分度,中间几组完全乱序。这种因子其实很脆弱,换个时间段可能就失效了。
2.1 分组方法
我习惯按因子值从小到大排序,等分成5组或10组。每组作为一个投资组合,定期调仓。
def group_backtest(df, factor_col, ret_col, groups=5):
"""
分组回测函数
df: 包含因子值和未来收益率的DataFrame
factor_col: 因子列名
ret_col: 未来收益率列名
groups: 分组数
"""
# 按因子值排序分组
df['group'] = pd.qcut(df[factor_col], groups, labels=False)
# 计算每组平均收益
group_returns = df.groupby('group')[ret_col].mean()
# 计算多空组合收益(第一组 - 最后一组)
long_short_ret = group_returns.iloc[0] - group_returns.iloc[-1]
return group_returns, long_short_ret
2.2 分组回测的评判标准
好的因子,分组收益应该是单调的。什么意思?就是组1收益 > 组2收益 > 组3收益 > ... 或者反过来。
- 单调性:各组收益严格递增或递减
- 区分度:第一组和最后一组的收益差距要足够大
- 稳定性:不同时间段(牛熊市)都能保持单调
避坑指南:我曾经遇到一个因子,全样本回测表现很好,但分年度看,2019年有效,2020年就失效了。所以一定要做滚动窗口测试,看看因子的稳定性。
三、多空组合收益计算
这是单因子测试的「终极检验」。多空组合,就是做多因子值最高的组,做空因子值最低的组。如果这个组合能稳定赚钱,说明因子真的有价值。
3.1 构建多空组合
以5分组为例:
def long_short_portfolio(df, factor_col, ret_col):
"""
构建多空组合
做多组4(因子值最高),做空组0(因子值最低)
"""
df['group'] = pd.qcut(df[factor_col], 5, labels=False)
# 多空组合每日收益
long_ret = df[df['group'] == 4].groupby('date')[ret_col].mean()
short_ret = df[df['group'] == 0].groupby('date')[ret_col].mean()
# 多空组合收益
ls_ret = long_ret - short_ret
# 累计收益
cum_ret = (1 + ls_ret).cumprod()
return ls_ret, cum_ret
3.2 多空组合的评价指标
| 指标 | 计算方式 | 理想值 |
|---|---|---|
| 年化收益率 | 日均收益 * 250 | > 5% |
| 年化波动率 | 日均标准差 * sqrt(250) | < 15% |
| 夏普比率 | 年化收益 / 年化波动 | > 1.0 |
| 最大回撤 | 累计收益曲线从峰值回落的最大幅度 | < 10% |
核心观点:多空组合的夏普比率如果超过1.5,这个因子基本就是「金矿」级别的了。我在实盘中用的几个核心因子,夏普都在1.2-1.8之间。
四、知识体系总览
下面这张图,是我自己总结的单因子测试框架。每次开发新因子,我都会按这个流程走一遍。
五、实战中的注意事项
框架讲完了,我再补充几个实战中容易踩的坑:
- 幸存者偏差:一定要包含已经退市、强赎的转债。我见过有人只拿存续转债回测,结果因子表现好得离谱,一实盘就露馅。
- 前视偏差:计算因子时只能用历史数据,不能用未来信息。比如用「当日收盘价」计算因子,但回测时用了「次日开盘价」的收益率,这就错了。
- 调仓频率:不是越高越好。日频调仓交易成本太高,我一般用周频或双周频。
我的习惯:每次测试新因子,我都会先做IC分析,如果IC均值绝对值小于0.01,直接放弃,不浪费时间做分组回测。这能帮我快速筛选掉90%的无效因子。
好了,单因子测试框架就讲到这里。这套方法我用了好几年,从最初的简单IC到现在的完整流程,每一步都是踩坑踩出来的。你按照这个框架走,至少能保证你的因子是「真有效」,而不是「碰巧有效」。