因子的各种处理方法
你的因子就像一个素颜的选手——底子不错,但稍加修饰就能惊艳全场。本文从"为什么需要处理"出发,到"用什么方法处理什么问题",再到"每招能改善哪个指标",帮你打通因子预处理的完整逻辑链。
一、从一张成绩单说起
假设你刚跑完一个因子回测,拿到这样一份报告:
::: hljs-center
| 指标 | 数值 | 评价 |
|---|---|---|
| 年化收益 | 18.60% | 还行 |
| 夏普比率 | 0.6856 | 中等 |
| 最大回撤 | 24.71% | 有点大 |
| IC均值 | 0.0171 | 信号偏弱 |
| Rank IC | 0.0361 | 比IC均值好 |
| IC标准差 | 0.0576 | 波动太大 |
| ICIR | 0.2977 | 偏低 |
| t统计量 | 2.4909 | 显著 |
| p-value | 0.0152 | 显著 |
| P(IC<-0.02) | 27.14% | 失效期太多 |
| P(IC>0.02) | 44.29% | 有效期不到一半 |
| 单调性 | 0.81 | 尚可 |
:::
一句话总结:因子有信号,但不够稳定、不够纯净、偶尔还"翻脸"。
问题出在哪?——————缺少一些处理,方便面没放调料包
二、为什么原始因子需要处理?
量化因子的原始数据,往往有四大"缺陷":
缺陷1:分布偏得离谱
市值因子是最典型的例子。A股最大公司市值2万亿,最小公司20亿,差了1000倍。如果你直接用原始市值做分组,第一组全是一两百亿的公司,最后一组就一个茅台——分组根本不均匀,分组收益自然也不单调。
受害者指标:单调性、IC均值、Rank IC
缺陷2:极端值横行
利润增速因子,去年赚1万今年赚100万,增速9900%。PE因子,微利公司PE能到5000倍。这些极端值就像班上考了0分和100分的同学——把平均分拉偏了,也把相关性拉偏了。
受害者指标:IC标准差、最大回撤、ICIR
缺陷3:行业"自带属性"
银行股天生市值大、ROE高、估值低;科技股天生市值小、ROE低、估值高。如果你不做处理,ROE因子其实在"选行业"而不是"选个股"——当银行板块下跌时,ROE因子就"翻脸"了。
受害者指标:IC均值、P(IC<-0.02)、ICIR
缺陷4:量纲各不相同
PE是比率,市值是绝对值,换手率是百分比——它们放在一起就像用"米""斤""度"同时描述一个人的身高。多因子合成时,谁量大谁说了算,这不公平。
受害者指标:多因子合成后的夏普比率、ICIR
三、八大"美容"手法,逐一拆解
手法1:对数化——专治"偏态脸"
一句话:把差几个数量级的值,压缩到同一个"量级赛场"上。
原理:log变换就像一个"压缩器",大值压缩得多,小值压缩得少。20000亿变5.3,500亿变3.7,5亿变1.7——原来差1000倍,现在差不到4倍。
操作:
factor_log = np.log(factor) # 确保因子为正
适用因子:市值、换手率、交易量、波动率等右偏因子
改善指标:
| 指标 | 可能的改善方向 | 原因 |
|---|---|---|
| IC均值 | ↑ | 分布对称后,因子与收益的线性关系更真实 |
| Rank IC | ↑ | 中间段排序更合理 |
| IC标准差 | ↓ | 消除长尾后,每期IC波动减小 |
| 单调性 | ↑ | 分组更均匀,组间收益递变更自然 |
手法2:缩尾——极值处理
一句话:不删除极端值,但把"刺头"按回合理范围。
原理:把超过1%和99%分位数的值,直接"拉回"到分位数边界。就像考试时,超过100分的按100分算,低于0分的按0分算——人还在,但分数被"截"住了。
操作:
from scipy.stats import mstats
factor_win = mstats.winsorize(factor, limits=[0.01, 0.01])
适用因子:PE、利润增速、动量、ROE等偶有极端值但不严重右偏的因子
改善指标:
| 指标 | 改善方向 | 原因 |
|---|---|---|
| IC标准差 | ↓ | 极端值是IC波动的主要来源,截断后IC更稳定 |
| 最大回撤 | ↓ | 极端持仓被抑制,不会因个别股票导致大幅回撤 |
| ICIR | ↑ | IC标准差下降的直接结果 |
| t统计量 | ↑ | 分母减小,显著性增强 |
| p-value | ↓ | 更显著 |
手法3:MAD去极值——缩尾的"升级版"
一句话:缩尾是"一刀切",MAD是"看情况切"。
原理:缩尾固定用1%/99%分位数,不管极端值多还是少。MAD(中位数绝对偏差)则基于中位数来判断什么是"极端"——如果数据本身就很分散,阈值就宽松;如果数据很集中,阈值就严格。自适应,更精准。
操作:
python
median = np.median(factor)
mad = np.median(np.abs(factor - median))
upper = median + 5 * mad
lower = median - 5 * mad
factor_mad = np.clip(factor, lower, upper)
适用因子:与缩尾相同,但效果通常更好
改善指标:与缩尾相同,IC标准差和最大回撤通常比缩尾再降5%-10%
手法4:排名标准化——"万能选手"
一句话:不管原始值长什么样,我只看谁大谁小。
原理:把因子值完全替换为排名百分比。市值最大的股票标1.0,最小的标0.0,中间的按比例排。原始值是20000亿还是5亿?不重要,我只知道你排第几。
操作:
python
from scipy.stats import rankdata
factor_rank = rankdata(factor) / len(factor)
适用因子:几乎所有因子,尤其是分布不规则、含负值、无法取对数的因子(如利润增速、PE)
改善指标:
|指标 | 改善方向 | 原因|
I|C标准差 ↓↓ 排名值有界[0,1],天然抗极端值,IC波动大幅减小
ICIR ↑↑ IC标准差大幅下降的直接结果
单调性 ↑ 排名分组天然均匀,每组股票数量相同
最大回撤 ↓ 不会因极端值导致持仓过于集中
P(IC<-0.02) ↓ IC更稳定,负向极端IC减少
代价:IC均值可能轻微下降——因为你丢失了原始值之间的"距离"信息。比如市值100亿和200亿差100亿,但排名可能只差0.01。不过通常ICIR的提升远比IC均值的轻微下降更划算。
生活类比:排名标准化就像高考只看省排名,不看具体分数——你考了680还是700不重要,重要的是你排第几名。
手法5:Box-Cox / Yeo-Johnson变换——"智能对数化"
一句话:对数化是手动选λ=0,Box-Cox让数据自己选最优λ。
原理:对数化是Box-Cox变换的特例(λ=0)。Box-Cox通过搜索找到让数据最接近正态分布的λ值——可能λ=0.3比λ=0更好,也可能λ=0.5更好。Yeo-Johnson是Box-Cox的升级版,还能处理零值和负值。
操作:
python
Box-Cox(要求正值)
from scipy.stats import boxcox
factor_bc, lambda_opt = boxcox(factor + 1e-6)
Yeo-Johnson(允许零值和负值)
from sklearn.preprocessing import PowerTransformer
pt = PowerTransformer(method='yeo-johnson')
factor_yj = pt.fit_transform(factor.reshape(-1, 1))
适用因子:对数化效果不够好时的"升级选项",尤其是负值多的因子用Yeo-Johnson
改善指标:与对数化相同,但ICIR和单调性可能比对数化再提升5%-15%
手法6:行业中性化——"卸掉行业包袱"
一句话:不让因子"替行业打工",只让它"选个股"。
原理:将因子对行业哑变量回归,取残差。残差代表的是"同一个行业里,你比别人好多少"——这才是因子的真实选股能力。
操作:
python
import statsmodels.api as sm
X = pd.get_dummies(industry)
factor_neutral = sm.OLS(factor, X).fit().resid
适用因子:几乎所有基本面因子,尤其是市值、ROE、PE
改善指标:
| 指标 | 改善方向 | 原因 |
|---|---|---|
| IC均值 | ↑ | 去掉行业混杂后,因子信号更纯净 |
| ICIR | ↑ | IC更稳定,不再受行业轮动干扰 |
| t统计量 | ↑ | IC均值提升 + IC标准差下降 |
| P(IC<-0.02) | ↓ | 行业轮动导致的"翻脸"期减少 |
| 夏普比率 | ↑ | 收益更稳定 |
生活类比:行业中性化就像"控制变量法"——比较两个学生的成绩,不能只看总分,要看同班级里的排名。银行股ROE高是"班级平均分高",不是"这个学生特别优秀"。
手法7:正交化——"去掉重复信号"
一句话:新因子必须带来"增量信息",不能和已有因子说一样的话。
原理:将因子对其他已知因子(如市值、行业)回归取残差。残差代表的是新因子"独有的、别人没有的"那部分信息。
操作:
python
X = sm.add_constant(existing_factors)
factor_orth = sm.OLS(factor, X).fit().resid
适用场景:因子合成前,确保新因子有增量信息
改善指标:
| 指标 | 改善方向 | 原因 |
|---|---|---|
| ICIR | ↑ | 独立信号更稳定 |
| t统计量 | ↑ | 独立信号的t值更显著 |
| P(IC<-0.02) | ↓ | 不再受其他因子周期性影响 |
| 夏普比率 | ↑ | 多因子组合中,正交因子贡献更分散 |
生活类比:正交化就像面试时问"你有什么别人没有的特长"——如果你只会Java,但团队里已经有三个Java高手,那你的"增量价值"就是零。
手法8:Z-Score标准化——"统一度量衡"
一句话:让所有因子站在同一条起跑线上。
原理:减去均值,除以标准差。变换后均值为0,标准差为1。不改变排序,不改变分布形态,只改变"刻度"。
操作:
python
factor_z = (factor - np.mean(factor)) / np.std(factor)
关键认知:Z-Score是"对齐"操作,不是"改善"操作。单独一个因子做Z-Score,IC不会变。但多因子合成时,如果PE是0-100的量级,换手率是0-0.5的量级,不做Z-Score的话PE会完全主导合成结果。
改善指标:
| 指标 | 改善方向 | 原因 |
|---|---|---|
| (多因子合成时) | 夏普比率 ↑ | 各因子贡献均衡 |
| (多因子合成时) | ICIR ↑ | 合成信号更稳定 |
生活类比:Z-Score就像把"人民币""美元""日元"统一换算成"购买力平价"——不是改变钱的价值,而是让它们可以公平比较。
四、实战组合拳——"因子的标准护肤流程"
就像护肤有"清洁→爽肤→精华→面霜"的顺序,因子处理也有标准流程:
原始因子
│
▼
① 行业中性化 ──→ 卸掉行业包袱,让因子"做自己的事"
│
▼
② 对数化 / Box-Cox ──→ 把偏态"掰正",让分布更对称
│
▼
③ 缩尾 / MAD ──→ 把极端值"按住",不让它们捣乱
│
▼
④ 排名标准化(可选)───→ 如果以上还不够稳,用排名"兜底"
│
▼
⑤ Z-Score ──→ 统一刻度,为多因子合成做准备
常见因子的"定制套餐":
因子 推荐处理流程
市值 log → 缩尾 → 行业中性化 → Z-Score
PE 取倒数→EP → 缩尾 → 排名标准化 → Z-Score
动量 缩尾 → Z-Score(不取对数)
换手率 log → 缩尾 → 行业中性化 → Z-Score
波动率 log → 缩尾 → Z-Score
ROE 缩尾 → 行业中性化 → 排名标准化
利润增速 缩尾(宽阈值)→ 排名标准化
五、对症下药——你的因子哪里弱,就补哪里
回到我们开头那份成绩单,逐项分析:
问题1:ICIR = 0.2977,太低了
病因:IC标准差太大(0.0576),信号时有时无
处方:
首选:排名标准化 → IC标准差大幅下降,ICIR通常能到0.5+
次选:缩尾/MAD → 截断极端值,减少IC波动
辅助:行业中性化 → 去掉行业混杂,IC更稳定
问题2:最大回撤 = 24.71%,太疼了
病因:极端值导致某期持仓过于集中
处方:
首选:缩尾 → 极端值被截住,不会重仓异常股票
次选:排名标准化 → 持仓天然分散
辅助:行业中性化 → 避免行业集中度风险
问题3:P(IC<-0.02) = 27.14%,失效期太多
病因:行业轮动和极端值导致IC周期性"翻脸"
处方:
首选:行业中性化 → 去掉行业混杂,IC不再随行业轮动反转
次选:正交化 → 去掉与其他因子的共线,减少周期性干扰
辅助:排名标准化 → IC更稳定,极端负值减少
问题4:IC均值 = 0.0171,信号偏弱
病因:分布偏态导致因子与收益的真实关系被掩盖
处方:
首选:行业中性化 → 释放被行业混杂掩盖的个股选择能力
次选:对数化/Box-Cox → 分布对称后,线性关系更显著
注意:排名标准化可能让IC均值轻微下降,但ICIR的提升通常更划算
问题5:单调性 = 0.81,还有提升空间
病因:分组不均匀,某组被极端值"拉偏"
处方:
首选:对数化 → 压缩极端值,分组更均匀
次选:排名标准化 → 每组股票数量相同,天然均匀
辅助:Box-Cox → 可能比对数化效果更好
六、一张图总结全文
因子原始问题 处理方法 改善指标
┌──────────┐ ┌──────────┐ ┌──────────┐
│ 偏态严重 │───────→──────│ 对数化 │───────→───│ IC均值↑ │
│ (市值等) │ │ Box-Cox │ │ 单调性↑ │
└──────────┘ └──────────┘ └──────────┘
┌──────────┐ ┌──────────┐ ┌──────────┐
│ 极端值多 │───────→──────│ 缩尾 │───────→───│ IC标准差↓│
│ (PE/增速) │ │ MAD │ │ 最大回撤↓│
└──────────┘ └──────────┘ └──────────┘
┌──────────┐ ┌──────────┐ ┌──────────┐
│ 分布不规则 │───────→──────│ 排名标准化 │───────→───│ ICIR↑↑ │
│ (含负值) │ │ │ │ 单调性↑ │
└──────────┘ └──────────┘ └──────────┘
┌──────────┐ ┌──────────┐ ┌──────────┐
│ 行业混杂 │───────→──────│ 行业中性化 │───────→───│ IC均值↑ │
│ (基本面因子)│ │ │ │ P(失效)↓ │
└──────────┘ └──────────┘ └──────────┘
┌──────────┐ ┌──────────┐ ┌──────────┐
│ 因子共线 │───────→──────│ 正交化 │───────→───│ ICIR↑ │
│ (多因子) │ │ │ │ t统计量↑ │
└──────────┘ └──────────┘ └──────────┘
┌──────────┐ ┌──────────┐ ┌──────────┐
│ 量纲不统一 │───────→──────│ Z-Score │───────→───│ 合成夏普↑│
│ (多因子) │ │ │ │ 合成ICIR↑│
└──────────┘ └──────────┘ └──────────┘
七、最后的话
因子预处理不是"造假",而是"还原真相"。
原始因子数据里,有太多"噪音"在掩盖信号——偏态让分组不均匀,极端值让IC波动,行业混杂让因子"替行业打工"。预处理的本质,是把这些噪音去掉,让因子真实的选股能力浮出水面。
记住一句话:
对数化治偏态,缩尾治极端,排名治波动,中性化治混杂,正交化治共线,Z-Score治量纲。
你的因子底子不错——IC显著、单调性尚可。只需稍加"修饰",就能从"还行"变成"真香"。
本文完整代码及因子处理工具包,可参考前文各方法代码片段组合使用。


评论