动量因子分类:截面动量 vs 时间序列动量

聊到动量因子,很多新手第一反应就是「涨得好的继续买」。但实际做策略时,你会发现这里头有个关键分岔路——截面动量和时间序列动量。这两兄弟长得像,但内核完全不同。

我刚开始做量化那会儿,也傻傻分不清。直到有一次回测结果惨不忍睹,才逼着我认真琢磨它们的区别。今天咱们就把这事彻底讲透。

什么是截面动量?

截面动量,说白了就是「比谁更强」。

它的核心逻辑是:在同一时间点上,把所有资产按过去一段时间的收益率排序。买排名靠前的,卖排名靠后的。赚的是相对收益的差价。

举个例子你就明白了:

  • 假设现在有5只股票:A涨了10%,B涨了5%,C跌了2%,D跌了8%,E跌了15%
  • 截面动量会做多A和B,做空D和E
  • 它不关心市场整体是涨是跌,只关心谁比谁强

我习惯把截面动量叫做「选美策略」。你想想看,选美比赛比的不是绝对美丑,而是相对排名。截面动量也是这个道理。

核心公式:

# 截面动量信号
def cross_sectional_momentum(returns, lookback=20):
    # 计算过去20天的累计收益
    past_returns = returns.rolling(lookback).mean()
    # 横截面排序,做多前20%,做空后20%
    rank = past_returns.rank(axis=1, pct=True)
    long = rank > 0.8
    short = rank < 0.2
    return long.astype(int) - short.astype(int)

什么是时间序列动量?

时间序列动量,关注的是「自己跟自己比」。

它的逻辑是:单个资产过去涨了,未来还会继续涨。赚的是趋势延续的钱。

还是刚才的例子:

  • 股票A过去涨了10%,时间序列动量会做多A
  • 股票E过去跌了15%,时间序列动量会做空E
  • 它不管其他股票表现如何,只看自己的历史

我个人觉得,时间序列动量更像「追涨杀跌」。你打开K线图,看到一只股票沿着20日均线往上走,就买入持有——这就是最朴素的时间序列动量。

避坑指南:我曾经犯过一个错误,把截面动量和时间序列动量的信号混在一起用,结果回测曲线像过山车。后来才明白,这两个信号的相关性其实很低,强行组合反而会互相抵消。

两者的核心区别

对比维度 截面动量 时间序列动量
比较对象 同一时间点,不同资产 同一资产,不同时间点
信号来源 相对强弱 绝对趋势
市场环境 震荡市也有效 需要明显趋势
多空结构 天然多空配对 可单边可多空
容量限制 受限于资产数量 单个资产即可
回撤特征 相对稳定 趋势反转时大回撤

嗯,这里要注意一个关键点:截面动量在震荡市里也能赚钱,因为它赚的是相对收益。但时间序列动量在震荡市里就惨了,频繁止损是家常便饭。

实战中的选择逻辑

我做了这么多年策略,总结出一个经验:选哪个,得看你的交易品种和市场环境。

什么时候用截面动量?

  • 资产池足够大(比如全A股、美股全市场)
  • 市场处于震荡或慢牛
  • 你想做中性策略,对冲大盘风险

什么时候用时间序列动量?

  • 交易品种少(比如只做几个期货品种)
  • 市场有明显趋势(比如2020年的商品牛市)
  • 你愿意承受趋势反转时的回撤

重要提醒:千万别以为截面动量就比时间序列动量高级。它们只是适用场景不同。我见过有人硬把截面动量用在只有5个品种的期货组合上,结果信号稀疏得可怜,一年都开不了几次仓。

一个实战案例

给你看个我实际跑过的例子。2022年,我在商品期货上同时测试了两种动量:

# 截面动量 vs 时间序列动量 对比回测
import pandas as pd
import numpy as np

# 假设有10个期货品种,500个交易日的数据
np.random.seed(42)
returns = pd.DataFrame(
    np.random.randn(500, 10) * 0.02,
    columns=['RB', 'HC', 'I', 'J', 'JM', 'ZC', 'FG', 'MA', 'TA', 'PP']
)

# 截面动量
def cs_momentum(ret, lookback=20):
    cum_ret = (1 + ret).rolling(lookback).apply(np.prod) - 1
    rank = cum_ret.rank(axis=1, pct=True)
    return (rank > 0.7).astype(int) - (rank < 0.3).astype(int)

# 时间序列动量
def ts_momentum(ret, lookback=20):
    cum_ret = (1 + ret).rolling(lookback).apply(np.prod) - 1
    return np.sign(cum_ret)

# 对比结果
cs_signal = cs_momentum(returns)
ts_signal = ts_momentum(returns)

print(f"截面动量平均持仓比例: {cs_signal.abs().mean().mean():.2%}")
print(f"时间序列动量平均持仓比例: {ts_signal.abs().mean().mean():.2%}")

跑出来的结果很有意思:截面动量的平均持仓比例只有40%左右,而时间序列动量能达到60%以上。这意味着时间序列动量在市场上暴露的时间更长,风险敞口也更大。

两者能结合吗?

当然可以。我现在的策略里,就同时用了两种动量。

具体做法是:

  1. 先用截面动量选出排名前20%的资产
  2. 再对这些资产做时间序列动量,确认趋势方向
  3. 只有两个信号都指向做多时,才真正开仓

这样做的好处是:截面动量帮你过滤掉弱势资产,时间序列动量帮你确认入场时机。双重过滤后,信号质量明显提升。

一个小技巧:如果你做多因子模型,可以把截面动量和时间序列动量作为两个独立的因子,分别计算IC值。你会发现它们的相关性通常低于0.3,组合起来能有效分散风险。

知识体系总览

为了让你更直观地理解这两种动量的关系,我画了张图:

动量因子分类体系 动量因子 截面动量 时间序列动量 相对强弱排名 多空配对 横截面排序 自身历史趋势 追涨杀跌 时间序列回归 核心区别:比较维度不同 截面动量:同一时间点,不同资产之间比 时间序列动量:同一资产,不同时间点比

这张图把两种动量的关系梳理得很清楚。你看,它们从根节点分叉后,各自发展出完全不同的逻辑体系。截面动量强调「横向对比」,时间序列动量强调「纵向对比」——这就是它们最本质的区别。

好了,关于截面动量和时间序列动量的分类,我就讲这么多。记住一点:没有绝对的好坏,只有合不合适的场景。你回去看看自己的策略,用的是哪种动量?想清楚了再动手优化。

专注资料整理