因子库很容易越做越大。同一个动量,换个回看窗口、换个股票池、换个中性化方式,就成了一个“新因子”。库里有 100 个因子,汇报里就写 100 个。可真正独立的信号有几个?
我在 alpha 的几何学 里写过平方根定律:N 个彼此独立、质量相近的信号,组合后的信息比率大约是单个的 √N 倍。这条定律成立的前提是“独立”。把同一个赌注的十种写法当成十个独立信号,√N 就会系统性地高估你的组合。
这篇讲怎么把这个数算出来,以及怎么去重。
有效因子数量:一个公式
对等权、等波动、质量相近的信号,这个特殊情形下的风险等价数量是:
其中 N 是信号个数,ρ̄ 是所有信号两两之间相关系数的平均值。在这些前提下,组合夏普大约是单个夏普的 √N_eff 倍。它是一个实用诊断量,不是通用的组合公式。
| 信号个数 | 平均两两相关 | 有效数量 | 组合夏普 / 单个夏普 |
|---|---|---|---|
| 6 | 0 | 6.0 | 2.45 |
| 6 | 0.2 | 3.0 | 1.73 |
| 6 | 0.4 | 2.0 | 1.41 |
| 11 | 0.13 | 4.8 | 2.19 |
平均相关 0.2 听起来很低,6 个信号却只剩一半。相关性对组合的伤害,比直觉大得多。
用公开数据算一遍
数据来自 Ken French 数据库在 linearmodels 7.0 里的那一份:美股,1949 年 1 月到 2017 年 3 月,月度,未扣交易成本。仓库保留了一份经转换的冻结快照,避免上游库或源数据更新后文章数字漂移。我用它搭了一个典型的“因子库”:
- 动量 4 个:标准动量因子 Mom,以及小盘、中盘、大盘里各自的“赢家减输家”;
- 价值 4 个:HML,以及小盘、中盘、大盘里各自的“高账面市值比减低账面市值比”;
- 规模 3 个:SMB,以及两种不同切法的“小盘减大盘”。
11 个因子,名字各不相同。每个因子先缩放到相同波动,再等权组合:
| 指标 | 数值 |
|---|---|
| 平均两两相关 | 0.13 |
| 有效数量 | 4.8 |
| 单个因子的平均夏普 | 0.46 |
| 按 √11 估算的组合夏普 | 1.51 |
| 11 个等权的实际组合夏普 | 1.00 |
这些夏普用全样本波动把每个因子缩放到同一尺度,也没有扣交易成本。它们描述的是相关结构,不是可交易的回测。
平均相关只有 0.13,看上去已经很分散了,有效数量却只有 4.8。在这套全样本标准化设置下,按 11 个独立信号估算,组合夏普是 1.5;观测值只有 1.0,差不多正好是 0.46 × √4.8。在这个实验里,这个差距可以由序列之间的相关性机械地解释。
去重:按相关性合并
做法很朴素:每次把平均相关最高的两组合并成一组,重复下去,直到任意两组之间的平均相关都低于 0.5。每一组用组内成员的等权平均来代表。
11 个因子最后分成了 4 组:
- 动量:Mom 和三个市值段的动量,组内平均相关 0.80;
- 价值:HML、中盘价值、大盘价值;
- 小盘价值,单独一组;
- 规模:SMB 和两个“小盘减大盘”。
| 指标 | 合并前 | 合并后 |
|---|---|---|
| 信号个数 | 11 | 4 |
| 有效数量 | 4.8 | 4.1 |
| 等权组合夏普 | 1.00 | 1.02 |
11 个变成 4 个,在这份冻结的全样本里,毛夏普没有下降(1.00 到 1.02 的小幅上升来自权重的重新分配,不是信息变多了)。在这个构造下,多出的 7 条序列没有增加独立赌注;这不证明分组在样本外必然无损。
第 3 组值得多说一句:同样叫“价值”,小盘价值和大盘价值的相关只有 0.25。它和 HML 是 0.64,和中盘价值是 0.60。跟已经合并的那组价值因子平均相关大约 0.50,刚好停在 0.5 的阈值下面,所以单独成组。阈值稍松一点,这一组就会并回去。名字相同不代表是同一个信号,名字不同也可能是同一个。去重要看收益序列,不能看名字。
为什么不能按个数等权
假设库里有 4 个动量变体、1 个价值、1 个规模。如果每个信号等权,动量就占了三分之二的权重:
| 做法 | 有效数量 | 组合夏普 |
|---|---|---|
| 6 个信号各自等权 | 2.6 | 0.87 |
| 先把 4 个动量合成一个,再 3 个等权 | 4.0 | 0.90 |
按个数等权,等于让“写法最多”的那个想法主导整个组合。先合并同源信号,再分配权重;在新权重下,暴露更分散,有效数量从 2.6 升到 4.0。合并没有创造新的独立性,只是不再让变体数量决定权重。
相关结构稳定吗
把样本从中间切开,用同样的 4 组再算一遍:
| 区间 | 平均两两相关 | 有效数量 | 11 个等权夏普 | 合并成 4 组后 |
|---|---|---|---|---|
| 1949–1983 | 0.13 | 4.75 | 1.33 | 1.22 |
| 1983–2017 | 0.12 | 4.95 | 0.77 | 0.90 |
在两个半段里,测得的相关结构相近,有效数量都在 5 左右。变的是收益水平:后半段的夏普更低。合并前后的夏普在两段里一升一降。由于分组用的是全样本,这只是权重重新分配的描述性比较,不是“样本外没丢信息”的证据。
先剥离共同暴露,再算相关
同一份数据里还有 12 个行业组合。直接算,平均相关 0.65,有效数量只有 1.5:风险主要被共同市场暴露主导。把每个行业对市场的 beta 剥离后,平均相关是 −0.006,风险等价数量为 12.8。平均残差相关略为负时,这个数可以略高于原始数量;它是诊断量,不是凭空多出了资产。
所以去重之前,要先剥离市场、行业、风格这些共同暴露,不然所有东西看起来都差不多。这也是 alpha 的几何学 里“先剥离,再谈 alpha”那道门的一个量化版本。
什么不算新的独立信号
下面几条是我在自己的研究里反复遇到、后来写进规矩的:
- 同一个风险源,换几个标的。同一套逻辑做在几只高度相关的 ETF 上,是一条腿,不是几条。
- 换一个交易时钟。下午的策略不会因为时间不同就自动和上午的独立,要用同期收益实测。
- 执行改善。降低滑点能实实在在地多赚钱,但它不是一个新信号,不该算进独立数量。
- 把淘汰过的弱信号拼起来。重新拼已经淘汰的弱信号,不算新的独立信号。新的独立性来自新的数据和新的机制,不来自重新组合剩下的东西。
- 多个信号合并后再选最终多空。这不等于几个独立组合相加,有效数量要在最终持仓上重新算。
更系统的组合构建方法,书里的组合构建与风险暴露管理和策略同质化与容量瓶颈两章有展开。
和“模型里的重复结构”是一回事
如果一个训练好的模型里有很多重复的局部结构,权重上也许可以合并,不用重新训练。因子库是一个小尺度类比:在这份样本里,11 条序列分成 4 组后,全样本毛夏普没有下降。
但我在记忆模型的迁移实验里看到过另一面:在一个要拿去迁移的先验里,被多种关系共用的结构,恰恰是能带到新世界的部分。两件事不矛盾,做的都是同一个动作:把独立的部分只数一次。在组合里,同一个赌注不会付你两次钱,所以要合并;在先验里,共享的结构只需要存一份,所以要共享。
代码
脚本只用 Python 标准库:factor_dedup.py。冻结 CSV 的数据来源、转换方式、日期范围、校验值与复用边界记在来源说明里。
# 自检:3 个隐藏来源、11 个带噪声的副本,检查聚类能否找回 3 组
python3 factor_dedup.py --self-test
# 复核本文表格。读仓库里冻结的序列,不依赖 linearmodels 以后的更新
python3 factor_dedup.py --article-check
# 从 linearmodels 7.0 自带的 Ken French 数据重算(截止 2017-03)
pip install 'linearmodels==7.0'
python3 factor_dedup.py --demo
# 用自己的数据:宽表 CSV,第一列是日期,每列是一个信号的收益
python3 factor_dedup.py --csv my_factors.csv --threshold 0.5
# CSV 里有市场收益列时,先剥离它的 beta 再算
python3 factor_dedup.py --csv my_factors.csv --strip market
# 直接读从 Ken French 网站下载的 CSV(只读第一段月度数据)
python3 factor_dedup.py --french 12_Industry_Portfolios.csv
输出包括:平均两两相关、有效数量、√N 估算和实际等权夏普的对比、聚类结果,以及合并之后的同一组指标。脚本里用全样本波动做缩放,所以这些夏普只用来描述相关结构,不是可交易的回测。