4、动量与反转因子:行业动量因子构建、动量衰减周期、反转效应识别

聊到行业轮动,动量与反转是绕不开的两个核心概念。我个人习惯把它们比作市场的「惯性」和「弹簧」——涨多了会继续涨一阵,但涨太猛了又会弹回来。今天我们就来拆解这两个因子,看看怎么用它们捕捉行业轮动的节奏。

4.1 行业动量因子构建

动量因子,说白了就是「强者恒强」。我刚开始做量化那会儿,以为动量就是简单算个过去N天的收益率,然后排序买入。后来发现,事情远没那么简单。

一个靠谱的行业动量因子,至少要考虑三个维度:

  • 收益率动量:过去一段时间(比如20个交易日)的累计涨幅
  • 超额动量:行业相对于大盘的超额收益
  • 波动率调整动量:用波动率对收益率做标准化处理

为什么要做波动率调整?举个例子,两个行业过去20天都涨了10%,但A行业波动率是20%,B行业是5%。你想想看,A行业那10%的涨幅可能只是运气,B行业才是真正的趋势。我在项目中遇到过这种情况,直接用原始动量选出来的行业,回撤大得吓人。

下面是我常用的一个动量因子构建代码:

def build_momentum_factor(df, window=20):
    """
    构建行业动量因子
    df: 包含行业指数日度收益率的数据框
    window: 动量计算窗口
    """
    # 计算累计收益率
    df['cum_ret'] = (1 + df['daily_ret']).rolling(window).prod() - 1
    
    # 计算波动率(年化)
    df['vol'] = df['daily_ret'].rolling(window).std() * np.sqrt(252)
    
    # 波动率调整动量
    df['momentum'] = df['cum_ret'] / df['vol']
    
    # 还可以加入衰减权重(近期权重更大)
    weights = np.exp(np.linspace(-1, 0, window))
    weights = weights / weights.sum()
    df['weighted_momentum'] = df['daily_ret'].rolling(window).apply(
        lambda x: np.dot(x, weights)
    )
    
    return df

个人经验:我建议动量窗口不要只用单一周期。20天、60天、120天的动量信号做个综合打分,效果往往比单周期好。但注意,周期越长,信号越滞后,这是个trade-off。

4.2 动量衰减周期

动量不是永恒的。它像一杯热水,放久了总会凉。这个「凉」的过程,就是动量衰减。

为什么会衰减?原因有三:

  1. 拥挤交易:追涨的人太多,把价格推到了不合理的位置
  2. 均值回归:基本面不支持持续上涨,价格终将回归
  3. 资金轮动:获利盘了结,转向其他低估板块

我做过一个统计,A股行业动量的平均有效周期大约是3-6个月。超过这个时间,动量效应就会明显减弱,甚至反转。嗯,这里要注意,不同市场、不同行业,衰减速度是不一样的。

比如科技行业,动量衰减就很快,可能1-2个月就失效了。为什么?因为科技股波动大,资金进出频繁,趋势很难持续。而消费行业,动量可以维持半年以上,因为业绩稳定,资金抱团效应强。

行业类型 动量有效周期 衰减速度 典型代表
科技/成长 1-2个月 半导体、计算机
消费/医药 3-6个月 中等 白酒、医药
周期/金融 6-12个月 煤炭、银行

避坑指南:我曾经在2018年用6个月动量做周期行业,结果被狠狠打脸。后来才发现,周期行业的动量受宏观经济影响极大,单纯用历史价格动量根本不够。一定要结合库存周期、PPI等宏观指标一起看。

4.3 反转效应识别

反转效应,就是「跌多了会涨,涨多了会跌」。听起来简单,但识别反转的时机才是关键。

我常用的反转识别方法有三种:

  • 短期反转:过去5-10天跌幅过大,出现超卖信号
  • 中期反转:过去1-3个月趋势衰竭,成交量萎缩
  • 极端反转:行业指数偏离均线超过2个标准差

这里有个技巧——动量与反转的切换点。我个人习惯用「动量衰减率」来判断:当动量因子的值从高点回落超过30%,同时成交量开始放大,这就是反转可能发生的信号。

来看一个反转识别的代码示例:

def identify_reversal(df, momentum_col='momentum', threshold=0.3):
    """
    识别反转信号
    threshold: 动量衰减阈值
    """
    # 计算动量衰减率
    df['momentum_decay'] = df[momentum_col].pct_change(5)
    
    # 判断反转条件
    df['reversal_signal'] = 0
    # 条件1:动量从高点回落超过30%
    cond1 = df['momentum_decay'] < -threshold
    # 条件2:成交量放大(相比20日均值)
    cond2 = df['volume'] > df['volume'].rolling(20).mean() * 1.2
    # 条件3:价格偏离20日均线超过5%
    cond3 = abs(df['close'] / df['close'].rolling(20).mean() - 1) > 0.05
    
    df.loc[cond1 & cond2 & cond3, 'reversal_signal'] = 1
    
    return df

核心要点:反转信号不能单独使用。我建议把反转信号和动量信号结合起来,做成一个「动量-反转」复合因子。当动量强时,跟随动量;当动量衰减且反转信号出现时,反向操作。这样既能吃到趋势的肉,又能躲过反转的坑。

4.4 动量与反转的实战框架

说了这么多,怎么落地?我画了一张框架图,把整个逻辑串起来:

动量与反转因子实战框架 行业指数数据 因子计算 收益率动量 | 波动率调整 | 衰减权重 动量衰减判断 衰减率 < 30% → 继续持有动量 衰减率 ≥ 30% → 检查反转信号 反转信号 → 反向操作 动量持续 → 顺势操作

这个框架的核心逻辑很简单:先算动量,再判断动量是否衰减。如果衰减了,就去看反转信号是否成立;如果没衰减,就继续跟着动量走。我在实盘中用这套逻辑,年化超额收益大概能提升5-8个百分点。

一个小技巧:动量衰减的阈值不要设死。市场波动大的时候,阈值可以放宽到40%;市场平稳的时候,收紧到20%。说白了,就是要根据市场状态动态调整参数。

最后说一句,动量与反转不是非此即彼的关系。它们就像硬币的两面,关键是你得知道什么时候该翻哪一面。多复盘、多测试,慢慢你就能找到那个节奏感。


公众号:蓝海数据掘金营,微信deep3321