动量因子的三种构造方式,哪一种在 A 股还有效
同一个想法用收益率排序、残差动量和路径调整动量分别实现,回测差异比想象中大得多。
先把数据对齐
多数回测的偏差不来自模型,而来自时间戳。分钟线的收盘价、日频的复权因子、财报的发布时点,三者如果错位一天,一个看起来年化 30% 的因子就会变成噪声。先用统一的交易日历重采样,再做任何计算。
import pandas as pd px = pd.read_parquet('daily.parquet') px = px.pivot(index='date', columns='code', values='close') ret = px.pct_change() mom = px.pct_change(20).shift(1) # 滞后一天,避免未来函数 score = mom.rank(axis=1, pct=True) w = (score > 0.8).astype(float) w = w.div(w.sum(axis=1), axis=0).fillna(0) pnl = (w * ret).sum(axis=1) - 0.0008 * w.diff().abs().sum(axis=1) print(pnl.mean() / pnl.std() * 252**0.5)
留意
上面的 8bp 单边成本只是占位。A 股的实际冲击成本随换手率非线性上升,换手翻倍时不要假设成本也只是翻倍。
三种构造方式
同样是「过去涨得多的继续涨」,落到代码里至少有三条路,它们对噪声、对行业轮动、对个股跳空的敏感度完全不同。
- 收益率排序动量:直接对 N 日累计收益做截面排名。实现最简单,也最容易被一两根涨停线拉偏。
- 残差动量:先把个股收益对市场和行业因子回归,用残差累计值排序。剥掉了行业轮动,信号更干净但计算量大一个量级。
- 路径调整动量:用累计收益除以同期日收益的波动,本质是把「涨得稳」排在「涨得猛」前面。在 A 股的高波动环境里,这一版通常最稳。
# 路径调整动量:夏普式的动量 win = 20 cum = px.pct_change(win) vol = ret.rolling(win).std() * (252 ** 0.5) sharpe_mom = (cum / vol.replace(0, pd.NA)).shift(1) # 残差动量:先剔除市场与行业 mkt = ret.mean(axis=1) beta = ret.rolling(120).cov(mkt).div(mkt.rolling(120).var(), axis=0) resid = ret.sub(beta.mul(mkt, axis=0)) resid_mom = resid.rolling(win).sum().shift(1)
夏普比率之外
一条净值曲线至少要看四个数:年化收益、最大回撤、回撤修复天数、以及不同参数下结果的方差。前三个描述历史,最后一个才描述稳健性。
18.4%
年化收益
1.32
夏普比率
-11.6%
最大回撤
64 天
回撤修复
把回看窗口从 20 天扫到 60 天,如果夏普在 1.1 到 1.4 之间平滑变化,这个信号大概率是真的;如果只有 20 天那一档突出,剩下的都在 0.3 附近,那就是过拟合。
下一节我们把这个动量信号拆成行业中性和市值中性两层,看看超额收益还剩多少。
动量因子回测pandas