← 返回量化与 AI 投研概念库

多因子合成:几个弱信号怎么变成一个强信号

最后更新 2026-08-18
R2 · 注意风险

你手上大概已经有了几个自己信得过的东西:一个看估值高低的,一个看最近涨得猛不猛的,还有一个看成交是不是活跃。单独拿哪一个出来看,都谈不上多准——对一半错一半,说服不了自己重仓。于是很自然会冒出一个念头:能不能把它们凑一块儿,互相补一补?

这个念头是对的,量化里管这件事叫「多因子合成」。但真动手的时候,绝大多数人第一反应是把三个数加起来除以三,然后就出事了——不是加法错了,是加法之前那一步没做。更麻烦的是,还有一类错误连结果都看不出异常:你合成了五个因子,回测比单个因子好一点点,你以为是合成起了作用,其实那五个因子说的是同一件事,你合了个寂寞。

这篇就把这两件事讲透。

先说人话:三个评委,三套尺子

想象一场比赛有三个评委给选手打分。

第一个评委用百分制,打出来的分集中在 80 到 95 之间。第二个评委爱打五星,最高 5 分最低 1 分。第三个评委干脆不打分,只给了个名次:第 1 名、第 2 名……第 30 名。

现在主办方说:把三个评委的分数加起来,谁总分高谁赢。

你立刻会发现荒唐在哪。百分制评委随手给的一个 88 分,就把五星评委的所有意见全淹没了——人家满打满算才 5 分。而名次评委更糟糕,名次是越小越好,直接加进去等于把他的意见反着用了。加法本身没问题,问题是三把尺子的刻度、量程、方向都不一样,不换算就往一起加,等于让嗓门大的那个人说了算。

这就是合成第一件必须做的事:先把所有因子换算到同一把尺子上,再谈怎么加权。 量化里这一步叫标准化。

还有第二件事,更隐蔽。假设这三个评委其实是同一个培训班出来的师兄弟,审美完全一致,三个人打出来的名次几乎一模一样。那么「三个评委投票」听起来很民主,实际效果和「一个评委说了算」没有任何区别——你以为自己有三个独立意见,其实只有一个。这就是相关性的坑,也是本篇下半场的主角。

为什么合成能变强:便宜的那部分是降噪

先搞清楚合成到底在赚什么钱,不然后面所有权重的争论都没有落脚点。

单个因子——也就是把「为什么会涨」变成一个可计算的数,这类东西统称 因子——给出的预测里混着两样东西:一点点真信号,一大堆噪音。所谓「弱」,指的就是信号占比很低。

把几个因子平均一下,会发生什么?如果它们各自的噪音是彼此无关的,那么平均之后噪音会互相抵消一部分,而真信号因为方向一致,会被保留下来。信噪比就这么提上去了。这跟为什么要分散持仓是同一套数学,只不过发生在「分数」这一层而不是「持仓」那一层——持仓层面的组合方差怎么随相关性变化,分散化的数学 那篇已经推得很细,这里不重复,你只要记住结论的形状:分散带来的好处,全部来自「彼此不同」这四个字。

反过来说,如果几个因子高度相似,噪音也是同一份噪音,平均之后一点都抵消不掉。加权公式再讲究,也变不出好处来。

所以合成方案的优劣,本质上是在回答两个问题:每个因子该占多大话语权,以及这些因子到底是不是真的在说不同的话

三种合成方式,各自在赌什么

方式 权重怎么定 它赌的是什么 最容易翻车的地方
等权 大家都一样 我分不清谁更强,那就都别猜 相关性高的因子扎堆,等于给某一类观点重复投票
IC 加权 谁历史上更准,谁权重大 过去准的,未来还会准 权重取自哪一段历史,取错就是偷看未来
回归 让机器解一组最优权重 数据里存在一组稳定的最优组合 因子相关时权重会乱飘,甚至一正一负互相抵消

等权:最笨,也最难被打败

等权就是每个因子一票,加起来平均。它看起来毫无技术含量,但在实盘里的口碑一直不差,原因很简单:它不需要估计任何东西。 后面两种方法都得先从历史里估出一组权重,而任何从历史里估出来的数,都带着「历史噪音」这份嫁妆,换个时间段就变。等权没有这个负担,因为它压根不估。

但等权有个前置条件不能省——必须先标准化,也就是前面说的换尺子。qlib 的平均集成模块把这一步直接写进了合成流程里,短短三行就是完整的等权合成:

