← 返回目录
p5.3动手⏱ 约 18 分钟

多因子选股方法论:合成、中性化与分层

中性化把 IC 削掉 12.8%,却让 ICIR 从 0.73 涨到 1.31;分层多空差从 0.10 pp 变成 5.55 pp

🔎 最后验证 2026-07📚 来源:300 只 × 198 个月末调仓的多因子流水线,2026-07-31 于 ECS 实测,输出见 code/outputs/🧰 qlab.portfolio(本课自研)、numpy 2.5.1、pandas 3.0.5、Python 3.12.13
为什么学这个

本节只讲方法,不给任何标的名单。所有输出都是"层"和"分布",不出现任何个股。

六个因子合成一个打分,做 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 个百分点绝不能被当成"选股能力"。

它里面混着三样东西,本课无法完全拆开:

  1. 幸存者偏差——名单是今天的,当年被剔除的公司一只都不在
  2. 等权 vs 市值加权——等权天然偏小盘,且日频再平衡消除波动拖累(p5.6 那个 +2.87 pp)
  3. 编制差异——指数还含深市成分与调整因子

另一个信号:2010 年平均只有 156 只标的有行情,2025 年后是 299 只。差值是当年尚未上市或尚未纳入的部分——它们的历史在本样本里是空白,而不是被剔除,所以偏差方向是抬高

所以本节的分层年化(10%~16%)不能与买入持有直接比较,一次也不行。

本节只用层与层之间的相对差下结论,不报任何绝对收益结论。

消除它需要历史成分股名单(每个调仓日当时的名单),而公开免费数据源普遍不提供。说不清就标出来,比悄悄用掉强。

一、六个因子与它们的单因子 IC

全部只用调仓日当天及之前的数据(p1.2/p4.3 的纪律在面板层的落实):

因子定义IC 均值ICIRIC>0 占比
MOM12_1过去 12 个月收益(剔除最近 1 个月)0.02840.4455.1%
LOWTURN过去 20 日平均换手取负0.02690.3854.3%
LOWVOL过去 60 日波动取负0.02400.3155.7%
REV1M过去 1 个月收益取负0.02360.4048.0%
SMALL流通市值对数取负0.00450.0849.7%
BM1 / pbMRQ0.00450.0649.2%

SMALL 和 BM 的 IC 几乎是 0。

这不是说市值和估值因子无效,而是:在沪深300 这个大盘股池里,它们没有区分度

因子的有效性依赖股票池。同一个 SMALL 因子换到全市场,结论会完全不同——但那也意味着完全不同的容量与流动性约束(p5.7)。

报告因子表现时,股票池是必须写明的口径,和区间、成本一样重要。

二、合成:等权打败了 IC 加权

合成方式IC 均值ICIR
等权 z 分0.04490.72
滚动 IC 加权(过去 12 期,不含本期)0.03020.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.04600.73
中性化后0.0401(-12.8%)1.31

IC 均值降了 12.8%,ICIR 却涨了 79%。

含义很清楚:被中性化掉的那部分,均值贡献小、波动贡献大——它是噪声,不是信号。

这也解释了一个常见的误解:"中性化会削弱因子"。 它确实会削弱 IC 均值。但如果你关心的是可持续性,该看的是 ICIR。

一个必须做的对齐

中性化会丢掉市值缺失的标的。所以代码里做了一步:把原始合成分也限制到同一个可打分集合(每期 223 只)。

否则你比较的是两个不同的样本,而不是两种处理方式。

四、分层测试:中性化前后

中性化前 年化%中性化后 年化%
L1(打分最高)10.9715.99
L214.1912.88
L316.5013.71
L411.6412.43
L5(打分最低)10.8710.45
L1 − L50.10 pp5.55 pp
逐层单调

(月度调仓、层内等权、含 2.5bp 佣金 + 5bp 印花税 + 5bp 滑点;每层约 44.8 只)

三件事要一起说:

  1. 中性化前几乎没有区分度(0.10 pp),而且最高的是中间层 L3——这种形状说明打分排的不是收益,是别的东西
  2. 中性化后多空差 5.55 pp,L1 与 L5 的夏普是 0.772 vs 0.500,方向清楚了
  3. 但仍然不是逐层单调: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 已实测)。

