2026-05-08源码:
ideas/S4_cn_etf_momentum_tilt/v2 + summaries/S4_cn_etf_momentum_tilt/v2- v1 ❌ — 搁置 v1。在 V1 共享基线(6 只宽基/行业 ETF、2020-2024、20 日 lookback、α=1.0、月频再平衡)上,横截面动量倾斜相对 S3 等权基线产生 -2.56% 年化超额、IR=-0.49、t-stat=-1.08,统计上未达到显著拒绝零假设,但所有尝试过的 α 与 lookback 组合方向上一致为负——属于经济意义上的系统性反向,不是噪音。
- v2 本文 ✅ — v2 的改进显著有效(CAGR +5.73pp / Sharpe ×3.7 / MaxDD 减半 vs v1,且首次跑赢 510300 BH +2.6%/yr),但收益主要来自「扩池 6 → 11」对 S3 baseline 的提升,动量 tilt 信号本身仍未带来 alpha(v2 vs S3-11 IR=-0.17,α 敏感性中 α=0 仍是最优档)。Ship v2 配置,但备注「价值在跨资产分散,而非动量」;status 为 shipped (pool value, not factor value)。
想法(Why)
一句话概括
把 v1 的 6-ETF 池(A股宽基/行业,高度同涨同跌)扩为 11-ETF 跨资产池(+ 港股/黄金/纳指/标普/十年国债),叠加严格 shift(1)、长 lookback(120)+ skip(5)、可选 vol-adjusted 信号 — 旨在让横截面动量真正有信息可用。
核心逻辑
- 标的池 = v1 的 6 只 A股 ETF + 5 只跨资产 ETF(恒生/黄金/纳指/标普500/十年国债)共 11 只。
- 每个再平衡日(每 20 个交易日)按
MomentumReturn(lookback=120, skip=5)计算各标的动量;可选改用VolAdjustedMomentum(lookback=120, skip=5, vol_lookback=60)。 target_weights内严格 shift(1):用df.loc[df.index < date]切片,保证信号只用前一日及更早数据,杜绝 same-bar 偷看。- 横截面 z-score → 线性倾斜
w_i = 1/N + α·z_i/N→ clip 到[0.03, 0.30](N=11 适配后比 v1 的 [0.05, 0.40] 对称收紧) → water-fill 归一化。 - 100% 满仓、无现金缓冲,与 S3 保持同口径再平衡。
假设与依据
v1 失败的根本原因诊断(来自 v1 conclusion 的 5 条经验):
- 6 只 A股 ETF 的 pairwise correlation 高、横截面 σ 小 → z-score 信息密度低、容易被噪音淹没
- 20 日动量在 A 股 ETF 上被短期反转(mean-revert)盖过
- 单边市(2020 普涨、2022 普跌)下「相对动量」≈ 几周噪音
v2 的对应假设:
- 扩池假设(最关键):跨 5 大资产类别(A 股 / 港股 / 美股 / 黄金 / 债券)让 pairwise correlation 真正下降,z-score 才能编码出信息;即使倾斜信号本身不强,更分散的池至少能给出 S3 等权的「免费 alpha」。
- 长 lookback + skip 假设:120 日(约一个季度)跨过短期反转噪声窗口;skip=5 跳过最近 1 周避免微观结构反转。理论上 A 股「短期反转 + 中长期趋势」经验下,这个组合应该比 lb=20 表现更稳。
- shift(1) 假设:S3 父类用
from_orders+targetpercent,rebalance 日 close 价成交;信号若用到当日 close 就有 same-bar lookahead。严格< date切片消除这个隐患。 - vol-adjust 假设:扩池后纳入了高波动(纳指/创业板)和低波动(国债/黄金)资产,原始动量倾斜会让权重过度偏向高波动资产;除以 60 日 vol 做风险调整后,权重变化更接近「横截面 risk parity 风格」,理论上 Sharpe 更稳。
标的与周期
- 市场:A股 ETF (
market: cn_etf) - 标的池(11 只):
- A股 6 只:
510300, 510500, 159915, 512100, 512880, 512170(与 v1 一致) - 跨资产 5 只:
159920(恒生)518880(黄金)513100(纳指)513500(标普500)511260(十年国债)
- A股 6 只:
- 频率:日线
- 数据起止:2020-01-01 ~ 2024-12-31
一句话结论
v2 的改进显著有效(CAGR +5.73pp / Sharpe ×3.7 / MaxDD 减半 vs v1,且首次跑赢 510300 BH +2.6%/yr),但收益主要来自「扩池 6 → 11」对 S3 baseline 的提升,动量 tilt 信号本身仍未带来 alpha(v2 vs S3-11 IR=-0.17,α 敏感性中 α=0 仍是最优档)。Ship v2 配置,但备注「价值在跨资产分散,而非动量」;status 为 shipped (pool value, not factor value)。
这个策略教会我什么
- 池的设计 > 因子的精细调参。v1 在 5α × 3lookback × 1 池上探索全负,v2 仅靠换池就把 NAV 从 1.001 推到 1.307。下次做 cross-section 因子,第一件事先看池内 pairwise correlation,再调因子。
- 「先验测过的失败」可能是「池子选错了」而非「因子错了」。但反过来也成立:v2 把池换好后动量 tilt 仍 IR < 0,才能下定论说横截面动量在 A 股可投资 ETF 上经济意义不显著——这个结论现在比 v1 更可靠。
- 更长 lookback 的边际帮助有限。v2 lb sweep 中 lb=250 是唯一 IR > 0 的档(+0.077),但 t≈0.17 完全不显著。在 5 年样本上做年度动量本质上只有 ~5 个独立观测,过拟合空间巨大。
- vol-adjusted 信号在跨资产场景下「不输」。raw signal IR=-0.17,vol_adj IR≈0,且 Sharpe/MaxDD 更优。这不是动量 alpha,是 risk parity 雏形。
- 不修父类、子类只覆盖钩子,并行写两个版本(v1, v2)共存 在多迭代研究中比 monkey-patch v1 更安全:同一份父类同一段数据,三个不同子策略一起跑,apples-to-apples 对比无懈可击。
- 「事前估计 → 事后差距」依然是最有用的诊断工具。事前估 IR ∈ [0.2, 0.5],v1 实测 -0.49(差 1σ+),v2 实测 -0.17(差 0.5σ)→ 与事前一致地朝错误方向偏离 → 强烈说明「我们对市场动量的先验仍然过于乐观」。
关键图表





