单因子分析基础:因子计算、IC分析、因子收益率、分组回测
单因子分析,说白了就是回答一个问题:这个因子到底有没有用?
我刚开始做量化那会儿,总觉得因子越多越好。后来被市场狠狠教育了一顿——一堆垃圾因子堆在一起,策略照样亏钱。从那以后,我养成了一个习惯:每个因子上线前,必须过一遍单因子分析四件套。
1. 因子计算:把原始数据变成信号
因子计算是第一步。你得把原始行情数据,加工成一个数值序列。这个数值就是因子值,代表你对未来收益的某种预测。
举个例子,最简单的动量因子:
import pandas as pd
import numpy as np
def calc_momentum_factor(close, window=20):
"""
计算过去20天的收益率作为动量因子
"""
# 因子值 = 当前收盘价 / 20天前收盘价 - 1
factor = close / close.shift(window) - 1
return factor
# 使用示例
close_prices = pd.Series([100, 102, 101, 105, 110, ...])
momentum = calc_momentum_factor(close_prices)
print(momentum.tail())
我的习惯:因子计算时,一定要处理缺失值和极端值。我见过有人直接用原始因子跑回测,结果因为前20天没有数据,全是NaN,回测结果惨不忍睹。
因子计算有几个关键点:
- 对齐时间戳:因子值必须和交易时间严格对齐。别把今天的因子值用到昨天的交易上。
- 处理停牌:停牌期间因子值怎么算?我个人建议直接填充NaN,或者用前值填充。
- 标准化:不同股票的因子值量级可能差很多。我习惯做z-score标准化,让因子值均值为0,标准差为1。
2. 因子IC分析:因子和未来收益的相关性
因子算出来了,怎么知道它有没有预测能力?IC(Information Coefficient)就是干这个的。
IC的定义很简单:因子值和未来收益的相关系数。常用的有两种:
- Pearson IC:线性相关系数。假设因子和收益是线性关系。
- Rank IC:秩相关系数。只看排序关系,对极端值不敏感。我个人更常用这个。
代码实现:
def calc_ic(factor_values, forward_returns):
"""
计算因子IC
factor_values: 因子值序列
forward_returns: 未来N日收益序列
"""
# 去掉缺失值
mask = factor_values.notna() & forward_returns.notna()
f = factor_values[mask]
r = forward_returns[mask]
# Pearson IC
pearson_ic = f.corr(r)
# Rank IC
rank_ic = f.rank().corr(r.rank())
return pearson_ic, rank_ic
# 计算每日IC
daily_ics = []
for date in factor_df.index.unique():
f = factor_df.loc[date]
r = forward_returns.loc[date]
ic = calc_ic(f, r)
daily_ics.append({'date': date, 'pearson_ic': ic[0], 'rank_ic': ic[1]})
ic_df = pd.DataFrame(daily_ics)
print(f"平均Rank IC: {ic_df['rank_ic'].mean():.4f}")
print(f"IC标准差: {ic_df['rank_ic'].std():.4f}")
print(f"ICIR: {ic_df['rank_ic'].mean() / ic_df['rank_ic'].std():.4f}")
关键指标:
- IC均值:>0.02算不错,>0.05算优秀
- IC标准差:越小越好,说明因子稳定
- ICIR(IC信息比率):IC均值/IC标准差,>0.5算合格
嗯,这里要注意:IC高不代表因子一定能赚钱。我曾经遇到一个因子,IC高达0.08,但回测一跑,亏得底裤都不剩。为什么?因为IC只衡量了相关性,没考虑交易成本、流动性这些实际问题。
3. 因子收益率分析:多空组合的表现
IC是相关性,因子收益率是真金白银的回报。做法很简单:
- 每天按因子值排序,分成10组(或5组)
- 做多因子值最高的组,做空因子值最低的组
- 计算这个多空组合的收益率
代码示例:
def calc_factor_return(factor_df, return_df, groups=10):
"""
计算因子多空组合收益率
"""
# 每天分组
def assign_group(x):
return pd.qcut(x, groups, labels=False, duplicates='drop')
group_df = factor_df.groupby(level='date').transform(assign_group)
# 计算每组平均收益
group_returns = return_df.groupby([group_df, return_df.index.get_level_values('date')]).mean()
# 多空组合:最高组 - 最低组
long_ret = group_returns.loc[groups-1].mean()
short_ret = group_returns.loc[0].mean()
factor_return = long_ret - short_ret
return factor_return
factor_ret = calc_factor_return(factor_df, forward_returns)
print(f"因子多空年化收益: {factor_ret * 252:.2%}")
避坑指南:我曾经犯过一个错误——直接用全市场股票做多空组合,结果发现因子收益很高,但实际交易时根本买不到小盘股。后来我加了市值和流动性过滤,因子收益直接腰斩。所以,一定要考虑现实约束。
4. 因子分组回测:看透因子的全貌
分组回测是单因子分析的终极检验。它把股票分成N组,看每组的表现。一个优秀的因子,应该满足:
- 单调性:分组收益从低到高严格递增(或递减)
- 区分度:最高组和最低组收益差距明显
- 稳定性:不同时间段表现一致
我习惯用一张图来展示分组回测结果:
这张图里,组5(因子值最高)的累计收益最高,组1(因子值最低)最低,而且中间各组严格排序。这就是完美的单调性。
我的经验:如果分组收益不是单调的,比如组3比组2还差,那这个因子可能有问题。我遇到过一种情况:因子在极端组(组1和组5)表现很好,但中间组乱成一团。这种因子往往是因为极端值驱动,去掉极端值后因子就失效了。
5. 综合评估:因子到底行不行?
把上面四个步骤的结果汇总,我一般会做一个因子评估表:
| 指标 | 优秀 | 合格 | 不合格 |
|---|---|---|---|
| Rank IC均值 | > 0.05 | 0.02 - 0.05 | < 0.02 |
| ICIR | > 1.0 | 0.5 - 1.0 | < 0.5 |
| 多空年化收益 | > 15% | 8% - 15% | < 8% |
| 分组单调性 | 严格单调 | 基本单调 | 乱序 |
| 最大回撤 | < 10% | 10% - 20% | > 20% |
你想想看,如果一个因子IC均值0.06,ICIR 1.2,多空年化20%,分组严格单调,最大回撤只有8%。这种因子,我二话不说直接上线。
但现实往往是:IC均值0.03,ICIR 0.6,多空年化10%,分组基本单调但偶尔乱序,最大回撤15%。这种因子,我会再观察一段时间,或者尝试做一些改进——比如换一种因子计算方法,或者加入行业中性化处理。
最后提醒一句:单因子分析只是第一步。一个因子在历史回测中表现好,不代表未来也能赚钱。我见过太多因子在样本内表现惊艳,一到样本外就崩盘。所以,永远留一手样本外数据做验证,这是对自己钱包负责。
公众号:蓝海资料掘金营,微信deep3321