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比较合适。
平滑窗口不要选太大。我试过用20日平滑,结果信号滞后了将近两周。做交易最怕的就是滞后。一般来说,平滑窗口取动量窗口的1/4到1/3比较合适。
4.4 动量因子排名与打分
最后一步,是把平滑后的动量值,转换成可交易的信号。怎么做?排名打分。
我常用的打分方法有两种:
- 百分位排名法:把全市场股票的动量值排序,取百分位作为分数。比如排名前10%的得1分,后10%的得0分。
- 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分钟,能让我看清市场资金的流向。
我每天收盘后,会跑一遍这个流程。先算20日动量,再算超额动量,然后EMA平滑,最后百分位排名。整个过程不到5分钟。但就是这5分钟,能让我看清市场资金的流向。