做量化配置与行业轮动时,很多人首先想到的路径是“宏观自上而下映射”: 跟踪 CPI、非农就业、制造业 PMI 与央行基准利率,依据经济周期处于复苏、过热、滞胀还是衰退,在科技、金融、能源与公用事业之间切换。

在实盘工程中,这条路径面临物理层面的硬约束:宏观数据的严重时滞。 核心 CPI 与非农数据通常在次月中旬披露,GDP 增长率滞后整个季度,且统计局在发布后数月内仍会多次修正历史初值。 金融市场是连续竞价的资产定价机器。当宏观统计数据终于形成确定性报告时,行业估值早已经历了 3 到 6 个月的预期吸收与价格重构。试图依据滞后的宏观统计去指导实盘换仓,极易陷入高位接盘或错过拐点的被动局面。

截面动量(Cross-Sectional Momentum)的核心逻辑在于放弃对宏观指标的主观预测,将全市场资产的价格走势视为全量资本博弈后的最优信息综合体。 通过对一组具备代表性的行业或大类资产 ETF 建立客观排序规则,捕捉资金流驱动下的中周期趋势,并通过绝对动量门限过滤系统性回撤风险。


一、行业周期分化与截面信息吸收

为了观察行业周期的真实运行规律,选取覆盖标普 500 的 11 个标准行业精选 ETF,统计 2020 年 1 月至 2026 年 9 月共 1,694 个交易日的真实后复权行情:

etf_sector_dispersion_and_correlation
点击放大
etf_sector_dispersion_and_correlation

观察上方图表面板 1 的累计走势,不同行业在宏观周期冲击下的分化极其剧烈:

  1. 2020 年宽货币周期。公共卫生事件爆发后,全球流动性释放,科技行业(XLK)单年涨幅达 +43.6%,可选消费(XLY)同步上行,传统周期与能源行业陷入停滞。
  2. 2022 年加息通胀周期。美联储开启超预期紧缩加息,纳斯达克与科技板块(XLK)暴跌 -27.3%,通信服务(XLC)下跌 -37.1%;地缘冲突与通胀推升油气价格,能源行业(XLE)逆势大涨 +44.6%,公用事业(XLU)取得正收益。两者之间形成了超过 70 个百分点的极端剪刀差。
  3. 2023 至 2024 年算力周期。大模型技术落地带动硬件与云服务爆发,科技板块再次成为领涨龙头,累计净值突破初始基准的 4 倍。

整个样本区间内,科技板块累计上涨 +310.5%,而房地产(XLRE)仅上涨 +28.0%,公用事业仅上涨 +40.9%。 如果把面板 2 的日收益率相关系数矩阵展开,可以看到传统股票行业之间通常保持较高相关性(0.60 至 0.85 之间)。 能源(XLE)与科技(XLK)的相关系数仅为 0.37,与公用事业的相关性仅为 0.39。 长端美债(TLT)与股票资产整体呈现弱负相关(-0.10 至 -0.24),黄金(GLD)呈现低相关(0.03 至 0.24)。

这种行业间周期的非同步性与相关性割裂,为量化截面动量轮动提供了物理收益空间。


二、从简单收益率到风险调整动量

截面动量的最基础形态,是在时点 tt 回溯过去 LL 个交易日的名义收益率:

Ri(t,L)=Pi(t)Pi(t−L)−1R_i(t, L) = \frac{P_i(t)}{P_i(t-L)} - 1

1. 简单动量的物理缺陷

直接用 Ri(t,L)R_i(t, L) 对全行业进行大小排序存在天然缺陷:高 Beta 与高波动资产的虚假优势。 科技(XLK)或半导体 ETF 的日度年化波动率长期在 25% 以上,而必选消费(XLP)与公用事业(XLU)的年化波动率通常在 12% 左右。 在宽幅震荡或牛市初期的脉冲行情中,高波动资产的名义涨幅天然高于低波动资产。若不加约束,策略会长期被高 Beta 行业垄断。一旦市场在高位遭遇流动性收紧,高波动行业往往也是回撤最剧烈、均值回归最深的方向。

2. 风险调整动量公式

