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

动量因子:追涨为什么有时候是对的

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

有件事你大概干过:账户里两只票,一只买进去就一路涨,另一只买完就套。要用钱的时候,你几乎不用想就把涨的那只卖了——落袋为安嘛——留着套牢的那只,等它回本。

结果往往是,卖掉的那只后来又涨了一大截,留着的那只继续躺在那儿。

这个动作背后的直觉是「涨多了该跌了」。而市场在很多时候给出的答案恰恰相反:在一个不算短也不算长的时间窗口里,前一段涨得好的那批,接下来倾向于继续跑赢。这件事有名字,叫动量。它听起来像是给追涨杀跌背书,实际上比这复杂得多——它对时间尺度敏感到什么程度呢?同一段价格数据,你把观察窗口从一个月换成半年,结论会整个翻过来。

先说人话:动量到底是个什么劲儿

想象你在一条美食街上找馆子。两家店,一家门口排了半小时的队,另一家空着。凭直觉你会去哪家?

大多数人去排队那家。理由不是「排队的东西一定好吃」,而是「这么多人愿意等,大概率不难吃」。更关键的是——队伍本身会把队伍变长。路过的人看到队,也去排;美食博主看到队,拍个视频;视频带来更多人。好口碑变成人流,人流又变成更大的口碑,中间隔着时间差,一层一层往外扩。

动量说的就是这层「往外扩要花时间」。一家公司业绩变好,不是全世界同一秒知道的。先是几个盯得紧的人察觉,然后是研报,然后是新闻,然后是你的同事在饭桌上提一句。信息像水渗进沙子一样慢慢铺开,价格也就跟着一段一段往上走,而不是一步到位跳到该去的位置。

另一半原因在人身上。手里的票赚了钱,你急着卖(怕煮熟的鸭子飞了);亏了钱,你死攥着不放(卖了就是承认自己错了)。这两个动作合起来,会人为压住上涨、也人为托住下跌——好消息被过早兑现的卖单摁住,价格慢慢磨上去;坏消息被不肯认赔的持有者拖着,价格慢慢磨下来。慢,就是动量的原料。

还有第三股力量:钱会追着业绩跑。哪个方向表现好,资金就往哪个方向流,流入本身又推高价格。这一层跟公司好不好没关系,纯粹是资金行为。

所以动量不是「涨了就还会涨」这么一句玄学,它背后至少压着三件具体的事:信息扩散有时滞、人性对盈亏不对称、资金追着业绩走。三件事都会累,累了动量就没了——这是后面失效边界的伏笔。

时间尺度:同一段价格,三个窗口,三种答案

这是本篇最该记住的一点,也是最多人栽跟头的地方。

很短的窗口里,占上风的是反转。 一只票最近几天猛拉,接下来的一小段时间偏向回落。原因不神秘:短期的剧烈波动里,很大一部分是有人急着买或急着卖,把价格推离了原本该待的位置。这类推力一停,价格自己就往回收。这一层的力量属于均值回归的地盘,均值回归那篇讲的钟摆,说的就是它。

中等的窗口里,占上风的是动量。 大致从几个月到一年这个跨度上,前面跑得好的那批,倾向于继续跑得好。前面说的三股力量,都在这个尺度上生效。

很长的窗口里,反转又回来了。 拉到三五年这个量级,涨过头的估值最终要还,跌过头的迟早被人捡起来。这一层是基本面在结账。

同一个「过去表现」,在三个尺度上指向三个不同的方向。这意味着一件很反直觉的事:你算动量的时候,窗口不是一个可以随便调的参数,它决定了你在赌哪一套机制。 一个把窗口设成三天的「动量」,本质上是在做反转;设成五年的「动量」,本质上是在做估值回归。名字都叫动量,赌的东西完全不同。

正因如此,实务上常见的做法是把最近这一小段掐掉——比如算过去一年的涨幅,但故意跳过最近一个月,用「一年前到一个月前」这段。原因就是不让短期反转的噪音混进中期动量的信号里。这个动作叫跳空(skip),它不是什么技巧,是承认「这两段时间里起作用的机制不是同一个」。

再分一层:动量还有两种问法,很多人会混。

一种是跟自己比:这个品种比它自己三个月前是涨了还是跌了?涨了就算有动量。这叫时序动量。

另一种是跟别人比:全市场几千只票,按过去一段涨幅从高到低排个序,取前面那批。这叫横截面动量。注意这时候「涨了多少」的绝对数不重要,重要的是名次——全市场都在跌的时候,跌得最少的那批,横截面上依然算高动量。

这两种在系统里长得完全不一样,混着说会出乱子。

系统层:动量在真实代码里长什么样

先说清楚这一节的定位:动量是概念,下面这些只是它在某一套系统里的一个具体形状,看看就好,别当成配方。