实现要点
展开完整实现记录
Implementation — A股 ETF 等权 + 动量倾斜(v2)
整体方案
派生自 Strategy 3 的 EqualRebalanceStrategy(沿用 S3 的 build_target_weight_panel / run),仅覆盖 target_weights(date, prices_panel) 钩子。与 v1 保持父类不变、保持 water-fill 归一化算法不变,但有 4 处显著改动:
- 扩池 6 → 11:默认 symbols 改为 11 只跨资产 ETF(A股 6 + 港股 1 + 黄金 1 + 美股 2 + 国债 1)
- 严格 shift(1):
target_weights内先用df.loc[df.index < date]切片再算因子(_slice_strict),把 shift 逻辑前移 - 更长 lookback + skip:默认
lookback=120, skip=5(v1 默认lookback=20, skip=0) - 可选 vol-adjusted 信号:
signal="vol_adj"时使用VolAdjustedMomentum而非MomentumReturn - 边界对称收紧:
[w_min, w_max] = [0.03, 0.30](v1 是[0.05, 0.40]),适配 N=11 的 1/N≈0.091
因子清单
| Factor 类 | 文件 | 参数 | 方向 | 是新增还是复用 |
|---|---|---|---|---|
MomentumReturn | src/strategy_lib/factors/momentum.py | lookback=120, skip=5 | +1 | 复用 |
MomentumReturn(可选 secondary) | 同上 | lookback=60, skip=5 | +1 | 复用 |
VolAdjustedMomentum | src/strategy_lib/factors/momentum.py | lookback=120, skip=5, vol_lookback=60 | +1 | 新增 |
新增因子
VolAdjustedMomentum
数学定义:mom_vol_adj = (close.shift(skip) / close.shift(skip+lookback) - 1) / std(log_returns over vol_lookback)
class VolAdjustedMomentum(Factor):
name = "mom_vol_adj"
required_columns = ("close",)
direction = 1
def __init__(self, lookback=60, skip=0, vol_lookback=60):
super().__init__(lookback=lookback, skip=skip, vol_lookback=vol_lookback)
def _compute(self, df):
c = df["close"]
mom = c.shift(skip) / c.shift(skip + lookback) - 1.0
vol = np.log(c).diff().rolling(vol_lookback).std()
return mom / vol.replace(0.0, np.nan)
- 为什么需要:扩池后池内资产波动差异巨大(纳指 σ ~25% / 国债 σ ~3%)。原始动量倾斜在跨资产场景下会过度偏向高 vol 资产(因为 raw return 的尺度本身和 vol 正相关),vol-adj 把信号正规化到「σ-单位 risk-adjusted return」。
- 细节:
vol.replace(0.0, np.nan)防 divide-by-zero;不加skip同步到 vol 窗口(vol 用[date - vol_lookback, date)区间,与 mom 的skip是分别独立的)。 - 不修改
factors/__init__.py:按硬约束由主 agent 合并。
策略配置
- 配置文件:
configs/S4_cn_etf_momentum_tilt_v2.yaml - 类型:
momentum_tilt_v2(与 v1 的momentum_tilt并列) - 关键参数:
tilt.alpha=1.0、tilt.w_min=0.03、tilt.w_max=0.30signal.type=raw(默认)/vol_adj(可切换);signal.vol_lookback=60factors[0].params={lookback: 120, skip: 5}rebalance: 20
- 与 v1 关系:
strategy.parent: cn_etf_equal_rebalance(仍是 S3 派生),不是 v1 派生
类签名
class MomentumTiltV2Strategy(EqualRebalanceStrategy):
DEFAULT_SYMBOLS_V2 = (
"510300", "510500", "159915", "512100", "512880", "512170", # A 股 6
"159920", "518880", "513100", "513500", "511260", # 跨资产 5
)
def __init__(
self,
symbols=None,
*, rebalance_period=20, drift_threshold=None,
lookback=120, skip=5,
secondary_lookback=None, secondary_skip=None,
alpha=1.0, w_min=0.03, w_max=0.30,
signal="raw", vol_lookback=60,
name="cn_etf_momentum_tilt_v2", **kwargs,
): ...
def target_weights(self, date, prices_panel) -> dict[str, float]:
sliced = self._slice_strict(prices_panel, pd.Timestamp(date))
scores = self._momentum_scores(sliced)
if scores.isna().all(): return {s: 1/N for s in symbols}
z = self._zscore(scores).fillna(0.0)
weights = self._tilt_weights(z)
return {s: float(weights.loc[s]) for s in symbols}
@staticmethod
def _slice_strict(panel, date):
return {s: df.loc[df.index < date] for s, df in panel.items()}
数据
- 标的池:11 只 ETF(V1 6 池 + 5 只跨资产)
- 数据范围:2020-01-01 ~ 2024-12-31
- 数据预处理:依赖
data/cn_etfloader 的前复权(akshareqfq) - 11 只 ETF 全部覆盖整段样本期(1211~1212 个交易日);交易日历差异极小,父类
build_target_weight_panel做 intersection 后 1208 个共同交易日
与 v1 接口契约的差异
| 维度 | v1 (MomentumTiltStrategy) | v2 (MomentumTiltV2Strategy) |
|---|---|---|
| 父类 | EqualRebalanceStrategy(带 ImportError 兜底) | EqualRebalanceStrategy(直接 import,不再 stub) |
| 默认 symbols | 显式传入或父类的 6 只 | 类属性 DEFAULT_SYMBOLS_V2(11 只) |
target_weights 参数 | (date, prices_panel) | 同 |
| 内部因子 API | _momentum_scores(date, prices_panel) | _momentum_scores(prices_panel)(已切片) |
| shift 处理 | _row_at(panel_wide, date) 取 ≤date 的行 | _slice_strict(panel, date) 用 < date 切片 |
| 边界 | [0.05, 0.40] | [0.03, 0.30] |
| signal 选项 | 仅 raw | raw 或 vol_adj(新增) |
踩过的坑
- shift 语义:
<vs<=在 close-to-close 模型里只差一个 bar,但 v2 显式选<以确保 rebalance 日的 close 价不进因子计算(vbtfrom_orders在 rebalance 日 close 就成交了,因子用了同日 close 等于「下单时的成交价已经被自己的信号知道」)。 - vol_adj 在低 vol 资产上的放大效应:511260(十年国债)vol 极低,vol_adj 会把它的动量信号放大数倍。被 z-score 截面化后这个绝对值放大被吃掉,但仍可能挤进 z-score 分布尾部。实测看 vol_adj 信号在 11 池上 IR ≈ 0,没出现明显异常。
- 跨资产交易日历:理论上港股/美股 ETF 与 A 股交易日不完全一致,但因为这些都是 A 股交易所上市的 ETF(513500/513100 在上交所),交易日历跟 A 股一致。loader 拉出来都是 1211~1212 行,可直接 intersection。
- 1/N 边界对称:N=11 时 1/N≈0.091。v1 的 [0.05, 0.40] 在 1/N=0.167 上是大致对称的;移植到 N=11 上会变成 [0.05, 0.40]→[0.55x, 4.4x] 严重偏右。改为 [0.03, 0.30] 对称为 [0.33x, 3.3x]。
相关 commits
- 实现:
<待提交> - v1 → v2 增量:仅
src/strategy_lib/strategies/cn_etf_momentum_tilt_v2.py、src/strategy_lib/factors/momentum.py(追加VolAdjustedMomentum)、configs/S4_cn_etf_momentum_tilt_v2.yaml、summaries/S4_cn_etf_momentum_tilt/v2/、ideas/S4_cn_etf_momentum_tilt/v2/
验证过程
展开完整验证记录
Validation — A股 ETF 等权 + 动量倾斜(v2)
每次新一轮回测就追加一个
## YYYY-MM-DD <轮次主题>小节。
2026-05-08 Smoke test(合成数据)
配置 & 数据
- 配置:
configs/S4_cn_etf_momentum_tilt_v2.yaml@ commit<待提交> - 数据:合成 panel,11 个 symbol、400 个交易日、不同 drift(-1.0‰ ~ +2.0‰/天)
- 父类:S3 真实落地的
EqualRebalanceStrategy(直接 import,不再 stub) - 入口:
python summaries/S4_cn_etf_momentum_tilt/v2/validate.py smoke
结果
| Case | 场景 | 期望 | 结果 |
|---|---|---|---|
| 1 | 默认 11 池 / lb=120 / skip=5 / α=1.0 / raw | sum=1, min≥0.03, max≤0.30, 高 drift 资产权重高 | sum=1.000000, min=0.0300, max=0.1652 ✅ |
| 2 | α=0 | 退化等权 1/11=0.0909 | 全 0.0909 ✅ |
| 3 | signal=vol_adj | sum=1,无异常 | sum=1.000000 ✅ |
| 4 | 严格 shift(1) (_slice_strict) | 切片后所有 max(idx) < date | 全部满足 ✅ |
| 5 | α=10 极端倾斜 | 边界 clip 生效,sum=1 | sum=1,min≥0.03、max≤0.30 ✅ |
| 6 | 数据不足(5 行) | 降级为等权 | 全 1/N ✅ |
关键观察
- 倾斜方向正确(高 drift → 高权重,monotone),上下限严格生效
- 严格 shift(1) 切片正确:所有 symbol 在切片后
max(index) < date - vol_adj 信号无除零异常、归一化正常
2026-05-08 真实数据回测(2020-01-01 ~ 2024-12-31)
配置 & 数据
- 入口:
python summaries/S4_cn_etf_momentum_tilt/v2/validate.py real(默认 11 池 + lb=120 + skip=5 + α=1.0 + raw) - Sweep:
python summaries/S4_cn_etf_momentum_tilt/v2/validate.py sweep - 11 只 ETF cache 全部命中,2020-01-02 ~ 2024-12-31,1211~1212 个交易日
- A 股 6:
510300, 510500, 159915, 512100, 512880, 512170 - 跨资产 5:
159920, 518880, 513100, 513500, 511260
- A 股 6:
- 父类:
EqualRebalanceStrategy,rebalance_period=20 - 共享基线:100k RMB 起步、fees=万 0.5、slippage=万 5
主结果
| 策略 | Final NAV | Total Ret | CAGR | Vol | Sharpe | MaxDD | Calmar |
|---|---|---|---|---|---|---|---|
| S4 v2 (11 pool, lb=120, skip=5, α=1) | 1.307 | +30.69% | +5.75% | 15.17% | 0.444 | -22.17% | 0.259 |
| S4 v2 on old 6 pool (same params) | 1.078 | +7.82% | +1.58% | 24.02% | 0.185 | -46.58% | 0.034 |
| S4 v1 default (6 pool, lb=20) | 1.001 | +0.11% | +0.02% | 24.18% | 0.121 | -47.93% | 0.001 |
| S3 equal_rebal (11 pool) | 1.366 | +36.60% | +6.73% | 16.06% | 0.486 | -23.03% | 0.292 |
| S3 equal_rebal (6 pool) | 1.138 | +13.82% | +2.74% | 23.78% | 0.232 | -44.26% | 0.062 |
| 510300 BH | 1.089 | +8.85% | +1.79% | 21.74% | 0.190 | -41.45% | 0.043 |
头条结论:v2 default 大幅优于 v1(NAV +30pp,Sharpe ×3.7,MaxDD 减半),但仍输 S3 (11 pool)(CAGR -1.0pp,Sharpe -0.04)。
与 S3 (11 pool) 的相对绩效(因子有效性核心)
| 指标 | 值 | 解读 |
|---|---|---|
| 年化超额(CAGR v2 − S3-11) | -1.06% | 动量倾斜每年烧掉 ~1pp(v1 烧 -2.56%;改善但仍负) |
| 信息比率 (IR) | -0.17 | v1 是 -0.49 → 显著改善但未扭转为正 |
| 跟踪误差 (TE) | 6.22% | 比 v1 (5.18%) 略高(lookback 长 → 信号更慢、TE 微升) |
| 超额收益 t-stat | -0.37 (n=1207) | |t|<2,统计上未显著(v1 是 -1.08,v2 更接近零) |
| 平均主动权重 mean|Δw| | 0.0465 | v1 是 0.092;v2 lookback 长 → 信号变化慢,倾斜幅度自然减半 |
| 年化目标权重周转 | 1.68 (v2) vs 0 (S3) | v1 是 4.26,v2 turnover 减 60%(lookback↑) |
v2 vs v1 head-to-head(异时验证 v2 改进是否有效)
| 指标 | 值 |
|---|---|
| 年化超额(CAGR v2 − v1) | +5.73% |
| IR (vs v1 daily returns) | +0.27 |
| TE | 14.16% (大,因池/参数都换) |
| t-stat | +0.59 |
v2 比 v1 实质性改善,但因为两边池子不同,IR/TE 对比不严格 → 看下一节做控制变量。
控制变量:扩池本身贡献了多少 alpha?
| 配置 | NAV | CAGR | Sharpe | MaxDD | vs 对应 S3 IR |
|---|---|---|---|---|---|
| v2 params, 11 pool (default) | 1.307 | +5.75% | 0.444 | -22.17% | -0.17 |
| v2 params, 6 pool (ablation) | 1.078 | +1.58% | 0.185 | -46.58% | -0.26 |
| v1 params, 6 pool | 1.001 | +0.02% | 0.121 | -47.93% | -0.49 |
| S3, 11 pool | 1.366 | +6.73% | 0.486 | -23.03% | (baseline) |
| S3, 6 pool | 1.138 | +2.74% | 0.232 | -44.26% | (baseline-old) |
分解 v2 - v1 的 +5.73% 年化 alpha:
- 「v2 params on 6 pool」vs「v1 params on 6 pool」 = +1.58% − +0.02% = +1.56% from params(lookback/skip/shift/边界)
- 「v2 params on 11 pool」vs「v2 params on 6 pool」 = +5.75% − +1.58% = +4.17% from pool
- 总和:+1.56% + +4.17% ≈ +5.73% ✓
- 结论:v2 总改进的 ~73% 来自扩池,~27% 来自参数。
进一步证据:v2 params 在 6 池上 vs S3 在 6 池上 IR = -0.26(仍负)→ 扩池不是让动量信号变好,而是让 S3 baseline 本身变好(S3 在 11 池上 CAGR 比 6 池高 +4.0%,Sharpe 高 +0.25)。真正的 alpha 属于「扩池 S3」,不属于「动量 tilt」。
与 510300 BH 的对比(绝对基准)
| 指标 | 值 |
|---|---|
| 年化超额 | +2.61% |
| IR | +0.21 |
| TE | 12.48% |
| 超额 t-stat | +0.46 (n=1207) |
v2 终于在绝对基准上跑赢 BH —— v1 是 -1.05%,v2 是 +2.61%。但这同样主要靠扩池。
分年度收益
| 年份 | S4 v2 (11) | S4 v1 (6) | S4 v2 on 6池 | S3 (11) | 510300 BH | v2 - S3 | v2 - v1 | v2 - BH | 备注 |
|---|---|---|---|---|---|---|---|---|---|
| 2020 | +23.92% | +21.32% | +36.49% | +27.75% | +31.11% | -3.83% | +2.60% | -7.19% | 单边大牛市;v2 因加入 1/3 防御资产(黄金/国债)跑不过纯股票池 |
| 2021 | +7.21% | +6.13% | +7.38% | +6.12% | -2.89% | +1.09% | +1.08% | +10.10% | A 股震荡港美强;v2 受益跨资产 |
| 2022 | -20.16% | -25.82% | -25.69% | -15.55% | -19.37% | -4.61% | +5.67% | -0.78% | 单边熊市;v2 倾斜倒过来加仓正在跌的资产 → 输给 S3 等权 |
| 2023 | +3.35% | -9.84% | -10.88% | +1.38% | -10.43% | +1.97% | +13.19% | +13.78% | 海外科技牛 + A 股震荡跌;v2 大幅胜过 v1(纳指 ETF 抓住) |
| 2024 | +19.21% | +16.24% | +11.08% | +17.69% | +18.39% | +1.52% | +2.96% | +0.82% | 美股延续 + A 股 9-10 月反弹;v2 双线受益 |
关键观察:
- v2 跑输 S3 主要在 2020/2022 两个单边市(同 v1 的发现:单边市动量 tilt 受损)
- v2 跑赢 v1 的 alpha 主要在 2023(+13.19%)— 来自纳指/标普500/黄金的跨资产分散,不来自动量信号
- 3/5 年 v2 ≥ S3(2021/2023/2024),但 2/5 年差距大 → 方向上不算稳定
α 敏感性(lookback=120, skip=5, raw, 11 pool)
| α | CAGR | Sharpe | MaxDD | vs S3 α_ann | vs S3 IR | vs S3 t | mean|Δw| | turnover/yr |
|---|---|---|---|---|---|---|---|---|
| 0.0 | 6.73% | 0.486 | -23.03% | 0.00% | NaN | NaN | 0.000 | 0.00 |
| 0.5 | 5.97% | 0.457 | -22.03% | -0.84% | -0.21 | -0.45 | 0.030 | 1.15 |
| 1.0 | 5.75% | 0.444 | -22.17% | -1.06% | -0.17 | -0.37 | 0.046 | 1.68 |
| 2.0 | 4.82% | 0.385 | -23.91% | -1.93% | -0.27 | -0.60 | 0.055 | 1.90 |
| 5.0 | 4.70% | 0.379 | -24.75% | -2.05% | -0.30 | -0.66 | 0.057 | 1.87 |
α=0(等权)依然是最优档——v1 的现象在 v2 上完整复现。任何非零倾斜都把 Sharpe 从 0.486 拉低到 0.38~0.46。这是一个非常重要的负面证据:换池、换 lookback、加 shift、改边界都没改变这个核心事实——动量信号在 A 股 ETF 跨资产截面上不带 alpha。
Lookback 敏感性(α=1, skip=5, raw, 11 pool)
| lookback | CAGR | Sharpe | MaxDD | vs S3 α_ann | vs S3 IR | vs S3 t | mean|Δw| |
|---|---|---|---|---|---|---|---|
| 20 | 5.64% | 0.423 | -23.36% | -1.03% | -0.18 | -0.38 | 0.050 |
| 60 | 4.76% | 0.375 | -27.52% | -1.92% | -0.29 | -0.63 | 0.049 |
| 120 | 5.75% | 0.444 | -22.17% | -1.06% | -0.17 | -0.37 | 0.046 |
| 250 | 7.48% | 0.563 | -23.09% | +0.50% | +0.077 | +0.169 | 0.043 |
lookback=250(年度动量)是唯一 vs S3 IR > 0 的档,但幅度极小(+0.077,t=+0.17 远未显著);CAGR 7.48% > S3 6.73%。这可能是唯一接近「动量 tilt 在跨资产 ETF 上有效」的证据——年度动量与「资产类别表现持续性」吻合(黄金牛、纳指牛、债券熊往往持续超过半年)。但样本仅 5 年(4~5 个独立年度动量周期),统计意义有限。
Signal 类型对比(lookback=120, α=1, 11 pool)
| signal | CAGR | Sharpe | MaxDD | vs S3 IR | vs S3 α_ann | vs S3 t |
|---|---|---|---|---|---|---|
| raw | 5.75% | 0.444 | -22.17% | -0.17 | -1.06% | -0.37 |
| vol_adj | 7.11% | 0.572 | -18.53% | +0.001 | +0.01% | +0.003 |
vol_adj 信号几乎完美匹配 S3 —— alpha ≈ 0、IR ≈ 0、t ≈ 0,但 Sharpe 和 MaxDD 都更优(Sharpe 0.572 vs S3 0.486;MaxDD -18.53% vs S3 -23.03%)。这是因为 vol-adj 让权重往低波动资产(国债/黄金)轻微倾斜——本质上趋近于 risk parity,而非动量。这告诉我们:扩池 + 简单的 vol-aware 权重就足以击败 S3 等权 baseline,跟动量信号关系不大。
关键图表
artifacts/equity_curve.png:4 条线 v2 vs v1 vs S3-11 vs BH。v2 全程在 v1/BH 之上,但被 S3-11 微微压住artifacts/drawdown.png:v2 最大回撤 -22% vs v1 -48% vs BH -41%artifacts/weight_evolution.png:11 资产堆积面积;2022 熊市期国债/黄金权重明显抬升artifacts/tilt_strength.png:v1(6 池, lb=20)vs v2(11 池, lb=120)的 z-score 分布对比 + 主动权重对比;v2 的 z-score 分布更集中、|Δw| 更小(因为 lookback 长信号变化慢)artifacts/pool_ablation.png:v2 参数在 11 池 vs 6 池的净值曲线对比;6 池版本基本贴着 v1 + S3-6- CSVs:
alpha_sweep.csv/lookback_sweep.csv/signal_sweep.csv
解读:动量 tilt 在 A 股 ETF 上是否有效(v2 之后的判断)
答案:
- 在 6 只 A 股 ETF 上:v1 已证伪(IR=-0.49);v2 用更长 lookback 在 6 池上 IR=-0.26,仍负。
- 在 11 只跨资产 ETF 上:v2 default IR=-0.17,4 个 lookback 中 3 个负,1 个 (lb=250) 微正 +0.077。整体仍判定为「不显著有效」。
- vol_adj 在 11 池上:IR ≈ 0 + Sharpe 改善 → 「不亏 + 风险控制更好」。但严格说不是动量信号在贡献 alpha,而是 vol-aware 权重避免高波动资产权重过大。
结论:横截面动量在 A 股可投资 ETF 池上经济意义不显著(统计显著性也不显著)。v2 主要的胜利来自扩池让 S3 baseline 本身变好。
解读:Bug / 问题
- shift(1) 严格切片:v2 显式实现,相比 v1 隐式更稳;实测对结果影响很小(vbt
from_orders本来就是次日成交),但作为代码契约更清晰 - 跨资产交易日历:因 11 只都是 A 股交易所上市的 ETF,交易日历一致,无须特殊处理
- vol_adj 在国债上的高得分:511260 vol≈3% 时 vol_adj 会把动量放大,但 z-score 截面化后被吃掉,未观测到异常
- 回测口径:与 v1 一致(
(1+r).cumprod()总收益、252 日年化),三组对比同口径
下一步
- 决策点:把 v2 标记为 shipped(因为大幅优于 v1 + 跑赢 BH 2.6%),但 status 备注「价值在扩池 S3,不在动量 tilt」
- 反推到 v3 候选:直接用「11 ETF + S3 等权」作为 baseline,跳过倾斜(α=0 始终最优)
- v3-alt:在 11 池上加「大盘 510300 200MA 过滤」二元开关,跌破退化等权 / 上方倾斜
- vol_adj 信号长 lookback (250) 单独再跑一档(理论上是「不亏 + 长动量」的最佳组合)
- 反哺 S3:把 11 池作为 S3 v2 候选 — 数据说明扩池 S3 显著比 6 池 S3 强(CAGR +4pp、Sharpe +0.25)
源文件
- 想法 · idea.md
- 讨论笔记 · notes.md
- 结论 · conclusion.md
- 实现 · implementation.md
- 验证 · validation.md
- 可复跑脚本 · validate.py
- 本版本目录(含 artifacts)
本文由 scripts/sync_strategies.py 从 Strategy-Lib 同步生成。