❓ 测验
某因子做完中性化后,IC 均值从 0.05 降到 0.04。你的同事说「中性化把因子削弱了,别做了」。正确的回应是?
✏️ 填空
合成因子时把缺失值填 0,会让大量标的拿到相同分数、分层时挤成并列块;正确做法是按 ___ 因子取均值并设最少个数要求。

📌 免责:本课为技术教学,只讲方法,不推荐任何标的;样本存在已量化说明的幸存者偏差,分层年化不可与买入持有比较,不构成投资建议。

✅ 小结

四件事:

  1. 样本先自曝:当前成分股名单回溯 16 年,等权组合年化 16.68% vs 指数 1.66%,差 15.02 pp;2010 年只有 156 只标的有行情,2025 年后 299 只。分层年化不可与买入持有比较,本节只用层间相对差下结论
  2. 等权合成打败滚动 IC 加权(IC 0.0449/ICIR 0.72 vs 0.0302/0.41)。IC 的标准差(0.180.28)远大于均值(0.0040.028),用噪声很大的量加权,引入的噪声大于信息——与 p5.4 的参数平原同理
  3. 中性化砍掉的是噪声:IC 均值 -12.8%(0.0460→0.0401),ICIR +79%(0.73→1.31);分层多空差从 0.10 pp 变成 5.55 pp。"中性化会削弱因子"只在 IC 均值上成立
  4. 两条必须一起报的口径:①分层要同时给多空差是否逐层单调——本节中性化后 5.55 pp 但 L3 仍高于 L2;②组合层是近似层,用前先与事件引擎对账(实测差 ≤0.29 bp),报绝对收益必须回到事件引擎

还有一个工程教训:合成时不能 fillna(0)。那会把缺失当成"恰好等于均值的真实观测",让大量标的并列,某些层被挤大、另一些被挤小——层大小都不同,层间比较根本不成立。

下一节回到单策略,处理另一个"看起来很优"的陷阱:参数网格上的最优点,大概率是噪声的最大值。

下一节 → 参数优化与参数平原:找平原,不找尖峰
🔎 来源与核验· 6 条,点开核对
本节每个关键论断都对应一个可追溯的来源 —— 这是本课程"靠谱、不过时"的底线。
「样本口径与幸存者偏差:当前沪深300 成分股 300 只等权组合 2010-01~2026-07 年化 16.68%,同期沪深300 指数 1.66%,差 15.02 pp;2010 年平均 156 只标的有行情,2025 年后 299 只」
📚 本节 code/survivorship.py,2026-07-31 于 ECS 实测,输出见 code/outputs/survivorship_stdout.txt✓ 已核验 2026-07
「六因子单因子 IC(月频 Spearman,198 个调仓日):MOM12_1 0.0284/ICIR 0.44、LOWTURN 0.0269/0.38、LOWVOL 0.0240/0.31、REV1M 0.0236/0.40、SMALL 0.0045/0.08、BM 0.0045/0.06」
📚 本节 code/factor_multi.py,2026-07-31 于 ECS 实测,输出见 code/outputs/stdout.txt✓ 已核验 2026-07
「合成对比:等权 z 分 IC 0.0449/ICIR 0.72,滚动 12 期 IC 加权 IC 0.0302/ICIR 0.41」
📚 同上实测✓ 已核验 2026-07
「中性化(市值 + 41 个行业哑变量,对齐到同一批 223 只标的):IC 均值 0.0460→0.0401(-12.8%),ICIR 0.73→1.31;合成因子与 SMALL 的横截面相关均值 0.141」
📚 同上实测✓ 已核验 2026-07
「5 层分层(月度调仓、层内等权、含成本,每层约 44.8 只):中性化前 L1~L5 = 10.97/14.19/16.50/11.64/10.87%,L1-L5 = 0.10 pp;中性化后 15.99/12.88/13.71/12.43/10.45%,L1-L5 = 5.55 pp,两者均非逐层单调」
📚 同上实测✓ 已核验 2026-07
智图软件的赞赏码
都看到这了,打个赏呗!
接下来 · p5.4
参数优化与参数平原:找平原,不找尖峰
继续读下一节 →