🚀 AI 一键生成 joinquant 策略代码
立即体验
👉 AI 生成代码 👈

为什么 JoinQuant 的 neutralize 函数一次性传入多个因子和分步传入结果不同?

joinquant | 发布时间: 2026/5/29
以下内容由 EasyQuant 生成。

问题描述

关于中性化函数 neutralize

请问中性化函数 neutralize 如下的两种写法为什么得到的结果不一致呢?

方法一:
neutralize(data, how=['jq_l1', 'market_cap'], date='2018-05-02', axis=1)

方法二:
data1 =neutralize(data, how=['jq_l1',], date='2018-05-02', axis=1)

data =neutralize(data1, how=['market_cap'], date='2018-05-02', axis=1)

只是把行业市值中性化拆成2次中性化,请问这么做为何和方法一得到的结果不同?
在中性化的时候必须一次把因子都放进去吗?

解决方案

这两种写法得到的结果不一致,根本原因在于底层数学原理(线性回归)的差异

在量化交易中,中性化(Neutralization)通常是通过普通最小二乘法(OLS)线性回归来实现的,取回归后的残差作为中性化后的因子值。

为什么结果不同?

方法一:一次性中性化(多元线性回归)

neutralize(data, how=['jq_l1', 'market_cap'], date='2018-05-02', axis=1)

这种写法相当于构建了一个多元线性回归模型
Data = α + β1 * 行业因子 + β2 * 市值因子 + 残差ε

在这个模型中,算法会同时考虑行业和市值对 Data 的影响,并找到最优的 β1 和 β2,使得残差 ε 最小。这个残差 ε 就是同时剔除了行业和市值线性影响后的纯净因子。

方法二:分步中性化(连续的一元线性回归)

data1 = neutralize(data, how=['jq_l1'], date='2018-05-02', axis=1)
data2 = neutralize(data1, how=['market_cap'], date='2018-05-02', axis=1)

这种写法相当于做了两次回归:

  1. 第一次回归:Data = α1 + γ1 * 行业因子 + 残差ε1 (此时 data1 就是 ε1
  2. 第二次回归:ε1 = α2 + γ2 * 市值因子 + 残差ε2 (此时 data2 就是 ε2

产生差异的核心在于:行业因子和市值因子本身是存在相关性的(共线性)。
不同行业的平均市值往往是不同的(例如银行股普遍市值偏大,而某些科技股偏小)。

在方法二的第二次回归中,你用剔除了行业影响的 data1 去对原始的市值因子做回归。但原始的市值因子中仍然包含了行业的信息!这会导致第二次回归时,模型试图从 data1 中再次剥离市值(包含行业特征)的影响,从而导致数学上的结果与多元回归(方法一)产生偏差。

根据计量经济学中的 Frisch-Waugh-Lovell (FWL) 定理,只有当“行业因子”和“市值因子”**完全正交(即相关系数绝对为0)**时,这两种方法得到的结果才会完全一致。而在现实股市中,这几乎是不可能的。

必须一次把因子都放进去吗?

是的,强烈建议一次性把需要中性化的因子都放进去(即使用方法一)。

在量化研究的业界标准中,如果你想同时剔除多个风格因子(如行业、市值、Beta等)的影响,标准的做法是将它们作为自变量放入同一个多元回归模型中。这样可以干净、彻底地同时剥离这些因子的共线性影响,得到真正纯粹的 Alpha 因子。

分步中性化不仅结果不准确,而且顺序不同(先行业后市值 vs 先市值后行业)得到的结果也会不一样,这在逻辑上是站不住脚的。