多因子选股方法论:合成、中性化与分层
中性化把 IC 削掉 12.8%,却让 ICIR 从 0.73 涨到 1.31;分层多空差从 0.10 pp 变成 5.55 pp
本节只讲方法,不给任何标的名单。所有输出都是"层"和"分布",不出现任何个股。
六个因子合成一个打分,做 5 层分层测试。中性化之前:
L1 10.97% L2 14.19% L3 16.50% L4 11.64% L5 10.87%
L1 − L5 = 0.10 pp
几乎没有区分度。最高的居然是中间那一层。
对市值 + 41 个行业做横截面回归取残差之后,同一批打分、同一批标的:
L1 15.99% L2 12.88% L3 13.71% L4 12.43% L5 10.45%
L1 − L5 = 5.55 pp
中性化把 IC 均值削掉了 12.8%(0.0460 → 0.0401),却把 ICIR 从 0.73 抬到 1.31。
它砍掉的不是信号,是噪声。
你想知道"哪个学生更努力",于是统计每个人的总分。
但总分里混着一大块「上的是哪所中学」。
不把学校这个因素回归掉,你排出来的顺序主要反映的是生源,不是努力。
中性化就是把这块先扣掉,再看剩下的残差。
零、先说样本的问题:15 个百分点的幸存者偏差
这一节用的是沪深300 当前成分股名单,回溯到 2010 年。
这是一份"今天还在指数里"的名单。2010 年在指数里、后来被剔除的公司,一只都不在。
当前 300 只成分股等权组合 年化 16.68%
沪深300 指数(市值加权) 年化 1.66%
差 15.02 pp
这 15 个百分点绝不能被当成"选股能力"。
它里面混着三样东西,本课无法完全拆开:
- 幸存者偏差——名单是今天的,当年被剔除的公司一只都不在
- 等权 vs 市值加权——等权天然偏小盘,且日频再平衡消除波动拖累(p5.6 那个 +2.87 pp)
- 编制差异——指数还含深市成分与调整因子
另一个信号:2010 年平均只有 156 只标的有行情,2025 年后是 299 只。差值是当年尚未上市或尚未纳入的部分——它们的历史在本样本里是空白,而不是被剔除,所以偏差方向是抬高。
所以本节的分层年化(10%~16%)不能与买入持有直接比较,一次也不行。
本节只用层与层之间的相对差下结论,不报任何绝对收益结论。
消除它需要历史成分股名单(每个调仓日当时的名单),而公开免费数据源普遍不提供。说不清就标出来,比悄悄用掉强。
一、六个因子与它们的单因子 IC
全部只用调仓日当天及之前的数据(p1.2/p4.3 的纪律在面板层的落实):
| 因子 | 定义 | IC 均值 | ICIR | IC>0 占比 |
|---|---|---|---|---|
| MOM12_1 | 过去 12 个月收益(剔除最近 1 个月) | 0.0284 | 0.44 | 55.1% |
| LOWTURN | 过去 20 日平均换手取负 | 0.0269 | 0.38 | 54.3% |
| LOWVOL | 过去 60 日波动取负 | 0.0240 | 0.31 | 55.7% |
| REV1M | 过去 1 个月收益取负 | 0.0236 | 0.40 | 48.0% |
| SMALL | 流通市值对数取负 | 0.0045 | 0.08 | 49.7% |
| BM | 1 / pbMRQ | 0.0045 | 0.06 | 49.2% |
SMALL 和 BM 的 IC 几乎是 0。
这不是说市值和估值因子无效,而是:在沪深300 这个大盘股池里,它们没有区分度。
因子的有效性依赖股票池。同一个 SMALL 因子换到全市场,结论会完全不同——但那也意味着完全不同的容量与流动性约束(p5.7)。
报告因子表现时,股票池是必须写明的口径,和区间、成本一样重要。
二、合成:等权打败了 IC 加权
| 合成方式 | IC 均值 | ICIR |
|---|---|---|
| 等权 z 分 | 0.0449 | 0.72 |
| 滚动 IC 加权(过去 12 期,不含本期) | 0.0302 | 0.41 |
用"过去表现好的因子给更高权重",反而更差。
原因是估计误差:IC 本身的标准差(0.180.28)远大于它的均值(0.0040.028)。你用一个噪声很大的量去加权,引入的噪声大于它带来的信息。
这是组合优化里的经典结论(等权常常打败"最优"权重),而它和 p5.4 的参数平原是同一个道理:估计出来的最优,不是真的最优。
合成时不能用 fillna(0)。
我第一版就是这么写的:六个 z 分求和,缺失的填 0。
后果是:大量标的拿到完全相同的合成分(全部因子缺失 → 分数恰好为 0),分层时挤成一个并列块。某些层被挤得明显偏大、另一些明显偏小——层的大小都不一样,层间比较根本不成立。
正确写法:按可得因子取均值,并要求至少 4/6 个因子非空。修正后每层稳定在 44.8 只。
"把缺失当成中性"听起来很合理,但它实际上是把缺失当成了一个真实的、恰好等于均值的观测。
三、中性化:砍掉的是噪声
先看这个合成因子和市值的关系:
等权合成因子与 SMALL(负市值)的横截面相关 均值 0.141,中位 0.148
相关性不算高,但足以污染排序。对市值 + 41 个行业哑变量做逐期横截面回归,取残差:
| IC 均值 | ICIR | |
|---|---|---|
| 中性化前 | 0.0460 | 0.73 |
| 中性化后 | 0.0401(-12.8%) | 1.31 |
IC 均值降了 12.8%,ICIR 却涨了 79%。
含义很清楚:被中性化掉的那部分,均值贡献小、波动贡献大——它是噪声,不是信号。
这也解释了一个常见的误解:"中性化会削弱因子"。 它确实会削弱 IC 均值。但如果你关心的是可持续性,该看的是 ICIR。
一个必须做的对齐
中性化会丢掉市值缺失的标的。所以代码里做了一步:把原始合成分也限制到同一个可打分集合(每期 223 只)。
否则你比较的是两个不同的样本,而不是两种处理方式。
四、分层测试:中性化前后
| 层 | 中性化前 年化% | 中性化后 年化% |
|---|---|---|
| L1(打分最高) | 10.97 | 15.99 |
| L2 | 14.19 | 12.88 |
| L3 | 16.50 | 13.71 |
| L4 | 11.64 | 12.43 |
| L5(打分最低) | 10.87 | 10.45 |
| L1 − L5 | 0.10 pp | 5.55 pp |
| 逐层单调 | 否 | 否 |
(月度调仓、层内等权、含 2.5bp 佣金 + 5bp 印花税 + 5bp 滑点;每层约 44.8 只)
三件事要一起说:
- 中性化前几乎没有区分度(0.10 pp),而且最高的是中间层 L3——这种形状说明打分排的不是收益,是别的东西
- 中性化后多空差 5.55 pp,L1 与 L5 的夏普是 0.772 vs 0.500,方向清楚了
- 但仍然不是逐层单调:L3(13.71%)高于 L2(12.88%)。5.55 pp 的多空差不等于逐层有序
报告分层结果时,"是否逐层单调"必须和多空差一起给。只报多空差,会把一个"两端有效、中间乱"的因子说成"排序能力强"。
🔧 动手做:中性化砍掉的到底是信号还是噪声?(6 分钟)
跑 python factor_multi.py,对比中性化前后的分层多空差。
你会看到(实测):对市值和行业做中性化后,IC 被削掉 12.8%,但 ICIR 从 0.73 涨到 1.31、分层多空差从 0.10pp 暴涨到 5.55pp。
想明白:中性化砍掉的 IC 是噪声不是信号——原始 IC 被"你的因子其实在赌小盘、赌某个行业"撑高了(虚的)。剥掉市值和行业暴露,剩下的才是因子真本事。所以看到一个"高 IC"因子,先问:它是不是只是市值/行业的马甲?
五、组合层的边界
本节用的是 qlab.portfolio——一个明确降级的近似层。它放弃了:
| 放弃 | 换来 |
|---|---|
| 整手取整、涨跌停撮合、T+1 锁定、逐笔委托记录 | 秒级跑完 300 只 × 16 年 × 198 次调仓 |
放弃之前先对账:在这些约束都关掉的等价设定下,组合层与 P4 事件引擎的期末权益差异最大 0.29 bp(4 只标的实测),差异来源是事件引擎按整数股下单、组合层用分数权重。
sh.600000 事件引擎 1,949,443 组合层 1,949,500 差 0.29 bp
sh.600009 事件引擎 1,677,597 组合层 1,677,621 差 0.14 bp
组合层的结论只用于因子与组合层面的相对比较(分层、多空、IC)。
一旦要报"这个策略能赚多少",必须回到事件引擎按单标的复核——被放弃的四条,每一条都只会让回测更好看(p4.1 已实测)。
📌 免责:本课为技术教学,只讲方法,不推荐任何标的;样本存在已量化说明的幸存者偏差,分层年化不可与买入持有比较,不构成投资建议。
四件事:
- 样本先自曝:当前成分股名单回溯 16 年,等权组合年化 16.68% vs 指数 1.66%,差 15.02 pp;2010 年只有 156 只标的有行情,2025 年后 299 只。分层年化不可与买入持有比较,本节只用层间相对差下结论
- 等权合成打败滚动 IC 加权(IC 0.0449/ICIR 0.72 vs 0.0302/0.41)。IC 的标准差(0.18
0.28)远大于均值(0.0040.028),用噪声很大的量加权,引入的噪声大于信息——与 p5.4 的参数平原同理 - 中性化砍掉的是噪声:IC 均值 -12.8%(0.0460→0.0401),ICIR +79%(0.73→1.31);分层多空差从 0.10 pp 变成 5.55 pp。"中性化会削弱因子"只在 IC 均值上成立
- 两条必须一起报的口径:①分层要同时给多空差和是否逐层单调——本节中性化后 5.55 pp 但 L3 仍高于 L2;②组合层是近似层,用前先与事件引擎对账(实测差 ≤0.29 bp),报绝对收益必须回到事件引擎
还有一个工程教训:合成时不能 fillna(0)。那会把缺失当成"恰好等于均值的真实观测",让大量标的并列,某些层被挤大、另一些被挤小——层大小都不同,层间比较根本不成立。
下一节回到单策略,处理另一个"看起来很优"的陷阱:参数网格上的最优点,大概率是噪声的最大值。
🔎 来源与核验· 6 条,点开核对