results = pd.concat(values, axis=1)
results = results.groupby("datetime", group_keys=False).apply(lambda df: (df - df.mean()) / df.std())
results = results.mean(axis=1)

拆开看。第一行把每个因子的输出并成一张宽表,一列一个因子。第二行是关键:datetime 分组,也就是把同一天、所有股票横着看的那一排数字(量化里管这个叫「截面」)拿出来,减掉这一天的均值,再除以这一天的标准差。第三行才是真正的平均。

为什么标准化要按天做、而不是把整段历史一起算?因为你要比的是「今天这只票在今天这批票里排第几」,不是「今天这只票和三年前那只票哪个数大」。选股是同一天里挑,尺子就得在同一天里立。这一步做完,每个因子在每一天都被压成了均值 0、标准差 1 的分布,五星评委和百分制评委终于能坐一桌了。

标准化不止一种口径。qlib 的数据处理层里还有两个常用的:一个是稳健版的 z-score,用中位数和绝对中位差替代均值和标准差,好处是不容易被极端值带偏;另一个是排名标准化,先把当天的股票按因子值排名转成百分位,再拉到均值 0 附近。排名法最狠——它把「差多少」这个信息整个丢掉,只留「谁在谁前面」。听起来是损失,但如果你的因子偶尔会蹦出一个离谱的极端值(财务数据里这事儿相当常见),排名法能让整个合成免疫于这一类污染。代价是,真正的巨大差异也被压平了。

IC 加权:让准的那个多说话

如果你觉得等权太傻——明明有个因子历史上明显更靠谱,凭什么和最弱的那个一人一票?那就轮到 IC 加权。

IC 说的是「今天这个因子给出的排序,和之后实际涨跌的排序,对得上多少」。qlib 的评估模块里,它就是逐日算一个截面相关系数:

ic = df.groupby(date_col).apply(lambda df: df["pred"].corr(df["label"]))
ric = df.groupby(date_col).apply(lambda df: df["pred"].corr(df["label"], method="spearman"))

两行的差别只在方法:上面一行是普通的相关系数,下面一行加了 method="spearman",也就是先转成名次再算相关——这个叫秩相关 IC,抗极端值。IC 到底怎么读、多少算有用、为什么还要看它的稳定性(IR),IC 与 IR 那篇讲得更细,这里只用它的结论:IC 是一个因子的成绩单。

IC 加权就是拿这份成绩单当权重:历史上 IC 高的因子权重大,低的权重小,负的甚至反着用。逻辑上无懈可击,实践里有两个必须绷紧的地方。

一是权重取自哪一段历史。如果你用整段回测期算出来的平均 IC,去给这段回测期里每一天的合成分配权重,那你在 2020 年的那次决策,用上了 2023 年才知道的成绩单。这是标准的 前视偏差,而且它偏偏只会让回测更好看——因为你把「事后证明有效」的因子权重调高了。正确的做法是滚动:只用决策日之前那一段窗口的 IC。

二是IC 本身也在漂。因子的有效性不是常数,昨天准不代表今天准。用一个会漂的量去加权,等于把权重的不稳定性叠加进合成结果。窗口取短了跟着噪音乱跳,取长了反应迟钝——这个取舍没有标准答案,得看你的因子换手有多快。

回归:让机器解一组最优权重

第三条路更直接:把「合成」当成一个拟合问题——已知每只票的一堆因子值,已知它之后真实的收益,那就求解一组权重,让加权之后的预测尽量贴近真实收益。这就是回归。

qlib 的线性模型把可选的解法列得很清楚,它的说明里写的是这四个优化目标:

- `ols`:   min_w |y - Xw|^2_2
- `nnls`:  min_w |y - Xw|^2_2,  s.t. w >= 0
- `ridge`: min_w |y - Xw|^2_2 + alpha*|w|^2_2
- `lasso`: min_w |y - Xw|^2_2 + alpha*|w|_1

不认识这些符号也不影响读懂,它们的区别用大白话说是这样:

  • ols 是最朴素的最小二乘,只要求拟合得最准,对权重本身不设任何限制。
  • nnls 加了一条硬约束:所有权重必须大于等于 0。这条约束看着不起眼,实战意义很大——它禁止了「把某个因子反过来用」这种解法。
  • ridge 在目标里加了一项对权重平方的惩罚,权重越大惩罚越重,效果是把所有权重往 0 的方向压一压。
  • lasso 惩罚的是权重绝对值,它的特点是会把一部分权重直接压成 0,相当于顺手做了因子筛选。