为了剥离波动率带来的虚假收益,学术界与实盘机构普遍采用风险调整动量(Risk-Adjusted Momentum)。将回溯期收益除以该标的在同一区间的年化已实现波动率(Realized Volatility):

Mi(t,L)=Ri(t,L)σi(t,L)M_i(t, L) = \frac{R_i(t, L)}{\sigma_i(t, L)}

其中:

σi(t,L)=252L−1∑τ=0L−1(ri(t−τ)−rˉi)2\sigma_i(t, L) = \sqrt{\frac{252}{L - 1} \sum_{\tau=0}^{L-1} \left( r_i(t-\tau) - \bar{r}_i \right)^2}

通过波动率归一化,低波动行业在温和单边上涨行情中能够凭借平稳走势获得更高的动量得分,避免组合过度向高方差资产单向倾斜。

3. 短期反转跳空(1-Month Reversal Skip)

另一个微观工程细节是动量窗口的时点切分。 学术经典文献(Jegadeesh & Titman 1993)指出,股票与行业资产在 1 周到 1 个月频段内存在显著的短期流动性供求冲击与均值反转效应。 如果回溯期紧贴当前交易日(如回溯过去 20 天),策略极容易在某行业发生短期非理性放量脉冲时高位买入,次日即面临流动性衰竭反噬。 实盘稳健的动量计算通常引入“跳空机制”(Skip Recent Period),例如计算过去 12 个月收益但剔除最近 1 个月(12-1 Momentum):

Riskip(t,L,Δ)=Pi(t−Δ)Pi(t−L−Δ)−1,Δ≈21R_i^{\text{skip}}(t, L, \Delta) = \frac{P_i(t - \Delta)}{P_i(t - L - \Delta)} - 1, \quad \Delta \approx 21

三、“纯相对动量”在熊市的死穴与双动量解法

在实盘中只使用截面排序会遇到一个致命痛点:相对动量的死穴是“矮子里面挑将军”。

截面排序的数学本质是比较运算符。如果在 2020 年 3 月全球流动性踩踏、或 2022 年加息股债双杀的系统性熊市中,11 个行业全部处于下行通道:

  • 科技(XLK)过去两个月下跌 -25%;
  • 工业(XLI)下跌 -18%;
  • 必需消费(XLP)下跌 -12%。

如果策略规则是“每月选出动量最高的前 2 个行业并满仓持有”,那么模型会精准把全部资金买入下跌 -12% 的必需消费品。 虽然在截面上跑赢了科技板块,但对于实盘账户而言,净值依然在不可逆地承受负收益与资金回撤。

双动量(Dual Momentum)机制

Gary Antonacci 提出的双动量模型,将截面相对动量与时序绝对动量(Trend / Absolute Momentum)融合:

[ 候选资产全集: 11 大行业 ETF ]
              │
              ▼
[ 步骤 1: 截面相对强弱排序 ] ──► 筛选动量最高的前 K 个行业 (Top K)
              │
              ▼
[ 步骤 2: 时序绝对动量检验 ] ──► 逐一检验当前资产是否处于安全趋势:
                                   条件: P_i(t) > MA200_i 且 R_i(t, L) > 0
              ├──► 满足条件 ──► 确认买入该行业 ETF (等权配置)
              └──► 跌破趋势 ──► 该份额强制切入防御资产 (黄金 GLD / 现金)
  1. 相对动量定标的。在候选行业池内,通过风险调整得分排序,选出综合动量最强的前 KK 个行业。
  2. 绝对动量定仓位。对入选的行业进行单品种趋势过滤:
    • 如果该行业最新收盘价高于其 200 日移动平均线(Pi(t)>MA200(t)P_i(t) > MA_{200}(t)),且中期回报率高于无风险收益门限(Ri(t,L)>0R_i(t, L) > 0),则判定绝对趋势向上,执行正常买入。
    • 一旦该行业跌破 200 日线或绝对收益转负,说明全市场处于系统性崩塌环境。策略强制将该部分仓位剥离,买入低相关防御资产(如实物黄金 GLD、长端国债 TLT 或货币基金)。

四、全流程向量化 Python 回测实现

以下给出零未来数据泄漏、包含风险调整与双动量状态转移的完整可执行代码模块:

