动量因子计算:N日收益率计算与标准化
好,咱们进入第三个核心环节——动量因子的计算。说白了,就是怎么把「过去N天涨了多少」这个想法,变成一个能用的数字。
我个人习惯把这一步拆成两个动作:先算原始收益率,再做标准化处理。别小看这两步,我在项目中见过太多人栽在细节上。
一、N日收益率:最简单的动量表达
动量因子的基础,就是N日收益率。公式很简单:
N日收益率 = (今日收盘价 / N天前收盘价) - 1
或者用对数收益率:
N日收益率 = ln(今日收盘价 / N天前收盘价)
嗯,这里要注意:对数收益率在时间序列上可加性更好,我一般用对数形式。但简单收益率更直观,看个人习惯。
关键参数:N怎么选?
- 短期动量(5-10日):捕捉快速反转,适合高频或短线
- 中期动量(20-60日):趋势跟踪的经典区间,我常用20日
- 长期动量(120-250日):过滤噪音,但反应慢
我曾经在回测中发现,同样的策略用5日和20日动量,结果天差地别。你想想看,5日动量在震荡市里频繁开仓,手续费都能吃掉利润。所以N的选择,本质上是在「灵敏度」和「稳定性」之间做取舍。
二、标准化:让不同股票站在同一起跑线
直接拿收益率排序?不行。茅台涨5%和小盘股涨5%,背后的波动率完全不同。所以我们需要标准化。
我常用的标准化方法有两种:
1. Z-score标准化(横截面标准化)
在每个时间截面上,对所有股票的N日收益率做标准化:
z_score = (收益率 - 截面均值) / 截面标准差
这样做的好处是:消除了市场整体涨跌的影响。比如今天大盘涨了2%,大部分股票都涨,但我们要找的是「相对更强」的股票。
我的经验:Z-score标准化后,动量因子大致服从标准正态分布。我一般会做一步「缩尾处理」——把超过±3的值拉回到±3,避免极端值干扰后续排序。
2. 排名标准化(百分位法)
把收益率转换成排名,再映射到[-1, 1]或[0, 1]区间:
rank_score = (排名 - 1) / (股票总数 - 1) * 2 - 1
这个方法对异常值更鲁棒。我记得有一次数据里混入了一只停牌后复牌的股票,一天涨了300%,用Z-score直接爆表,但排名法稳稳的。
三、代码实现:从数据到因子
直接上代码,我习惯用Pandas处理:
import pandas as pd
import numpy as np
def calc_momentum_factor(df, N=20, method='zscore'):
"""
计算动量因子
df: DataFrame,包含'close'列,index为日期,columns为股票代码
N: 收益率计算周期
method: 'zscore' 或 'rank'
"""
# 1. 计算N日对数收益率
returns = np.log(df / df.shift(N))
# 2. 去掉前N天的NaN
returns = returns.iloc[N:]
if method == 'zscore':
# 横截面Z-score标准化
mean = returns.mean(axis=1)
std = returns.std(axis=1)
factor = returns.subtract(mean, axis=0).div(std, axis=0)
# 缩尾处理
factor = factor.clip(-3, 3)
elif method == 'rank':
# 排名标准化
ranks = returns.rank(axis=1, pct=True)
factor = ranks * 2 - 1
return factor
避坑指南:我曾经在计算时忘记处理停牌数据。停牌期间价格不变,N日收益率会变成0,但这不是真正的「动量中性」。建议在计算前先剔除停牌超过N/2天的股票。
四、知识体系总览
下面这张图,把本章的核心逻辑串起来了:
五、实战中的几个坑
讲几个我踩过的坑,你遇到了能少走弯路:
- 幸存者偏差:回测时只用了当前还在交易的股票。我早期做回测时,没剔除退市股票,结果动量因子表现好得离谱——因为差股票早就退市了。
- 前视偏差:计算N日收益率时,用了未来数据。比如用今天的数据计算因子,然后去预测今天的涨跌。嗯,这属于低级错误,但压力测试时容易犯。
- 行业中性化:标准化之后,我建议再做一步行业中性化。因为不同行业的动量特征不同,比如科技股动量持续性比公用事业强。具体做法:在每个行业内分别做Z-score。
一个小技巧:我习惯在标准化后,把因子值乘以-1,这样「正因子值」就代表「强动量」,「负因子值」代表「弱动量」。逻辑上更顺——你想想看,做多时我们选因子值大的股票,对吧?
好了,动量因子的计算和标准化,核心就这些。说白了就是三步:算收益率、做标准化、处理细节。代码我已经给了,你拿回去跑一遍,遇到问题再回来翻翻这部分。