这里有个细节值得注意:源码里 fit_intercept 的默认取值是不拟合截距(当前默认值,可配置)。对合成因子这个场景来说这挺合理——你要的是一个用来排序的分数,整体上抬或下压一个常数,对「谁排前面」没有任何影响。

最大的坑:相关性一高,合成就白做了

前面三种方式的取舍,全都建立在一个前提上:这些因子在说不同的话。 前提一破,三种方式一起塌,而且塌得各有各的难看。

等权下的塌法:无声的重复投票

你选了五个因子:20 日动量、60 日动量、相对强弱、创新高天数、均线偏离度。看起来五个,其实全都是「最近涨得猛不猛」的不同写法,彼此的相关系数高得吓人。

等权合成之后会发生什么?名义上五票,实际上「动量」这个观点拿了满票。如果你原本还想加一个估值因子进来平衡,它一票对五票,基本被淹没。你以为自己做了一个五因子模型,实际上做了一个加了点杂音的动量模型。

最坏的情况是:当动量这类风格集体失效时,你的组合会一次性全线扑街,因为它根本没有第二条腿。这跟持仓层面「买了十只票但全是同一个行业」是一模一样的错觉,只不过发生在因子层,更难被肉眼看见——毕竟持仓的行业标签写在那儿,因子之间的相关性得你自己去算。

怎么办:合成之前,先把因子两两的相关系数矩阵拉出来看一眼。 高度相关的一簇,要么只留一个代表,要么整簇合成一个再参与上层等权(也就是先组内平均、再组间平均),别让它们各自占一票。相关系数具体怎么读、多高算高、以及它在市场剧烈波动时会怎么变,见 相关性:分散化的真正前提

回归下的塌法:权重会疯

如果说等权遇到高相关只是「白做」,回归遇到高相关则是会给出看起来很聪明、实际很危险的答案

这个现象叫共线性——几个自变量彼此高度相关,导致方程组接近无解,或者说有无数组差不多好的解。回归会从中挑一组,但挑哪一组几乎是随机的。典型症状是:给 20 日动量一个 +8 的权重,给 60 日动量一个 −7 的权重。数学上它拟合得非常好,因为这两个几乎相同的东西一正一负相减,恰好把训练集里那点残差给消掉了。

问题在于,这组权重是在训练数据的噪音上找到的平衡。换一段数据,噪音变了,这个 +8 和 −7 的脆弱平衡立刻崩塌,两个大数一相减,误差被放大到离谱。你会看到样本内漂亮得不像话、样本外一塌糊涂。

这正是 ridge、lasso、nnls 存在的理由:

  • ridge 通过压小权重,直接掐死了「+8 配 −7」这种极端解——它让所有权重都待在小而分散的范围里,是应对共线性最常规的手段。
  • lasso 在一簇高相关因子里倾向于只留一个、把其余压成 0,等于替你做了去重。
  • nnls 那条「权重非负」的约束,从结构上就杜绝了一正一负互相抵消的把戏。

选哪个不是玄学,是在回答「你更怕什么」:怕权重乱飘就 ridge,想顺手筛因子就 lasso,认定所有因子方向都该是正的就 nnls。

多样性可以是设计出来的

还有一种更主动的思路:不是被动地检查因子相不相关,而是主动制造不相关

qlib 那个双重集成模型里有一段特征选择的逻辑,做法挺有启发。它先给每个特征算一个重要性得分——把这一列的值随机打乱,看整个集成的损失涨了多少,涨得越多说明这一列越关键,同时还除以了变化的标准差,等于只认「稳定地重要」而不认「偶尔一次特别重要」。

有意思的是选择那一步:

g["bins"] = pd.cut(g["g_value"], self.bins_fs)
for i_b, b in enumerate(sorted_bins):
    b_feat = features[g["bins"] == b]
    num_feat = int(np.ceil(self.sample_ratios[i_b] * len(b_feat)))
    res_feat = res_feat + np.random.choice(b_feat, size=num_feat, replace=False).tolist()