import numpy as np
import pandas as pd

class ETFSectorDualMomentumBacktester:
    """
    ETF 行业轮动与多资产双动量回测引擎
    包含风险调整动量排序、200 日均线时序过滤、换手率统计与交易成本扣除
    """
    def __init__(
        self,
        prices: pd.DataFrame,
        sector_cols: list[str],
        defense_col: str = "US.GLD",
        top_k: int = 2,
        lookback_days: int = 180,
        skip_days: int = 21,
        rebalance_step: int = 42,
        cost_bps: float = 8.0,
    ):
        self.prices = prices.sort_index()
        self.returns = self.prices.pct_change()
        self.sector_cols = sector_cols
        self.defense_col = defense_col
        self.all_cols = list(prices.columns)
        self.top_k = top_k
        self.lookback = lookback_days
        self.skip = skip_days
        self.rebal_step = rebalance_step
        self.cost_rate = cost_bps / 10000.0  # 单边交易摩擦

    def run(self, start_idx: int = 260) -> dict:
        dates = self.prices.index[start_idx:]
        nav = [1.0]
        turnovers = []
        curr_weights = pd.Series(0.0, index=self.all_cols)
        
        dates_list = list(dates)
        for i in range(len(dates_list) - 1):
            d = dates_list[i]
            next_d = dates_list[i + 1]
            
            # 调仓时点
            if i % self.rebal_step == 0:
                hist_p = self.prices.loc[:d]
                
                # 1. 动量计算 (带 21 天反转剔除)
                if self.skip > 0 and len(hist_p) > self.lookback + self.skip:
                    p_end = hist_p[self.sector_cols].iloc[-self.skip]
                    p_start = hist_p[self.sector_cols].iloc[-(self.lookback + self.skip)]
                    mom = p_end / p_start - 1.0
                else:
                    mom = hist_p[self.sector_cols].iloc[-1] / hist_p[self.sector_cols].iloc[-self.lookback] - 1.0
                
                # 2. 过去同一窗口的年化已实现波动率
                vol = (
                    hist_p[self.sector_cols]
                    .pct_change()
                    .iloc[-self.lookback:]
                    .std() * np.sqrt(252)
                )
                vol = vol.replace(0, np.nan)
                
                # 3. 风险调整动量得分
                score = mom / vol
                top_candidates = score.nlargest(self.top_k).index.tolist()
                
                # 4. 双动量过滤 (200 日均线与绝对收益检验)
                target_weights = pd.Series(0.0, index=self.all_cols)
                ma200 = hist_p[self.sector_cols].iloc[-200:].mean()
                slot_weight = 1.0 / self.top_k
                
                for s in top_candidates:
                    is_above_ma = hist_p[s].iloc[-1] > ma200[s]
                    is_positive_mom = mom[s] > 0.0
                    
                    if is_above_ma and is_positive_mom:
                        target_weights[s] += slot_weight
                    else:
                        # 触发防御: 切入低相关避险资产
                        target_weights[self.defense_col] += slot_weight
                
                # 5. 换手率与交易成本
                turnover = (target_weights - curr_weights).abs().sum() / 2.0
                turnovers.append(turnover)
                curr_weights = target_weights.copy()
                cost = turnover * self.cost_rate
            else:
                cost = 0.0
                
            # 每日收益结算与权重因涨跌自然漂移
            r_day = (curr_weights * self.returns.loc[next_d]).sum() - cost
            nav.append(nav[-1] * (1.0 + r_day))
            
            curr_weights = curr_weights * (1.0 + self.returns.loc[next_d])
            total_w = curr_weights.sum()
            if total_w > 0:
                curr_weights = curr_weights / total_w

        # 整理绩效指标
        s_nav = pd.Series(nav, index=dates)
        cagr = (s_nav.iloc[-1]) ** (252.0 / len(s_nav)) - 1.0
        daily_r = s_nav.pct_change().dropna()
        ann_vol = daily_r.std() * np.sqrt(252)
        sharpe = (cagr - 0.02) / ann_vol if ann_vol > 0 else 0.0
        dd = (s_nav - s_nav.cummax()) / s_nav.cummax()
        max_dd = dd.min()
        ann_turnover = np.mean(turnovers) * (252.0 / self.rebal_step) * 100.0
        
        return {
            "nav_series": s_nav,
            "cagr": cagr,
            "annual_vol": ann_vol,
            "sharpe": sharpe,
            "max_drawdown": max_dd,
            "annual_turnover": ann_turnover,
        }

