单因子分析基础:因子值计算、因子IC分析、因子IR分析、因子分组收益分析
做量化投资的朋友都知道,因子挖掘是核心。但挖出来的因子到底有没有用?
不能光靠感觉。得用数据说话。
这一章,我们就来聊聊单因子分析的那些基本功。说白了,就是一套标准流程,用来检验一个因子是不是真的能赚钱。
因子值计算:从原始数据到可用信号
因子值计算,是第一步。也是最容易出错的一步。
我刚开始做因子研究时,就吃过亏。当时拿到的数据里有些极端值,没处理就直接算IC,结果发现因子表现特别好。后来一查,原来是某只股票因为停牌后复牌,一天涨了200%,把整个结果带偏了。
嗯,从那以后,我养成了一个习惯:因子值计算前,先做预处理。
因子值计算的标准流程:
- 数据清洗:剔除缺失值、停牌数据、ST股票
- 去极值:通常用MAD法或百分位法,把极端值拉回来
- 标准化:让因子值变成均值为0、标准差为1的分布
- 中性化:剔除市值、行业等干扰因素的影响
举个例子,假设我们有一个简单的动量因子——过去20天的收益率。
import pandas as pd
import numpy as np
# 假设df是包含股票日收益率的数据框
# 列名:'date', 'stock_code', 'return'
def calc_momentum_factor(df, window=20):
"""
计算动量因子:过去window天的累计收益率
"""
# 按股票分组,计算滚动收益率
df = df.sort_values(['stock_code', 'date'])
df['momentum'] = df.groupby('stock_code')['return'].transform(
lambda x: x.rolling(window, min_periods=1).sum()
)
# 去极值:MAD法
median = df['momentum'].median()
mad = np.abs(df['momentum'] - median).median()
upper = median + 5 * mad
lower = median - 5 * mad
df['momentum'] = df['momentum'].clip(lower, upper)
# 标准化:Z-score
df['momentum_z'] = (df['momentum'] - df['momentum'].mean()) / df['momentum'].std()
return df
你看,代码其实不复杂。但每一步都有讲究。
去极值为什么用MAD而不是标准差?因为MAD对异常值更鲁棒。标准化为什么用Z-score?因为这样因子值就有了可比性。
因子IC分析:因子与收益的相关性
因子值算好了,接下来要问:这个因子和未来收益有没有关系?
IC(Information Coefficient)就是干这个的。它衡量的是因子值与未来一期收益之间的相关性。
IC的两种常见形式:
- Pearson IC:线性相关系数,假设因子和收益是线性关系
- Spearman Rank IC:秩相关系数,只关心排序关系,更稳健
我个人习惯用Rank IC。为什么?因为因子值本身可能受极端值影响,但排序关系相对稳定。我在项目中遇到过,Pearson IC看起来很好,但Rank IC却一般,说明因子只在极端值上有效,这不是我们想要的。
def calc_ic(factor_df, return_df, method='spearman'):
"""
计算截面IC
factor_df: 某一天的因子值,index为股票代码
return_df: 未来一天的收益率,index为股票代码
"""
combined = pd.DataFrame({
'factor': factor_df,
'return': return_df
}).dropna()
if method == 'spearman':
ic = combined['factor'].corr(combined['return'], method='spearman')
else:
ic = combined['factor'].corr(combined['return'], method='pearson')
return ic
IC分析不能只看一天。要看一段时间内的IC序列。
我一般会计算过去N天的日度IC,然后看几个指标:
- IC均值:正还是负?绝对值越大越好
- IC标准差:越小越好,说明因子表现稳定
- IC>0的比例:超过50%说明因子有正向预测能力
- ICIR:这个下面会讲
因子IR分析:信息比率,衡量因子的稳定性
IC告诉你因子有没有预测能力。但光有预测能力还不够,还得稳定。
因子IR(Information Ratio)就是IC均值除以IC标准差。说白了,就是每承担一单位IC波动,能获得多少IC收益。
IR = mean(IC) / std(IC)
IR越高,说明因子越稳定。一般IR大于0.5就算不错,大于1.0就是很好的因子了。
我记得有一次,一个因子的IC均值高达0.08,但IR只有0.2。为什么?因为IC波动太大了,今天0.2,明天-0.1,后天0.15。这种因子你敢用吗?反正我不敢。
def calc_ir(ic_series):
"""
计算信息比率
ic_series: 一段时间内的IC序列
"""
mean_ic = ic_series.mean()
std_ic = ic_series.std()
if std_ic == 0:
return 0
ir = mean_ic / std_ic
return ir
注意:IR的计算依赖于IC序列的长度。太短了不靠谱,太长了可能有结构性变化。我一般用过去12个月的数据,大约250个交易日。
因子分组收益分析:最直观的验证方法
IC和IR是统计指标。但做投资的人,更喜欢看真金白银的收益。
分组收益分析,就是把股票按因子值从大到小分成N组(通常是5组或10组),然后看每组未来的平均收益。
你想想看,如果因子真的有效,那么第1组(因子值最大)的收益应该显著高于第5组(因子值最小)。而且,组别之间的收益应该是单调的——从第1组到第5组,收益逐渐下降。
def group_analysis(factor_df, return_df, n_groups=5):
"""
分组收益分析
factor_df: 某一天的因子值
return_df: 未来一天的收益率
n_groups: 分组数
"""
combined = pd.DataFrame({
'factor': factor_df,
'return': return_df
}).dropna()
# 按因子值分组
combined['group'] = pd.qcut(combined['factor'], n_groups, labels=False)
# 计算每组平均收益
group_returns = combined.groupby('group')['return'].mean()
# 计算多空组合收益(第1组 - 第N组)
long_short_return = group_returns.iloc[0] - group_returns.iloc[-1]
return group_returns, long_short_return
分组分析还能画图。把每天的组别收益累加起来,就是各组的时间序列。如果第1组的净值曲线一直往上走,第5组的净值曲线一直往下走,那这个因子就很有价值。
分组分析的小技巧:
- 分组数不要太多,5组或10组就够了。太多组会导致每组股票太少,统计不显著。
- 除了看平均收益,还要看每组收益的波动率。有些组收益高但波动大,实际效果可能不好。
- 多空组合的夏普比率,是衡量因子质量的好指标。
知识体系总览
这一章的内容,可以用下面这张图来总结。它展示了单因子分析的核心流程和各个模块之间的关系。
这张图把整个流程串起来了。从因子值计算开始,到IC分析、IR分析,再到分组收益分析,最后输出结论。每一步都有它的意义,缺一不可。
说实话,很多初学者容易犯一个错误:只看IC均值,不看IR。或者只看分组收益,不看IC的稳定性。这些都是片面的。
一个真正好的因子,应该是IC高、IR高、分组收益单调。三个条件都满足,才值得进一步研究。
避坑指南:
我曾经犯过一个错误:用全样本数据做因子分析,结果发现因子表现特别好。后来回测时才发现,这个因子在样本外完全失效。为什么?因为我在计算因子值时,用了未来的信息——标准化时用了全样本的均值和标准差。
正确的做法是:用滚动窗口计算因子值。每次只使用截至当前日期的数据,避免未来信息泄露。
好了,这一章的内容就到这里。单因子分析是因子挖掘的基础,也是检验因子质量的第一道关卡。把这些基本功练扎实了,后面的多因子组合、机器学习模型才能站得住脚。