在 qlib 这类量化研究框架里,价格特征是用一种表达式语言批量生成的。整个 Alpha158 特征集里,跟「过去表现」有关的写法有好几种,每一种量的是不同侧面。它们共用一组滚动窗口,源码里的当前默认值是 5、10、20、30、60 这几个交易日长度(可配置)。

最直白的一个叫 ROC:

# qlib/contrib/data/loader.py 快照 79633dd
fields += ["Ref($close, %d)/$close" % d for d in windows]
names  += ["ROC%d" % d for d in windows]

Ref($close, d) 是「d 天前的收盘价」。这一行的意思是:把 d 天前的价格除以今天的价格。

请注意这个除法的方向——分子是过去,分母是现在。所以涨得越多,这个数越小。它跟你直觉里的「涨幅」符号是反的。这不是 bug,框架只是选了一种归一化写法(除以最新价是为了去掉量纲,让不同价位的股票能放在一起比)。但对使用者来说,这就是一个非常典型的、能让人白忙一周的坑:你以为数值越大动量越强,实际上正好倒过来,跑出来的结果全反了,还找不到原因。

任何一个因子,先确认它的符号方向,再看它的数值大小。 这句话值得贴在显示器上。

再往下,同一份文件里还有几种「过去表现」的不同刻画方式,摆在一起看很有意思:

# 同一份文件里的另外几种写法(快照 79633dd)
"Slope($close, %d)/$close"   # BETA:  过去 d 天价格的线性斜率
"Rsquare($close, %d)"        # RSQR:  这条斜线拟合得有多好
"Resi($close, %d)/$close"    # RESI:  偏离这条斜线的残差
"Rank($close, %d)"           # RANK:  今天的价格在过去 d 天里排第几
"IdxMax($high, %d)/%d"       # IMAX:  距离上一次创新高过了多久

这一串东西回答的是同一个问题的不同侧面。ROC 只说「涨了多少」;BETA 说「涨得多陡」;RSQR 说「这个涨是一条顺滑的斜线,还是上蹿下跳凑出来的」;RANK 说「今天的位置在最近这段里靠不靠前」;IMAX 说「最近一次创新高是不是还热乎」。

两只票过去六十天涨幅一模一样,一只是天天小涨的直线,一只是先崩一半再暴力拉回来的过山车——ROC 看不出区别,RSQR 一眼就看出来了。「涨了多少」和「涨得稳不稳」是两件事,而后者往往才是你真正在意的那件。这也是为什么实务里很少有人只用一个动量数值,通常要搭上质量、稳定性的刻画一起看。想系统理解这类「把一句判断变成一列可计算的数」的思路,回到因子那篇。

最后还有一层容易被忽略:算出来的动量数值,往往不能直接用。一段行情里某个行业整体大涨,那么这个行业的股票在动量排名上会集体冲到前面——你以为自己选出了「有动量的公司」,实际上只是选出了「那个行业」。要把这类不想要的暴露剔掉,得先做中性化。不做这一步,你的动量组合很可能只是一个伪装过的行业押注。

A 股这边,动量会撞上几堵墙

动量这套逻辑本身不分国界,但能不能吃到它,取决于你所在市场的交易规则。A 股的规则会在几个地方硬生生把它掐断。

第一堵墙:涨停买不进。 横截面动量的做法是选出涨得最猛的那批。可 A 股有涨跌幅限制,最猛的那批里,相当一部分当天封在涨停板上——挂单排在几万手后面,根本成交不了。

这堵墙在代码里非常具体。Vibe-Trading 的 A 股引擎里,每一笔单子在成交前都要先过一道可执行性检查,撞上价格带就直接返回不可成交:

# agent/backtest/engines/china_a.py 快照 3a752d5
# 3. Price limits, tested at execution time (see _blocked_by_limit).
if _blocked_by_limit(self, symbol, direction, bar, _price_limit(symbol)):
    return False

这里有个细节值得琢磨:这道检查用来算价格带的基准价,取的是下单之前市场已知的价格(前收盘价,或上一根 K 线的收盘),而不是当天的收盘价。文件注释里写得很明白,早先用当天收盘价的实现是错的——那等于在下单时用了当天收盘后才知道的信息。

对动量来说,这道检查的存在意味着什么?意味着回测里那条漂亮的动量收益曲线,有一部分是靠买入根本买不到的票堆出来的。一套动量方法在没有涨跌停约束的回测里表现优异,接上这道检查之后收益大幅缩水,是很常见的事。缩水的那部分不是运气差,是原本就不该属于你。

第二堵墙:T+1。 同一份引擎的规则里,当天买进的股票当天不能卖。动量信号常常来得急去得也急,今天入选明天就该换掉——T+1 直接给这类快速切换加了一天的强制持有期,中间发生什么你只能受着。

