4、单因子分析框架:IC/IR分析、分组回测、因子收益率计算
做量化交易这些年,我见过太多人一上来就搞复杂模型。结果呢?因子本身靠不靠谱都没搞清楚。说白了,单因子分析就是给每个因子做「体检」。
今天咱们就聊聊这个体检怎么做。我个人习惯把单因子分析拆成三个核心模块:IC/IR分析、分组回测、因子收益率计算。这三板斧砍下去,一个因子是好是坏,基本就现原形了。
4.1 IC/IR分析——因子的「预测力」体检
IC,全称是信息系数(Information Coefficient)。说白了,就是看因子值和未来收益率的相关性。我刚开始做这行时,总觉得IC越高越好。后来踩过坑才发现——光看IC不行,还得看稳定性。
IC的计算方式有两种主流做法:
- Spearman秩相关系数:用排名算,对极端值不敏感。我个人更常用这个。
- Pearson相关系数:用原始值算,对极端值敏感。适合因子值分布比较正态的情况。
代码实现其实不复杂:
import pandas as pd
import numpy as np
from scipy.stats import spearmanr
def calc_ic(factor_series, return_series, method='spearman'):
"""
计算单期IC值
factor_series: 因子值序列
return_series: 未来收益率序列
"""
if method == 'spearman':
ic, p_value = spearmanr(factor_series, return_series)
else:
ic = factor_series.corr(return_series, method='pearson')
return ic
# 举个实际例子
# 假设我们有100只股票的因子值和下月收益率
ic_values = []
for month in range(12):
ic = calc_ic(factor_data[month], return_data[month])
ic_values.append(ic)
print(f"月均IC: {np.mean(ic_values):.4f}")
print(f"IC标准差: {np.std(ic_values):.4f}")
print(f"IR: {np.mean(ic_values)/np.std(ic_values):.4f}")
这里有个关键指标——IR(信息比率)。它是IC的均值除以标准差。IR越高,说明因子的预测能力越稳定。
嗯,这里要注意一点。IC分析有个坑——你算的是截面IC还是时序IC?截面IC是同一时间点不同股票的因子值和收益率的相关性。时序IC是一只股票在不同时间点的因子值和收益率的相关性。两者含义完全不同,千万别搞混。
4.2 分组回测——眼见为实的收益表现
IC分析告诉你因子有没有预测力。但分组回测告诉你——这个预测力能不能转化成真金白银。
分组回测的逻辑很简单:每个月把所有股票按因子值从大到小排序,分成N组(通常是5组或10组)。然后看每组未来的平均收益。如果第1组(因子值最大)的收益显著高于第N组(因子值最小),那这个因子就有区分度。
我曾经遇到过一个因子,IC值挺漂亮,0.05左右。但分组回测一做,发现第1组和第5组的收益差距只有0.2%。这说明什么?说明因子的预测力集中在少数极端股票上,对大部分股票没什么用。
分组回测的代码框架:
def group_backtest(factor_df, return_df, n_groups=5):
"""
分组回测
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 g in range(n_groups):
start = g * group_size
end = (g + 1) * group_size if g < n_groups - 1 else len(sorted_factors)
group_stocks = sorted_factors.index[start:end]
group_return = returns[group_stocks].mean()
results.setdefault(f'group_{g+1}', []).append(group_return)
# 计算每组累计收益
group_cum_returns = {}
for g in range(n_groups):
group_cum_returns[f'group_{g+1}'] = np.cumprod(
1 + np.array(results[f'group_{g+1}'])
)
return group_cum_returns
4.3 因子收益率计算——多空组合的真相
因子收益率,也叫多空组合收益。做法是:做多因子值最高的那组,做空因子值最低的那组。这个组合的收益,就是因子真正的「纯收益」。
为什么这么说?因为多空组合对冲掉了市场风险。你想想看,如果市场涨了10%,你做的股票涨了12%,做空的股票涨了8%,那多空组合的收益就是4%。这4%才是因子带来的超额收益。
计算因子收益率时,我习惯用市值加权而不是等权。原因很简单——大市值的股票流动性好,交易成本低,更接近实际可实现的收益。
def factor_return(factor_df, return_df, market_cap_df, n_groups=5):
"""
计算因子收益率(市值加权)
"""
long_returns = []
short_returns = []
for date in factor_df.index:
factors = factor_df.loc[date].dropna()
returns = return_df.loc[date]
caps = market_cap_df.loc[date]
# 对齐数据
common = factors.index.intersection(returns.index).intersection(caps.index)
factors = factors[common]
returns = returns[common]
caps = caps[common]
# 排序分组
sorted_idx = factors.sort_values().index
n = len(sorted_idx)
long_stocks = sorted_idx[-n//n_groups:] # 因子值最大的组
short_stocks = sorted_idx[:n//n_groups] # 因子值最小的组
# 市值加权计算收益
long_weight = caps[long_stocks] / caps[long_stocks].sum()
short_weight = caps[short_stocks] / caps[short_stocks].sum()
long_ret = (returns[long_stocks] * long_weight).sum()
short_ret = (returns[short_stocks] * short_weight).sum()
long_returns.append(long_ret)
short_returns.append(short_ret)
# 多空组合收益
long_short = np.array(long_returns) - np.array(short_returns)
return {
'long': long_returns,
'short': short_returns,
'long_short': long_short,
'annual_return': np.mean(long_short) * 12,
'annual_vol': np.std(long_short) * np.sqrt(12),
'sharpe': np.mean(long_short) / np.std(long_short) * np.sqrt(12)
}
- 年化收益率 > 5%:因子有实际价值
- 夏普比率 > 1:因子收益风险比良好
- 最大回撤 < 10%:因子表现稳定
4.4 单因子分析框架总览
说了这么多,咱们用一张图把整个框架串起来。我习惯把单因子分析看作一个三层漏斗:
这张图是我做单因子分析时的标准流程。从左到右,从粗到细。IC/IR分析是快速筛选,分组回测是详细验证,因子收益率计算是最终量化。
最后说一句。单因子分析不是一次性工作。市场在变,因子的表现也会变。我习惯每个月重新跑一遍这个框架,看看哪些因子在退化,哪些因子在变强。这才是动态的因子管理。