halo 的技术博客

返回

因子选股里最容易被忽视、也最容易让人亏钱的误区,不是”因子没 alpha”,而是你以为自己在赚 alpha,其实在赌风格。一个典型的 value 信号(低 PE / 高 book-to-market)在 A 股里天然就和小盘股、金融/能源行业高度绑定——因为历史上这些板块更”便宜”。于是你排个序、做多最便宜的 20%、做空最贵的 20%,账面上确实赚钱,但那笔钱里有多少来自”价值 alpha”、多少来自”我恰好重仓了小盘和金融”?这俩在 2021 年以后的行情里表现天差地别。

结论先放这:用「对行业哑变量 + 对数市值做线性回归、取残差」做截面中性化,可以把风格暴露几乎清零,同时保留大部分 alpha。 在我们的 300 只股票模拟截面里,原始多空组合的行业净权重范数高达 0.55、市值暴露 −1.36(净做多小盘);中性化后这两项分别降到 0.05−0.02,而多空收益只从 1.80 降到 1.25——约 69% 的 alpha 被保住。代价也很明确:你放弃了风格因子本就支付的溢价,得到的是一个更纯净、更抗 regime 切换的信号。

信号 vs 市值散点:同行业抱团、小盘整体更高,说明信号被风格污染

一、信号为什么会被”污染”#

所谓截面因子信号 sis_i,理想情况下应该只承载你想表达的那一点信息(比如”被低估的程度”)。但现实里,几乎所有基本面特征都和行业市值纠缠在一起:

  • 行业绑定:金融、能源的估值中枢天然低于科技、消费。于是”低估值”信号在金融/能源天然偏高、在科技/消费天然偏低。
  • 市值绑定:小盘股的 book-to-market 普遍高于大盘股(规模溢价的一部分)。于是”价值”信号天然偏向小盘。

如果你直接对原始信号排序建多空,多头会自动堆在”便宜的行业 + 小盘”,空头会自动堆在”贵的行业 + 大盘”。你的 P&L 就不再只由 alpha 驱动,而被行业因子和市值因子”带着走”。这正是很多因子”样本内漂亮、实盘一换风格就崩”的根因——它根本没在表达你想表达的东西。

二、构造一个被污染的 value 信号(可复现)#

为了把问题讲透,我们模拟一个干净的截面:300 只股票、6 个行业、市值对数正态。关键设定是——让”纯净价值 alpha”与行业/市值完全独立,但让观测到的信号被它们污染,同时让未来收益同时由 alpha 和风格因子驱动(即风格因子本身也”给钱”)。

注意 signal 里混进了 ind_value_tiltlogmcap,而 fwd 里也混进了它们——这就是真实市场的结构:你想要的 alpha 和你想剔除的风格,同时出现在信号和未来收益里。这决定了中性化不是”免费午餐”,我们下面会看到它的真实代价。

三、中性化怎么做:对风格变量回归取残差#

核心思想一句话:把信号里能被「行业 + 市值」线性解释的部分减掉,剩下的残差就是”剥离风格后的纯信号”。写成回归:

si=α+k=1KβkI[ik]+γlogmcapi+εis_i = \alpha + \sum_{k=1}^{K}\beta_k \cdot \mathbb{I}[i\in 行业_k] + \gamma \cdot \text{logmcap}_i + \varepsilon_i

取拟合值 s^i\hat s_i 为”风格部分”,残差 s~i=sis^i\tilde s_i = s_i - \hat s_i 即中性化信号。残差在构造上就与行业和市值正交,所以用它排序建的组合,行业/市值暴露必然≈0。

IC 从 0.64 掉到 0.42,这不意外也不丢人:原始 IC 里有一部分来自”信号和收益都受风格驱动”的虚假共鸣,剥掉风格后,剩下的才是 alpha 与收益的”真”相关。

四、前后对比:行业与市值暴露#

光看收益会被误导,真正的判决在暴露。我们定义多空组合的行业净权重 = 多头行业占比 − 空头行业占比;市值暴露 = 多头平均 log 市值 − 空头平均 log 市值。

def net_industry(w):
    long_mask, short_mask = w > 0, w < 0
    out = []
    for k in range(K):
        lw = np.sum((ind == k) & long_mask) / np.sum(long_mask)
        sw = np.sum((ind == k) & short_mask) / np.sum(short_mask)
        out.append(lw - sw)
    return np.array(out)

net_raw = net_industry(w_raw)
net_neu = net_industry(w_neu)
size_exp_raw = logmcap[w_raw > 0].mean() - logmcap[w_raw < 0].mean()
size_exp_neu = logmcap[w_neu > 0].mean() - logmcap[w_neu < 0].mean()
python