第三堵墙:换手快,信号也就衰减快。 A 股的换手率在全球范围内偏高,个人投资者占比也高,这意味着「信息慢慢渗透」这个前提被压缩了——扩散过程更快、更嘈杂,题材从起来到熄火的周期也更短。同样一个动量窗口,在换手慢的市场里可能刚好,在换手快的市场里可能已经过期。这就引出了另一个绕不开的问题:信号衰减——一个动量信号从产生到失效能撑多久,直接决定你得多频繁地调仓,而调仓频率又直接决定你要交多少手续费和印花税。很多动量方法不是败在信号不对,是败在换手太勤,赚的那点差价全填了成本。

第四堵墙:停牌与 ST。 一只票停牌两个月,它的「过去六十日涨幅」是拿什么算的?复牌当天补跌,又算谁的?涨跌幅限制在不同板块、不同状态下也不一样,代码注释里就列了主板、创业板科创板、ST 三档不同的带宽。这些都不是理论问题,是你算动量时每一天都要处理的具体麻烦。

顺带说一句,「动量」这个词在中文语境里常被简化成「追涨」,两者不是一回事。追涨通常指看到某只票今天大涨就冲进去,那是几小时到几天的尺度,正好落在前面说的反转区间里,而不是动量区间。要看这类短周期的涨速刻画,MTM 动量指标那篇讲的是另一件事,别把两者混着用。

它什么时候会咬人

动量最著名的问题,是它的亏损方式很难看。

平时它慢慢赚,每次赚一点,曲线看着挺顺。然后在某些时刻——通常是市场从深度恐慌里急速反弹的那几天——它会在极短时间里把很长一段时间积累的收益吐回去。这个现象在文献和业内都被反复讨论,叫动量崩溃。

机制并不复杂。恐慌期里,跌得最惨的那批票会被动量排到最后一名,动量组合天然站在它们的对立面。而当情绪突然反转,反弹力度最猛的恰恰就是这批跌得最惨的。你站错的那一边,恰好是弹性最大的一边。动量在下跌久了之后,会不知不觉变成一个赌「不反弹」的头寸,而这个赌注的赔率极差——它平时赢小钱,输起来一次就够。

另外几条也值得记住。

拥挤。 动量是最容易被复现的因子之一,算法简单、数据要求低。用的人越多,同一批票被同时买入,价格被提前推到位,剩给你的空间就越薄;更糟的是撤退时也会一起挤门。

窗口敏感。 前面说过,换个窗口机制就变了。这带来一个很实际的风险:你在历史数据上试了十几个窗口,挑出表现最好的那个——这个动作本身就是在拿历史噪音做选择,选出来的窗口在未来大概率不成立。

不解释「为什么」。 动量因子只知道这只票涨得好,不知道它为什么涨得好。是业绩真的变好,还是有人在讲故事,因子分不出来。这一层的缺失,得靠别的东西补。

🚧 避坑

别把「动量有效」读成「追涨可行」。 学术意义上的动量,说的是一个分散的、按名次构建的、定期再平衡的组合,在特定时间尺度上呈现出的统计倾向。它跟「看到某只票涨停就买进去」是两码事——后者的时间尺度落在反转区间,持仓也不分散,承受的是完全不同的风险。把前者的证据拿来给后者背书,是这个概念最常见、也最贵的一次误用。

小结

  • 动量说的是:在中等时间尺度上,过去表现好的那批倾向于继续表现好。它背后是信息扩散有时滞、人对盈亏的反应不对称、资金追着业绩跑这三件具体的事。
  • 时间尺度决定你在赌哪套机制:很短的窗口里占上风的是反转,中等窗口里是动量,很长的窗口里反转又回来。窗口不是可以随手调的参数。
  • 跟自己比(时序)和跟别人比(横截面)是两种不同的动量,系统里的实现和承受的风险都不一样。
  • 系统里的动量不止一个数:涨了多少、涨得多陡、涨得稳不稳、位置靠不靠前、创新高多久了——刻画的是同一件事的不同侧面,其中「稳不稳」往往比「涨了多少」更值钱。
  • 用之前先确认符号方向。框架里的归一化写法可能让「数值越大」对应「涨得越少」。
  • A 股这边,涨停买不进、T+1、换手快导致信号衰减快、停牌与 ST 的特殊带宽,每一条都会直接吃掉纸面上的收益。回测不带这些约束,结果不能信。
  • 它的亏损形态是「平时小赚、反转时一次性大亏」,因为跌久之后它会悄悄变成一个赌不反弹的头寸。

一句话记住这篇:强者恒强这件事有时候是真的,但只在一段特定的时间长度里是真的,而且你得能真的买得进去。

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

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

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