没有直接留下得分最高的那一批。而是把所有特征按得分切成几个箱,从最重要的箱开始,每个箱都随机抽走一部分保留——越重要的箱保留比例越高,但最不重要的那个箱也不会被清空(源码里那组保留比例是从高到低递减的一串默认值,可配置)。而且抽取是随机的,于是每个子模型拿到的特征集合都不一样。

这个设计说明了一件事:如果每个子模型都只用那几个最强的特征,它们会变得高度相似,集成也就失去了意义。 留一点弱的、让每个子模型看到的世界略有不同,多样性是被刻意保护出来的。这个思路值得从代码里搬到你自己的因子库管理上——最强的那几个因子往往同源,一味按 IC 排名筛选,筛到最后会得到一堆双胞胎。

🚧 避坑

「合成之后回测变好了」不能证明合成是有效的。 常见的假象有两种。一是你用全样本 IC 定的权重,好看是因为偷看了未来;二是你合成的因子高度同源,回测改善其实来自权重悄悄向某个单一风格倾斜,而不是来自分散。检验方法很朴素:把合成因子和它成分里最强的那一个单独比,如果差距很小,那你多加的那几个因子就没干活;再把权重估计窗口往前挪一挪,如果结果剧烈变化,说明权重是拟合在噪音上的。

失效边界与常见误用

跳过标准化直接加权。 这是新手最高频的错误,而且不报错。市盈率的倒数在 0.02 到 0.1 之间晃,20 日涨幅在 −0.3 到 0.3 之间晃,直接相加就是后者说了算。任何加权方案,前面都必须先有换尺子这一步。

用整段历史的统计量做标准化。 标准化要按截面(同一天所有股票)做。如果你按整段时间序列做,等于把「这只票今天比它自己历史上高」当成了「这只票今天比别的票高」,这两件事完全不是一回事,而且这么做还会把牛市里所有票一起变高的那部分整体信息偷偷混进来。

权重估计窗口和调仓频率不匹配。 一个月调一次仓,却用五年的平均 IC 定权重,那你的权重实际上是常数,IC 加权退化成了带偏见的等权。反过来,用两周的 IC 给一个季度调一次的组合定权重,权重会跟着短期噪音乱跳。

把回归权重当成因子重要性来解读。 共线性存在的时候,回归权重不代表因子有多重要,它只代表「在这组特定的因子里,为了拟合这段数据,这个因子被分配到了什么角色」。多加一个相关因子进去,原来那个的权重可能就变了符号。想看重要性,得用打乱一列看损失变化那类方法,别读回归系数。

因子数量越多越好。 每加一个因子,都在给权重估计增加一个待定参数,也在给过拟合增加一分空间。加进来的如果是老因子的近亲,收益是零、成本是实打实的。判断标准从来不是数量,是增量——这个新因子在扣掉与已有因子重合的部分之后,还剩下多少独立的解释力。

小结

  • 合成的收益全部来自「彼此不同」。因子之间相关性越低,凑一起降噪的效果越好;相关性接近 1,合成就是自我安慰。
  • 加权之前必须先标准化,而且要按同一天的截面做。常见三种口径:普通 z-score、用中位数抗极端值的稳健版、彻底只留名次的排名法。
  • 等权最笨但最稳,因为它不估计任何东西;IC 加权更聪明但要滚动取窗口,用全样本 IC 就是偷看未来;回归最灵活但遇到共线性会给出一正一负互相抵消的脆弱解,ridge 压权重、lasso 顺手去重、非负约束堵死反向用因子的路。
  • 因子高度相关时,等权是无声的重复投票,回归是权重发疯。前者你看不出来,后者你会在样本外看出来,代价更大。
  • 好的因子库不是「最强的一堆」,是「各说各话的一堆」;多样性需要被刻意保护,否则按成绩排名筛到最后全是近亲。

一句话记住这篇:几个说法凑一起才有意义,前提是它们本来就在说不同的事;把同一句话重复五遍,声音大了,信息一点没多。

本文所引源码来自上述快照版本,是阅读代码得出的机制说明,不是运行结果;具体行为请以你手上那个版本为准。回 量化与 AI 投研概念库 看其它概念各自在说什么;本站内容为投资教育,不构成任何投资建议,边界见 免责声明

内容有错、看不懂、或想看下一篇?告诉我们 →

本文为投资教育整理,不构成任何投资建议;不荐个股/基金、不预测点位、不承诺收益。关键数据与结论请结合权威来源自行验证,并见风险免责声明