3. 单因子分析基础:因子值计算、因子分组回测、多空组合收益与IC/IR分析
各位同学,欢迎来到《量化选股因子实战应用》的第一章。今天咱们聊点实在的——单因子分析。说白了,就是检验你手里这个因子到底有没有用。
我刚开始做量化那会儿,也犯过傻。拿到一个因子,觉得逻辑挺漂亮,直接扔进模型里跑。结果呢?回测曲线惨不忍睹。后来我才明白,因子分析不是拍脑袋,而是有一套标准流程的。今天我就把这套流程拆开揉碎了讲给你听。
核心逻辑:单因子分析 = 因子值计算 + 分组回测 + 多空组合 + IC/IR检验。四步走完,你才能说“这个因子我摸透了”。
3.1 因子值计算——别让数据坑了你
因子值计算,听起来简单吧?把公式套进去就行。但这里有个坑——数据预处理。我见过太多人直接拿原始数据算因子,结果跑出来的IC值忽高忽低,根本没法用。
举个例子,你算一个“市盈率倒数”因子。原始数据里可能有极端值,比如某只股票市盈率1000倍,或者负值。这些异常值会严重扭曲分组结果。所以第一步,去极值。
我的习惯做法:用MAD(中位数绝对偏差)法去极值,比3-sigma更稳健。尤其是金融数据,分布往往有厚尾,MAD更靠谱。
第二步,标准化。把因子值映射到0-1或者Z-score。我一般用Z-score,因为后续做IC分析时,标准化后的因子更容易解释。
import pandas as pd
import numpy as np
def factor_preprocess(factor_series):
# 去极值:MAD方法
median = factor_series.median()
mad = np.abs(factor_series - median).median()
upper = median + 5 * mad
lower = median - 5 * mad
factor_clipped = factor_series.clip(lower, upper)
# 标准化:Z-score
factor_z = (factor_clipped - factor_clipped.mean()) / factor_clipped.std()
return factor_z
嗯,这里要注意:中性化处理也很关键。比如市值因子和很多因子都相关,你不做市值中性化,分组回测时可能把市值效应误认为是因子效应。我一般用回归取残差的方式做中性化。
3.2 分组回测——5组还是10组?
分组回测,说白了就是把股票按因子值从高到低排序,分成N组,然后看每组未来的收益表现。如果高分组收益显著高于低分组,说明因子有区分度。
我个人习惯先做5组,快速验证因子方向。如果5组有单调性,再细化到10组看细节。为什么?因为5组样本量大,统计更稳定。10组虽然精细,但每组股票数量少,容易受个别股票影响。
| 分组数 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 5组 | 统计稳定,不易受噪声干扰 | 区分度较粗 | 初步筛选因子 |
| 10组 | 细节丰富,能看到边际效应 | 每组样本少,波动大 | 精细调参、因子增强 |
我曾经在做一个动量因子时,5组结果很漂亮,单调性完美。但细化到10组后,发现第8组和第9组收益倒挂了。后来一查,是某只妖股在作怪。所以你看,分组回测不是走形式,而是帮你发现问题的。
def group_backtest(factor_df, return_df, n_groups=5):
"""
factor_df: 因子值,index为日期,columns为股票
return_df: 下期收益,index为日期,columns为股票
"""
results = {}
for date in factor_df.index:
# 按因子值排序分组
factors = factor_df.loc[date].dropna()
groups = pd.qcut(factors, n_groups, labels=False)
# 计算每组等权收益
group_returns = {}
for g in range(n_groups):
stocks = factors[groups == g].index
group_returns[g] = return_df.loc[date, stocks].mean()
results[date] = group_returns
return pd.DataFrame(results).T
避坑指南:我曾经在分组时直接用因子值排序,忽略了行业分布。结果高分组全是科技股,低分组全是银行股。收益差异其实是行业效应,不是因子效应。所以分组时最好做行业中性化,或者按行业分层分组。
3.3 多空组合收益——真正的alpha来源
多空组合,就是做多高分组、做空低分组。这个指标能直接告诉你:剔除市场整体涨跌后,因子能赚多少钱。
你想想看,如果高分组涨了10%,但市场整体涨了15%,那因子其实是负贡献。多空组合收益把市场风险对冲掉了,剩下的才是因子真正的alpha。
计算方式很简单:
# 假设group_returns是分组收益DataFrame
long_short_ret = group_returns.iloc[:, -1] - group_returns.iloc[:, 0]
cumulative_ret = (1 + long_short_ret).cumprod()
# 年化收益率
annual_ret = cumulative_ret.iloc[-1] ** (252 / len(cumulative_ret)) - 1
# 年化波动率
annual_vol = long_short_ret.std() * np.sqrt(252)
# 夏普比率
sharpe = annual_ret / annual_vol
我一般要求多空组合的年化夏普比率至少大于1,才考虑把这个因子纳入模型。低于0.5的因子,说实话,噪声成分太大。
3.4 IC/IR分析——因子的“体检报告”
IC(信息系数)衡量的是因子值与未来收益的相关性。IR(信息比率)则是IC的均值除以标准差,衡量因子的稳定性。
说白了,IC告诉你因子准不准,IR告诉你因子稳不稳。一个因子IC很高但IR很低,说明它时灵时不灵,你敢用吗?
我常用的IC有两种:
- Pearson IC:因子值与收益的线性相关系数。对异常值敏感。
- Rank IC:因子值排名与收益排名的相关系数。更稳健,我推荐用这个。
def calculate_ic(factor_df, return_df, method='rank'):
ic_series = pd.Series(index=factor_df.index)
for date in factor_df.index:
f = factor_df.loc[date].dropna()
r = return_df.loc[date, f.index]
if method == 'rank':
ic_series[date] = f.corr(r, method='spearman')
else:
ic_series[date] = f.corr(r, method='pearson')
return ic_series
# IR = mean(IC) / std(IC)
ir = ic_series.mean() / ic_series.std()
我的经验阈值:
- |IC| > 0.02:有预测能力
- |IC| > 0.05:预测能力较强
- IR > 0.5:因子稳定可用
- IR > 1.0:优秀因子
当然,这些阈值因市场而异。A股市场因子IR普遍偏低,0.3以上就算不错了。
最后,别忘了看IC的累计曲线。如果累计IC稳步上升,说明因子持续有效。如果忽上忽下,甚至长期横盘,那就要警惕了——可能因子已经失效了。
好了,单因子分析的基础框架就这些。记住:因子值计算是基础,分组回测看区分度,多空组合看alpha,IC/IR看稳定性。四步走完,你就能对这个因子有个全面的判断。
一个小技巧:我每次做完单因子分析,都会把结果整理成一张“因子体检表”。包括:分组收益图、多空累计收益曲线、IC时序图、IC分布直方图。一张表看下来,因子好坏一目了然。