五、实测表现与参数敏感性分析

将上述回测框架部署到 2021 年至 2026 年真实样本中,测试纯相对动量、双动量以及基准表现,并对回溯周期与调仓频率进行网格敏感性压力测试:

etf_dual_momentum_backtest_and_frontiers
点击放大
etf_dual_momentum_backtest_and_frontiers

1. 熊市对冲与回撤收窄

观察上方图表面板 1 的策略净值轨迹:

  • 标普 500 基准(SPY)。2021 至 2026 年年化复合回报率(CAGR)为 13.3%,年化波动率 15.2%,夏普比率 0.74,在 2022 年加息周期中最大回撤达到 -22.6%。
  • 纯截面相对动量。年化复合收益率仅 5.9%,最大回撤达到 -28.6%,表现落后于指数。原因正如前文分析:在 2022 年大盘单边杀跌时,相对动量策略在缺乏绝对防御过滤的情况下频繁在各下跌行业之间切换,被动承担了极高的换手磨损与深度回撤。
  • 风险调整双动量。年化复合回报率达 8.1%,虽然牛市后期弹性略低于纯持股龙头,但最大回撤显著收窄至 -19.4%。在 2022 年整个三四季度,双动量模型敏锐捕捉到能源行业(XLE)的强势,同时在其他行业跌破 200 日线时坚决切入黄金(GLD),净值曲线走出明显的抗震荡防御特征。

2. 动量参数前沿面:换手率与净夏普的权衡

面板 2 揭示了回溯窗口(Lookback Days)与调仓频率对策略真实表现的决定性影响:

  • 过度反应区(高换手与噪音磨损)。 采用超短回溯(20 天)配合高频调仓(周频 5D 或双周频 10D)时,策略年化换手率高达 1,200% 至 1,879%。 微观层面的频繁反复打脸使摩擦成本成倍累加,即便每次交易单边费率仅为 8 bps,高频换手仍严重侵蚀收益,净夏普比率直接衰减至 0.38 至 0.52。
  • 动量平滑甜点区(Sweet Spot)。 当回溯窗口拉长至中长周期(120 至 252 天),调仓周期设定在月频(21D)或双月频(42D)时,策略表现进入高性价比区间。 其中,180 天回溯配合双月调仓取得了 0.88 的最高净夏普比率,策略年化换手率被平滑压缩至 210% 左右。
  • 动量迟钝区。 若将回溯周期无限拉长(如超过 300 天)且调仓降至季频(63D),策略在牛熊切换节点调头迟缓,错过行业结构轮动的核心主升浪,夏普比率重新滑落至 0.70 附近。

六、工程落地检查清单

在将 ETF 行业轮动与多资产动量策略推向日常交易时,建议按以下四项客观标准核对:

  1. 复权类型必须对齐。行业 ETF 包含季度分红与再投资收益。回测信号计算必须采用后复权或全收益指数(Total Return Index)收盘价,绝不能使用除权除息跳空的价格序列。
  2. 拒绝收盘价当日撮合。在回测中,动量计算只能基于交易日 TT 的收盘价;实际交易指令应在 T+1T+1 日开盘通过 TWAP 或市价单执行,绝不能在 TT 日收盘秒级内同时完成信号计算与收盘价精确成交。
  3. 保持适度持仓分散。行业 ETF 虽然内部已有一篮子股票分散,但单行业仍具备强烈的产业政策与供给扰动。建议每次轮动持有 K≥2K \ge 2 到 3 个行业,避免将全部资产押注在单一赛道。
  4. 绝对动量门限必须刚性执行。当选拔出的领头行业跌破 200 日均线或收益为负时,必须有预设的防御资产承接(现金、短债或黄金)。不能在熊市中主观取消绝对动量限制,否则策略退化为纯相对动量并承受深度亏损。