原始多空组合的行业净权重:严重偏斜,金融大幅超配、科技大幅低配

原始多空组合的行业净权重严重偏斜——金融被大幅超配、科技被大幅低配,行业净权重范数高达 0.55;市值暴露 −1.36 意味着组合净做多小盘。这根本不是”纯价值”,而是”价值 + 小盘 + 金融”的叠加 bet。

中性化后多空组合的行业净权重:全部贴近 0

中性化后,六个行业的净权重全部贴近 0,行业净权重范数降到 0.05;市值暴露降到 −0.02。组合终于只表达”价值”这一件事。

五、alpha 还在吗?收益 vs 风格暴露的诚实权衡#

很多人拒绝中性化,是怕”把收益也中性掉了”。我们的实证给出一个平衡的答案:

收益 vs 风格暴露对比:中性化后 alpha 保留约七成,风格暴露几乎清零

  • 多空收益:原始 1.80 → 中性化 1.25,保留了约 69% 的 alpha。
  • 风格暴露综合指数(行业权重范数 + |市值暴露|):原始 0.55 → 中性化 0.05,几乎清零。

这里必须诚实地说清代价:中性化后收益下降,是因为风格因子(行业、市值)本身在样本里是”给钱”的——你剥掉的不只是噪声,还有一部分真金白银的风格溢价。所以中性化不是”让因子更强”,而是把表达从”混杂的风格 bet”换成”纯净的 alpha bet”。它的价值不在更高的收益,而在:

  1. 可解释性:你的 P&L 终于对应你声称在做的那件事;
  2. 稳健性:当风格因子进入逆风(比如小盘跑输、金融杀估值),中性化组合不会因为”顺带押了风格”而额外挨打;
  3. 可叠加性:纯净 alpha 才能和其他因子正交地组合,否则多个因子会重复押注同一批风格。

一句话:中性化是用”风格溢价”换”信号的纯度与稳健”。如果你的信念是”我能持续赚风格溢价的钱”,那另说;但多数时候,我们建因子是为了赚 alpha,而不是为了替自己再开一个风格暴露的仓位。

六、真实陷阱(不踩坑你一定会掉进去)#

1. 中性化会顺手剥掉”真 alpha 里的行业/市值成分”。 如果某个行业的确系统性更便宜、未来确实涨更多(即行业因子和 alpha 同源),回归残差会把这部分也减掉——你”净化”了信号,也”净化”掉了一部分该赚的钱。这不是 bug,是中性化的定义代价,必须心里有数。

2. 哑变量设定错误会制造伪中性。 用申万一级行业还是二级?用总市值还是流通市值?把”电新”和”军工”硬塞进一个哑变量,残差里就会残留这个粗分行业的暴露。中性化的质量上限,取决于你回归里”风格变量”刻画得有多准。

3. 极端市值差导致杠杆失衡。 对数市值跨度大时,回归残差在小盘股上方差更大,排序后会变相超配小盘(哪怕均值已中性)。实务上常改为分行业内做市值中性(先按行业分组,组内再对 log 市值取残差),或加市值分桶,避免这种隐性偏斜。

4. 中性化 ≠ 风险暴露为零。 残差只保证与”你放进回归的变量”正交。如果还有没建模的风格(动量、流动性、换手率),组合照样会暴露。中性化是”列出来的才管”,漏掉的照样咬人。

5. 高频调仓下中性化有滞后与换手成本。 每次换仓都重估回归,权重可能剧烈跳变;残差信号的换手往往比原始信号更高,摩擦成本要在回测里真实计入,别只看毛收益。

小结#

因子中性化不是玄学,就是一句话:把信号对行业和市值回归、取残差,让组合只表达你想表达的那点 alpha。 我们在一个 300 只股票、信号被行业/市值双重污染的模拟截面里验证:中性化把行业净权重范数从 0.55 压到 0.05、市值暴露从 −1.36 压到 −0.02,而多空收益只从 1.80 降到 1.25——约七成 alpha 得以保留。它真正的价值不是”收益更高”,而是让你的 P&L 终于对应你声称在做的策略,并且在风格逆风时不再因为”顺带押了风格”而额外挨打。记住它的边界:中性化会剥掉同源的风格溢价、依赖哑变量设定、只管得住列出来的变量——把它当作”让因子说实话”的工具,而不是”让因子变强”的魔法。

因子截面对冲:用行业市值中性化剔除风格暴露
https://blog.halo26812.eu.org/blog/factor-neutralization-industry
Author halo
Published at 2026年7月11日
版权声明 CC BY-NC-SA 4.0
Comment seems to stuck. Try to refresh?✨