4. 动量因子构建:过去N日收益率动量、过去N日超额收益动量、动量因子平滑处理、动量因子排名与打分

动量因子,说白了就是「追涨杀跌」的量化版本。别笑,这招在A股市场里,其实挺管用的。

我个人习惯把动量因子拆成四个步骤来构建:先算原始动量,再算超额动量,然后做平滑处理,最后排名打分。每一步都有坑,我一个个说。

4.1 过去N日收益率动量

这是最基础的动量。公式很简单:

动量 = (当前收盘价 - N日前收盘价) / N日前收盘价

嗯,这里要注意。N的取值很关键。我见过有人用5日、20日、60日,甚至250日。到底用哪个?

我的经验是:短周期动量(5-10日)噪音大,长周期动量(60日以上)反应慢。我个人偏向用20日,也就是一个月左右的交易窗口。为什么?因为A股的一个完整情绪周期,差不多就是20个交易日。

代码实现也很直接:

import pandas as pd
import numpy as np

def calc_momentum(price_series, window=20):
    """
    计算过去N日收益率动量
    """
    momentum = price_series.pct_change(periods=window)
    return momentum

# 示例:计算个股的20日动量
stock_data['momentum_20d'] = calc_momentum(stock_data['close'], 20)
避坑指南
我曾经踩过一个坑:直接用复权价格算动量。结果发现,除权除息日那天动量会突然跳变。后来我改用前复权价格,才解决了这个问题。记住,一定要用前复权价格

4.2 过去N日超额收益动量

光看个股自己的涨跌还不够。你得知道它跑赢了市场没有。这就是超额收益动量。

公式也很简单:

超额动量 = 个股动量 - 基准动量

基准一般用沪深300或者全A指数。我个人习惯用全A等权指数,因为能更真实反映市场整体情况。

def calc_excess_momentum(stock_momentum, benchmark_momentum):
    """
    计算超额收益动量
    """
    excess_momentum = stock_momentum - benchmark_momentum
    return excess_momentum

# 示例
stock_data['excess_momentum_20d'] = calc_excess_momentum(
    stock_data['momentum_20d'], 
    benchmark_data['momentum_20d']
)
一个小技巧
如果你发现某只股票动量很强,但超额动量很弱,说明它只是跟着大盘涨。这种票,我一般会排除掉。真正值得关注的,是那些超额动量持续为正的股票。

4.3 动量因子平滑处理

原始动量数据,噪音太大了。你想想看,一天的大涨大跌,就能让动量值剧烈波动。所以我们需要做平滑处理。

我常用的方法有三种:

方法 说明 我的评价
简单移动平均(SMA) 对过去N期动量取平均 简单粗暴,但滞后明显
指数加权移动平均(EMA) 近期数据权重更大 我最常用,反应快且平滑
中位数平滑 取过去N期中位数 抗极端值能力强

我个人偏好EMA。为什么?因为它既平滑了噪音,又保留了近期趋势的变化。你看代码:

def smooth_momentum(momentum_series, method='ema', window=5):
    """
    动量因子平滑处理
    """
    if method == 'sma':
        return momentum_series.rolling(window=window).mean()
    elif method == 'ema':
        return momentum_series.ewm(span=window, adjust=False).mean()
    elif method == 'median':
        return momentum_series.rolling(window=window).median()
    else:
        raise ValueError("不支持的方法")

# 示例:使用EMA平滑
stock_data['momentum_smoothed'] = smooth_momentum(
    stock_data['excess_momentum_20d'], 
    method='ema', 
    window=5
)
注意
平滑窗口不要选太大。我试过用20日平滑,结果信号滞后了将近两周。做交易最怕的就是滞后。一般来说,平滑窗口取动量窗口的1/4到1/3比较合适。

4.4 动量因子排名与打分

最后一步,是把平滑后的动量值,转换成可交易的信号。怎么做?排名打分。

我常用的打分方法有两种:

  1. 百分位排名法:把全市场股票的动量值排序,取百分位作为分数。比如排名前10%的得1分,后10%的得0分。
  2. Z-score标准化法:计算每个股票动量值偏离均值多少个标准差。偏离越大,分数越高。

我个人更推荐百分位排名法。为什么?因为它不受极端值影响。你想想看,如果某只股票突然涨停,Z-score会变得非常大,但百分位排名最多也就是100%。

def rank_and_score(momentum_series, method='percentile'):
    """
    动量因子排名与打分
    """
    if method == 'percentile':
        # 百分位排名,返回0-1之间的分数
        scores = momentum_series.rank(pct=True)
    elif method == 'zscore':
        # Z-score标准化
        scores = (momentum_series - momentum_series.mean()) / momentum_series.std()
    else:
        raise ValueError("不支持的方法")
    
    return scores

# 示例:对全市场股票进行排名打分
all_stocks['momentum_score'] = rank_and_score(
    all_stocks['momentum_smoothed'], 
    method='percentile'
)
核心要点
动量因子的最终分数,决定了你的行业轮动信号。分数高的行业,说明近期动量强,应该超配;分数低的行业,说明动量弱,应该低配或回避。

到这里,动量因子的构建就完成了。你可能会问:就这么简单?嗯,其实核心逻辑确实不复杂。但真正难的是细节处理——比如数据清洗、异常值处理、复权方式选择等等。这些我在后面的章节会详细展开。

最后送你一句话:动量因子不是万能的,但没有动量因子是万万不能的。在行业轮动策略里,它是最基础也最有效的因子之一。

我的习惯
我每天收盘后,会跑一遍这个流程。先算20日动量,再算超额动量,然后EMA平滑,最后百分位排名。整个过程不到5分钟。但就是这5分钟,能让我看清市场资金的流向。
动量因子构建流程 步骤1 过去N日收益率动量 (PCT_CHANGE, 20日) 步骤2 超额收益动量 (个股 - 基准) 步骤3 平滑处理 (EMA, 5日) 步骤4 排名与打分 (百分位排名) 输出结果 动量因子分数 (0-1之间) 用于行业轮动信号识别 关键参数:动量窗口=20日,平滑窗口=5日,排名方法=百分位 ⚠️ 注意事项 1. 必须使用前复权价格计算动量,避免除权除息干扰 2. 平滑窗口建议取动量窗口的1/4到1/3 3. 百分位排名比Z-score更稳健,抗极端值能力强 4. 每日收盘后重新计算,保持信号时效性
专注资料整理