[{"content":" ✅ shipped (in-sample) / OOS-pending · 最终化：2026-05-08 源码：ideas/S9_crypto_basket_equal/v1 + summaries/S9_crypto_basket_equal/v1 想法（Why） 一句话概括\nV1 的 S3 等权机制原样套到 5 只头部加密货币，作为 Crypto V2 Suite 的 baseline。\n核心逻辑\nrisky pool（5 只）：BTC/USDT、ETH/USDT、SOL/USDT、BNB/USDT、XRP/USDT（CRYPTO_TOP_5 universe） 再平衡：月度（rebalance_period=20 个交易日；crypto 24/7 但保持与 V1 同口径，每 20 日重平） 现金代理：USDT（无利息，相当于 V1 的 511990 但 carry=0） 基准：BTC/USDT buy-and-hold（crypto 默认） 代码：复用 EqualRebalanceStrategy（同 V1 S3）；factory s3_equal_rebalance(CRYPTO_TOP_5) 一行启动 假设与依据\n核心假设：V1 揭示的「池子 \u0026raquo; 信号 \u0026raquo; 仓位」+「等权 + 月度再平衡是简单胜出」如果是普适规律，那么在 crypto 上应该再次胜过其他复杂策略。\n为什么先做 baseline 而不是直接做 trend/momentum：\nV1 S4/S5/S7 等复杂版本最终都被 S3 11 池 / S3 overseas_4 这些\u0026quot;简版\u0026quot;打爆 crypto 常识告诉我们「BTC + ETH 是 90% 收益」，但常识也告诉我们「crypto 趋势超强、应当用 timing」 必须先建立 baseline 数据，后续 V2-S2/V2-S3 的\u0026quot;复杂版本\u0026quot;才有 alpha 度量基准 为什么选 TOP_5 而不是 TOP_10：\nTOP_10 的 DOGE/ADA/AVAX/LINK/DOT 中部分 2021 年下半年才上 binance，回测窗口 \u0026lt; 主测期 TOP_5 全部 2020 年前已经流动性充足，能完整覆盖 2021-2024 4 年回测 TOP_3 (BTC/ETH/SOL) 太集中，5 只更像\u0026quot;crypto bluechip basket\u0026quot; 标的与周期\n市场：crypto（CCXT → Binance spot） 标的池：BTC/USDT, ETH/USDT, SOL/USDT, BNB/USDT, XRP/USDT cash 代理：USDT（处理：每日价格 = 1.0，无变动） 频率：日线（1d）—— 与 V1 同口径 数据范围：2021-01-01 ~ 2024-12-31（4 年；起点选 2021 是为了 SOL 流动性充足） 暖机：120 日（与 V1 S3 一致） 一句话结论 V1 的 S3 等权机制在 crypto 上 0 代码改动直接跑出 Sharpe 1.41 / CAGR +118%（vs BTC BH +84.84%/yr alpha）——同一个机制跨市场普适。NO_SOL ablation（2026-05-08 第二轮）修正：去 SOL 后仍 CAGR +83.46% / Sharpe 1.195 / vs BTC BH +49.84%/yr，证明 alpha 不完全依赖 SOL（SOL 贡献 ~41% alpha，剩余 59% 来自等权再平衡机制本身）。但 OOS 风险仍极高（4 年样本 + crypto 极端 outlier 频繁）。\n关键数据 值 样本期 2021-01-01 ~ 2024-12-31 (in-sample, 4y) 样本外 待 2025+ 数据验证 NAV (100k 起) 2,282,604 (22.8x) CAGR +118.46% Sharpe 1.410 Sortino (未单算，估 ~1.6-1.8) MaxDD -78.9% Calmar 1.50 vs BTC/USDT BH alpha/yr +84.84% vs ETH/USDT BH alpha/yr +72.22% 2022 单年 -71.49% 信息比率 vs BTC BH ~0.96 在什么情况下有效，什么情况下失效 ✅ 有效（已验证）：\ncrypto 长周期上行（每个 5y 窗口期内有 1+ 个标的 100x，等权再平衡能反复\u0026quot;卖盈\u0026quot;） 池中含极速崛起的 alt（2021 SOL 是关键） 5 标的等权（TOP_5），不要再加更多 alt（边际递减） ❌ 失效（已验证 / 风险大）：\n系统性崩盘期：2022 LUNA/FTX 让 5 池等权 -71%，没有任何 diversification 价值（crypto 内部高度同向） 没有 100x 标的的池子（btc_eth_2 仅 +43% CAGR，与 BTC BH 接近） 2025+ OOS：SOL 已 100x 后再 100x 概率低，BTC 在 95k 附近回归压力大 任何持仓限制 / 大资金（\u0026gt;1M USDT）—— 2021 SOL 暴涨期流动性可能不够支持等权大额买卖 这个策略教会我什么 V1 工具的普适性是真的：Universe + EqualRebalanceStrategy 0 代码改动从 A 股迁移到 crypto，跑出有效结果。这证明 V1 后期的「池子选择」抽象是正确的设计。\nPer-vol-unit alpha 才是公平比较：\nV1 S8: Sharpe 0.85 / Vol 14% = 6.1 per pp vol V2-S1: Sharpe 1.41 / Vol 76% = 1.85 per pp vol 看似 V2-S1 Sharpe 高 66%，但单位波动产生的 alpha 远低于 V1 S8 下次跨市场对比策略，先除以 vol 看 risk-efficiency 等权多元化在 crypto 系统性崩盘下完全失效：2022 LUNA/FTX 期间 5 池齐跌 -71%，比单 BTC -65% 更深。crypto 内部相关性 0.85+ 让\u0026quot;等权 = 多元化\u0026quot;是错觉。真正的 diversification 需要跨市场（crypto + 股 + 债），不是 crypto 内部分散。\n池中含一个 100x 标的就足够撬动整体：去 SOL 后 V2-S1 等于 BTC BH。这意味着 crypto 等权策略本质是「投个 100x 标的，靠等权再平衡及时获利离场」的赌博。下个周期（2025+）哪个标的会 100x 没人知道——这是 V2-S1 最大 OOS 风险。\n跨市场命题验证应当先做最简对照：本验证用 V1 完全相同的 S3 等权机制 + 完全相同的 rebalance_period=20 + 完全相同的工具链。跨市场普适性的证据是「机制不变下数据本身的差异」，不是\u0026quot;我们调整了 N 个参数让它在 crypto 工作\u0026quot;。\n关键图表 实现要点 展开完整实现记录 Implementation — V2-S1 (S9) crypto_basket_equal 整体方案 完全没有新代码。复用 V1 的 EqualRebalanceStrategy（S3 同款）+ V2 新注册的 CRYPTO_TOP_5 universe。一行启动：\nfrom strategy_lib.strategies.cn_etf_equal_rebalance import EqualRebalanceStrategy from strategy_lib.universes import CRYPTO_TOP_5 strat = EqualRebalanceStrategy( symbols=list(CRYPTO_TOP_5.symbols), rebalance_period=20, # 月度（与 V1 同口径） ) panel = CRYPTO_TOP_5.load_panel(since=\u0026#34;2020-09-01\u0026#34;, until=\u0026#34;2024-12-31\u0026#34;, include_cash=False) result = strat.run(panel, init_cash=100_000, fees=0.001, slippage=0.001) 因子清单 无。纯等权再平衡机制。\n标的池组成（CRYPTO_TOP_5） symbol 类别 注释 BTC/USDT 数字黄金 起 11.6k → 终 93.6k（5y 8x） ETH/USDT L1 EVM 生态 SOL/USDT L1 2021 暴涨 100x，2022 -95% 暴跌 BNB/USDT 平台币 Binance 平台风险敞口 XRP/USDT 跨境支付 监管风险高 数据 来源：CCXT → Binance spot 时间频率：1d 数据范围：2020-09-01 ~ 2024-12-31（4 个月暖机 + 4 年绩效） 已缓存：5 个标的全部已存 data/raw/crypto/*_1d.parquet，每个 1583 bars 数据质量：5 个标的 2020-09 起 binance 上 USDT spot 流动性充足，无明显断档 V2 vs V1 关键参数差异 参数 V1 (A 股) V2 (crypto) 原因 初始资金 100,000 RMB 100,000 USDT 量级相当 现金代理 511990 (~2% APY) USDT (无利息) crypto 无被动 carry fees 5 bp 10 bp 现实 binance 0.1% 现货费 slippage 5 bp 10 bp crypto 大额订单滑点高 年化天数 252 365 24/7 vs 工作日 rebalance_period 20 (月度) 20 (与 V1 同) 保持横向可比 策略配置 等权机制：5 标的目标权重各 1/5 = 0.20 月度再平衡：每 20 个交易日（这里\u0026quot;交易日\u0026quot;= crypto 1 天，约 4 周一次） 满仓 risky，零现金缓冲 vbt: Portfolio.from_orders(close, size=weights_df, size_type=\u0026quot;targetpercent\u0026quot;, group_by=True, cash_sharing=True) 踩过的坑 CryptoLoader 缓存覆盖：第一次小测试只拉 BTC 12 月数据 → cache 命中后再拉全量被截断。修复：用 loader = get_loader('crypto', refresh=True) 重拉一次 BTC，覆盖部分缓存。 Matplotlib 中文字体警告：Glyph missing warning 但图正常输出。后续可以改用英文标题或安装中文字体。 年化天数差异：V1 用 252，V2 用 365。compute_perf_metrics() 默认是 252，所以 V2-S1 没用 sweep.compute_perf_metrics，自己写了 calc_crypto_metrics。未来 V2 全套都需要这个适配。 与 V1 代码的接口契约 V2-S1 完全用 V1 既有代码：\nEqualRebalanceStrategy.run() 不做任何 crypto 特殊处理 vbt.Portfolio.from_orders 在 1583 bars × 5 symbols 输入下正常工作 Universe.load_panel() 是 V1 引入的工具，对 crypto market 同样有效 无任何 V2 专属代码改动 这是 V1 工具普适性的最佳证据：从 A 股 ETF 迁移到 crypto 0 代码改动。\n验证过程 展开完整验证记录 Validation — V2-S1 (S9) crypto_basket_equal 2026-05-08 真实数据回测（V2 第一个 baseline） 配置 \u0026amp; 数据 共享基线：docs/benchmark_suite_v2_crypto.md 数据：CRYPTO_TOP_5（BTC/ETH/SOL/BNB/XRP），2020-09-01 ~ 2024-12-31，1d 绩效统计窗口：2021-01-01 ~ 2024-12-31（4 年） 配置：100k USDT / fees 10bp / slippage 10bp / rebalance=20 / 年化天数 365 工作目录脚本：summaries/S9_crypto_basket_equal/v1/validate.py 真实数据 summary：artifacts/real_backtest_summary.json V2-S1 主结果 指标 值 最终 NAV (100k 起) 2,282,604 (22.8x) CAGR +118.46% 年化波动 76.1% Sharpe 1.410 MaxDD -78.9% Calmar 1.50 与 BH 基准对比 NAV CAGR Sharpe MaxDD V2-S1 2,282.6k +118.46% 1.410 -78.9% BTC/USDT BH 319.0k +33.62% 0.777 -76.6% ETH/USDT BH 457.9k +46.25% 0.875 (~) alpha vs BTC BH +84.84%/yr +0.633 -2.3 pct alpha vs ETH BH +72.22%/yr +0.535 (~) V2-S1 在所有维度（除 MaxDD 略深）暴揍单一标的 BH。信息比率 ≈ 0.96 跨 4 年（α 84.84% / 跟踪误差估计 ~88%）。\n分年度收益 年份 V2-S1 BTC BH ETH BH 解读 2021 +1031.22% +57.57% +404.35% SOL 2021-Q4 100x 暴涨被等权再平衡反复\u0026quot;卖盈\u0026quot;放大 2022 -71.49% -65.34% -68.23% LUNA/FTX 双崩盘；V2-S1 比单 BTC/ETH 跌得更深 2023 +183.38% +154.46% +90.10% crypto 寒冬触底反弹 2024 +132.74% +111.81% +41.91% ETF 通过 + BTC 新高 关键观察：\n2021 +1031% 是绝对支配 5y 累计的关键年 2022 -71% 比单 BTC -65% 更深，说明等权多元化在系统性崩盘期没有缓冲作用——所有 crypto 同向下跌 2023+2024 V2-S1 仍然跑赢 BTC BH，说明 alpha 不只是 2021 一年的运气 Universe ablation: 等权 S3 在 4 个 crypto universe Universe n NAV CAGR Sharpe MaxDD crypto_btc_eth_2 2 424.7k +43.52% 0.874 -76.2% crypto_top_3 3 1904.2k +108.79% 1.343 -85.2% crypto_top_5 5 ★2282.6k ★+118.46% ★1.410 -78.9% crypto_top_10 10 2267.8k +118.11% 1.351 -82.0% 关键观察：\n加 SOL（top_3 vs btc_eth_2）是 alpha 跳跃的关键：CAGR +43.5% → +108.8% 一步翻倍 TOP_5 是 sweet spot：再加 BNB/XRP 让 Sharpe 略升 (1.34 → 1.41) TOP_10 加 8 个 alt 反而 Sharpe 略降（-0.06）：DOGE/ADA/AVAX 等中长尾 alt 在 2021-22 大跌时拖累 池子 alpha 单调性 ≈ 成立：n=2 → n=5 显著上升，n=5 → n=10 边际递减 与 V1 不同：V1 是「池子越大越好」单调，V2 是「池子大到 5 就饱和」 V1 S8 vs V2-S1 横向对比（同 S3 等权机制，跨市场） V1 S8 (cn_etf_overseas_4) V2-S1 (crypto_top_5) Δ 市场 A 股 ETF (CN-listed) Crypto Spot 池大小 4 5 资金 100k RMB 100k USDT 期间 2020-2024 (5y) 2021-2024 (4y) NAV 171.3k (1.71x) 2,282.6k (22.8x) +13x CAGR +11.87% +118.46% +106.6 pct Sharpe 0.851 1.410 +0.56 Vol ~14% 76.1% +62.1 pct MaxDD -20.9% -78.9% -58 pct Sharpe / Vol 6.1 (per pp vol) 1.85 -4.2 结论：\n同一个 S3 等权机制在 crypto 上 Sharpe +66%、CAGR +10x，但 MaxDD 4x 更深 「等权 + 月度再平衡」命题跨市场有效 但 per-vol-unit 来看 V1 S8 是更好的 risk-adjusted alpha 来源（6.1 vs 1.85） 2 个市场是不同 risk profile：V1 S8 适合追求 Sharpe，V2-S1 适合追求绝对收益（acceping 巨大波动） 关键图表 artifacts/equity_curve.png — V2-S1 vs BTC BH vs ETH BH 净值曲线（log scale） artifacts/drawdown.png — 三者回撤对比（2022 LUNA/FTX 高亮红色） artifacts/weight_evolution.png — 5 资产权重时序堆积（再平衡日清晰可见） artifacts/yearly_returns.png — V2-S1 vs BTC BH 分年度条形图 解读 \u0026amp; 问题 V2-S1 alpha 本质归因：\n主要由 SOL 在 2021 单年 100x 表现提供 等权 + 月度再平衡 = 在 SOL 暴涨时反复减仓、把利润转移到其他标的 类似 V1 S8 的「等权 + 跨资产」机制，但 crypto 池内 SOL 提供了远超 V1 任何标的的 carry 没有 SOL（btc_eth_2）的版本只有 +43.5% CAGR，与 BTC BH 接近 2022 的失败：等权多元化在 crypto 系统性崩盘时完全失效（5 池 -71% vs BTC -65% 更深）。V1 中 overseas_4 在 2022 也跌但只 -20% 左右；crypto 5 池 -71% 是质的不同。没有真正的 cross-asset diversification（crypto 内部高度同向）。\nOOS 风险（最大未知）：\nSOL 2021 100x 是一次性事件，不会重复 2024-12 的 BTC 在 95k 附近，OOS（2025+）极可能均值回归 4 年样本含一次完整周期但样本太小，事前难判断未来 risk profile 与 V1 已知教训的关系 ✅ 池子 \u0026raquo; 信号 \u0026raquo; 仓位 在 crypto 上部分成立：\nTOP_5 (5 池) 击败 TOP_3 (3 池) 验证「池子越大越好」 但 TOP_10 不再改善，说明 crypto 池有 saturation point 没测过的：crypto 上 timing/factor 是否仍然边际价值低？需要后续 V2-S2/S3/S4/S5 验证 ❌ 避险靠 sizing 不靠 timing 在 crypto 上不成立：\nV2-S1 等权满仓在 2022 -71%，没任何避险机制 这点说明 crypto 上 timing 可能比 A 股更有价值（高 vol + 长趋势） 后续 V2-S3 (BTC MA filter) 应当能验证 下一步 实现 V2-S2 ~ V2-S5（DCA / MA filter / trend tilt / momentum tilt 在 crypto 上） 考虑 4h 频率 vs 1d 的对比（crypto 24/7 可能 4h 更合适） OOS 测试（2025+）—— 这是最大未知，必须做 缩短样本到 2022-2024（剔除 2021 SOL 暴涨这个 outlier 年）做 robustness 测试 验证「2022 -71% 是否可避免」—— 用 V2-S3 BTC MA filter 看能否在 LUNA 崩盘前离场 2026-05-08（追加）V2 Crypto Sweep + NO_SOL Ablation 配置 \u0026amp; 数据 跑法：python scripts/v2_crypto_sweep.py 4 strategies × 5 universes = 20 次回测（其中 7 个因工程问题 fail） 窗口与 V2-S1 主测一致：2021-01-01 ~ 2024-12-31，100k USDT，fees+slip 各 10bp，年化 365 sweep 工具升级：compute_perf_metrics(trading_days_per_year=...)，run_on_universe 按 universe.market 自动 252/365 适配 新注册 universe：CRYPTO_TOP_5_NO_SOL = (BTC, ETH, BNB, XRP)，剔除 SOL 验证 alpha 来源 完整 csv: results/v2_crypto_sweep_\u0026lt;ts\u0026gt;.csv 4 × 5 完整结果 Sharpe 矩阵 btc_eth_2 top_3 top_5_no_sol top_5 top_10 S3 equal_rebal 0.874 1.343 1.195 1.410 1.351 S4v2 momentum_tilt NaN NaN 1.172 ★1.511 1.423 S5v2 trend_tilt 0.710 1.533 0.879 1.373 ★1.583 S7v2 ma_filter NaN NaN NaN NaN NaN Final NAV (100k USDT) btc_eth_2 top_3 top_5_no_sol top_5 top_10 S3 equal_rebal 424.7k 1904.2k 1134.8k 2282.6k 2267.8k S4v2 momentum_tilt NaN NaN 1081.5k ★3138.2k ★3798.6k S5v2 trend_tilt 173.1k 456.3k 219.6k 377.1k 481.2k CAGR (%) btc_eth_2 top_3 top_5_no_sol top_5 top_10 S3 equal_rebal +43.52 +108.79 +83.46 +118.46 +118.11 S4v2 momentum_tilt NaN NaN +81.27 ★+136.55 ★+148.11 S5v2 trend_tilt +14.69 +46.12 +21.71 +39.32 +48.07 MaxDD (%) btc_eth_2 top_3 top_5_no_sol top_5 top_10 S3 equal_rebal -76.2 -85.2 -73.8 -78.9 -82.0 S4v2 momentum_tilt NaN NaN -76.4 -77.1 -79.4 S5v2 trend_tilt -32.1 -34.5 -36.8 -34.6 ★-30.0 【关键 ablation】SOL 贡献的边际 alpha（TOP_5 vs NO_SOL） 策略 TOP_5 (5 池含 SOL) NO_SOL (4 池) Δ CAGR Δ Sharpe SOL alpha 占比 S3 equal_rebal +118.46% / 1.41 +83.46% / 1.20 -35.00 pct -0.215 ~41% S4v2 momentum_tilt +136.55% / 1.51 +81.27% / 1.17 -55.27 pct -0.339 ~50% S5v2 trend_tilt +39.32% / 1.37 +21.71% / 0.88 -17.61 pct -0.495 ~75% 核心发现 1（修正 V2-S1 conclusion）：\nV2-S1 conclusion 之前判断\u0026quot;alpha 主要来自 SOL\u0026quot;——ablation 数据修正这个判断：\n没 SOL，S3 等权仍然 CAGR +83.46% / Sharpe 1.195 / NAV 1134.8k 这仍然显著跑赢 BTC BH (319k / +33.62%)，alpha vs BTC BH = +49.84%/yr（去 SOL 后） vs 含 SOL 的 +84.84%/yr，SOL 贡献 alpha 的 ~41%，59% alpha 来自 BNB/ETH/XRP 的等权再平衡机制本身 不再说\u0026quot;SOL 是唯一原因\u0026quot;；正确表述：\u0026ldquo;等权再平衡机制本身贡献了大部分 alpha，SOL 100x 把 alpha 又翻倍\u0026rdquo; 核心发现 2（与 V1 命题对照）：\nV1 命题（A 股 ETF） 在 crypto 上的验证结果 「池子 \u0026raquo; 信号 \u0026raquo; 仓位」 部分成立：n=2→5 单调改善，n=5→10 saturate；但 S4v2 momentum 在 top_5 比 S3 等权 Sharpe 还高（1.51 vs 1.41）— 信号在 crypto 有意义 「横截面动量在小池反向」 完全反转：S4v2 在 crypto top_5 / top_10 是冠军（NAV 3.1M / 3.8M），V1 中 momentum 是负 IR vs S3 「避险靠 sizing 不靠 timing」 sizing 在 crypto 显著有效：S5v2 trend tilt 把 MaxDD 从 -78% 砍到 -30%，避险幅度比 V1 更大；timing (S7v2) 因工程问题未跑出 「等权胜出复杂版本」 不成立：crypto top_5 上 S4v2 momentum (Sharpe 1.51) \u0026gt; S3 等权 (Sharpe 1.41)；与 V1 完全相反 工程问题留痕（待修） S7v2 ma_filter 全 fail：错误 KeyError: 'panel 缺少 symbol: USDT'。原因：universe.load_panel(include_cash=True) 调用 loader.load_many(['USDT'])，但 ccxt 没有 USDT/USDT pair。 修复方案：(1) 在 ccxt loader 检测 cash_proxy 是 stable coin 时返回 const=1 series；或 (2) MarketMAFilterStrategy 对 USDT 类 stable cash 提前判断不调 panel 优先级：实现 V2-S2 (BTC MA filter dedicated) 时一并修 S4v2 momentum 在 btc_eth_2 / top_3 上 fail：ValueError: w_min*N 与 w_max*N 与归一化和=1 矛盾（默认 w_min=0.03, w_max=0.30, N=2 → 0.06 与 0.6 不能同时满足 sum=1） 修复方案：v2 类应当根据 N 动态调整上下限；或在 N \u0026lt; 4 时禁用 alpha tilt 优先级：低，crypto 主要测 top_5+ 解读 \u0026amp; 命题修正 V1 → V2 最大差异：信号在 crypto 上有意义：\nV1 强诊断 S4v2 momentum 在 6 池上 IR 全负（动量在 A 股反向） V2 在 5/10 池上 momentum 是冠军（CAGR +136% / +148%） 可能原因：crypto 趋势更强、动量持续性更长、池内\u0026quot;赢家通吃\u0026quot;更显著（SOL 持续涨 1 年 → momentum 正向加权 SOL → 进一步放大） 但要警告：crypto top 5 太小（5 标的），momentum 排名信号噪声/信号比可能更高，OOS 风险大 S5v2 vol-target sizing 在 crypto 是真正的避险：\nV1 中 S5v2 cash 与下跌相关性仅 0.03（V1 conclusion 说\u0026quot;避险靠 sizing 不靠 timing\u0026quot;） V2 中 S5v2 把 MaxDD 从 -78% 砍到 -30%（改善 48 pct！），明显比 V1 改善 27pct (-47%→-20%) 更显著 机制：crypto 高 vol 让 sizing 信号更频繁触发；vol-target 在高波动环境天然更有效 但代价：CAGR 从 +118% 降到 +39%（损失 67 pct），risk-adjusted（Sharpe 1.41 → 1.37）几乎持平 TOP_10 vs TOP_5 不再 saturate：\nV2-S1 报告说\u0026quot;TOP_10 让 S3 Sharpe 略降\u0026quot; 本次 sweep 数据：S3 TOP_10 NAV 2267.8k vs TOP_5 2282.6k，几乎相同 但 S4v2 momentum 在 TOP_10 NAV 3798.6k 比 TOP_5 3138.2k 更高，+660k 反超 说明：在动量信号下，更多 candidate 资产让 ranking 更可靠 给 V2-S2/3/4 的优先级修订 基于本次 sweep（特别是 momentum 在 crypto 上反转 + sizing 真避险），V2 后续应当：\n优先 1：V2-S4 crypto_momentum_tilt 的 dedicated 验证（之前预期负，实测正且最佳）—— 写完整 idea/notes/impl/validation 优先 2：修 S7v2 USDT 工程问题，跑 V2-S2 BTC MA filter（看 timing 在 crypto 是否能进一步改善 / 与 sizing 结合） 优先 3：V2-S3 dedicated trend_tilt 验证 sizing 与 timing 哪个 risk-adjusted 更好 新的 OOS 风险点 S4v2 momentum 在 crypto top_5/10 是冠军：5 标的池排名信号噪声大；且 SOL 在 momentum tilt 下被进一步加权（暴涨过滤器），1 个标的 100x 放大了 momentum 的\u0026quot;虚假胜利\u0026quot; 需在 OOS（2025+）专门验证 momentum 是否持续 源文件 想法 · idea.md 讨论笔记 · notes.md 结论 · conclusion.md 实现 · implementation.md 验证 · validation.md 可复跑脚本 · validate.py 本版本目录（含 artifacts） 本文由 scripts/sync_strategies.py 从 Strategy-Lib 同步生成。\n","permalink":"https://lizhao903.github.io/0xBroleez/posts/2026/05/v2-s1-crypto-%E5%A4%B4%E9%83%A8-5-%E5%8F%AA%E7%AD%89%E6%9D%83--%E6%9C%88%E5%BA%A6%E5%86%8D%E5%B9%B3%E8%A1%A1/","summary":"✅ \u003cstrong\u003eV1 的 S3 等权机制在 crypto 上 0 代码改动直接跑出 Sharpe 1.41 / CAGR +118%（vs BTC BH +84.84%/yr alpha）\u003c/strong\u003e——同一个机制跨市场普适。\u003cstrong\u003eNO_SOL ablation（2026-05-08 第二轮）修正\u003c/strong\u003e：去 SOL 后仍 CAGR +83.46% / Sharpe 1.195 / vs BTC BH +49.84%/yr，证明 alpha 不完全依赖 SOL（SOL 贡献 ~41% alpha，剩余 59% 来自等权再平衡机制本身）。但 OOS 风险仍极高（4 年样本 + cr","title":"V2-S1 · Crypto 头部 5 只等权 + 月度再平衡"},{"content":"这是一个占位文章，用来演示「跑步」section 的结构和样式。\n你可以怎么写 训练日志：今天跑了多少 K、配速、心率、感受 比赛复盘：赛前准备、临场状态、关键节点的决策、赛后总结 装备评测：跑鞋、手表、补给的实测体验 伤病恢复：受伤经过、恢复过程、避坑经验 同目录引用图片 ![GPS 路线](route.png) ![心率曲线](hr.png) 把图片直接放在与 index.md 相同的文件夹下，markdown 里用相对路径就能引用。\n写完发布 git add content/running/ git commit -m \u0026#34;running: 第一次 10K 长距离\u0026#34; git push 约 1 分钟后线上可见。\n","permalink":"https://lizhao903.github.io/0xBroleez/running/2026-05-08-%E7%AC%AC%E4%B8%80%E7%AF%87%E8%B7%91%E6%AD%A5%E6%97%A5%E5%BF%97/","summary":"占位篇 — 等你写第一次真正的训练记录把它替换掉。","title":"第一篇跑步日志"},{"content":" ⏳ validating · 最终化：2026-05-08 源码：ideas/S8_cn_etf_overseas_equal/v1 + summaries/S8_cn_etf_overseas_equal/v1 想法（Why） 一句话概括\n把 S3 等权机制套在 4 只 CN-listed 跟踪海外/黄金的 ETF 上，完全去掉 A 股标的。\n核心逻辑\nrisky pool（4 只）：159920 恒生 ETF（港股代理）/ 513100 纳指 ETF / 513500 标普500 ETF / 518880 黄金 ETF 再平衡：S3 同款月度（rebalance_period=20）等权 现金代理：511990（不主动持有；与 S3 一致永远满仓） 基准：510300 buy-and-hold（与 V1 共享基线） 代码：复用 EqualRebalanceStrategy，无新策略类，仅 universe 切换 假设与依据\n起源：scripts/universe_sweep_demo.py 跑了 4 strategies × 4 universes 的 grid，发现 cn_etf_overseas_4 (= 上述 4 只) 在所有 4 个策略上都暴揍 base_6 / broad_3 / expanded_11：\nbase_6 broad_3 expanded_11 overseas_4 S3 等权 Sharpe 0.217 0.244 0.465 0.851 S3 等权 NAV (100k) 111.9k 115.5k 134.3k 171.3k S3 等权 CAGR +2.36% +3.04% +6.36% +11.87% S3 等权 MaxDD -45.1% -43.5% -22.9% -20.9% 一致性证据：池子 alpha 在 4 个策略 (S3/S4v2/S5v2/S7v2) 上单调递增：base_6 \u0026lt; broad_3 \u0026lt; expanded_11 \u0026lt; overseas_4。\n机制解读：\n2020-2024 期间 A 股板块整体平盘 / 负收益（510300 5y CAGR ~+0.86%） 海外 + 黄金 大类全部正贡献：纳指 (+15%/yr)、标普500 (+10%/yr)、黄金 (~+10%/yr)、恒生 (~0%) 等权配置 + 月度再平衡 = \u0026ldquo;被动跨资产 risk parity\u0026rdquo;；4 只资产相关性低让组合波动天然下降 标的与周期\n市场：A 股 ETF（CN-listed 但跟踪海外资产） 标的池：159920 / 513100 / 513500 / 518880 cash 代理：511990 频率：日线 数据范围：2020-01-02 ~ 2024-12-31（已缓存） 暖机：120 日（与 S3 一致，月度再平衡需要的最少历史） 一句话结论 S3 等权机制 + 4 只 CN-listed 海外/黄金 ETF（完全无 A 股）在 2020-2024 in-sample 跑出 NAV 171.3k / CAGR +11.87% / Sharpe 0.851 / MaxDD -20.9%——是 V1 全套（含所有 v2、所有 universe sweep）的全维度第一。但窗口偶然性是最大未知，OOS 验证必须先做才能 ship。\n关键数据 值 样本期 2020-01-02 ~ 2024-12-31 (in-sample) 样本外 (待 2025+ 数据) NAV 171,303 CAGR +11.87% Sharpe 0.851 MaxDD -20.9% Calmar 0.57 vs BH 510300 alpha/yr +11.01% vs S3 expanded_11 alpha/yr +5.51% vs S4v2 (前 V1 best) alpha/yr +6.12% 在什么情况下有效，什么情况下失效 ✅ 有效（已验证）：\n任何 5y in-sample 期 A 股相对海外+黄金 underperform 的环境 多元跨资产类别下的等权 risk parity 思路（与 Mebane Faber GTAA 同根源） 4 池标的相关性低（A股海外平时 corr ~0.3-0.5）让组合波动天然下降 ❌ 可能失效（未验证、需 OOS）：\nA 股牛市 + 美股熊市的反向情景（如 2014-15 / 2008-09 / 2017） QDII 额度断供导致跟踪误差爆发（513100/513500 历史多次溢价 5-10%） 美元周期反转（2014-15 强美元期金价跌 30%） 单一 ETF 的 idiosyncratic 风险（如 518880 黄金 ETF 重大 redemption） 这个策略教会我什么 池子选择是 alpha 的最大来源：在 V1 全套验证「池子 \u0026raquo; 信号 \u0026raquo; 仓位」之后，本策略再次量化证实——同一个 S3 等权机制，换池子 Sharpe 从 0.217 跳到 0.851（4 倍）。下次设计任何策略前，先用 sweep() 工具跑一下 universe 比较，避免在错误的池子上做大量参数调优。\nexpanded_11 的「6+5」组合是一种妥协：当时设计 expanded_11 是「base_6 加 5 跨资产」的并集，看起来更全面。实际 sweep 显示 6 只 A 股是结构性拖累，5 跨资产单独跑反而更优。「上策略」要敢于做减法，不一定 superset 就更好。\n「最简方案」常常是真 winner：S8 = 等权 + 月度再平衡 + 4 个标的，没有任何因子、timing、sizing。这种最简配置碾压所有\u0026quot;复杂版本\u0026quot;。奥卡姆剃刀在策略研究里同样适用。\n样本外验证比样本内胜出更重要：S8 in-sample 的\u0026quot;全维度第一\u0026quot;如果 2025+ 失效，所有数字立刻贬值。Ship 决策必须等 OOS 数据。\n实现要点 展开完整实现记录 Implementation — S8 v1 (overseas_equal) 整体方案 完全没有新代码。S8 = EqualRebalanceStrategy (S3 同款) + CN_ETF_OVERSEAS_4 universe。\nfrom strategy_lib.strategies.factories import s3_equal_rebalance from strategy_lib.universes import CN_ETF_OVERSEAS_4 from strategy_lib.backtest import run_on_universe metrics = run_on_universe(s3_equal_rebalance, CN_ETF_OVERSEAS_4) 因子清单 无。\n新增因子（如有） 无。\n策略配置 配置文件：configs/S8_cn_etf_overseas_equal_v1.yaml 类型：equal_rebalance（复用） 关键参数：rebalance_period=20（月度） 池组成：159920 恒生 / 513100 纳指 / 513500 标普500 / 518880 黄金 数据 标的池来源：手工选 4 只 CN-listed 跟踪海外/黄金的 ETF（去掉 A 股内部标的） 数据范围：2019-09-01 ~ 2024-12-31（120 日暖机 + 5 年绩效统计） 数据预处理：复用 cn_etf loader 的前复权（akshare adjust=\u0026ldquo;qfq\u0026rdquo;） 已缓存：所有 4 标的 + 现金代理 511990 + benchmark 510300 都在 data/raw/cn_etf/ 中 关键代码（无新增，引用现有） 等权机制：src/strategy_lib/strategies/cn_etf_equal_rebalance.py::EqualRebalanceStrategy.run() universe 定义：src/strategy_lib/universes.py::CN_ETF_OVERSEAS_4 factory: src/strategy_lib/strategies/factories.py::s3_equal_rebalance 踩过的坑 没踩过坑：S8 的实测在 sweep 工具里第一次就跑通，没有改任何代码 唯一注意：159920 / 513500 / 513100 / 518880 在 akshare 上数据齐全（5y ~1338 bars），但 159920（恒生 ETF）在 QDII 额度紧张时段可能有 1-2% 跟踪误差，长期累积误差暂时未单独计算 相关 commits （uncommitted；S8 与所有 V1 v2 共在一个工作树）\n与现有代码的接口契约 S8 复用 S3 的 target_weights 钩子（默认实现 = 等权 1/N） universe 切换通过 factory(universe) 模式自动适配 不影响 S3 v1 / S4v2 / S5v2 / S7v2 等任何已有策略 验证过程 展开完整验证记录 Validation — S8 v1 (overseas_equal) 2026-05-08 初版验证（来自 universe_sweep_demo 数据，未做专门 run） 数据来源 复用：scripts/universe_sweep_demo.py 跑 4 strategies × 4 universes 的 grid 时 S3 在 overseas_4 上的结果 详细 CSV：results/universe_sweep_demo_\u0026lt;timestamp\u0026gt;.csv 中 (strategy=S3 equal_rebal, universe=cn_etf_overseas_4) 行 配置：configs/S8_cn_etf_overseas_equal_v1.yaml（与 sweep demo 等价） 主结果（in-sample 2020-01-02 ~ 2024-12-31） 指标 值 最终 NAV (100k 起) 171,303 CAGR +11.87% Sharpe 0.851 MaxDD -20.9% 年化波动 ~14.0% Calmar 0.57 切换 / 再平衡 月度（rebalance_period=20，5y ~60 次） 年化换手 ~50%（估，依据再平衡量） vs V1 全套已知最佳对比 策略 NAV CAGR Sharpe MaxDD 来源 S8 (本) 171.3k +11.87% 0.851 -20.9% sweep S4v2 (11 池含 A 股) 130.7k +5.75% 0.44 -22.2% V1 v2 直接验证 S3 11 池等权 (含 A 股) 134.3k +6.36% 0.465 -22.9% S7v2 ablation S5v2 6 池 112.9k +2.57% 0.28 -20.5% V1 v2 直接验证 S7v2 11 池 119.0k +3.70% 0.40 -17.5% S7v2 主测 BH 510300 104.3k +0.86% 0.18 -44.8% 基准 S8 在所有 7 个策略 + V1 v2 中：Sharpe 第 1 / NAV 第 1 / CAGR 第 1，MaxDD 第 4（劣于 S5v2 -20.5% / S7v2 -17.5% / S5v2+overseas_4 -8.5%）。\n池子单调性（4 strategies × 4 universes 全部成立） base_6 broad_3 expanded_11 overseas_4 S3 equal_rebal Sharpe 0.217 0.244 0.465 0.851 S4v2 momentum Sharpe 0.191 0.218 0.548 0.730 S5v2 trend Sharpe 0.113 0.004 0.368 0.717 S7v2 ma_filter Sharpe 0.304 0.298 0.403 0.457 关键观察：池子改进对 sizing/factor 类策略（S3/S4v2/S5v2）效果显著（Sharpe 翻 4-6 倍），对 timing 类（S7v2）改进有限（仅 +50%）。\n关键图表 未生成（本次仅复用 sweep 数据，未做专门 plot）。下一步 dedicated validate.py 应输出：\nequity_curve.png — S8 vs S4v2 vs S5v2 vs BH（4 条线） drawdown.png — 同上 weight_evolution.png — 4 资产权重时间序列堆积图 yearly_returns.csv — 分年度对比 rolling_window.csv — 3 个 1.5y 子窗口（2020H1-2021H2 / 2021H2-2023H1 / 2023H1-2024H2） 解读 \u0026amp; 问题 数据强烈暗示「无 A 股」是 alpha 主因：池子 alpha 单调递增，4 个策略一致 窗口偶然性是最大未知：2020-2024 美股最强 / A 股最弱组合事后看明显，但事前并非显然 overseas_4 中谁贡献了 alpha：尚未做单标的 ablation；猜测 513100 纳指 + 518880 黄金为主，159920 恒生（HK 跟踪）和 513500 标普500 是稳定器；下一步 dedicated validate.py 应做单 ETF 留一交叉 已发现的策略类问题 无（复用 S3 现成代码） 下一步 写专门的 summaries/S8_cn_etf_overseas_equal/v1/validate.py，输出 dedicated 图表 + 分年度 + 单标的 ablation 跑 3 个滚动 1.5y 子窗口验证 in-sample 一致性 OOS 测试（2025+ 数据可得后） 与 S5v2 + overseas_4 的 risk profile 对比（高收益 vs 低回撤的 trade-off） 5 池版（+511260 十年国债）作为 v2 候选 源文件 想法 · idea.md 讨论笔记 · notes.md 结论 · conclusion.md 实现 · implementation.md 验证 · validation.md 本版本目录（含 artifacts） 本文由 scripts/sync_strategies.py 从 Strategy-Lib 同步生成。\n","permalink":"https://lizhao903.github.io/0xBroleez/posts/2026/05/a%E8%82%A1-etf-%E6%B8%AF%E8%82%A1-%E6%B5%B7%E5%A4%96-%E9%BB%84%E9%87%91-4-%E6%B1%A0%E7%AD%89%E6%9D%83-%E6%97%A0-a-%E8%82%A1/","summary":"⏳ S3 等权机制 + 4 只 CN-listed 海外/黄金 ETF（\u003cstrong\u003e完全无 A 股\u003c/strong\u003e）在 2020-2024 in-sample 跑出 \u003cstrong\u003eNAV 171.3k / CAGR +11.87% / Sharpe 0.851 / MaxDD -20.9%\u003c/strong\u003e——是 V1 全套（含所有 v2、所有 universe sweep）的\u003cstrong\u003e全维度第一\u003c/strong\u003e。但\u003cstrong\u003e窗口偶然性是最大未知\u003c/strong\u003e，OOS 验证必须先做才能 ship。","title":"A股 ETF 港股+海外+黄金 4 池等权 (无 A 股)"},{"content":" ❌ shipped (vs v1) / shelved (vs S3 11 池) · 最终化：2026-05-08 源码：ideas/S7_cn_etf_market_ma_filter/v2 + summaries/S7_cn_etf_market_ma_filter/v2 本策略的其他版本 v1 ❌ — 避险命题部分兑现：搁置（shelved）。在 2020-2024 样本期 S7（MA=200, lag=2）NAV 108.1k vs S3 110.8k vs S5v1 120.3k vs S5v2 112.9k vs BH 104.2k —— 跑输 S3 与 S5v1/v2，但 2022 单年回撤 ≈ 0%（5 个策略中最佳）证明了\u0026rsquo;市场代理 timing 在熊市能避险\u0026rsquo;。然而代价是 2024 单边行情完全错过（-3.6% vs S5v1 +28.1%/BH +18.4%），且 Sharpe 0.18 \u0026lt; S5 的 0.28。la v2 本文 ❌ — v2 在 v1 默认 (lag=2 6池) 上全面胜出（NAV +10.8k / Sharpe 翻倍 / MaxDD 改善 12.8 pct），但跑输首次直测的 S3 等权 11 池 baseline（-2.66%/yr，NAV 差 15.3k）—— timing 在 11 池上反而损害收益，最大新发现是 S3 等权 11 池是当前 best ship 候选。 想法（Why） 一句话概括\n把 v1 sensitivity sweep 已验证的\u0026quot;真最佳配置\u0026quot;变成默认 ship 版本。\n核心逻辑\n信号：510300 收盘 vs 200 日 MA（与 v1 同） 滞后过滤：lag=1（无滞后）——v1 默认是 lag=2 切换在次日开盘（shift(1) 防 lookahead，与 v1 同） risky pool：11 跨资产 ETF 等权 1/11——v1 默认是 6 池 cash pool: 511990 假设与依据\nv1 sensitivity sweep 给出两个反直觉但稳健的结论：\nlag 单调反向：lag=1 (NAV 119.1k) \u0026gt; lag=2 (108.1k) \u0026gt; lag=3 (99.1k) \u0026gt; lag=5 (90.8k)\n事前直觉「加层过滤减少假突破噪音」完全错误 真实数据：A 股快速下跌中，每延迟 1 天就少接收一波避险机会 4 档 lag 的 2022 单年都 ≈0%，所以避险结果是 MA 信号的稳健属性，不是 lag 调出来的；但收益侧 lag 越大越亏 11 池 = 真 alpha 来源（继承 S4v2 ablation）：S4v2 总 +5.73%/yr 中 73% 来自扩池\nS7 v1 的 risk-on 持仓是 6 池，v2 直接换 11 池 把这两条结论组合，假设 v2 能同时获得：\nv1 lag=1 的 timing alpha（vs S3 +1.34%/yr） 扩池的 baseline alpha（继承 S4v2 +4%/yr 的 73%） 预期 v2 vs S3 alpha ≥ +3%/yr（如果两条 alpha 简单相加），更现实预期 +2%/yr。\n标的与周期\n市场：A 股 ETF risky pool：11 只跨资产 ETF（同 S4v2） 信号：510300（不在 risky pool 里也行；这里包含） cash：511990 频率：日线 数据范围：2019-07-01 ~ 2024-12-31（含 200MA 暖机） 一句话结论 v2 在 v1 默认 (lag=2 6池) 上全面胜出（NAV +10.8k / Sharpe 翻倍 / MaxDD 改善 12.8 pct），但跑输首次直测的 S3 等权 11 池 baseline（-2.66%/yr，NAV 差 15.3k）—— timing 在 11 池上反而损害收益，最大新发现是 S3 等权 11 池是当前 best ship 候选。\n关键数据 值 样本期 2020-01-02 ~ 2024-12-31 样本外 (待 2025+ 数据) 最终 NAV 119,010（init 100k） CAGR +3.70% Sharpe 0.403 MaxDD -17.5%（5 个策略中最佳） 2022 单年 +0.01% 2024 单年 +7.82% 切换次数 31（5y） ON 占比 36.2% vs BH 510300 +2.84%/yr vs S3 6 池 +1.54%/yr vs S3 11 池 -2.66%/yr 在什么情况下有效，什么情况下失效 ✅ 有效（vs 6 池版本）：v2 显著改进 v1 默认。lag=1 + 11 池组合的两项贡献接近相等 ✅ 有效（避险）：MaxDD -17.5% 是迄今 7 个策略中最佳，证明 timing + 多元化的协同 ❌ 失效（vs S3 11 池）：在已经多元化的 11 池上，timing 多砍 5 pct MaxDD 不抵 5y +15k NAV 损失 ⚠️ 过拟合警告：lag=1 是 4 档 sweep 中最佳，可能仅是 5y 窗口特性。OOS 验证迫切 这个策略教会我什么 timing 的边际价值与 baseline 风险水平耦合：6 池 MaxDD -45% 时 timing 砍一半很有价值；11 池 MaxDD -22% 时 timing 再砍一半的代价超过收益。下次评估 timing 类策略，先看 baseline 风险水平。\nSensitivity sweep 应当反向使用：v1 默认 lag=2 是事前直觉（\u0026ldquo;加层过滤更稳\u0026rdquo;），sweep 推翻了它（lag=1 反而最好）。新策略的\u0026quot;默认参数\u0026quot;不应靠直觉，必须 sweep 后再定。\n首次直测 baseline = 真正的发现：本次 ablation 第一次单独测了\u0026quot;S3 等权 11 池\u0026quot;，结果 NAV 134.3k / Sharpe 0.465 直接打爆所有 7 策略。做新策略前先把\u0026quot;最简 baseline\u0026quot;当独立策略测一下——可能根本不需要复杂版本。\n关键图表 实现要点 展开完整实现记录 Implementation — S7 v2 (lag=1 + 11 池) 整体方案 把 v1 sensitivity sweep 已验证的两条结论变成默认 ship：\nlag_days = 1（v1 sweep 4 档中唯一显著跑赢 S3 的） risky pool = 11 跨资产 ETF（S4v2 已证扩池贡献 73% alpha） 代码组织 最小化代码差异：v2 类 MarketMAFilterV2Strategy 只继承 v1 类，仅改默认参数：\nclass MarketMAFilterV2Strategy(MarketMAFilterStrategy): DEFAULT_SYMBOLS = RISKY_POOL_11 # 11 池 def __init__(self, ..., lag_days=1, ...): # 默认 1（v1 默认 2） super().__init__(..., lag_days=lag_days, ...) v1 类不动，作为 baseline 对比版保留。\n因子清单 无新增。MA200 信号在 v1 已实现。\n11 池组成 symbol 类别 来自 510300 沪深300 V1 6 池 510500 中证500 V1 6 池 159915 创业板 V1 6 池 512100 中证1000 V1 6 池 512880 证券 V1 6 池 512170 医疗 V1 6 池 159920 恒生 ETF (港股) S4v2 扩池 518880 黄金 ETF S4v2 扩池 513100 纳指 ETF S4v2 扩池 513500 标普500 ETF S4v2 扩池 511260 十年国债 ETF S4v2 扩池 策略配置 配置文件：configs/S7_cn_etf_market_ma_filter_v2.yaml 信号资产：510300（沪深300），用 200MA 做过滤 ON 时仓位：11 ETF 等权 1/11 OFF 时仓位：511990 货币基金 100% 切换：信号变化 → vbt 自动 from_orders + targetpercent 踩过的坑 plot_signal_overlay 当 result.signal 与 panel index 长度不一致时（暖机期处理）fill_between 会 raise size mismatch → 修：先用 .intersection() 对齐到共同 index 再画 v2 类继承 v1 实例化时如果不传 symbols，super().__init__(symbols=None, ...) 会用 v1 的 DEFAULT_SYMBOLS 而不是 v2 的 → 已通过覆盖 DEFAULT_SYMBOLS class attr 解决 相关 commits （uncommitted at this stage; 与 v1 共用代码体）\n验证过程 展开完整验证记录 Validation — S7 v2 (lag=1 + 11 池) 2026-05-08 真实数据回测 配置 \u0026amp; 数据 配置：configs/S7_cn_etf_market_ma_filter_v2.yaml 数据：2019-07-01 ~ 2024-12-31（200MA 暖机），绩效统计 2020-01-02 ~ 2024-12-31 11 池：510300/510500/159915/512100/512880/512170 + 159920/518880/513100/513500/511260 cash: 511990，signal: 510300 v2 主结果 指标 v2 v1 lag=2 6池（默认） Δ 最终 NAV (100k 起) 119.0k 108.1k +10.8k CAGR +3.70% +1.64% +2.06% Sharpe 0.403 0.181 +0.222 (翻倍) MaxDD -17.5% -30.3% +12.8 pct 2022 单年 +0.01% -0.02% (~) 2024 单年 +7.82% -3.61% +11.4 pct 切换次数 31 25 +6 ON 占比 36.2% 39.8% -3.6 pct 4 档 ablation 配置 NAV CAGR Sharpe MaxDD 2022 2024 v2 lag=1 11池 119.0k +3.70% ★0.403 ★-17.5% +0.01% +7.82% v1 lag=1 6池 119.1k +3.71% 0.304 -27.3% +0.01% +7.27% lag=2 11池 112.9k +2.56% 0.300 -19.9% +0.01% +1.30% v1 lag=2 6池（v1 默认） 108.1k +1.64% 0.181 -30.2% +0.01% -3.61% 关键观察：\n2 维改进各贡献相当：lag=1 改进（+1.06% CAGR）≈ 11 池改进（+0.92% CAGR），加起来从 v1 默认 +1.64% → v2 +3.70% MaxDD 改进主要来自 11 池：6→11 池让 MaxDD 从 -27.3% 变 -17.5%（改善 9.8 pct），lag=1→2 只改 ~3 pct 2024 单边市改进显著：v2 +7.82% vs v1 默认 -3.61%（差 +11.4 pct）—— 因为 11 池含黄金/海外 ETF，单边离场代价被分散 横向对比 baselines 策略 NAV CAGR Sharpe MaxDD 来源 BH 510300 104.2k +0.86% 0.148 -44.7% 基准 S3 v1 6 池 110.8k +2.16% 0.208 -45.2% V1 baseline S3 11 池等权（隐含 S4v2 baseline） ★134.3k ★+6.36% ★0.465 -22.9% 本次 ablation 首次直测 S4v2 momentum tilt 11 池 130.7k +5.75% 0.44 -22.2% V1 v2 S5v2 trend tilt 6 池 112.9k +2.57% 0.28 -20.5% V1 v2 S7 v1 lag=2 6池（默认） 108.1k +1.64% 0.18 -30.3% V1 S7 v1 lag=1 6池（sweep 最佳） 119.1k +3.71% 0.30 -27.3% V1 sweep S7 v2 lag=1 11池（本次） 119.0k +3.70% 0.40 -17.5% 本版本 🚨 关键发现：S7 v2 跑输 S3 等权 11 池 baseline S7 v2 NAV 119.0k / CAGR +3.70% / Sharpe 0.40 / MaxDD -17.5%，是迄今 S7 最好版本，但：\nvs S3 等权 11 池：v2 -2.66%/yr，NAV 落后 15.3k，Sharpe 0.40 vs 0.465 vs S4v2 momentum tilt 11 池：v2 -2.05%/yr，NAV 落后 11.7k → timing 在 11 池上反而损害收益：11 池本身已 raised the bar（MaxDD -22.9% 接近 S7 v2 -17.5%），timing 多砍 5pct MaxDD 不抵 5y NAV 损失 15.3k。\n核心诊断：S7 v2 33% ON 占比意味着 67% 时间在持现金，这在多元化 11 池上是过度避险。6 池上 timing 有效是因为 6 池本身回撤大（-45.2%），timing 砍一半有用；11 池本身只 -22.9%，砍空间小但代价大（错过反弹）。\n解读 S7 v2 vs v1 默认显著改善（CAGR 翻倍，Sharpe 翻倍，MaxDD 改善 12.8 pct）—— 把 sensitivity 已证的最优配置 ship 是对的 但 timing 框架在 11 池上的边际价值很低：S3 11 池等权是更好的 baseline 2026-05-08 揭示的最大新发现：S3 11 池等权 = 当前测得的 best 策略（Sharpe 0.465 / NAV 134.3k） timing 仍在 6 池上有用：S7 v1 lag=1 6池 vs S3 6池 有 +1.55%/yr alpha；只是这个 alpha 被\u0026quot;扩池本身的 alpha\u0026quot;完全 dominated 下一步 创建 S8（不带 timing 不带因子） = \u0026ldquo;S3 等权 11 池\u0026quot;作为 ship 候选 S7 v3 探索更快信号（ATR / 通道突破）能否在 11 池上挽回 timing 价值 OOS 测试（2025+）验证 lag=1 是否仍然稳健 源文件 想法 · idea.md 讨论笔记 · notes.md 结论 · conclusion.md 实现 · implementation.md 验证 · validation.md 可复跑脚本 · validate.py 本版本目录（含 artifacts） 本文由 scripts/sync_strategies.py 从 Strategy-Lib 同步生成。\n","permalink":"https://lizhao903.github.io/0xBroleez/posts/2026/05/%E5%A4%A7%E7%9B%98-ma-%E8%BF%87%E6%BB%A4-v2-lag1--11-%E8%B7%A8%E8%B5%84%E4%BA%A7%E6%B1%A0-v2/","summary":"❌ \u003cstrong\u003ev2 在 v1 默认 (lag=2 6池) 上全面胜出\u003c/strong\u003e（NAV +10.8k / Sharpe 翻倍 / MaxDD 改善 12.8 pct），\u003cstrong\u003e但跑输首次直测的 S3 等权 11 池 baseline\u003c/strong\u003e（-2.66%/yr，NAV 差 15.3k）—— \u003cstrong\u003etiming 在 11 池上反而损害收益\u003c/strong\u003e，最大新发现是 S3 等权 11 池是当前 best ship 候选。","title":"大盘 MA 过滤 v2 — lag=1 + 11 跨资产池 · v2"},{"content":" ❌ shelved · 最终化：2026-05-08 源码：ideas/S7_cn_etf_market_ma_filter/v1 + summaries/S7_cn_etf_market_ma_filter/v1 本策略的其他版本 v1 本文 ❌ — 避险命题部分兑现：搁置（shelved）。在 2020-2024 样本期 S7（MA=200, lag=2）NAV 108.1k vs S3 110.8k vs S5v1 120.3k vs S5v2 112.9k vs BH 104.2k —— 跑输 S3 与 S5v1/v2，但 2022 单年回撤 ≈ 0%（5 个策略中最佳）证明了\u0026rsquo;市场代理 timing 在熊市能避险\u0026rsquo;。然而代价是 2024 单边行情完全错过（-3.6% vs S5v1 +28.1%/BH +18.4%），且 Sharpe 0.18 \u0026lt; S5 的 0.28。la v2 ❌ — v2 在 v1 默认 (lag=2 6池) 上全面胜出（NAV +10.8k / Sharpe 翻倍 / MaxDD 改善 12.8 pct），但跑输首次直测的 S3 等权 11 池 baseline（-2.66%/yr，NAV 差 15.3k）—— timing 在 11 池上反而损害收益，最大新发现是 S3 等权 11 池是当前 best ship 候选。 想法（Why） 一句话概括\n用沪深300 长周期均线作为单一开关，控制整体仓位 ON/OFF —— Faber 2007 GTAA 的简化版。\n核心逻辑\n单一信号 + 二元仓位：每日检查 510300 收盘价相对其 200 日 MA 的位置。\nclose_510300 \u0026gt; MA200_510300 → risk-on：满仓持有 S3 等权 6 ETF（510300/510500/159915/512100/512880/512170） close_510300 ≤ MA200_510300 → risk-off：全部转入 511990 货币基金（事实空仓 + 年化 ~2% carry） 切换在信号变化的次日开盘成交（用 shift(1) 显式处理 lookahead）。为避免假突破抖动，加滞后过滤：连续 N 日满足同向才切换（默认 N=2）。\n假设与依据\nFaber (2007) GTAA：跨多个市场（US 股票/债券/商品/REITs/外汇）跑长周期 MA 二元过滤都能在 risk-adjusted 上跑赢 BH，是最经典的「定时退出」基线，几十年样本外都没失效。 V1 复盘的核心发现（S5v1+v2 conclusion）： S5v1 全空仓天数占 18.2%，但 cash 与 BH 下跌相关性仅 0.033 ≈ 随机 → 逐标的趋势对真实下跌时点识别力弱 S5v2 把 MaxDD 从 -47.8% 砍到 -20.5%，但避险来自 sizing 而非 timing（2022 改善 14pct 主要靠 vol filter 强制降仓 + bond carry，不是趋势信号识别） 市场代理 vs 个体趋势的信息差：6 个 ETF 池里 5 个是宽基/2 个行业，与 510300 高度同步。S5 的 6 个独立趋势判断本质是 6 次同一信息的重复采样 + 各自的局部噪音 —— 用 1 个市场代理避开冗余 + 噪音。 A股板块联动：A股 ETF 在系统性下跌中相关性飙到 0.9+，「6 个 ETF 同时趋势翻负」才能挡住下跌的 S5 cutoff=0 设计本身在快下跌中几乎不可能及时触发。S7 用大盘代理跳出这个困境。 标的与周期\n市场：cn_etf（A股 ETF） 信号资产：510300（沪深300，代表 A 股大盘） 持仓资产（risk-on）：S3 等权 6 ETF (510300/510500/159915/512100/512880/512170) 持仓资产（risk-off）：511990 华宝添益（场内货币基金，T+0） 频率：日线 数据起止：2019-07-01（暖机 200MA） ~ 2024-12-31 一句话结论 避险命题部分兑现：搁置（shelved）。在 2020-2024 样本期 S7（MA=200, lag=2）NAV 108.1k vs S3 110.8k vs S5v1 120.3k vs S5v2 112.9k vs BH 104.2k —— 跑输 S3 与 S5v1/v2，但 2022 单年回撤 ≈ 0%（5 个策略中最佳）证明了\u0026quot;市场代理 timing 在熊市能避险\u0026quot;。然而代价是 2024 单边行情完全错过（-3.6% vs S5v1 +28.1%/BH +18.4%），且 Sharpe 0.18 \u0026lt; S5 的 0.28。lag=1 sweep 档位（NAV 119.1k / Sharpe 0.30）才是真正的 v1 最佳设置，但 200MA 是唯一 work 的长度（100/150/250 全部 CAGR 为负），有单点过拟合嫌疑。\n在什么情况下有效，什么情况下失效 ✅ 极端下跌年（2022）：完美避开，整年 0% 收益（5 个策略最佳，胜过 S5v2 -7.6%） ✅ 持续单边趋势上升后 + 持续单边下行后：信号能稳定捕捉 regime ✅ 信号稳定性：5 年 25 次切换、平均 ON 段 37 天、OFF 段 56 天，与\u0026quot;市场 regime\u0026quot;节奏吻合 ❌ 快速反转：2020-Q1 疫情急跌后的 V 型反弹，200MA 太慢、4-12 月反弹错过 ❌ 结构性单边牛市启动早期：2024 9-10 月行情前 510300 还在 MA200 下方，整段错过 +18% BH 收益 ❌ 震荡市：2023 -12.4% 跑输 S3 -10.2%（频繁假突破吃成本） ❌ 风险调整收益：Sharpe 0.18 \u0026lt; S3 0.21 \u0026lt; S5 0.28（lag=2 主跑） ⚠️ lag\u0026gt;1 越大越差：lag=1→3→5 NAV 119k→108k→99k→91k，过滤反而损害收益 这个策略教会我什么 A 股 ETF 池上\u0026quot;避险 timing\u0026quot;的下限：S5v1/v2 的 cash↔down corr 0.03 不是 逐标的趋势的局限，而是所有滞后型 MA 信号在 A 股 ETF 池上的共性。 200MA 也只能做到 0.05-0.10，远低于 V1 baseline 命题假设的 0.20+。 教训：想要真正的 timing 类避险，必须用更快的信号（ATR、价格突破带宽、 vol-of-vol），而不是任何形式的 MA。\n市场代理 vs 逐标的趋势的差距是\u0026quot;信号稳定性\u0026quot;，不是\u0026quot;timing 准确性\u0026quot;：\nS7 切换 25 次 vs S5v1 (60+ 评估机会) S7 OFF↔down corr 0.052 vs S5 0.033（小幅改善） S7 的优势是\u0026quot;少假信号、避免成本耗损\u0026quot;，不是\u0026quot;更早识别下跌\u0026quot; 教训：当 6 标的高度同源时，1 个市场代理 ≈ 1.5 倍信噪比的 6 个独立信号， 不是质变。如果想要质变，需要跨资产类别（股+债+商品）或跨市场（A+港+美） 滞后过滤是事前合理、事后无效的\u0026quot;保险机制\u0026quot;：\n设计时认为 N=2 能过滤假突破 实测 lag=1 反而最佳（NAV +10% / Sharpe +0.12 / corr +0.05） 200MA 本身的低通滤波已经够（窗口 199 天的平均），再加 N=2 是过度平滑 教训：长 MA + 短 lag 优于 短 MA + 长 lag；过滤层次越多越糟糕 \u0026ldquo;避险命题兑现\u0026quot;和\u0026quot;shipped\u0026quot;是两个概念：\n2022 单年 0% 是 5 个策略最佳 → 命题在那一年完美兑现 但 5 年总 NAV 跑输 S3 → 不能 ship 取代 S3 教训：defense-only strategy 的成立标准应当是\u0026quot;在熊年提供保护、其他年不显著拖后腿\u0026rdquo;， S7 在 2024 牛市的 -3.6% 损失太大（比 S5v2 +0.47% 还差），所以 shelved 解决方向：v2 用更快信号（再次回到上一条） 回测的 lag 选择应当跑 sensitivity 再决定主参数：\n事前以 lag=2 为主跑、lag=1 作\u0026quot;消融\u0026quot; 事后发现 lag=1 是真正的最佳 教训：任何超参的\u0026quot;事前默认\u0026quot;都应当被 sensitivity 推翻；如果 sensitivity 里的某档位明显胜过事前默认，要把那档作为新主参数（v2 直接采用） 关键图表 实现要点 展开完整实现记录 Implementation — Strategy 7 v1：A股 ETF 大盘 MA 过滤 整体方案 新策略类（不继承 S3）：src/strategy_lib/strategies/cn_etf_market_ma_filter.py → MarketMAFilterStrategy\n为什么不继承 EqualRebalanceStrategy？ S3 的核心是「按再平衡日历产生稀疏权重 panel（NaN 表示不下单）」，而 S7 是 「逐日产生密集权重 panel（每日都有 0/1 状态切换）」。两者结构差异大，独立 实现更清晰。但下游回测引擎完全一致：vbt.Portfolio.from_orders + size_type=\u0026quot;targetpercent\u0026quot; + cash_sharing=True，与 S3/S4/S5 保持回测可比。\n核心流程：\nbuild_signal(panel) —— 生成两条信号序列： raw_signal：close_510300[t] \u0026gt; MA_N[510300, t] 的 0/1 序列。MA 暖机期 = 0 signal（filtered）：滞后过滤 —— 连续 lag_days 日同向才切换（粘滞） build_target_weight_panel(panel, signal) —— 逐日权重 DataFrame： 信号 ON → risky pool 等权（6 只 ETF 各 1/6，cash_symbol = 0） 信号 OFF → cash_symbol 100%（risky 全 0） 每日 sum = 1，无 NaN（与 S3 的「触发日才有值」不同） run(panel, init_cash, fees, slippage, signal_lag=1) —— vbt 主回测： weights.shift(signal_lag).fillna(0) 把 t 日权重错位到 t+1 → 防 lookahead 头 signal_lag 行手动设为 cash 100%（避免 NaN 全 0 被解读为全清仓） 因子清单 Factor 类 文件 参数 方向 是新增还是复用 N/A — — — 本策略不使用任何 Factor 类（直接 close.rolling(N).mean()） 设计上故意不依赖 factors/trend.py 的 MABullishScore / DonchianPosition：\nS7 命题是「单一市场信号」，不需要多因子组合 简化超参空间：只有 ma_length 和 lag_days 两个核心参数 与 S5（MA + Donchian + cutoff + score_weights）形成对比，S7 是更简的版本 新增因子（如有） 无。S7 v1 完全用 pandas 原生 rolling.mean() 实现 MA，避免引入新的 Factor 类。\n策略配置 配置文件：configs/S7_cn_etf_market_ma_filter_v1.yaml 类型：market_ma_filter（自定义；非现有 single_threshold/cs_rank/weight_based） 关键参数： signal_symbol: \u0026quot;510300\u0026quot;（沪深300 大盘代理） cash_symbol: \u0026quot;511990\u0026quot;（华宝添益货币基金，risk-off 时 carry ~2%/yr） ma_length: 200（Faber 经典；同时跑 100/150/200/250 敏感性） lag_days: 2（连续 2 日同向才切换；同时跑 1/2/3/5 敏感性） weight_mode: \u0026quot;equal\u0026quot;（risk-on 时 6 ETF 等权 1/6） 标的池：6 只 risky ETF（V1 baseline）+ 1 只 cash ETF 回测参数：100k / fees=0.00005 / slippage=0.0005（V1 共享基线） 数据 数据范围：2019-07-01（暖机 200MA） ~ 2024-12-31 来源：本地缓存 data/raw/cn_etf/{510300,510500,159915,512100,512880,512170,511990}_1d.parquet 复权：前复权（akshare qfq） 共同交易日历：取 risky 6 池 + cash_symbol 的 index 交集 信号生成实现细节 滞后过滤的粘滞行为 当 lag_days = N \u0026gt; 1 时：\n滚动窗口大小 = N 窗口内 raw 全 1 → filtered 切到 1 窗口内 raw 全 0 → filtered 切到 0 否则 filtered 保持上一日（粘滞） 这样确保单日穿越 / 反复穿越不会触发切换，只有信号\u0026quot;稳定\u0026quot;了才动。 副作用：每次切换会延迟 N-1 天（这是用噪音过滤换的代价）。\nlookahead 防护 raw_signal[t] 只用 close[≤t] 计算（rolling.mean 默认右对齐） weights[t] 是 signal[t] 决定的目标仓位 weights.shift(1) 把 weights[t] 错位到 t+1 → vbt 在 t+1 bar 用 close[t+1] 成交 等价于「t 日信号、t+1 日开盘成交」（以 close 近似） 不变量（v2 子类如果出现请遵守） 每日权重 sum == 1：要么 risky ON 要么 cash ON，无中间态（与 S5v2 连续 ramp 不同） 从 risk-off 启动：策略首日强制 cash 100%（避免 shift 导致首日全空仓） 信号 ↔ 持仓解耦：signal_symbol 可以与 risky symbols 完全无交集（虽然 v1 默认 510300 在两者中） 踩过的坑 weights.shift(1) 头 N 行变 NaN→0 → vbt 解读为全清仓：vbt 看到 size=0 会执行清仓而不是「保持现状」。手动把头 signal_lag 行设为 cash 100% 避免 这个边界 bug（影响微弱但严重的话会导致首日就开/平仓多吃一笔成本）。 滞后过滤的\u0026quot;暖机\u0026quot;：rolling(N) 在前 N-1 天返回 NaN。我把这段强制保持 last=0（默认 risk-off）—— 与「策略保守启动」的逻辑一致。 信号资产 ≠ 持仓资产时的资产对齐：_all_assets() 自动把 signal_symbol、 symbols、cash_symbol 去重合并，避免 signal 资产没在 vbt portfolio 里 → 取 close 时 KeyError。v1 默认 510300 既是信号也是持仓，这个 bug 不会触发，但 代码已防御性写好。 pf.value() 返回类型：cash_sharing=True 时返回 Series；某些 vbt 版本 返回单列 DataFrame。统一在外面 if isinstance(eq, DataFrame): eq = eq.iloc[:, 0] 防御性处理（沿用 S3/S5 v2 的模式）。 与 S5 实现的对比 维度 S5 (trend_tilt) S7 (market_ma_filter) 父类 EqualRebalanceStrategy (S3) 独立类 信号源 6 个 ETF 各自的 trend_score 1 个市场代理（510300）的 close vs MA 信号数量 6（每只 ETF 一个） 1（全局） 权重生成时机 每 rebalance_period 触发 每日（连续） 权重连续度 v1 双峰、v2 连续 ramp 二元（0 或 1/N） 因子依赖 MABullishScore + DonchianPosition 无 超参数 ma_short/mid/long + donchian + cutoff + score_weights (5+) ma_length + lag_days (2) 切换次数（5y） 每 20 日重算 = 60+ 次评估 25 次实际切换 相关 commits 实现：\u0026lt;待提交\u0026gt; 验证过程 展开完整验证记录 Validation — S7 cn_etf_market_ma_filter 每次新一轮回测/验证就追加一个 ## YYYY-MM-DD \u0026lt;轮次主题\u0026gt; 小节，不要覆盖。\n2026-05-08 v1 初版（smoke + 真实数据） 配置 \u0026amp; 数据 配置：configs/S7_cn_etf_market_ma_filter_v1.yaml 信号资产：510300 沪深300 ETF 风险池：510300 / 510500 / 159915 / 512100 / 512880 / 512170（V1 baseline 6 池） Cash 等价：511990 华宝添益（货币基金） 主参数：ma_length=200, lag_days=2, weight_mode=equal 回测窗口：2020-01-02 ~ 2024-12-31（1209 个交易日） 暖机：2019-07-01 起拉数据（199 日预热 200MA） 成本：fees=0.00005, slippage=0.0005, init_cash=100,000 Smoke 测试（合成数据） 8 个用例全通过：\n测试 验证什么 结果 test_signal_warmup_zero MA 暖机期前 199 天信号 = 0 warmup_sum=0 ✅ test_signal_strong_uptrend 持续上行 → 信号几乎 100% ON on_ratio=1.000 ✅ test_signal_strong_downtrend 持续下行 → 信号几乎 100% OFF on_ratio=0.000 ✅ test_lag_filter_blocks_single_cross 单日穿越不切换；连续 N 日才切换 sequence 完全匹配预期 ✅ test_lag_filter_lag1_passthrough lag=1 时退化为 raw 信号 完全相等 ✅ test_weights_on_off_split ON 日 risky 等权、OFF 日 cash 100% 每日 sum=1, 头尾验证 ✅ test_v_shape_switches V 型曲线触发至少 1 次切换 switches=1, on_ratio=44.8% ✅ test_run_smoke_e2e 端到端跑通 vbt 回测 final=131,926, equity 无 NaN ✅ 主绩效表（2020-01-02 ~ 2024-12-31） 策略 NAV (100k) CAGR Sharpe Vol MaxDD Calmar S7 (MA200, lag=2) 108.1k +1.64% 0.18 15.9% -30.25% 0.05 S3 equal 110.8k +2.16% 0.21 23.8% -45.18% 0.05 S5v1 trend tilt 120.3k +3.92% 0.28 22.0% -47.80% 0.08 S5v2 (cont+vol+bond) 112.9k +2.57% 0.28 11.1% -20.52% 0.13 510300 BH 104.2k +0.86% 0.15 21.8% -44.75% 0.02 横向对比表（题目要求的核心表） 指标 S3 v1 S5 v1 S5 v2 S7 (MA200, lag=2) NAV (100k) 110.8k 120.3k 112.9k 108.1k CAGR +2.16% +3.92% +2.57% +1.64% Sharpe 0.21 0.28 0.28 0.18 MaxDD -45.2% -47.8% -20.5% -30.3% 2022 单年 -23.5% -21.6% -7.6% -0.0% 空仓 vs BH 跌相关性 — 0.033 0.031 0.052 切换次数（5y） 0（无 timing） 高（每 20d 重算 60+ 次） 中（连续 ramp） 25 空仓天数占比 0% 18.2%（双峰 0/100） 70%（连续中段） 60.2%（二元） 关键诊断 2022 单年回撤接近 0%（-0.02%）—— 5 个策略中最优\nS5v2 -7.6% 是带 bond carry 的；S7 纯 cash 也做到了 200MA 的\u0026quot;慢\u0026quot;信号在 2022 早期及时切到 OFF，整年大部分时间停在 511990 这是 S7 的\u0026quot;避险命题兑现\u0026quot;的最强证据 总收益 +8.10% 跑输 S3 +10.80%\n主要在 2024 单边行情错过：S7 -3.6% vs S3 +8.8% / BH +18.4% / S5v1 +28.1% 200MA 相对 close 的滞后让 2024 9-10 月行情前 510300 还没站上 MA200 同时 2023 -12.4% 也跑输（震荡市频繁假突破吃成本） Sharpe 0.18 \u0026lt; S3 0.21 \u0026lt; S5 0.28\nVol 砍到 15.9%（vs S3 23.8%），但 CAGR 也低 风险调整收益没改善 —— 与 S5v2 教训一致：「降仓 ≠ 改善 Sharpe」 OFF↔BH 跌相关性 0.052 略高于 S5（0.03）但仍接近随机\n命题 \u0026ldquo;市场代理 timing 比逐标的 timing 准\u0026rdquo; 小幅成立但不显著 这是因为 200MA 本身是滞后信号，1 个滞后信号 vs 6 个独立滞后信号差距有限 真正改善必须靠「更快的信号」（短 MA / ATR / 突破带宽） 切换 25 次 / 13 次 ON 段 / 13 次 OFF 段 / 平均 ON 37 天 OFF 56 天\n适中频率：5 年 25 次，平均每 2 个月 1 次，与\u0026quot;市场 regime\u0026quot;切换节奏吻合 单段最长：ON 段 279 天（一段长牛市）/ OFF 段 257 天（一段长熊市）—— 200MA 的\u0026quot;低频\u0026quot;特性兑现 MA 长度敏感性 MA NAV CAGR Sharpe MaxDD 2022 switches on% corr 100 67.9k -7.77% -0.39 -48.4% -12.6% 30 45.8% 0.033 150 73.8k -6.15% -0.32 -47.1% -4.3% 35 43.2% 0.021 200 108.1k +1.64% +0.18 -30.3% 0.0% 25 39.8% 0.052 250 77.9k -5.08% -0.30 -31.1% 0.0% 21 33.8% 0.033 MA=200 是唯一 CAGR 为正的档位，其他 3 档全部转负。这强烈暗示：\nMA=100/150 太短，被震荡市的假突破吃光 MA=250 太长，2024 单边和 2020 反弹都进得太晚 200 是这个数据上的\u0026quot;sweet spot\u0026quot;，但只有一个甜点意味着没有稳健的参数空间 ⚠️ 警惕：单点最优在 5 年样本上有过拟合嫌疑（虽然 200 是 Faber 先验默认） 滞后敏感性（MA=200 fixed） lag NAV CAGR Sharpe MaxDD 2022 switches on% corr 1 119.1k +3.71% +0.30 -27.3% 0.0% 31 40.0% 0.099 2 108.1k +1.64% +0.18 -30.3% 0.0% 25 39.8% 0.052 3 99.1k -0.18% +0.06 -31.8% 0.0% 19 39.2% 0.044 5 90.8k -1.98% -0.07 -38.0% 0.0% 17 39.7% 0.027 lag=1（无过滤）才是最佳档位：\nNAV 119.1k \u0026gt; S5v2 112.9k ≈ S3 110.8k 但 \u0026lt; S5v1 120.3k Sharpe 0.30 ≥ S5v1/v2 的 0.28，这是基准簇 5 个策略中 Sharpe 最高的并列前茅 MaxDD -27.3% 介于 S5v2 -20.5% 和 S5v1 -47.8% 之间 2022 单年 0% —— 4 档全部命中 corr 0.099 显著高于 S5 的 0.03 —— timing 命题在 lag=1 上明显成立 结论：滞后过滤反而伤害收益。原因：A 股 ETF 节奏快，信号需要尽快执行，N=2/3/5 的过滤延迟让进场出场都慢半拍，错过最重要的拐点。这也意味着 200MA 自带的平滑度已经够（199 天平均），不需要再加滞后过滤。\n⚠️ lag=1 表现这么好，是否应当作为主参数？\nv1 主跑选 lag=2 的理由：事前预期 A 股震荡市假突破多 实测发现 200MA 本身的低通滤波已经足够 v2 候选：把主参数改为 lag=1，保留 lag=2 作为对照 关键图表 — S7 vs S3 vs S5v1 vs S5v2 vs BH 五条净值曲线 — 完整回撤 + 2022 单年特写 — 核心可视化：510300 close + MA200 + ON/OFF 色块 — 切换日标注 + ON/OFF 段持续时长直方图 artifacts/ma_length_sensitivity.csv — 4 档 MA 敏感性 artifacts/lag_sensitivity.csv — 4 档滞后敏感性 解读 \u0026amp; 问题 避险命题\u0026quot;部分兑现\u0026quot;：2022 完美避开（最佳的 5 策略），但 2024 完全错过 → 净效果 NAV 输给 S3。 Sharpe 没改善：用了 timing 信号 + cash carry，仍然 \u0026lt; S5 0.28（lag=2 主跑）；只有 lag=1 才追平 S5。 过拟合担忧：MA=200 是唯一 work 的档位 → 在 OOS（2025+）可能完全不 work。 timing 命题：边际成立：cash↔down corr 0.05-0.10 比 S5 的 0.03 高，但仍然算\u0026quot;弱信号\u0026quot;。 真正的发现：滞后过滤无效，无过滤（lag=1）反而最好 —— 与事前预期相反。 下一步 v1 真实数据回测，决定 status = shelved（避险兑现，但总收益跑输 S3，shelve 主参数；保留代码与 lag=1 sweep 数据） v2 候选：改主参数 lag_days=1，重新评估 v2 候选：weight_mode=\u0026quot;signal_only\u0026quot; 测试纯 510300 持仓 + cash 切换（去除 6 池本身的 alpha） v2 候选：双 MA 系统（50/200 黄金交叉），看能否在保留避险的同时抓住 2024 v3 方向：信号资产换\u0026quot;等权 6 池的合成净值\u0026quot;作为 risk-on 时的同源代理 OOS：等 2025 H1 数据后跑一次 源文件 想法 · idea.md 讨论笔记 · notes.md 结论 · conclusion.md 实现 · implementation.md 验证 · validation.md 可复跑脚本 · validate.py 本版本目录（含 artifacts） 本文由 scripts/sync_strategies.py 从 Strategy-Lib 同步生成。\n","permalink":"https://lizhao903.github.io/0xBroleez/posts/2026/05/a%E8%82%A1-etf-%E5%A4%A7%E7%9B%98-ma-%E8%BF%87%E6%BB%A4%E5%B8%82%E5%9C%BA%E4%BB%A3%E7%90%86-risk-on/off-v1/","summary":"❌ 避险命题部分兑现：搁置（shelved）。在 2020-2024 样本期 S7（MA=200, lag=2）NAV 108.1k vs S3 110.8k vs S5v1 120.3k vs S5v2 112.9k vs BH 104.2k —— 跑输 S3 与 S5v1/v2，但 2022 单年回撤 ≈ 0%（5 个策略中最佳）证明了\u0026rsquo;市场代理 timing 在熊市能避险\u0026rsquo;。然而代价是 2024 单边行情完全错过（-3.6% vs S5v1 +28.1%/BH +18.4%），且 Sharpe 0.18 \u0026lt; S5 的 0.28。la","title":"A股 ETF 大盘 MA 过滤（市场代理 risk-on/off） · v1"},{"content":" ✅ shipped(partial) · 最终化：2026-05-08 源码：ideas/S6_cn_etf_value_averaging/v1 + summaries/S6_cn_etf_value_averaging/v1 想法（Why） 一句话概括\n跳出 DCA 框架——按目标净值路径而非按固定金额定投：每月把账户净值拉回到一条预设的复利目标线上，差额从货币池补/抽。\n核心逻辑\n每月第一个交易日 T（决策日，T+1 开盘成交）：\n计算目标 NAV（基于路径函数 target(t)，本版采用 B：复利路径）：\ntarget(t) = init_cash × (1 + cagr_target / 12) ** months_elapsed months_elapsed = 自起始日已过的月份数（整数）。\n计算差额：gap = target(t) - NAV(t)\ngap \u0026gt; 0（NAV 落后）：从货币池抽 min(gap, max_buy_per_period) 等额买入 6 只 ETF；货币池不足时按余额成交，不外部注资（VA 的\u0026quot;刹车\u0026quot;） gap \u0026lt; 0（NAV 超前）：等比例减仓 6 只风险 ETF，回流货币池；卖出金额 = min(|gap|, max_sell_per_period) |gap| 极小（\u0026lt; min_action_amount）：跳过当月动作 货币池触底：当 cash_value \u0026lt; gap 时只买 cash_value（拒绝外部注资变成杠杆）；该月起 VA 退化为 S1 后段的\u0026quot;全在风险池\u0026quot;状态\n不做日内 swing 触发：所有动作仅发生在月度决策日，避免 S2 v1 的频繁交易\n下单：T 日决策、T+1 open + slippage 成交（与 S1/S2 一致防未来函数）\n假设与依据\nEdleson 1988/1991 经典论证：VA 在均值回归市场上长期跑赢同期 DCA，因为机制天然让\u0026quot;低位多买、高位少买（甚至卖）\u0026quot;——这是 DCA 想做但做不到的事。\nA股 ETF 池在 2020-2024 的特性：\n2020 V反 / 2021 抱团切换 / 2024 9月反弹 → VA 强制减仓获利 2022 单边熊 / 2023 震荡下行 → VA 强制加大投入抄底 理论上 VA 在 2024 反弹年应当跑赢 S2 v1（v1 在 2024 跑输 BH -11.7pct，因为高抛过早离场；VA 也会高抛，但只在超过 target 时才卖，target 本身在涨） 目标路径选 B（复利）的理由：\nA 线性增长：目标增长率被钉死，5 年后增长 50%/80% 显得武断；与 BH 期望收益脱钩 B 复利：与\u0026quot;长期投资目标 CAGR\u0026quot;的语义最自然一致；可与 BH 期望收益直接对齐（510300 历史 CAGR ~3-5%，本回测期内 ~0.86%——目标 CAGR 在 6%-12% 区间扫描即可覆盖\u0026quot;略激进/中性/激进\u0026quot;三档预期） C 有底线 + 复利：等价于 B + max 操作，本质是禁止 NAV 跌破 init_cash 时强制卖出。在 5 年回测里 510300 BH NAV 多数时间在 100k 上下徘徊，C 会让前期几乎全是买入、几乎不出现卖单——退化为 DCA 变体；不选 目标 CAGR 选择：默认 cagr_target = 0.08（年化 8%）。理由：\n略高于 BH 长期期望（约束 VA 在涨势中也会买） 远低于风险池历史 vol（避免 NAV 短期超目标过多 → 频繁卖单） 4 档敏感性（6%/8%/10%/12%）见 notes.md 标的与周期\n市场：A股 ETF（market: cn_etf） 标的池：与 S1/S2 完全一致（共享基线 7 只：511990 货基 + 6 只风险 ETF） 频率：日线 数据起止：2020-01-01 ~ 2024-12-31（共享基线） 一句话结论 VA 在 6 ETF 池 / 2020-2024 / cagr_target ∈ [6%, 12%] 上，机制层完美对称（高/低抛比 0:0）但 5 年里 0 次卖出动作触发—— 因为该池子实际 NAV 全程低于任一档目标路径；货币池在 2021-08 ~ 2022-03 区间耗尽后 VA 退化为\u0026quot;100% 风险池静态等权\u0026quot;， VA 没有创造比 S1/S2 更好的 alpha，反而因为更激进地\u0026quot;打光货币池\u0026quot;在 2020 H1 反弹中错失更多。\nVA 在该池子上机制成立 / 效果失败——这与 S2 v2 的诊断在 VA 框架上独立印证：6 ETF 池在 5 年内整体偏多头是 alpha 上限的真因，而不是节奏机制。\n在什么情况下有效，什么情况下失效 ✅ 机制层对称成立：在 smoke 合成数据 up 模式（年化漂移 +27%）下 VA 触发 SELL 动作 1 次—— 核心机制对称性可工作，与 DCA / S2 形成方法论级别的差异 ✅ \u0026ldquo;刹车\u0026quot;机制工作：货币池触底 NOOP 35 次，杠杆全程未出现，无外部注资幻觉 ✅ 与 S2 v2 conclusion 的独立印证：6 ETF 池在 5 年里整体偏多头是 alpha 上限的真因——任何\u0026quot;对称做T\u0026rdquo; 框架在该池子上都偏单方向触发 ❌ 6 ETF 池 / 2020-2024 / cagr_target ≥ 6%：实际 NAV 全程 \u0026lt; target → SELL 0 次 → 机制对称未在效果层兑现 ❌ 2020 H1 V反：单月 15k 上限让 VA 在 6 月底才投入 ~30%，错过反弹（VA -26pct vs BH 是单一最大代价） ❌ 货币池耗尽后：VA 退化为 S1/S3 类静态组合，alpha = 0；2022-2024 期间 VA 行为与 S1 v1 几乎一致 这个策略教会我什么 \u0026ldquo;机制对称\u0026quot;不等于\u0026quot;效果对称\u0026rdquo;：机制层可以完美对称（VA 的 BUY/SELL 用同一个 gap 公式），但市场结构性偏置（标的池长期方向）会让单边触发占绝对多数。研究\u0026quot;对称做T\u0026quot; 必须先用历史数据验证目标路径与实际路径会互相穿越——本版没做这一步是 v1 的认知盲区。\nVA 的\u0026quot;卖出锁利\u0026quot;机制需要满足的两个前提条件：\n标的池长期 CAGR ≥ 目标 CAGR（否则 NAV 永远落后 target，0 次卖出） 单标的而非 ETF 池（ETF 池等权后 vol 被分散，回到 target 上方的概率低） 这两个条件在共享基线（6 ETF / 2020-2024）上都不成立。\n\u0026ldquo;VA 退化为 S1 后段\u0026quot;是货币池耗尽的必然：当 init_cash 一次性给定 + 不允许杠杆，VA 终将耗尽货币池——区别仅是\u0026quot;耗尽得更快/更慢\u0026rdquo;。要让 VA 持续可工作，必须放开\u0026quot;持续注资\u0026quot;假设（Edleson 1991 原版假设）—— 但这脱离 100k 一次性投入的共享基线。\n反直觉：cagr_target 越激进 NAV 越好（在跑输目标的池子上）：这是因为 max_buy_per_period 的存在——cagr_target 越大 → 单月 gap 越大 → 越容易触发 max_buy 上限 → 货币池越早全部投出 → 越接近\u0026quot;早期全仓\u0026quot;。所以这个发现不是 VA 的胜利，是\u0026quot;早期全仓\u0026quot;在 6 ETF 池上比\u0026quot;DCA 慢入场\u0026quot;更有 alpha——和 S2 v2 conclusion #3 (\u0026ldquo;6 ETF 整体偏多头\u0026rdquo; + \u0026ldquo;Beta 决定一切\u0026rdquo;) 高度一致。\n核心 KPI 的设计要写入 idea.md：本版 idea.md 把\u0026quot;高/低抛比 \u0026lt; 3:1\u0026quot;作为 shipped 标准，结果机制层完美兑现（0:0）但本质上没意义（因为分母为 0）。下次设计\u0026quot;对称做T\u0026quot; 类策略，必须把 KPI 写成\u0026quot;双向触发都至少出现 N 次以上\u0026quot;——避免分母为零的退化情况。\n关键图表 实现要点 展开完整实现记录 Implementation — A股 ETF 价值平均法（VA） 整体方案 S6 是 V1 共享基线下的「权重驱动」策略，但锚点不再是资产权重（S2/S3 系列）也不是现金流量（S1），而是NAV 路径。\n每月第一个交易日 T 收盘评估：\ntarget(t) = init_cash × (1 + cagr_target / 12) ** months_elapsed (复利路径，B 方案) gap = target(t) - NAV(t) 下一个交易日 T+1 开盘：\ngap \u0026gt; min_action_amount（NAV 落后）：从货币池抽 min(gap, max_buy_per_period, cash_value) → 等额 6 等分买入风险 ETF gap \u0026lt; -min_action_amount（NAV 超前）：按当下市值比例分摊到 6 只 ETF 卖出 min(|gap|, max_sell_per_period)，回流货币池 |gap| ≤ min_action_amount 或货币池为空：跳过（NOOP / SKIP） 机制层关键点：\n没有阈值触发的高频动作：所有动作仅在月度决策日；与 S2 v1 的 ~177 次/5 年 swing 相比量级低 1-2 个数量级 机制层对称：差\u0026gt;0 买、差\u0026lt;0 卖；不预设方向 货币池触底硬刹车：cash_value \u0026lt; min_action_amount 时 BUY 退化为 NOOP（不杠杆、不外部注资） 单月上限 = 流量阀：max_buy/sell 各 15k；防止极端市场单次砸光资金池 因子清单 本策略不使用任何 Factor 类。\nFactor 类 文件 参数 方向 是新增还是复用 —— —— —— —— —— VA 的\u0026quot;目标 NAV 路径\u0026quot;不抽出 Factor 类——仅在策略内部计算（私有方法 _target_value），不暴露 IC 接口。\n新增因子（如有） 无。\n策略实现要点 文件：src/strategy_lib/strategies/cn_etf_value_averaging.py 类：ValueAveragingStrategy（不继承 S1/S2/S2v2，独立实现） 主真值：纯 python simulate() 算 NAV；vectorbt 仅作可选 trade analyzer（不存在时降级 portfolio=None） 时序：T 日 close 决策；T+1 open + slippage 成交 → 与 S1/S2 一致防未来函数 # 核心循环简化伪代码 for t in range(n_days): if pending_action: execute_at_open(pending_action, day=t) # T+1 成交 nav[t] = mark_to_market(close[t]) if next_day_is_month_first: target_t = init_cash * (1 + cagr_target/12) ** months_elapsed gap = target_t - nav[t] if gap \u0026gt; min_action: pending_action = (\u0026#34;BUY\u0026#34;, min(gap, max_buy_per_period)) elif gap \u0026lt; -min_action: pending_action = (\u0026#34;SELL\u0026#34;, min(-gap, max_sell_per_period)) 策略配置 配置文件：configs/S6_cn_etf_value_averaging_v1.yaml 类型：value_averaging（本类型不在 strategies/registry.py 注册，避免修改硬约束保护文件） 关键参数： target_path_kind: compound （B 方案；可选 linear / compound_floor） cagr_target: 0.08（默认；4 档敏感性 0.06/0.08/0.10/0.12） max_buy_per_period: 15000，max_sell_per_period: 15000 min_action_amount: 500 数据 标的池：与共享基线一致（511990 货基 + 6 只风险 ETF） 数据范围：2020-01-01 ~ 2024-12-31（共 1209 个交易日） 数据来源：data/raw/cn_etf/\u0026lt;sym\u0026gt;_1d.parquet（cache hit，无网络请求） 复权：前复权（akshare qfq） 成本：万 0.5 fees + 万 5 slippage（共享基线） 踩过的坑 货币池\u0026quot;近耗尽\u0026quot; vs \u0026ldquo;彻底耗尽\u0026rdquo;： simulate 中 cash_exhausted_date 的判定用 cash_value \u0026lt; min_action_amount=500 而不是 \u0026lt; 0—— 因为浮点剩余几十块时再继续 NOOP 没意义。文档里\u0026quot;耗尽\u0026quot;指此点，不是货币池真的为零。\n目标路径月份对齐： months_elapsed 用 pandas.Period.n 差值算，而不是按交易日数 / 21。这样在交易日数偏离 21 的月份（节假日多 / 春节）路径仍然平滑。\nvbt from_orders 的 amount 字段： 订单日志里增加了 amount（人民币金额）字段；turnover_annual 计算优先用它，避免 size × price 在不同 ETF 上的浮点累计误差。\nNAV 重算时初始日 t=0 必须先初始化货基持仓： init_buy 把 init_cash 全部投入 511990，作为起始 NAV。第一个月底如果 target\u0026gt;init_cash 才会触发 BUY；如果第一个月就触发 BUY，要确保此前 t=0 的 holdings_hist 已被记录否则 NAV 重算对不上。\n决策日是 T，成交是 T+1： month_first_set 判定用 index[t+1] in month_firsts（T 是月底/月中、T+1 是月初）。这种实现保证不偷看下个月数据。\n决策记录 选 B（复利）而非 A（线性）/ C（有底线）：\nA 在终值上同样能配出敏感性，但与\u0026quot;目标 CAGR\u0026quot;语义脱钩（线性增长 50% 等价 CAGR 8.4% 但只在 5 年端点），不直观 C 在 5 年回测里基本退化为 DCA 变体（NAV 长期 \u0026lt; init_cash + 复利→ max() 钳制 → 几乎全是买）；不能测对称性 4 档 cagr_target = [6%, 8%, 10%, 12%]：\n6% 是温和（接近本期 BH+5pct） 8% 是默认（正好高于 BH 长期期望） 10%/12% 是激进档（用于看货币池什么时候耗尽） 单月上限 15k 不调：\n原始假设：100k init_cash × 15k/月 = 6.7 月可耗尽（极端跌市） 实测：8% 档 2022-01-04 耗尽（约 24 个月），印证上限是合理保守值 不引入 cash_min_reserve：\n让货币池能耗尽是核心实验意图——观察 VA 的极限行为 相关 commits 实现：\u0026lt;待提交\u0026gt; 调参：—— 本版无调参 验证过程 展开完整验证记录 Validation — A股 ETF 价值平均法（VA, S6 v1） 每次新一轮回测/验证就追加一个 ## YYYY-MM-DD \u0026lt;轮次主题\u0026gt; 小节，不要覆盖。\n2026-05-08 Smoke Test（合成数据） 配置 \u0026amp; 数据 配置：configs/S6_cn_etf_value_averaging_v1.yaml（默认参数 cagr_target=0.08） 数据：合成 7 个 symbol（1 货基 + 6 风险）OHLCV，504 个交易日（2020-01-02 起，B 频率） 三种漂移模式：balanced（年化漂移 ~7%）、down（年化 -15%）、up（年化 +27%） 目的：验证 VA 双向调节机制、货币池非负、NAV 重算恒等 因子层（IC 分析） 本策略不使用任何 Factor，跳过 IC / 分组分析。\nSmoke 结果 模式 n_buy_months n_sell_months n_noop_months 货币池耗尽 final_risk_w final_nav balanced 23 0 0 2021-12-01 99.7% 111,556 down 21 0 2 2021-10-01 100.0% —— up 22 1 0 （未耗尽） 78.2% —— 机械正确性检查 ✅ NAV 重算恒等（(holdings × close).sum() ≈ nav.iloc[-1]，浮点误差 \u0026lt; 1e-9） ✅ 起始时货基持仓 ≈ init_cash（前向 buy 之前 cash_weight \u0026gt; 0.99） ✅ 货币池始终非负（cash_holdings \u0026gt;= -1e-9 全程） ✅ down 模式 BUY+NOOP 月数 ≥ 12（VA 在跌市持续加仓直到货币池耗尽） ✅ up 模式 SELL 月数 ≥ 1（VA 在涨市强制减仓）—— 核心机制对称性兑现 ✅ target 序列单调递增（cagr_target \u0026gt; 0 时） 解读 smoke up 模式触发了 SELL —— 这是 VA 区别于 DCA / S2 的关键机制证据：当 NAV 超过 target 路径时强制卖出。在 S2 v1 整个 5 年里 swing_sell 177 次但资金净流入；VA 是 NAV 净流出。 balanced 模式 cash_exhausted_date=2021-12-01 是一个良性事件：货币池被规整地\u0026quot;投光\u0026quot;——剩余 0.3% 是浮点剩余，下个月 BUY 计划被 NOOP 跳过。 smoke down 模式 NOOP 月数偏少（仅 2）：因为 max_buy_per_period=10000（smoke 测试值）让货币池能持续 10 个月才耗尽；最后一个月剩余刚好等于 min_action_amount 才触发 NOOP——量级敏感于风控参数选择。 VA 的目标 NAV 路径（cagr_target=8%）在 balanced 漂移（~7% 漂移 - 6 ETF 等权后偏多头）下 NAV final 略落后 target final（111k vs 116k）—— 因为风控上限 + 6 ETF 等权后实际 beta \u0026lt; 1。 2026-05-08 真实数据回测（cagr_target=8% 默认档） 配置 \u0026amp; 数据 代码版本：本仓库工作树（首次提交前）；运行点 summaries/S6_cn_etf_value_averaging/v1/validate.py::backtest_real() 策略参数（ValueAveragingStrategy() 默认值）： cash_symbol=511990，risk_pool=(510300,510500,159915,512100,512880,512170) target_path_kind=compound，cagr_target=0.08 max_buy_per_period=15000，max_sell_per_period=15000 min_action_amount=500 资金/成本（共享基线）：init_cash=100,000、fees=0.00005、slippage=0.0005、复权 = qfq 回测窗口：2020-01-01 ~ 2024-12-31，共 1209 个交易日 数据来源：data/raw/cn_etf/{...}_1d.parquet（cache hit） 回测绩效（VA vs S2 v1 vs S1 v1 vs 510300 BH） 指标 S1 v1 (DCA) S2 v1 (Swing) S2 v2 (改进) S6 (VA) 510300 BH NAV (100k 起) 89,647 113,808 114,209 82,940 104,957 CAGR -2.25% +2.75% +2.82% -3.82% +0.86% Sharpe -0.11 0.152 0.164 -0.20 0.039 MaxDD -45.13% -37.10% -35.12% -44.36% -44.75% 高抛次数（卖出动作） 0 177 187 0 — 低吸次数（买入动作） 0 15 16 24（月）= 144 笔单子 — 高抛/低吸比（核心） — 11.80:1 11.69:1 0.00:1 — 年化换手 21.3% 153.9% 96% 44.1% 0% 货币池耗尽时点 2021-09 未耗尽 未耗尽 2022-01-04 — 跟踪指标：\n超额总收益（vs BH）：-21.20% 信息比率：-0.374 跟踪误差（年化）：14.23% VA 特有：\nBUY 月数 = 24（货币池耗尽前每月 1 次） SELL 月数 = 0（5 年里 NAV 从未超过 8% target 路径） NOOP 月数 = 35（货币池耗尽后想买买不到） 终态 final_target = 147,998（目标）vs final_nav = 82,940（实际）→ gap = 65k 永远没补回来 final_risk_weight = 100% / final_cash_weight = 0% 分年度收益 年份 S6 VA S2 v1 S1 v1 510300 BH VA vs BH 2020 +4.94% +34.32% +13.35% +31.11% -26.17pct 2021 +3.63% +3.93% +3.66% -5.24% +8.87pct 2022 -22.90% -17.71% -22.91% -21.37% -1.53pct 2023 -10.33% -8.38% -10.56% -10.71% +0.38pct 2024 +11.53% +8.41% +11.25% +20.11% -8.58pct 4 档 cagr_target 敏感性（artifact CSV） cagr_target NAV CAGR Sharpe MaxDD n_buy n_sell n_noop 货币池耗尽 6% 82,868 -3.83% -0.206 -42.86% 25 0 33 2022-03-01 8% (默认) 82,940 -3.82% -0.200 -44.36% 24 0 35 2022-01-04 10% 84,598 -3.42% -0.176 -44.40% 21 0 38 2021-10-08 12% 86,183 -3.04% -0.154 -44.58% 19 0 40 2021-08-02 关键观察：\n4 档全部 0 SELL——VA 的卖出机制在 5 年内一次都没触发（实际 NAV 全程低于任一档目标路径） cagr_target 越高 → 货币池越早耗尽 → BUY 月数越少 → NOOP 月数越多 反直觉：cagr_target 越高 NAV 越好！原因：12% 档要求每月 BUY 上限 = 15k 全压；货币池在 2021-08 之前被全部投入风险池——正好赶上 2022 熊市前的高位——NAV 反而比 6% 档更接近 BH。即\u0026quot;目标越激进 → 货币池被打光越早 → 越早全仓 → 5 年累积收益越接近 100% 风险池 BH\u0026quot;。 这印证了 S2 v2 conclusion 的洞察：该 6 ETF 池在 2020-2024 的 alpha 上限来自\u0026quot;全仓择时\u0026quot;而非\u0026quot;DCA/VA 节奏\u0026quot;——节奏机制在该池上无 alpha。 关键图表（artifacts/） equity_curve.png —— S6 VA vs S1 vs S2 v1 vs 510300 BH（4 条线归一化） drawdown.png —— 4 个策略的回撤曲线对比 target_vs_actual.png —— 核心可视化：目标路径（CAGR 8%）vs 实际 VA NAV vs BH NAV，含 cash_exhausted 时点标线 va_actions.png —— 月度 BUY/SELL 金额条形（绿正红负），NOOP 标 X cash_vs_risk.png —— 货币池 / 风险池占比时间序列 cagr_sensitivity.csv —— 4 档 cagr_target 敏感性表 解读 \u0026amp; 关键发现 1. VA 机制层完全对称（核心 KPI 兑现） 高/低抛比 = 0:0（5 年内 0 次卖出） 这与 S2 v1（11.80:1 不对称）形成鲜明对照——但不是因为 VA 机制有 bug，而是因为： 2. VA 在 2020-2024 A股 ETF 上\u0026quot;对称机制 vs 行情结构性\u0026quot;完全错位 5 年里 6 ETF 等权 NAV 几乎从未超过 init_cash × (1+8%/12)^m 的目标路径 即使 cagr_target=6%（5 年终值 134k），实际 NAV 在 2020 高点 121k 也不够碰 终态 gap = 65k（target 148k vs actual 83k）—— 这是 5 年累积\u0026quot;目标 vs 实际\u0026quot;的总缺口 VA 的 SELL 机制被这种结构性多头偏置（市场跑输目标）从未启动 → 机制对称没有创造对称的实际行为 3. 2022-01-04 货币池耗尽是关键拐点 之前：VA 月度 BUY 15k 上限消化 init_cash + 复利累积下来的 ~100k 货基 之后：VA 退化为\u0026quot;100% 风险池等权静态组合\u0026quot;——与 S1 v1 在 2021-09 之后的状态本质等价 但 S6 比 S1 更早进入\u0026quot;全仓\u0026quot;状态（2022-01 vs 2021-09 后段，但 S6 是更激进地买入），结果在 2022 熊市中 VA -22.90% vs S1 -22.91%——几乎一样 4. 2020 大幅跑输（VA -26pct vs BH）的双重原因 上半年：100k 货基逐月解锁 15k → 6 月底才把货基投入 ~30%；错过 2020 H1 反弹 下半年：VA 仍在执行 BUY 节奏（每月 15k），并未利用\u0026quot;已涨过 target 该卖\u0026quot;的机制（因为 target 也在向上） 这就是 idea.md 里说的\u0026quot;15k 单月上限在 V 反市场中导致少买\u0026quot;的实证 5. 2024 跑输 BH 8.58pct 的原因（与 S2 v1 的 11.7pct 类似但不同） VA 在 2024 已经货币池耗尽近 2 年了——没有任何\u0026quot;卖→等→买\u0026quot;动作可言 2024 全年 VA 仅是 100% 风险池等权浮动；6 ETF 等权（含创业板/医疗）跑输 510300 主板 这个 -8.58pct 本质是池子构成问题，不是 VA 机制问题——和 S1 v1 的 2024（-8.41pct）几乎一致 6. VA 是否真的解决了 S2 揭示的\u0026quot;DCA 不对称结构性矛盾\u0026quot;？ 机制层：是（高/低对称比 0:0；任意 cagr_target 档同结论） 效果层：完全否——因为 5 年内目标路径 \u0026gt; 实际路径，对称机制只单边触发 更深的发现：S2 v2 conclusion 已正确诊断「6 ETF 在 5 年里整体偏多头」，S6 在另一个机制（VA）上给出独立验证——任何\u0026quot;对称做T\u0026quot; 框架在该池子上都偏向单方向 货币池极限情况是否触发？ 是——4 档全部触发（最早 2021-08，最晚 2022-03） 触发后行为符合设计：NOOP 跳过、不杠杆、不外部注资 这印证 idea.md 风险点 1（\u0026ldquo;目标路径\u0026rsquo;卡脖子\u0026rsquo;问题\u0026rdquo;）：A股 ETF 池跑输 6% 目标 CAGR 的概率 = 100%（5 年回测） 已知边界 VA 在 5 年回测里没有完整测试 SELL 路径：若想真正测对称性，需要：\n换更弱的池子（target 更容易超过）—— 例如把 cagr_target = 0% 或负值；但语义上不再是\u0026quot;价值平均法\u0026quot; 换更强的市场样本（例如 2010-2014 中证 500）——超出本基线时间窗 注资规模放大（\u0026ldquo;100k init\u0026quot;换成\u0026quot;100k init + 5k/月固定追加\u0026rdquo;）——但脱离基线 本版选择保留与 S1/S2 完全可比的基线；不增加 OOS 不可验证性\nvbt portfolio 字段：在当前环境下 _run_with_vbt 因 vbt API 兼容已正常运行（result.portfolio 非 None），但所有指标用 simulate 主真值，vbt 仅作旁证\n下一步（候选 v2 方向，不在 v1 上事后调参） 方向 a：换池子——5 年回测 BH = 4% CAGR；想测 VA 对称性需要\u0026quot;NAV 与 target 互相穿越\u0026quot;的池子；候选：单标的 510300（去掉 6 ETF 偏多头的池子结构），或把池子换成 BH≈3% 的混合池 方向 b：动态 cagr_target——按过去 1 年实际 CAGR ± 调整（OOS 不可验证性增加，谨慎） 方向 c：放开 init 规模约束——按 Edleson 1991 原版\u0026quot;持续注资 + 可借入\u0026quot;重做基线 方向 d（共池研究）：以 S6 + S1 + S2 + S3 在统一池子下做\u0026quot;alpha 来源消融\u0026quot;——确认\u0026quot;6 ETF 池的 alpha 上限来自全仓择时不是节奏\u0026quot; 在 summaries/README.md 索引追加 S6 v1 一行 源文件 想法 · idea.md 讨论笔记 · notes.md 结论 · conclusion.md 实现 · implementation.md 验证 · validation.md 可复跑脚本 · validate.py 本版本目录（含 artifacts） 本文由 scripts/sync_strategies.py 从 Strategy-Lib 同步生成。\n","permalink":"https://lizhao903.github.io/0xBroleez/posts/2026/05/a%E8%82%A1-etf-%E4%BB%B7%E5%80%BC%E5%B9%B3%E5%9D%87%E6%B3%95value-averaging-va/","summary":"✅ \u003cstrong\u003eVA 在 6 ETF 池 / 2020-2024 / cagr_target ∈ [6%, 12%] 上\u003c/strong\u003e，机制层完美对称（高/低抛比 0:0）但 5 年里 0 次卖出动作触发——","title":"A股 ETF 价值平均法（Value Averaging, VA）"},{"content":" ❌ shelved · 最终化：2026-05-08 源码：ideas/S5_cn_etf_trend_tilt/v2 + summaries/S5_cn_etf_trend_tilt/v2 本策略的其他版本 v1 ❌ — 边际有效但不及预期：搁置（shelved）。在 2020-2024 样本期 S5 总收益 +20.26% 优于 S3 (+10.80%) 和 510300 BH (+4.18%)，Sharpe 也更高 (0.28 vs 0.21 vs 0.15)，但核心命题「下行保护」未兑现——2022 单年 S5 -21.61% 与 BH -21.68% 几乎完全重合，超额主要来自 2024 单年的板块行情。 v2 本文 ❌ — 避险命题数据上兑现（MaxDD -47.8% → -20.5%，2022 -21.6% → -7.6%），但 Sharpe 与 v1 持平、CAGR 倒退 1.35 pct/yr，且 bond overlay 占用 37% 平均仓位让策略漂移成「股债混合」——v2 实现了「保守型变体」但没有产生新 alpha。状态：shelved（保留代码作为 v1 的 risk profile 对照，不推荐替代 v1）。 想法（Why） 一句话概括\n在 v1 的趋势倾斜框架上解决两个明确问题——把现金占比从「双峰二值」改成「随趋势强度连续变化」，加一层「池中超过半数 ETF 高波时强制降仓」的避险闸门，并用十年国债 ETF 替代部分现金，让避险时段不仅是空仓而是实际有正收益的 carry。\n假设与依据\n为什么把权重连续化（而不是单纯调高 cutoff） v1 的 cutoff 敏感性显示：cutoff 提高反而恶化（CAGR 转负）。这说明问题不在 cutoff 高低，而在「分数归一化」的瞬变。即使 score 在 cutoff 附近徘徊，归一化让权重瞬间从 0 跳到 1/N。v2 的连续 ramp 让权重正比于 score 强度，避免「弱信号也满仓」和「强信号也只到 1/N」两种过度反应。\n为什么加波动率过滤（不靠趋势信号本身改进） v1 复盘的关键发现：MA/Donchian 对 A股快速下跌识别过慢。这不是参数问题（用 MA10/30/60 也不会快太多），而是「趋势信号」与「下跌起点」的固有错位。\n波动率与趋势是正交信号——高波不一定意味着方向，但高波 = 风险预算被消耗。这是经典 vol-target portfolio 的逻辑：当 realized vol 上升，缩减仓位以维持目标波动。在 A股 ETF 上特别有效，因为：\n牛市顶到熊市底的过渡通常伴随 vol 飙升（VIX-like 行为） 2022-04 的疫情底、2024-09 的政策底都先有 vol 显著上升 为什么用 511260 十年国债 ETF 与股票相关性低（多数时段微负） 年化收益 ~3-4%，比 511990 货基（~2%）高 2022 年股市深跌时，国债正收益（避险 carry 双重受益） 已经在缓存里（无需重新拉数据） 不直接 100% 替代是因为 2024 出现过股债同跌的极端情形，留 60% 留为纯现金（cash_gap - bond_max_weight 的部分）作为安全垫。\n因子选择 沿用：DonchianPosition(120) —— v1 已验证有效 替换：MABullishScore → MABullishContinuous(20/60/120, k=20) —— 把 sign() 离散化改成 tanh 连续化 新增：AnnualizedVol(60) —— 60 日年化波动作为 vol filter 输入 新因子写在 factors/trend.py 和 factors/volatility.py 末尾，不动 factors/__init__.py（按硬约束）。\n标的与周期\n市场：cn_etf 风险池：v1 同 6 只 ETF（510300 / 510500 / 159915 / 512100 / 512880 / 512170） 新增：511260 十年国债 ETF（仅作 cash overlay，不参与 trend 排序） 频率：日线 1d 数据起止：2020-01-01 ~ 2024-12-31（暖机自 2019-07-01） 一句话结论 避险命题数据上兑现（MaxDD -47.8% → -20.5%，2022 -21.6% → -7.6%），但 Sharpe 与 v1 持平、CAGR 倒退 1.35 pct/yr，且 bond overlay 占用 37% 平均仓位让策略漂移成「股债混合」——v2 实现了「保守型变体」但没有产生新 alpha。状态：shelved（保留代码作为 v1 的 risk profile 对照，不推荐替代 v1）。\n在什么情况下有效，什么情况下失效 ✅ 极端下跌年：2022 v2 -7.6%（v1 -21.6%, BH -21.7%）—— vol filter + 连续降仓真正发挥 ✅ 震荡市：2023 v2 -8.0%（v1 -18.9%）—— bond carry + 降仓双重得益 ✅ 任何时候的 risk-adjusted return：Calmar 0.13 是基准簇 5 个策略中最佳 ❌ 结构性单边牛市：2024 v2 +0.5%（v1 +28%, BH +18%）—— 9-24 行情前 vol 没飙升、score_full=1.0 偏严、bond 占 40% 让组合稀释成股 60/债 40，错过单边 ❌ 疫情冲击急跌后的 V 型反弹：2020 v2 +22%（v1 +41%）—— 2020-03 vol filter 触发后没及时撤防，错过 4-12 月反弹 ⚠️ 常态期：v2 cash median = 79%，趋势倾斜的纯度被稀释，更像「股债混合 + 趋势 sizing」 这个策略教会我什么 「连续化」与「真避险」是两件事： v2 完美解决了 cash 双峰（70% 介于 5-95% 中间段） 但 cash↔BH down 相关性几乎没动（0.033 → 0.031） 因为 v2 的避险来自结构性降仓（sizing）而非 timing——常态期就只 50-70% 仓位，下跌时被动跌一半 教训：「连续 ramp」不等于「精准择时」。如果想要 timing 类避险，需要更短 lookback 的快速反转信号（ATR、价格突破均线幅度），而不只是平滑权重曲线 vol filter 是 sizing 工具不是 timing 工具： 在 sample 内的 5 年里，vol 飙升通常滞后于真实下跌起点（vol 是 ex-post 度量） vol filter 真正的价值是「让组合在高波 regime 自动收缩」，与「预测下跌」无关 教训：定位 vol filter 为「risk budget control」而不是「avoid drawdown」 bond overlay 是双刃剑： 在债牛年（2022/2023）确实贡献 +3% 左右 carry 但默认 40% 上限太激进，让 risky 暴露被结构性压低 教训：bond overlay 应只在 vol filter 触发时打开（regime-conditional），不是常开 Sharpe 持平意味着没有新 alpha： v2 与 v1 Sharpe 都是 0.28——v2 用更低 vol 换 CAGR，risk-adj 没改进 真正的 alpha 提升要么来自更准的信号（识别拐点）、要么来自更好的 sizing 函数（vol-target 的精细化） 教训：避险命题的合格指标是 Calmar/MaxDD/单年极值；Sharpe 是「真有信息」的指标，v2 没通过 关键图表 实现要点 展开完整实现记录 Implementation — A股 ETF 等权 + 趋势倾斜 v2 整体方案 继承 v1 的 TrendTiltStrategy（间接继承 S3 EqualRebalanceStrategy），覆盖 4 个方法解决 v1 的两大问题：\n_tilt_weights：从「正分数 / 正分数和」（归一化）改成「(score - cutoff) / score_full / N」（不归一化）→ 让 cash 自然存在 compute_trend_scores：用连续版 MA 因子（tanh 风格）替代 v1 的离散 sign 版；同时跳过 bond_symbol 不参与 trend 排序 target_weights：在 risky 权重之上叠加（a）波动率过滤（b）债券 overlay __init__：透传父类参数 + 新增 v2 的 5 个参数 v1 的 _validate_weights 直接复用（v1 已经放宽过 sum\u0026lt;1 的约束）。\n因子清单 Factor 类 文件 参数 方向 新增/复用 MABullishContinuous factors/trend.py（末尾新增） short=20, mid=60, long=120, k=20 +1 新增 DonchianPosition factors/trend.py lookback=120 +1 复用 v1 AnnualizedVol factors/volatility.py（末尾新增） lookback=60 -1 新增 factors/__init__.py 不修改（按硬约束）。下游直接 import：\nfrom strategy_lib.factors.trend import MABullishContinuous, DonchianPosition from strategy_lib.factors.volatility import AnnualizedVol 新增因子（详细说明） MABullishContinuous(short, mid, long, k) score = mean( tanh(k * (close/MA - 1)) for MA in (short, mid, long) ) # 取值 ∈ (-1, +1) 为什么：v1 的 MABullishScore 用 sign() 输出 ∈ {-3,-1,+1,+3}，离散阶跃让权重在 ramp 区间瞬变。tanh 把价格相对均线的偏离平滑映射到 (-1, +1)，三层 MA 求平均后仍 ∈ (-1, +1)。 k=20 的标定：close/MA - 1 = 5% → tanh(20·0.05) = tanh(1) ≈ 0.76。让 5% 偏离对应明显但不饱和的信号；这与 A股 ETF 典型 σ 在 1.5%/天 的 6 小时窗口偏离量级一致。 暖机期：MA_long 还没 ready 时返回 NaN。 AnnualizedVol(lookback) ret = log(close).diff() vol = ret.rolling(lookback).std() * sqrt(252) 与 RealizedVol 的差别仅在 √252 缩放，便于策略侧直接用 0.30 这种「年化口径」阈值比较。 关键设计决策 1. 连续 ramp 替代归一化（修复双峰） v2 的 _tilt_weights：\ndef _tilt_weights(self, scores: pd.Series) -\u0026gt; dict[str, float]: valid = scores.dropna() n_risky = max(len(valid), 1) weights = {} denom = self._score_full # 默认 1.0 for sym, score in valid.items(): raw = (score - self._cutoff) / denom raw = max(0.0, min(1.0, raw)) if raw \u0026gt; 0: weights[sym] = raw / n_risky return weights 关键点：weight_i = raw_i / n_risky，不再除以 Σraw_j。score 在 cutoff 与 cutoff+score_full 之间时，权重正比于 score；超过 score_full 饱和到 1/n_risky。\n2. 波动率过滤（修复避险命题） def _vol_breadth(self, date, prices_panel) -\u0026gt; float: # 池中 vol \u0026gt; vol_high 的资产占比 ∈ [0, 1] def target_weights(...): risky_weights = self._tilt_weights(scores) if self._vol_breadth(date, panel) \u0026gt;= self._vol_breadth_threshold: risky_weights = {s: w * self._vol_haircut for s, w in risky_weights.items()} ... 默认 vol_high=0.30 / vol_breadth_threshold=0.5 / vol_haircut=0.5：池中 ≥ 50% 的风险 ETF 60 日年化波动 \u0026gt; 30% 时，全体权重 ×0.5。\n与趋势信号正交：vol filter 不依赖方向判断，只看「整体风险 regime」。这是 vol-target portfolio 的标准思路，弥补 v1 「趋势信号对快速下跌识别滞后」的固有问题。\n3. bond overlay（修复空仓 carry） risky_sum = sum(risky_weights.values()) cash_gap = 1.0 - risky_sum bond_w = min(cash_gap, self._bond_max_weight) if bond_w \u0026gt; 0.01: out[self._bond_symbol] = bond_w bond_max_weight = 0.4 是上限——即便 cash_gap = 1（全空仓），bond 也只占 40%，剩余 60% 留作纯现金（防股债同跌）。\nbond_symbol 在 compute_trend_scores 里被显式跳过——它不参与 trend 排序，只作为现金缺口的填充工具。\n4. cash_ratio 的计算口径（vbt 集成） v2 含 7 个 symbol（6 risky + 1 bond），vbt 的 pf.cash() 把 bond 占比也算成「非现金」。验证脚本里改用：\ncash_ratio = 1 - sum(risky_asset_value) / total_value 确保 bond 不被算作 cash，这样 cash_ratio 真实反映「无 risk 资产仓位」。\n5. 避免 lookahead 与 v1 一致：compute_trend_scores 内部对每个 symbol 做 df.loc[df.index \u0026lt;= date] 切片再算因子。_vol_breadth 同样切片。父类 from_orders 默认 t+1 成交。\n策略配置 配置文件：configs/S5_cn_etf_trend_tilt_v2.yaml 类型：trend_tilt_v2（不注册到 registry，本任务范围内） 父策略：v1 cn_etf_trend_tilt → S3 cn_etf_equal_rebalance 关键参数： 趋势：ma_short/mid/long=20/60/120, donchian=120, cutoff=0.0, score_full=1.0, use_continuous_score=True 波动率：vol_lookback=60, vol_high=0.30, vol_breadth_threshold=0.5, vol_haircut=0.5 债券：bond_symbol=511260, bond_max_weight=0.4 数据 标的池：v1 的 6 只风险 ETF + 511260 十年国债 ETF（已缓存） 数据范围：2020-01-01 ~ 2024-12-31；暖机自 2019-07-01（120 日） 复权：akshare qfq 前复权（与 v1/S3 一致） 与 v1 / S3 的关系 直接继承 TrendTiltStrategy(v1)，复用其 compute_trend_scores 框架的部分代码（_normalize_to_unit）和 _validate_weights 不修改 v1 任何文件（按硬约束） 不修改 S3 父类（v1 已验证 vbt sum\u0026lt;1 兼容性，v2 继承同一保证） 不修改 factors/init.py 踩过的坑 1. cash_ratio 计算口径 最初用 pf.cash() 计算 cash_ratio，但 vbt 在多 asset 共享资金池时 pf.cash() 包含的是「未配置到任何 asset 的现金」——bond 占比不算 cash。但用户问的「cash_ratio」语义上想看「真正没投资风险资产的比例」，所以改成 1 - risky_value/total_value。\n2. bond_symbol 在 trend 排序里要跳过 最初没跳过，导致 511260 也被算 trend_score。但国债的 close \u0026gt; MA 形态对应「债券走牛」，反而得到正分数，与「股市趋势」语义错位。改成 compute_trend_scores 里显式 if symbol == bond_symbol: continue。\n3. MABullishContinuous 已经 ∈ (-1, +1)，不要再 / 3 v1 的 _normalize_to_unit(ma, max_abs=3) 是为离散 score ∈ {-3, -1, +1, +3} 设计的。v2 替换连续因子后已经在 (-1, +1) 内，再除以 3 会让它 max_abs ~ 0.33 远小于 Donchian 的 1.0，破坏两者权重平衡。compute_trend_scores 里加了 isinstance 判断分支处理。\n4. 浮点 score_full 与 cutoff 的边界 当 cutoff = 0、score_full = 1 时，score = 0.5 → raw = 0.5 → weight = 0.5/N。这是预期。但若 cutoff = 0.3、score_full = 0.5，需要 score ≥ 0.8 才饱和。文档里把这个含义说清楚以防误用。\n与并行实现的对接点 v1 文件已存在并合并，本子代理直接 import：from strategy_lib.strategies.cn_etf_trend_tilt import TrendTiltStrategy, _normalize_to_unit factors 已存在 v1 新增的 MABullishScore / DonchianPosition，v2 在文件末尾追加 MABullishContinuous 和 AnnualizedVol registry / __init__.py 注册由集成 PR 统一处理 相关 commits 实现：\u0026lt;待commit\u0026gt; 验证过程 展开完整验证记录 Validation — A股 ETF 等权 + 趋势倾斜 v2 每次新一轮回测/验证就追加一个 ## YYYY-MM-DD \u0026lt;轮次主题\u0026gt; 小节，不要覆盖。\n2026-05-08 v2 真实数据回测 配置 / 数据 / 暖机 数据：v1 同 6 只风险 ETF + 511260 十年国债 ETF，akshare qfq 前复权 回测窗口：2020-01-02 ~ 2024-12-31（共 1209 个交易日），暖机自 2019-07-01 资金：100,000 RMB，佣金万 0.5（fees=5e-5），滑点万 5（slippage=5e-4）—— V1 共享基线 v2 默认参数： 趋势：rebalance_period=20, ma=20/60/120, donchian=120, cutoff=0.0, score_full=1.0, use_continuous_score=True 波动率：vol_lookback=60, vol_high=0.30, vol_breadth_threshold=0.5, vol_haircut=0.5 债券：bond_symbol=511260, bond_max_weight=0.4 执行入口：PYTHONPATH=src python summaries/S5_cn_etf_trend_tilt/v2/validate.py real Smoke tests（合成数据） 测试 验证目标 结果 test_v2_warmup_returns_empty 暖机期数据不足 → 空仓 OK test_v2_all_strong_full_invest 全强上行 → 总和 ≈ 1（满仓） OK (sum=1.000) test_v2_all_downtrend_zero 全下行 → 总和 = 0 OK test_v2_continuous_ramp_subunit_sum 部分弱上行（score 在 ramp 中段）→ sum ∈ (0, 1) OK (sum=0.96) test_v2_vol_haircut_triggers 半数高波 → 全体权重 ×0.5 OK (off=0.79 → on=0.40) test_v2_bond_overlay cash_gap \u0026gt; 0 → bond 拿到 min(cash_gap, 0.4) OK 6/6 通过。\n主结果 样本期 2020-01-02 ~ 2024-12-31，初始净值 100,000：\n策略 总收益 CAGR Sharpe Vol(ann) MaxDD Calmar S5 v2 +12.93% +2.57% 0.28 11.07% -20.52% 0.13 S5 v1 +20.26% +3.92% 0.28 22.01% -47.80% 0.08 S3 equal +10.80% +2.16% 0.21 23.78% -45.18% 0.05 510300 BH +4.18% +0.86% 0.15 21.77% -44.75% 0.02 v2 的核心改善：\n波动率减半：22.0% → 11.1%（vol filter 显效） MaxDD 砍半：-47.8% → -20.5%（避险命题首次真正兑现） Calmar 翻倍：0.08 → 0.13 Sharpe 不变：0.28（v2 用更低波动换 CAGR，risk-adjusted 持平） CAGR 退化：3.92% → 2.57%（-1.35 pct/yr，主要损失在 2024） 与各基准的 alpha 对照 alpha (ann) TE (ann) IR v2 vs 510300 BH -0.50% 17.43% -0.029 v2 vs S3 equal -1.80% 18.76% -0.096 v2 vs S5 v1 -3.11% 15.91% -0.195 v2 对 BH 几乎打平（α=-0.50% 不显著），对 S3 微负，对 v1 显著为负——这是「换 risk profile 不换 alpha」的明确信号。\n分年度收益（避险命题验证的关键） 年份 v2 v1 S3 510300 BH 2020 +22.13% +41.12% +40.23% +31.11% 2021 +8.26% +4.66% +5.62% -4.32% 2022 -7.58% -21.61% -23.47% -21.68% 2023 -8.01% -18.91% -10.17% -10.43% 2024 +0.47% +28.09% +8.82% +18.39% 2022 单年 v2 -7.58%（v1 -21.61%, BH -21.68%）—— 改善 14 pct，避险命题首次兑现 2023 v2 -8.01% 也优于 v1 -18.91%（震荡市的 vol filter 与 bond carry 双重得益） 2020 v2 +22.13% 落后 v1 +41.12% —— 牛市初期 vol_filter 在 2020-03 触发（疫情急跌），错过随后反弹 2024 v2 +0.47% 远落后 v1 +28.09% —— 9-24 行情前 vol 没飙升，但 score_full=1.0 的常态保守 + bond 占 40% 让组合变成「股 60/债 40」错过单边 v2 关键指标 Cash ratio 是否真正连续 统计 v1 v2 解读 min 0.000 0.000 都能满仓 median 0.000 0.789 v1 中位是 0；v2 中位是 79% 现金/债 max 1.000 1.000 都能全空仓 std 0.386 0.294 v2 标准差小（分布更均匀） 介于 5%-95% 的天数比例 0.0% 70.2% v1 完全双峰；v2 真正连续 全空仓 (≥99%) 天数比例 18.2% 24.8% v2 略高（vol filter 加码触发） 满仓 (≤1%) 天数比例 81.8% 1.8% v1 几乎一半时间满仓；v2 只 1.8% 满仓 结论：v2 的 cash 分布达成了完全连续化——70% 的交易日 cash ratio 在 5%-95% 区间内，符合「随趋势强度连续变化」的设计目标。\n避险命题：cash 是否对齐 BH 下跌 度量 v1 v2 cash≥0.99 vs BH down corr 0.033 0.031 cash≥0.50 vs BH down corr — -0.001 cash≥0.30 vs BH down corr — 0.010 continuous cash vs BH down indicator — 0.021 意外发现：v2 的相关性没有显著提升（甚至略低）。这说明 v2 的避险不是来自「精准对齐下跌时刻」，而是**「结构性降仓」**——常态期就只 50-70% 仓位，所以 BH 跌时 v2 也跌但只跌一半。\n这是 vol-target portfolio 的本质（不是 timing，是 sizing），与「趋势退出 = 择时」的初衷有偏差。避险命题部分兑现：MaxDD/2022 数据兑现，但相关性指标没改善。\nVol filter 触发情况 67 个 rebalance 日中，vol haircut 在 16 天触发（24%） 主要时段：2020-03（疫情急跌）、2022 全年、2024-09 行情前后 触发时全体权重 ×0.5 Bond overlay 用量 触发率（bond \u0026gt; 1%）：98.2%（几乎所有交易日 bond 都在仓） 触发时中位 bond 权重：40.0%（满档） 平均 bond 权重：36.8% bond 长期占 40% 是 score_full=1.0 严格 + 池中常态信号偏弱的副作用 思考：v2 实际是个「30% 趋势 + 40% 债 + 30% 现金」的混合体，趋势倾斜的纯度被稀释。如果想保留更多趋势性，可以把 bond_max_weight 降到 0.2 或仅在 vol haircut 触发时启用 bond。\n关键图表 artifacts/equity_curve.png —— v2 / v1 / S3 / 510300 BH 四条净值曲线 artifacts/drawdown.png —— v2 vs v1 vs BH 的回撤序列（看 2022 v2 显著浅） artifacts/cash_ratio.png —— 关键：v2 连续 vs v1 双峰的现金分布（含直方图） artifacts/regime_overlay.png —— v2 高现金段（≥70%）叠加在 510300 BH 净值上 artifacts/vol_filter_trace.png —— 池中高波资产占比时间序列 + haircut 触发段 解读：v2 是否解决了 v1 的两个问题 问题 v1 现象 v2 表现 兑现否 现金分布双峰 0.0% 介于 5-95% 70.2% 介于 5-95%，std 0.29 ✅ 完全兑现 2022 避险未兑现 -21.6% 与 BH 几乎一致 -7.6% vs BH -21.7%（改善 14 pct） ✅ 完全兑现 MaxDD 改善 -47.8% -20.5%（砍半） ✅ 超出预期 cash↔BH down 相关性 0.033 0.031 / 0.001 / 0.010 ❌ 未改善（说明避险来自 sizing，不是 timing） CAGR 提升 +3.92% +2.57% ❌ 倒退 Sharpe 提升 0.28 0.28 ⚠️ 持平 v2 是否值得 ship 核心结论：v2 实现了「下行保护」目标，但代价是「降 alpha」。\n支持 ship 的理由：\nMaxDD -20.52% 是 5 个策略里最佳（之前最佳是 S2 的 -37.1%） Vol 11% 在 ETF 组合里非常低，符合「保守型策略」定位 2022 / 2023 两个困难年都跑赢 v1 / S3 / BH（避险路径多场景兑现） Calmar 0.13 是 5 个策略里最佳 不支持 ship 的理由：\nCAGR 2.57% 与 S3 (2.16%) 几乎打平，alpha vs S3 = -1.80%（统计意义上不显著） Sharpe 0.28 与 v1 完全相同——风险调整后没有改进 bond overlay 主导（占 37% 平均仓位），策略性质从「趋势倾斜」漂移到「股债混合」 2024 大幅落后 v1 +28%——单边趋势市的捕获能力丢失 避险命题的「相关性」指标未改善——说明本质是「降仓 sizing」而非「精准 timing」 下一步 待用户决策：\n状态 = shelved with notes（避险命题数据上兑现，但 alpha 没提升、参数自由度增加）—— 推荐 或：状态 = shipped (defensive variant)，作为 v1 的「保守型」对照存档 后续可探索： 关掉 bond overlay 单跑（看纯 vol filter + 连续 ramp 的效果） score_full = 0.5 让常态期更激进（试图保留 2024 alpha） 把 vol_haircut 与 trend 强度联动（强趋势 + 高波 = 仍小幅持仓） 用更短 lookback (vol_30) 提升避险时效性 等 2025 H1 数据后做 OOS 验证 源文件 想法 · idea.md 讨论笔记 · notes.md 结论 · conclusion.md 实现 · implementation.md 验证 · validation.md 可复跑脚本 · validate.py 本版本目录（含 artifacts） 本文由 scripts/sync_strategies.py 从 Strategy-Lib 同步生成。\n","permalink":"https://lizhao903.github.io/0xBroleez/posts/2026/05/a%E8%82%A1-etf-%E7%AD%89%E6%9D%83--%E8%B6%8B%E5%8A%BF%E5%80%BE%E6%96%9C%E8%BF%9E%E7%BB%AD%E7%8E%B0%E9%87%91--%E6%B3%A2%E5%8A%A8%E7%8E%87%E8%BF%87%E6%BB%A4--%E5%80%BA%E5%88%B8%E6%9A%B4%E9%9C%B2-v2/","summary":"❌ \u003cstrong\u003e避险命题数据上兑现（MaxDD -47.8% → -20.5%，2022 -21.6% → -7.6%），但 Sharpe 与 v1 持平、CAGR 倒退 1.35 pct/yr，且 bond overlay 占用 37% 平均仓位让策略漂移成「股债混合」\u003c/strong\u003e——v2 实现了「保守型变体」但没有产生新 alpha。状态：\u003cstrong\u003eshelved\u003c/strong\u003e（保留代码作为 v1 的 risk profile 对照，不推荐替代 v1）。","title":"A股 ETF 等权 + 趋势倾斜（连续现金 + 波动率过滤 + 债券暴露） · v2"},{"content":" ✅ shipped (pool value, not factor value) · 最终化：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） 一句话概括\n把 v1 的 6-ETF 池（A股宽基/行业，高度同涨同跌）扩为 11-ETF 跨资产池（+ 港股/黄金/纳指/标普/十年国债），叠加严格 shift(1)、长 lookback（120）+ skip(5)、可选 vol-adjusted 信号 — 旨在让横截面动量真正有信息可用。\n核心逻辑\n标的池 = 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 \u0026lt; 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 保持同口径再平衡。 假设与依据\nv1 失败的根本原因诊断（来自 v1 conclusion 的 5 条经验）：\n6 只 A股 ETF 的 pairwise correlation 高、横截面 σ 小 → z-score 信息密度低、容易被噪音淹没 20 日动量在 A 股 ETF 上被短期反转（mean-revert）盖过 单边市（2020 普涨、2022 普跌）下「相对动量」≈ 几周噪音 v2 的对应假设：\n扩池假设（最关键）：跨 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。严格 \u0026lt; date 切片消除这个隐患。 vol-adjust 假设：扩池后纳入了高波动（纳指/创业板）和低波动（国债/黄金）资产，原始动量倾斜会让权重过度偏向高波动资产；除以 60 日 vol 做风险调整后，权重变化更接近「横截面 risk parity 风格」，理论上 Sharpe 更稳。 标的与周期\n市场：A股 ETF (market: cn_etf) 标的池（11 只）： A股 6 只：510300, 510500, 159915, 512100, 512880, 512170（与 v1 一致） 跨资产 5 只：159920（恒生）518880（黄金）513100（纳指）513500（标普500）511260（十年国债） 频率：日线 数据起止：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)。\n这个策略教会我什么 池的设计 \u0026gt; 因子的精细调参。v1 在 5α × 3lookback × 1 池上探索全负，v2 仅靠换池就把 NAV 从 1.001 推到 1.307。下次做 cross-section 因子，第一件事先看池内 pairwise correlation，再调因子。 「先验测过的失败」可能是「池子选错了」而非「因子错了」。但反过来也成立：v2 把池换好后动量 tilt 仍 IR \u0026lt; 0，才能下定论说横截面动量在 A 股可投资 ETF 上经济意义不显著——这个结论现在比 v1 更可靠。 更长 lookback 的边际帮助有限。v2 lb sweep 中 lb=250 是唯一 IR \u0026gt; 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 处显著改动：\n扩池 6 → 11：默认 symbols 改为 11 只跨资产 ETF（A股 6 + 港股 1 + 黄金 1 + 美股 2 + 国债 1） 严格 shift(1)：target_weights 内先用 df.loc[df.index \u0026lt; date] 切片再算因子（_slice_strict），把 shift 逻辑前移 更长 lookback + skip：默认 lookback=120, skip=5（v1 默认 lookback=20, skip=0） 可选 vol-adjusted 信号：signal=\u0026quot;vol_adj\u0026quot; 时使用 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)\nclass VolAdjustedMomentum(Factor): name = \u0026#34;mom_vol_adj\u0026#34; required_columns = (\u0026#34;close\u0026#34;,) 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[\u0026#34;close\u0026#34;] 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.30 signal.type=raw（默认）/vol_adj（可切换）；signal.vol_lookback=60 factors[0].params={lookback: 120, skip: 5} rebalance: 20 与 v1 关系：strategy.parent: cn_etf_equal_rebalance（仍是 S3 派生），不是 v1 派生 类签名 class MomentumTiltV2Strategy(EqualRebalanceStrategy): DEFAULT_SYMBOLS_V2 = ( \u0026#34;510300\u0026#34;, \u0026#34;510500\u0026#34;, \u0026#34;159915\u0026#34;, \u0026#34;512100\u0026#34;, \u0026#34;512880\u0026#34;, \u0026#34;512170\u0026#34;, # A 股 6 \u0026#34;159920\u0026#34;, \u0026#34;518880\u0026#34;, \u0026#34;513100\u0026#34;, \u0026#34;513500\u0026#34;, \u0026#34;511260\u0026#34;, # 跨资产 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=\u0026#34;raw\u0026#34;, vol_lookback=60, name=\u0026#34;cn_etf_momentum_tilt_v2\u0026#34;, **kwargs, ): ... def target_weights(self, date, prices_panel) -\u0026gt; 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 \u0026lt; date] for s, df in panel.items()} 数据 标的池：11 只 ETF（V1 6 池 + 5 只跨资产） 数据范围：2020-01-01 ~ 2024-12-31 数据预处理：依赖 data/cn_etf loader 的前复权（akshare qfq） 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) 用 \u0026lt; date 切片 边界 [0.05, 0.40] [0.03, 0.30] signal 选项 仅 raw raw 或 vol_adj（新增） 踩过的坑 shift 语义：\u0026lt; vs \u0026lt;= 在 close-to-close 模型里只差一个 bar，但 v2 显式选 \u0026lt; 以确保 rebalance 日的 close 价不进因子计算（vbt from_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 实现：\u0026lt;待提交\u0026gt; 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 \u0026lt;轮次主题\u0026gt; 小节。\n2026-05-08 Smoke test（合成数据） 配置 \u0026amp; 数据 配置：configs/S4_cn_etf_momentum_tilt_v2.yaml @ commit \u0026lt;待提交\u0026gt; 数据：合成 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) \u0026lt; 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) \u0026lt; date vol_adj 信号无除零异常、归一化正常 2026-05-08 真实数据回测（2020-01-01 ~ 2024-12-31） 配置 \u0026amp; 数据 入口：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 父类：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）。\n与 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|\u0026lt;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 对比不严格 → 看下一节做控制变量。\n控制变量：扩池本身贡献了多少 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：\n「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」。\n与 510300 BH 的对比（绝对基准） 指标 值 年化超额 +2.61% IR +0.21 TE 12.48% 超额 t-stat +0.46 (n=1207) v2 终于在绝对基准上跑赢 BH —— v1 是 -1.05%，v2 是 +2.61%。但这同样主要靠扩池。\n分年度收益 年份 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 双线受益 关键观察：\nv2 跑输 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。\nLookback 敏感性（α=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 \u0026gt; 0 的档，但幅度极小（+0.077，t=+0.17 远未显著）；CAGR 7.48% \u0026gt; S3 6.73%。这可能是唯一接近「动量 tilt 在跨资产 ETF 上有效」的证据——年度动量与「资产类别表现持续性」吻合（黄金牛、纳指牛、债券熊往往持续超过半年）。但样本仅 5 年（4~5 个独立年度动量周期），统计意义有限。\nSignal 类型对比（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，跟动量信号关系不大。\n关键图表 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 之后的判断） 答案：\n在 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 本身变好。\n解读：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 同步生成。\n","permalink":"https://lizhao903.github.io/0xBroleez/posts/2026/05/a%E8%82%A1-etf-%E7%AD%89%E6%9D%83--%E5%8A%A8%E9%87%8F%E5%80%BE%E6%96%9Cv2-%E6%89%A9%E6%B1%A0--shift1--%E9%95%BF-lookback--vol-adjust-v2/","summary":"✅ \u003cstrong\u003ev2 的改进显著有效\u003c/strong\u003e（CAGR +5.73pp / Sharpe ×3.7 / MaxDD 减半 vs v1，且首次跑赢 510300 BH +2.6%/yr），\u003cstrong\u003e但收益主要来自「扩池 6 → 11」对 S3 baseline 的提升，动量 tilt 信号本身仍未带来 alpha\u003c/strong\u003e（v2 vs S3-11 IR=-0.17，α 敏感性中 α=0 仍是最优档）。\u003cstrong\u003eShip v2 配置\u003c/strong\u003e，但备注「价值在跨资产分散，而非动量」；status 为 \u003cstrong\u003eshipped (pool value, not factor value)\u003c/strong\u003e。","title":"A股 ETF 等权 + 动量倾斜（v2 — 扩池 + shift(1) + 长 lookback + vol-adjust） · v2"},{"content":" ❌ shelved · 最终化：2026-05-08 源码：ideas/S2_cn_etf_dca_swing/v2 + summaries/S2_cn_etf_dca_swing/v2 本策略的其他版本 v1 ❌ — S2 在 2020-2024 跑出 +1.11% 年化 alpha、信息比率 0.11、最大回撤优于 BH 7.6 pct， v2 本文 ❌ — 「停 DCA 灌入」无法治愈 v1 的高抛/低吸不对称——根因是 5 年里 6 只 ETF 整体偏多头 + 资金长期占在风险池， 想法（Why） 一句话概括\nv1 baseline 上修复「DCA 净流入推权重 → 高抛远多于低吸」的结构性偏差： DCA 优先回流（A）+ 波动率自适应阈值（C）——让 DCA 节奏先把权重拉回目标，仅在偏离仍然过大时才动 swing；阈值随实现波动率呼吸，避免高波期反复刷单。\n核心逻辑\n每个交易日 T 收盘：\n月度 DCA（修正）：若次日是月初，根据当下 w_risk 选择 DCA 模式：\nOFF（停灌）：把 5000 留在 511990 不动 BOOST（加速）：取 7500 = 5000 × 1.5 等额买 6 只 ETF NORMAL：取 5000 等额买 6 只 ETF（与 v1 一致） 波动率自适应阈值：算过去 60 日 NAV 的实现波动率 vol_ann，得到当日 band_t = clip(0.6 × vol_ann, 0.10, 0.30)；前 60 日 warmup 期内 band_t = 0.20（与 v1 一致避免冷启动）\nswing 触发：和 v1 一致——按 band_t 判定每只 ETF 的相对偏离，超过则部分回归（cooldown 5d，adjust_ratio 0.50）\n下单：T 日决策、T+1 open + slippage 成交（防未来函数）\n假设与依据\nDCA 净流入是 v1 偏差的\u0026quot;根因\u0026quot;，不是「市场单边」：v1 的 11.8:1 不对称在 5 年里横跨 3 种行情（2020 V 反、2021 抱团切换、2023 震荡下行）都成立——这强烈暗示是结构问题而非行情。 波动率 → 阈值 是 well-known 的实务做法（PIMCO 的 vol-targeting，CTA 的 rolling vol bands）。在 ETF rebalance 上，Bernstein 也提到过「band 应当随实现波动调整」。 风险偏差：A 的副作用是 2024 那种 9 月剧烈反弹时，前 8 个月 w_risk 被高抛压低，9 月单边后 BOOST 模式触发——理论上能比 v1 更快地把仓位补回来（v1 是月度 5000 慢慢补；v2 是 7500 加速补，且高波时期阈值放宽 swing 触发更少，避免再次过早高抛）。 过拟合担忧：A 引入 2 个新参数（dca_band_high/low），C 引入 2 个新参数（vol 系数 0.6、上下限 0.10/0.30），共 4 个旋钮。本版全部用「圆整、非优化」值，不在 v1 数据上调参；如果 OOS（未来年度数据）失败再回头反思。 标的与周期\n市场：A股 ETF（market: cn_etf） 标的池：与 v1 完全一致（共享基线 7 只） 频率：日线 数据起止：2020-01-01 ~ 2024-12-31（共享基线） 一句话结论 「停 DCA 灌入」无法治愈 v1 的高抛/低吸不对称——根因是 5 年里 6 只 ETF 整体偏多头 + 资金长期占在风险池， 单边停 DCA 只是减缓权重上漂、没有反转它。 v3 若要继续应改用方向 B（不对称阈值）或完全跳出 DCA 框架。\n这个策略教会我什么 在 DCA 框架下做\u0026quot;对称做T\u0026quot;是矛盾命题。如果坚持 DCA + 再平衡两条腿，要么接受不对称（v1），要么改用不对称阈值（B 方向）从机制上承认这个事实。 波动率自适应阈值的设计中，系数选择直接决定 band 长期撞下限还是中段游走。vol_band_coef × vol_ann_60d ∈ [min, max] 这个公式在 vol_ann≈18% 时会把 0.6 系数压到 0.108 → 撞下限 0.10。下次写 vol-adaptive 应当先用历史 vol 的中位数测算系数选什么值能让 band 落在 mid-range（比如 [min, max] 的中点），而不是机械给 0.5/0.6 之类的\u0026quot;圆整值\u0026quot;。 多旋钮组合在样本内的\u0026quot;最佳值\u0026quot;未必能改善样本内 KPI，但增加了 OOS 不可验证性。v2 加了 4 个新参数；从这次结果看，去掉 C（保留 A）甚至可能效果更好（A 单独的效果会更纯）——但这只是回头看的猜测，必须重新在 idea 阶段单独写 v3 才能验证，不能在 v2 上事后调参。 MaxDD/换手率这种\u0026quot;被动副产品\u0026quot;才是 v2 的真实收获。换手降 58pct 是真金白银，下次设计应该把这两类副产品在 idea.md 阶段就写明（而不是只盯一个 KPI）。 关键图表 实现要点 展开完整实现记录 Implementation — A股 ETF DCA + 阈值再平衡 V2 整体方案 V2 落到代码上是一个独立、自包含的 weight-based 策略类，不继承 v1： src/strategy_lib/strategies/cn_etf_dca_swing_v2.py::DCASwingV2Strategy。\n不复用 v1 类的理由（与 idea.md 一致）：\nv1 的 simulate 把月度 DCA 写得很「死」（固定 5000 等额买 6 只）；v2 要切三态分支。继承会让代码读不顺。 v1 的 swing 阈值是固定参数；v2 要按日动态算 band_t，需要在循环里维护 NAV 历史用于 vol 计算。 避免「v1 修 bug 牵连 v2 / v2 加 feature 影响 v1」这种相互纠缠。 v1 已 shipped，按硬约束不修改 v1 任何文件。 实现路径与 v1 同样是「纯 numpy/pandas 单循环 + 可选 vbt Portfolio 包装」。\n因子清单 V2 不依赖外部 Factor 类。唯一新引入的中间量是「过去 60 日组合 NAV pct_change 的实现波动率」，但这是策略内部 rolling 计算，不抽象成独立 Factor（不暴露 IC、不被其他策略复用）。\n策略配置 配置文件：configs/S2_cn_etf_dca_swing_v2.yaml 类型：dca_swing_v2（未注册到 registry，遵守硬约束） 加载方式：由 summaries/S2_cn_etf_dca_swing/v2/validate.py 直接实例化 DCASwingV2Strategy 参数（与 v1 共享 vs v2 新增）：\n参数 v2 默认 来源 说明 risk_target_weight 0.70 v1 风险池目标合计权重 monthly_dca_amount 5000.0 v1 NORMAL 模式下的 DCA 金额 adjust_ratio 0.50 v1 swing 单次拉回比例 cooldown_days 5 v1 同标的触发冷却天数 fees/slippage/init_cash 共享基线 v1 不变 dca_band_high 0.05 v2 新 DCA OFF 触发的上沿余地（w_risk \u0026gt; 73.5%） dca_band_low 0.05 v2 新 DCA BOOST 触发的下沿余地（w_risk \u0026lt; 66.5%） dca_boost_factor 1.5 v2 新 BOOST 模式下 DCA 金额乘数 vol_lookback 60 v2 新 NAV 实现波动率回看天数 vol_band_coef 0.60 v2 新 band_t = coef × vol_ann 的系数 vol_band_min/max 0.10 / 0.30 v2 新 band_t clip 区间 warmup_band 0.20 v2 新 前 60 日 warmup 期固定阈值（与 v1 一致） 数据 与 v1 完全一致：\n标的池：511990 + 510300/510500/159915/512100/512880/512170 时间窗：2020-01-01 ~ 2024-12-31 数据预处理：akshare qfq → parquet 缓存；inner join 7 只共有交易日 起始 T0 全部 init_cash 买入 511990 货基 关键设计决策 1. DCA 三态判定（A 方向核心） 每月第一个交易日 T 的前一日 T-1 决策（pending_dca 写在 t 循环末尾，t+1 是月初）。判定基于 T 收盘的风险池总权重 w_risk：\ntarget = self.risk_target_weight # 0.70 if w_risk \u0026gt; target * (1 + dca_band_high): # \u0026gt; 0.735 pending_dca = \u0026#34;OFF\u0026#34; # 5000 留货基不动 elif w_risk \u0026lt; target * (1 - dca_band_low): # \u0026lt; 0.665 pending_dca = \u0026#34;BOOST\u0026#34; # 7500 等额买 6 只 else: pending_dca = \u0026#34;NORMAL\u0026#34; # 5000 等额买 6 只（与 v1 一致） T+1 开盘执行——保持与 v1 相同的「T 决策、T+1 成交」无未来函数节奏。\n2. 波动率自适应阈值（C 方向核心） 每个交易日 t 在循环里直接算：\nwindow_nav = nav_hist[t - vol_lookback : t + 1] # 含 t；只用历史 rets = np.diff(window_nav) / window_nav[:-1] vol_ann = np.std(rets, ddof=0) * np.sqrt(252) band_t = clip(vol_band_coef * vol_ann, vol_band_min, vol_band_max) 注意：\nnav_hist[:t+1] 是当日 close 后估值的 NAV，只用历史信息——无未来函数。 前 vol_lookback=60 个交易日 warmup 期 band_t 固定 warmup_band=0.20（与 v1 完全一致），避免冷启动噪声把 band 压到下限。 band_t 立即用于当日的 swing 触发判定（向 t+1 下单）。 3. swing 触发逻辑（保持与 v1 一致，仅替换阈值变量） upper = w_target_per_symbol * (1 + band_t) lower = w_target_per_symbol * (1 - band_t) adjust_ratio=0.5、cooldown_days=5、单标的偏离绝对额 \u0026lt; 1 RMB 不动单（防小数噪声）——全部沿用 v1。\n4. 防止 v1 bug 传染 V2 不导入 v1 类。DCASwingV2Strategy 是独立类；DCASwingV2Result 是独立 dataclass。validate.py 同时实例化 v1（仅作对比）和 v2，但运行轨道独立。\n5. 现金/持仓不足缩单 与 v1 同样的兜底：\nDCA OFF 不动钱 DCA BOOST 时若 511990 余额不足 7500，缩单到可用上限（极端连续 BOOST 会受此影响） swing 加仓现金不足按可用缩；swing 减仓持仓不足按可用缩 踩过的坑 DCA mode 决策时点：起初想把判定逻辑放到 t=月初当日（\u0026ldquo;今天是月初就决定\u0026rdquo;），但这违背 v1 的 pending_dca 模型（v1 是 t-1 决策、t 执行）。改为「t 是月初前一交易日时决策」→ index[t+1] in month_firsts 时算 mode → t+1 执行。这样保持和 v1 完全相同的时间轴。 vol 窗口含 t 还是不含：写成 nav_hist[t - vol_lookback : t + 1] 含当日 close。当日 NAV 基于 T close，是已经实现的信息——不属于未来函数（与 v1 的 swing 决策同源时点）。 warmup 期固定 0.20：第一版尝试在 warmup 期就用 t=10/20 的短窗口算 vol，结果 band_t 在前 60 日剧烈震荡。改为固定 0.20（v1 默认值）后早期 NAV 与 v1 几乎重合，便于事后归因到底是「DCA 路由」还是「vol 自适应」贡献了差异。 相关 commits 待提交（2026-05-08 v2 批次）。\n后续 follow-up 如果 v2 结果显示「band_t 长期撞底（vol_ann × 0.6 \u0026lt; 0.10）」，考虑把 vol_band_coef 从 0.6 上调到 0.9（让低波期更接近 v1 的 0.20） 如果 BOOST 几乎不触发，考虑放宽 dca_band_low 到 0.03（更敏感） 真实数据下 v2 vs v1 head-to-head 出表（在 validation.md） 验证过程 展开完整验证记录 Validation — A股 ETF DCA + 阈值再平衡 V2 每次新一轮回测/验证就追加一个 ## YYYY-MM-DD \u0026lt;轮次主题\u0026gt; 小节，不要覆盖。\n2026-05-08 V2 smoke test（合成数据） 配置 \u0026amp; 数据 配置：configs/S2_cn_etf_dca_swing_v2.yaml @ commit \u0026lt;待提交\u0026gt; 实现：src/strategy_lib/strategies/cn_etf_dca_swing_v2.py::DCASwingV2Strategy 数据：合成 OHLCV — 1 货基（年化 2% 线性）+ 6 ETF（GBM，drift ∈ [-2%, 15%]，vol ∈ [18%, 30%]） 时间窗：2020-01-02 起 504 个交易日（2 年） Smoke test 断言通过项 ✅ 月度 DCA 触发笔数 = 活跃模式数 × 6（NORMAL=21 + BOOST=2 → 23 × 6 = 138 笔 dca_buy） ✅ DCA OFF=0 / NORMAL=21 / BOOST=2（合成数据偏多头，OFF 没触发是正常的：风险权重稳定在 0.70 附近不会超 0.735） ✅ NAV 重算恒等（相对误差 \u0026lt; 1e-9） ✅ swing cooldown 拦截：所有同标的间隔 ≥ 5 自然日 ✅ 起始建仓：T0 把 100% init_cash 买入 511990 ✅ band_t 范围 ∈ [0.10, 0.10]（合成数据 NAV vol 较低，band 触底，符合「低波时阈值收紧到下限」的设计预期） 合成数据回测绩效（仅功能验证，不代表真实预期） 指标 值 总收益 11.66% 年化收益 5.68% 年化波动 7.13% Sharpe 0.797 最大回撤 -7.60% Calmar 0.747 DCA NORMAL/OFF/BOOST 21/0/2 swing buy/sell 19/139 关键观察：合成数据下 v2 NAV vol 仅 7.13%，导致 band_t 全程 0.10（撞下限）。这相当于在 v1 基础上收紧 swing 阈值，触发会更频繁——这是合成数据特性，不代表真实数据下的行为（真实 vol_ann 一般 15-25%，band_t 会落到 0.10-0.15）。\n2026-05-08 V2 真实数据回测 配置 \u0026amp; 数据 实现：DCASwingV2Strategy() 默认参数（即配置文件中的 v2 参数） 数据：本地缓存 parquet（akshare qfq 已预拉取） 货基：511990 风险池：510300 / 510500 / 159915 / 512100 / 512880 / 512170 时间窗：2020-01-01 ~ 2024-12-31（1209 个共有交易日，与 v1 完全一致） 共享成本：fees=0.00005 / slippage=0.0005 / init_cash=100,000 v2 关键参数：dca_band_high=0.05 / dca_band_low=0.05 / dca_boost_factor=1.5 / vol_lookback=60 / vol_band_coef=0.60 / vol_band_min=0.10 / vol_band_max=0.30 / warmup_band=0.20 V2 vs V1 head-to-head（核心对比表） 指标 v1 v2 Δ NAV (init=100k) 113,808 114,209 +401 总收益 +13.87% +14.27% +0.40 pct CAGR +2.75% +2.82% +0.07 pct 年化波动 18.10% 17.17% -0.93 pct Sharpe 0.152 0.164 +0.012 MaxDD -37.10% -35.12% +1.98 pct（改善） Calmar 0.074 0.080 +0.006 Alpha (年化) +1.11% +1.02% -0.09 pct 信息比率 0.110 0.100 -0.010 跟踪误差 10.09% 10.23% +0.14 pct 高抛次数 177 187 +10 低吸次数 15 16 +1 高抛/低吸比 11.80 : 1 11.69 : 1 -0.11（几乎未改善） 2024 单年 vs BH -11.70 pct -11.42 pct +0.28 pct（边际改善） 年化换手 153.92% 95.60% -58.32 pct（显著降低） 与 510300 BH 的对比（v2 视角） 指标 v2 510300 BH 最终净值 114,209.31 104,957.43 总收益 +14.27% +4.18% CAGR +2.82% +0.86% 年化波动 17.17% 21.80% Sharpe 0.164 0.039 MaxDD -35.12% -44.75% Calmar 0.080 0.019 V2 特有指标 指标 值 说明 DCA NORMAL 次数 31 风险权重在中性区（66.5% ~ 73.5%） DCA OFF 次数 25 风险权重 \u0026gt; 73.5% → 停 DCA 流入 DCA BOOST 次数 3 风险权重 \u0026lt; 66.5% → 5000 × 1.5 = 7500 加灌 DCA 总月数 59 与 v1 月份一致 DCA OFF 占比 42.4% 5 年里近一半月份 v2 主动停灌——A 方向核心机制确实在工作 band_t 均值 0.111 比 v1 固定 0.20 更敏感（vol 系数 0.6 较小） band_t 最小值 0.100 经常撞下限（低波时段） band_t 最大值 0.229 高波时段（2022 熊市/2024.9 反弹） 与 510300 BH 各年度对比 年份 v2 v1 510300 BH v2 vs BH v1 vs BH v2 vs v1 2020 +32.71% +34.32% +31.11% +1.60pct +3.21pct -1.61pct 2021 +3.71% +3.93% -5.24% +8.95pct +9.17pct -0.22pct 2022 -16.78% -17.71% -21.37% +4.59pct +3.66pct +0.93pct（更优） 2023 -7.99% -8.38% -10.71% +2.72pct +2.33pct +0.39pct（更优） 2024 +8.69% +8.41% +20.11% -11.42pct -11.70pct +0.28pct（边际） 关键观察 — V2 设计的实际效果 A 方向（DCA-priority routing）确实在运转，但对高抛/低吸比影响微弱\n5 年 59 个月里 DCA OFF 触发了 25 次（42.4% 月份），每次「不灌」相当于把 5000 RMB 留在 511990 不入风险池 然而高抛次数仍然 187 vs v1 177，实际反而 +10——为什么？ 当 DCA OFF 时，权重 \u0026gt; 73.5% 的状态本来就是「轻度过热」，swing 上沿（v1 84%、v2 因 band 收窄到 ~78%）依然容易触发 v2 的 band_t 平均仅 0.111（远小于 v1 的 0.20），swing 触发更敏感——这与「DCA OFF 减少高抛」的设计意图互相抵消 核心原因：A 和 C 在「频次」上相互抵消（A 想减少高抛 + C 让 swing 更敏感）；高抛/低吸比的根因是「DCA + 市场结构性偏多头」，单靠停 DCA 不能逆转 5 年里风险权重大部分时间在目标线上方的事实 C 方向（vol-adaptive band）真的让 band_t 在低波时收紧\nband_t 均值 0.111 vs v1 固定 0.20，几乎一半时间 band 撞下限 0.10 高波时段（2022 / 2024.9）band_t 最高到 0.229——比 v1 的 0.20 略宽，避免高波期反复刷单 但 vol_band_coef=0.6 偏低，让 band 整体偏紧，触发频率比 v1 高，vol 自适应\u0026quot;放宽\u0026quot;的作用没有充分体现 换手率显著降低（154% → 96%，-58 pct）\n这是 v2 最明显的实际收益——主要来自 DCA OFF 节省的月度流入（25 次 × 5000 × 7 笔/月 = 87.5 万 RMB，5 年累计成交额降低约 5%——但年化换手率分母是平均 NAV，这部分数据贡献到换手率比想象的大） 附加好处：手续费/滑点累积成本降低，对长期复利友好 MaxDD 改善（-37.10% → -35.12%）\n主要发生在 2022 熊市段：DCA OFF 在 2022 上半年高位时停灌、避免在熊市继续往下加仓；同时 band_t 在 2022.4 高波时拉宽 swing 阈值，减少了\u0026quot;越跌越买\u0026quot;的反复 Calmar 从 0.074 升到 0.080，回撤效率小幅改善 2024 跑输 BH 的幅度几乎没改善（-11.70 → -11.42pct）\n期望 BOOST 能在 9 月反弹时加速补仓——但 5 年里 BOOST 仅触发 3 次，且时机分散 9.24 反弹后 1-2 个月才让 w_risk 跌破 66.5%（之前持仓本来就饱和）；BOOST 反应不够快 这是 A 方向的结构限制：DCA 月度节奏太慢，赶不上单边反弹的乘数效应；要解决 2024 这种\u0026quot;大反弹\u0026quot;问题，得改成\u0026quot;主动补仓\u0026quot;（不依赖月度触发），但那已经超出 v2 范围 核心 KPI 高抛/低吸比未改善（11.80 → 11.69，仅 -0.11）\n我在 idea.md 里的预期是降到 3:1 ~ 6:1 实际结果说明：A 方向的\u0026quot;停 DCA\u0026quot;无法逆转风险池权重长期偏多头的事实——只要市场结构性向上、DCA 不停、风险标的内部不再平衡，v1 的不对称就会持续 真正能扭转这个比例的方法是 B 方向（不对称阈值），或者根本性改变资金流（比如「不做月度 DCA、改成 DCA 仅在 w_risk \u0026lt; target 时触发」）——后者已经接近 S3 等权再平衡 解读 — 这次 v2 实验告诉我们什么 假设 验证结果 A 方向能减少 DCA 引起的「上沿偏置」 ✗ 未兑现：DCA OFF 触发了 25/59 次，但 swing 高抛次数反增（187 vs 177） C 方向能在低波期更敏感、高波期更宽容 ✓ 部分兑现：band_t mean=0.111，max=0.229，确实有动态范围；但系数偏低让整体偏紧 高抛/低吸比降到 3:1 ~ 6:1 ✗ 未兑现：仍 11.69:1（v1 11.80:1） 2024 vs BH 改善 \u0026gt; 3pct ✗ 未兑现：仅改善 0.28 pct Sharpe 不降、MaxDD 不深 ✓ 兑现：Sharpe +0.012，MaxDD 浅 1.98pct 换手率降低 ✓ 超出预期：154% → 96%，-58pct 根因诊断：\n不对称问题的根因不是\u0026quot;DCA 流入\u0026quot;，而是\u0026quot;6 只 ETF 在 5 年里整体偏多头 + 资金长期占在风险池\u0026quot;——任何不在「单只 ETF 维度」做不对称调整的方案都会失败 A 和 C 互相抵消：A 减少了 DCA 灌入（应该减少高抛），但 C 让 swing 更敏感（增加高抛）——净效应几乎抵消 A 的\u0026quot;停灌\u0026quot;只是停了\u0026quot;额外注入\u0026quot;，没有帮助消化已有的过热权重；除非配合 swing 主动减仓，否则 w_risk \u0026gt; 73.5% 的状态可以持续好几个月 关键图表 — V2 vs V1 vs 510300 BH 标准化净值（三条线） — V2 vs V1 vs BH 回撤 — V2 高抛/低吸事件叠加在风险池权重曲线 + DCA OFF/BOOST 阈值 — V2 风险权重轨迹（上）+ band_t 动态阈值（下） 附原始数据：artifacts/nav_series.csv / orders.csv / weights.csv / band_t.csv / dca_modes.csv / real_backtest_summary.json\n下一步 判定 v2 status：核心 KPI（高抛/低吸比、2024 vs BH）均未达预期 → shelved 若未来要做 v3，方向应该是： B 方向（不对称阈值）：上沿严格（如 +12% 触发）、下沿宽松（如 -25%）—— 直接治标 或者单只 ETF 维度的相对表现剃头（领涨标的减仓更敏感）—— 但这开始接近 momentum tilt 的反向（S4 已 shelved） 或者完全去掉 DCA：S3 等权再平衡已经做了（Sharpe 0.26），可能比修补 S2 更值 V2 的换手降低（154%→96%）是真实收益，可单独作为 v1 的「成本优化版本」保留——但这与「修复不对称」的初衷不一致 源文件 想法 · idea.md 讨论笔记 · notes.md 结论 · conclusion.md 实现 · implementation.md 验证 · validation.md 可复跑脚本 · validate.py 本版本目录（含 artifacts） 本文由 scripts/sync_strategies.py 从 Strategy-Lib 同步生成。\n","permalink":"https://lizhao903.github.io/0xBroleez/posts/2026/05/a%E8%82%A1-etf-dca--%E9%98%88%E5%80%BC%E5%86%8D%E5%B9%B3%E8%A1%A1-v2dca-%E4%BC%98%E5%85%88%E5%9B%9E%E6%B5%81--%E6%B3%A2%E5%8A%A8%E7%8E%87%E8%87%AA%E9%80%82%E5%BA%94%E9%98%88%E5%80%BC-v2/","summary":"❌ 「停 DCA 灌入」无法治愈 v1 的高抛/低吸不对称——根因是 5 年里 6 只 ETF 整体偏多头 + 资金长期占在风险池，","title":"A股 ETF DCA + 阈值再平衡 V2（DCA 优先回流 + 波动率自适应阈值） · v2"},{"content":" ❌ shelved · 最终化：2026-05-08 源码：ideas/S5_cn_etf_trend_tilt/v1 + summaries/S5_cn_etf_trend_tilt/v1 本策略的其他版本 v1 本文 ❌ — 边际有效但不及预期：搁置（shelved）。在 2020-2024 样本期 S5 总收益 +20.26% 优于 S3 (+10.80%) 和 510300 BH (+4.18%)，Sharpe 也更高 (0.28 vs 0.21 vs 0.15)，但核心命题「下行保护」未兑现——2022 单年 S5 -21.61% 与 BH -21.68% 几乎完全重合，超额主要来自 2024 单年的板块行情。 v2 ❌ — 避险命题数据上兑现（MaxDD -47.8% → -20.5%，2022 -21.6% → -7.6%），但 Sharpe 与 v1 持平、CAGR 倒退 1.35 pct/yr，且 bond overlay 占用 37% 平均仓位让策略漂移成「股债混合」——v2 实现了「保守型变体」但没有产生新 alpha。状态：shelved（保留代码作为 v1 的 risk profile 对照，不推荐替代 v1）。 想法（Why） 一句话概括\n在 S3 等权再平衡基础上，按每只 ETF 自身的趋势强度 调整权重——趋势向下时直接清仓该 ETF，避免在熊市里被等权强制满仓。\n核心逻辑\n继承 S3 的 EqualRebalanceStrategy，每个再平衡日（默认 20 个交易日）覆盖 target_weights(date, prices_panel) 钩子：\n对池中 6 只 ETF 各自计算两个时序趋势指标： MABullishScore：sign(close\u0026gt;MA20) + sign(MA20\u0026gt;MA60) + sign(MA60\u0026gt;MA120)，取值 ∈ {-3,-1,+1,+3}（中间值由不完美排列产生）。 DonchianPosition：(close - low_120) / (high_120 - low_120)，取值 ∈ [0,1]，反映价格在过去 120 日通道里的位置。 把两个指标各自标准化到 [-1, +1] 后等权相加得到 trend_score ∈ [-2, +2]。 空仓阈值：trend_score ≤ 0 的 ETF 权重置 0（弱趋势退出）；trend_score \u0026gt; 0 的 ETF 权重 ∝ trend_score，再做归一化（使留下的 ETF 权重和 = 1 - cash_residual）。 特殊情况：所有 6 只 ETF 都 ≤ 0 时返回 {}（空 dict），剩余 100% 隐式留作现金（依赖 S3 把缺失键视作 0 权重）。 假设与依据\n动量 vs 趋势的概念区分（S4 vs S5 分立的核心理由） 维度 动量（S4） 趋势（S5） 视角 横截面：A 比 B 涨得多 时序：A 自身相对历史/均线在涨 典型公式 ret(t-N, t) 排名 close \u0026gt; MA, ADX, Donchian 位置 决策类型 选谁、配多少 进场 / 离场 / 趋势力度 失效模式 行业齐涨齐跌时分不出强弱 震荡市频繁假突破 文献 Jegadeesh \u0026amp; Titman 1993 Moskowitz, Ooi \u0026amp; Pedersen 2012 (Time-Series Momentum) S4 关心的是「在确定要持有的资产之间分配权重」（横截面排名），S5 关心的是「这个资产现在到底要不要持有」（时序方向）。两者经常被混用，但用在不同任务上有结构性差异——尤其在熊市，S4 仍然给排名最靠前的（哪怕是亏最少的）资产正权重，S5 可以全体退出。这是把它们做成两个独立基准的原因。\n为什么用 MA 多头排列 + Donchian 位置（不和 S4 重复） MA 多头排列：经典的趋势跟踪信号，可以离散化捕捉「短中长期均线方向是否一致」，对参数（具体 MA 长度）相对鲁棒。 Donchian 通道位置：连续值，反映价格在通道中的绝对位置，与突破策略一脉相承（Turtle 体系），对 MA 系是天然补充。 明确不重复：S4 必然会用 MomentumReturn 做横截面排名；这里我们避开累计收益类指标，从均线状态 + 通道位置两个完全时序的视角切入；MACDDiff 虽然也是趋势类但已被 momentum.py 占用且方向定义偏短期信号，留给以后做 momentum 复合时复用。 ADX 也考虑过，但它只衡量趋势强度不区分方向（需要再加 +DI/-DI），实现复杂度高，先搁置。 标的与周期\n市场：cn_etf 标的池：510300 / 510500 / 159915 / 512100 / 512880 / 512170（与 S3 一致） 频率：日线 1d 数据起止：2020-01-01 ~ 2024-12-31 一句话结论 边际有效但不及预期：搁置（shelved）。在 2020-2024 样本期 S5 总收益 +20.26% 优于 S3 (+10.80%) 和 510300 BH (+4.18%)，Sharpe 也更高 (0.28 vs 0.21 vs 0.15)，但核心命题「下行保护」未兑现——2022 单年 S5 -21.61% 与 BH -21.68% 几乎完全重合，超额主要来自 2024 单年的板块行情。\n这个策略教会我什么 「时序方向」信号在 A股 ETF 上对真实下跌起点识别能力较弱：MA 多头排列 + Donchian 通道这套教科书组合的滞后性，在快速下跌的 A股节奏里效用有限。 「全空仓」是合法状态——子类放宽校验比父类预先支持更解耦：通过覆盖 _validate_weights 优雅扩展 S3 钩子契约，没有污染共享父类。 vectorbt targetpercent + cash_sharing 完全支持权重和 \u0026lt; 1：缺失的资金会留作 cash group 的现金，无需绕路。这个验证扫除了 S5 路径上最大的工程风险点。 事前预期与事后结果的方向性偏差：原以为 S5 主要靠 2022 熊市避险加分，实际主要靠 2024 单边行情加分——再次说明回测结论很容易被单年事件主导，5 年样本不够。 关键图表 实现要点 展开完整实现记录 Implementation — A股 ETF 等权 + 趋势倾斜 (S5) 整体方案 继承 S3 (EqualRebalanceStrategy)，覆盖两个钩子：\ntarget_weights(date, prices_panel)：核心倾斜逻辑——按每只 ETF 的时序趋势分数分配权重，弱趋势退出。 _validate_weights(weights)：放宽父类「sum == 1」「禁止全 0」两条约束，使全空仓在熊市可表达。 实现拆分两个独立函数（便于单测）：\ncompute_trend_scores(date, panel) -\u0026gt; pd.Series：把面板 → 每只 ETF 的标量 trend_score ∈ [-2, +2] _tilt_weights(scores) -\u0026gt; dict：trend_score → 归一化权重 dict（≤0 的 symbol 不出现） 因子清单 Factor 类 文件 参数 方向 是新增还是复用 MABullishScore src/strategy_lib/factors/trend.py short=20, mid=60, long=120 +1 新增 DonchianPosition src/strategy_lib/factors/trend.py lookback=120 +1 新增 新增因子（详细说明） MABullishScore score = sign(close \u0026gt; MA_short) + sign(MA_short \u0026gt; MA_mid) + sign(MA_mid \u0026gt; MA_long) # 取值 ∈ {-3, -1, +1, +3}（中间组合产生 ±1） 为什么用：经典趋势跟踪信号。三个二元判断的离散组合，抗参数扰动。 暖机期：MA_long 还没 ready 时返回 NaN（被上游处理为「该 ETF 此期间不参与」）。 DonchianPosition pos = (close - low_N) / (high_N - low_N) ∈ [0, 1] # 1.0 = 通道顶端（强势），0.0 = 通道底部（弱势） 为什么用：与 MA 系列在数学性质上互补——一个看离散方向，一个看连续位置。Turtle 体系经典指标。 除零保护：通道宽度 = 0（常量价格）时返回 NaN。 这两个因子没有改 factors/__init__.py 按硬约束要求，factors/__init__.py 不修改。下游用法：\nfrom strategy_lib.factors.trend import MABullishScore, DonchianPosition # ✓ # from strategy_lib.factors import MABullishScore # ✗ 暂不可用，等合并 合并到主分支时外部代理应在 factors/__init__.py 增补两行 import 与 __all__ 项；本子代理任务范围内不动。\n关键设计决策 1. 全空仓的契约扩展（与 S3 协调点） S3 当前实现的 _validate_weights 强制：\nsum(weights) == 1.0（误差 \u0026lt; 1e-6），违反则自动重归一化 total \u0026lt;= 0 抛 ValueError(f\u0026quot;target_weights 全为 0，无法再平衡\u0026quot;) S5 需要在熊市返回 sum \u0026lt; 1 甚至全 0（全空仓）。采用方案 B：在子类覆盖 _validate_weights，放宽这两条约束：\n允许 sum ∈ [0, 1+ε]，不重归一化（保留现金留底语义） 允许 total == 0（全空仓 → 让 vectorbt 把所有 size 设为 0 → 全平仓） 仍然校验非负 / keys ⊂ symbols / sum \u0026gt; 1+ε 时归一化（防御性） 这是对 S3 钩子契约的扩展。S3 父类 build_target_weight_panel 调用 target_weights → _validate_weights → 写入 weights_df，本子类的覆盖刚好生效。已确认 S3 父类不在其他地方再次断言 sum == 1。\n2. 趋势分数的归一化 两个因子的原生量纲不同：\nMABullishScore ∈ [-3, +3] DonchianPosition ∈ [0, 1] 把它们各自映射到 [-1, +1] 后等权相加：\nMA：score / 3 → 截断到 [-1, +1] Donchian：2 * pos - 1 → 截断到 [-1, +1] 最终 trend_score = w_ma * ma_norm + w_dc * dc_norm，理论值域 [-2, +2]（默认 score_weights = (1.0, 1.0)）。好处：cutoff 阈值在不同标的之间可比；调超参时改 score_weights 即可改变两个信号的相对重要性。\n3. 避免 lookahead compute_trend_scores(date, panel) 内部对每个 symbol 做 df.loc[df.index \u0026lt;= date] 切片再算因子，确保 t 时刻只用 t 及之前的数据。S3 父类的 from_orders 会在下一根 bar 成交，叠加之后实现 t 信号 → t+1 开盘成交，无未来函数。已用 test_lookahead_bias 验证（截断 panel 与完整 panel 在同一截止日给出完全相同分数）。\n策略配置 配置文件：configs/cn_etf_trend_tilt.yaml 类型：trend_tilt（自定义；尚未注册到 strategies/registry.py，注册由后续 PR 处理） 父策略：继承 cn_etf_equal_rebalance 关键参数：rebalance_period=20、ma_short/mid/long=20/60/120、donchian_lookback=120、cutoff=0.0 数据 标的池：V1 共享基线 6 只 ETF（与 S3 完全一致） 数据范围：2020-01-01 ~ 2024-12-31；额外回拉 6 个月（2019-07-01 起）做暖机 数据预处理：依赖 data.get_loader('cn_etf') 的 qfq 复权 踩过的坑 S3 _validate_weights 的严格断言：第一次实现时直接返回 {}，但 S3 父类在 _validate_weights 里把 total \u0026lt;= 0 当成致命错误抛错。最终通过覆盖 _validate_weights 解决，没有改 S3 父类。 smoke test flake：最初用 GBM 合成数据，drift \u0026gt; 0 也偶发把某些 symbol 在最近一日的 close 推到 MA 下方导致权重变 0。改用纯指数路径（无噪声）做断言，排除随机性。 120 日暖机：MA120 / Donchian120 在数据起点前 120 日均为 NaN，必须让 data loader 多拉至少 120 个交易日（约 6 个月）历史；不然回测开头几个月会被全空仓。 与并行实现的对接点 等 S3 主线合并：本类的 import from strategy_lib.strategies.cn_etf_equal_rebalance import EqualRebalanceStrategy 已经能 resolve（S3 文件已存在于本仓库 src/strategy_lib/strategies/cn_etf_equal_rebalance.py）。 等 factors/__init__.py 合并增补：暂时使用 from strategy_lib.factors.trend import ... 直接 import。 strategies/__init__.py 与 strategies/registry.py 的注册由集成 PR 统一处理，本子代理范围内不动。 相关 commits 实现：\u0026lt;待commit\u0026gt; 2026-05-08 vectorbt sum\u0026lt;1 兼容性验证（事后） 背景：S5 允许 target_weights 返回 sum\u0026lt;1 的权重 dict（含全空仓），需要先确认 vectorbt 在 from_orders(size_type=\u0026quot;targetpercent\u0026quot;, group_by=True, cash_sharing=True) 模式下能正确把缺失资金留作 cash，而不是错误地分配给某个资产。\n验证脚本：/tmp/vbt_subunit_test.py（临时文件，已用毕）\n关键测试与结果：\n测试 输入 期望 实测 sum=0.6（[0.3, 0.3, 0]） 三资产 A=30000, B=30000, C=0, cash=40000 完全一致 全清仓（[0,0,0]） 先满仓再清仓 资产全 0，cash=全 equity 资产=0, cash=111494（含期间收益） NaN 表示「不下单」 A=0.3, B=0.3, C=NaN C 持仓不变 行为正确 结论：vectorbt 完整支持权重和 \u0026lt; 1 的 targetpercent。S3 父类 run() 不需任何修改，本子类只覆盖 _validate_weights 即可。这扫除了 S5 工程路径上最大风险点。\n2026-05-08 真实数据回测发现（追加） 价值密度命题失败：原以为 S5 主要靠 2022 熊市避险加分，实际 2022 单年 S5 (-21.61%) 与 BH (-21.68%) 几乎完全重合。S5 vs S3 的超额主要来自 2024 单年的板块行情（+19.27 pct）。 现金占比是双峰分布而非连续：cash_ratio 几乎只有 0 / 1 两种状态，因为 _tilt_weights 用「正分数 / 正分数和」做归一化——只要有 ≥1 个标的的 trend_score \u0026gt; cutoff，权重和就 = 1。要做出连续的现金缓冲，需要把权重计算改成「正分数 / N」（不归一化）+ 上限 clip，或者引入大盘 trend_score 作为整体仓位调节器。 cutoff 灵敏度方向出乎意料：cutoff 从 0.0 提到 0.3/0.5 反而恶化（CAGR 转负）。说明趋势分数在 [0, 0.3] 区间携带了主要的有用信号——A股 ETF 的「弱趋势但还在涨」比「强趋势」更值得介入。 空仓时段命中率低：220 个全空仓天数与 BH 「当日下跌」的相关性仅 0.033，几乎随机。MA + Donchian 这套信号对 A股快速下跌的反应速度不够。 验证过程 展开完整验证记录 Validation — A股 ETF 等权 + 趋势倾斜 (S5) 每次新一轮回测/验证就追加一个 ## YYYY-MM-DD \u0026lt;轮次主题\u0026gt; 小节，不要覆盖。\n2026-05-08 Smoke test：趋势倾斜逻辑独立验证 背景 S3 子代理与本（S5）子代理并行运行；为了不阻塞，本轮不跑真实回测，只用合成数据 + stub 父类（如果 S3 还未合并）独立验证倾斜逻辑的数学一致性。\n验证脚本 summaries/cn_etf_trend_tilt/validate.py 跑法：PYTHONPATH=src python3 summaries/cn_etf_trend_tilt/validate.py 内置：自动检测 S3 父类是否存在；不存在时注入 stub。同时注入 loguru 桩，避免开发环境缺依赖时启动失败。 Smoke test 全部通过（8 / 8） 测试 验证目标 结果 test_factors_independent MABullishScore 暖机期 NaN，取值 ∈ [-3,3]；DonchianPosition 暖机期 NaN，取值 ∈ [0,1] OK test_warmup_returns_empty 数据 \u0026lt; 120 日时所有 ETF 因子 NaN → target_weights = {}（暖机期空仓） OK test_all_uptrend_near_equal_weight 6 只 ETF 同等强上行（无噪声）→ 权重全部 = 1/6，sum = 1.000000 OK test_mixed_trends_filters_negatives 3 上 3 下 → 下行的 3 只权重为 0，上行的 3 只各 1/3，sum = 1 OK test_all_downtrend_returns_empty 6 只全下行 → 返回 {}（全空仓，S3 契约扩展生效） OK test_validate_weights_allows_subunit_sum 覆盖父类 _validate_weights 后允许 sum \u0026lt; 1 / 全 0 / 仍校验非负 OK test_compute_trend_scores_range trend_score ∈ [-2, +2]；纯指数上行得分 ≈ +1.98，下行 ≈ -1.98 OK test_lookahead_bias 截断 panel 与完整 panel 在同一 cutoff 日给出相同分数 → 无未来函数 OK 因子层（IC 分析） 不适用。S5 是单标的时序方向信号，不做横截面排名 → 不适用 IC / Rank IC。\nS4 (动量倾斜) 的横截面 IC 与本策略不可对比；本策略的因子有效性由「全空仓时段的下行保护」体现，需要在真实回测里看（见下方 blocker）。\n分位数分组 不适用（同上，单标的时序）。\n回测绩效 未跑。原因：\nBlocker 详情 解除条件 真实数据未拉 data/raw/cn_etf/*.parquet 未提供 跑 data.get_loader('cn_etf').load() 拉取 6 只 ETF + 510300 基准 vectorbt 未在本环境验证可用 当前开发机 import vbt 未实测 CI / 真实跑回测环境 loguru 未装 顶层包 import 触发 ImportError pip install loguru 或在 CI 镜像里包含 S3 父类的 from_orders 端到端是否真接受 sum \u0026lt; 1 的权重 需要看 vectorbt 是否在 targetpercent 模式下把缺失资金留作现金 真实回测一次小窗口验证 关键图表 （无 — 暂未跑真实回测）\n解读 \u0026amp; 下一步与 S3/S4 对比设计 S5 vs S3 对比目标 核心命题：「趋势退出」是否真的提供下行保护？ 预期方向：在 2022 熊市年（S3 预计回撤 30%+），S5 应该明显更小（目标 15-25%） 代价指标：换手率上升、牛市顶部因迟疑而少赚 关键观测窗口：2022-04（疫情底）、2022-10（二次探底）、2024-02（节前反弹）的趋势拐点 S5 vs S4 对比目标 核心命题：「时序方向」与「横截面排名」哪个更稳健？ 关键差异：S4 在熊市仍然满仓（只是把权重分给「跌得最少的」），S5 可以全空仓 预期方向： 牛市（2020/2021 上半年）：S4 ≈ S5（都跟得上） 单边熊市（2022）：S5 \u0026gt; S4（S5 空仓避险） 震荡市（2023-2024）：S4 \u0026gt; S5（S5 频繁假突破被成本侵蚀） 输出：S4 与 S5 的累计收益分年度对比 + 最大回撤期叠加图 关键统计指标（除 V1 通用清单外，S5 特有） 全空仓天数占比 (cash_days_ratio)：在 5 年 1217 个交易日中，trend_score 全负的交易日占比 空仓时段与基准跌幅相关性：空仓时 510300 是否真的在跌 下一步 数据接入：等 data/cn_etf loader 就绪，拉 2019-07-01 ~ 2024-12-31（含暖机） 真实回测：跑 V1 共享基线指标 + S5 特有指标（cash_days_ratio） 与 S3、S4 三策略并排对比图（净值曲线、年度收益、回撤） 敏感性：(ma_short, ma_mid, ma_long) ∈ {(10,30,90), (20,60,120), (30,90,180)}；cutoff ∈ {0, 0.3, 0.5}；donchian_lookback ∈ {60, 120, 240} 2026-05-08 真实数据回测 配置 / 数据 / 暖机说明 数据：data/raw/cn_etf/{510300,510500,159915,512100,512880,512170}_1d.parquet，akshare qfq 前复权日线 回测窗口：2020-01-02 ~ 2024-12-31（共 1209 个交易日），暖机自 2019-07-01 起多取约 6 个月覆盖 MA120/Donchian120 资金：100,000 RMB，佣金万 0.5（fees=5e-5），滑点万 5（slippage=5e-4）—— V1 共享基线 S5 默认参数：rebalance_period=20, ma_short=20, ma_mid=60, ma_long=120, donchian_lookback=120, cutoff=0.0, score_weights=(1.0, 1.0) 执行入口：PYTHONPATH=src python summaries/cn_etf_trend_tilt/validate.py real vectorbt sum\u0026lt;1 兼容性验证（关键风险点已解除） 独立写了 5 行脚本（/tmp/vbt_subunit_test.py，已删）验证 vbt.Portfolio.from_orders(size_type=\u0026quot;targetpercent\u0026quot;, group_by=True, cash_sharing=True) 是否在权重和 \u0026lt; 1 时正确把缺失资金留作现金。结论：完全支持。\n测试 输入权重 期望 实测 结论 sum\u0026lt;1 [A=0.3, B=0.3, C=0.0] A=30k, B=30k, C=0, cash=40k A=30000, B=30000, C=0, cash=40000 OK 全清仓 先满仓后 [0,0,0] 全平仓，cash=全部 equity 资产全 0, cash=111494（已含期间收益） OK NaN（不下单） [A=0.3, B=0.3, C=NaN] C 持仓不变（保持 0） C=0，行为正确 OK 结论：S3 父类 run() 不需要任何修改即可正确支持 S5 的 sum\u0026lt;1 / 全空仓权重。S3 父类 _validate_weights 的「sum 必须 = 1 / total \u0026gt; 0」严格断言已被本子类覆盖（cn_etf_trend_tilt.py:158-175）规避，下游进 vectorbt 时直接被正确解释。\n主结果（默认参数） 样本期 2020-01-02 ~ 2024-12-31，初始净值 100,000：\n策略 总收益 CAGR Sharpe Vol(ann) MaxDD Calmar S5 trend_tilt +20.26% +3.92% 0.28 22.01% -47.80% 0.08 S3 equal +10.80% +2.16% 0.21 23.78% -45.18% 0.05 510300 BH +4.18% +0.86% 0.15 21.77% -44.75% 0.02 S4 momentum +16.68% +3.27% 0.25 24.19% -48.90% 0.07 S5 vs 510300 BH：alpha_ann = +2.60%，跟踪误差 14.69%，IR = 0.177 S5 vs S3 equal：alpha_ann = +1.31%，跟踪误差 12.47%，IR = 0.105\nS5 特有指标 cash_days_ratio（≥99% 现金天数 / 总交易日）= 0.182（220 / 1209） 现金占比分布：实际是双峰——要么 0%（满仓 6 标的或 N 个正分数标的等权），要么 100%（全空仓）。介于 0 和 1 之间的天数 ≈ 0。这是因为 _tilt_weights 用「正分数 / 正分数和」归一化，只要有 ≥1 个正分数就满仓那部分。 空仓与 510300 当日下跌相关性：0.033（很低）—— 全空仓的天数里 510300 下跌的比例并不显著高于平均。 全空仓时 510300 平均日收益：-0.0035%（整体均值 +0.0128%）—— 空仓时段平均确实小幅下行，但优势很小。 触发再平衡次数：67 次（理论上限 1209 / 20 ≈ 60，多出来是因为权重切换会触发，drift 模式未启用） 分年度收益（重点：2022 年的下行保护到底有多少？） 年份 S5 S3 510300 BH S4 2020 +41.12% +40.23% +31.11% +46.41% 2021 +4.66% +5.62% -4.32% +6.00% 2022 -21.61% -23.47% -21.68% -25.28% 2023 -18.91% -10.17% -10.43% -9.85% 2024 +28.09% +8.82% +18.39% +11.61% 2022 单年 S5 = -21.61%，S3 = -23.47%，BH = -21.68%：S5 比 S3 少跌 1.86 pct，但几乎与 510300 BH 持平——「趋势退出」并未提供期望中的明显下行保护。 S5 在 2022 内的最大回撤是 -22.35%（峰值始于年初），与 BH/S3 量级相同。 2023 年 S5 显著跑输（-18.91% vs S3 -10.17%）——震荡市中频繁假突破的代价显现，趋势退出反而吃到拐点反复的进出成本。 2024 年 S5 大幅领先（+28.09% vs S3 +8.82%）——「9-24 行情」是结构性单边趋势，趋势倾斜捕获到强势板块（512880 证券、159915 创业板）。 整体上 S5 的优势主要来自 2024，而非 2022——这与事前假设方向相反。 cutoff 敏感性 cutoff 总收益 CAGR Sharpe MaxDD Calmar cash_days_ratio 0.0 +20.26% +3.92% 0.28 -47.80% 0.08 0.182 0.3 -6.15% -1.32% 0.03 -45.44% -0.03 0.281 0.5 -1.42% -0.30% 0.08 -42.68% -0.01 0.331 cutoff = 0.0 是当前最佳档位。提高 cutoff 让更多标的被过滤，cash_days_ratio 上升，但年化 / Sharpe 显著下滑——A股 ETF 的「弱趋势但还在涨」状态相当常见，过分严格地要求强趋势会错过中等行情。 MaxDD 随 cutoff 略改善（-47.80% → -42.68%），但收益跌掉的远多于回撤改善。 关键图表 artifacts/equity_curve.png —— S5 / S3 / 510300 BH / S4 净值曲线 artifacts/drawdown.png —— 四条策略的回撤序列 artifacts/cash_ratio.png —— S5 每日现金占比时间序列（清晰可见双峰特征） artifacts/regime_overlay.png —— S5 全空仓段叠加在 510300 BH 净值上 解读：趋势退出在 A股 ETF 上是否值得 初步结论：边际有效但不显著，不及预期。\nS5 比 S3 / BH 总收益高 ~10/16 pct，但主要贡献来自 2024 年的板块大行情（一次性事件），2022 年的「下行保护」假设未兑现——S5 在 2022 与 BH 几乎同涨同跌（差 0.07 pct），与 S3 也只差 ~2 pct。 空仓时机不准：全空仓与 510300 当日下跌的相关性仅 0.033，且空仓时段 510300 平均日收益仅微负（-0.0035%）。趋势信号用 MA + Donchian 在 A股 ETF 上对真实下行起点的识别能力较弱——A股下跌往往很急，等 MA 排列翻空时已经跌一半了。 震荡市代价显著（2023 跑输 8.7 pct）：在没有明显趋势的年份，趋势退出导致频繁假突破成本累积。 cutoff 调高反而变差：说明趋势信号的有效区域在「弱趋势就介入」一端，而非「严格强趋势」一端。 Sharpe（0.28）相对 S3（0.21）和 BH（0.15）有提升但绝对值仍很低。从「策略 vs 简单基准」角度，S5 提供了一定的样本期增益（CAGR +3.06 pct vs BH，IR=0.18），但样本只有 5 年且 2024 单年贡献过大，无法仅凭本回测得出「趋势退出值得长期使用」的结论。 下一步 关键诊断：把 2024 年单独剔除，重做指标——若剔除后 S5 vs S3 不显著，结论会从「边际有效」改成「不显著」 触发时延优化：尝试更短的 MA（10/30/60）或更短的 Donchian（60），看是否能更早识别 2022 起跌 加入波动过滤：在 trend_score 之外叠加 ATR 或已实现波动作为乘数（高波时折扣权重） 用 511990 货基替代纯现金：S5 全空仓时段（220 天）若投入货基年化 ~2%，可加约 +0.24 pct 收益，量级很小但系统性 smooth 化权重：现在的 0/1 双峰太硬。考虑 cutoff 之上的「线性 ramp」——softplus 之类 样本外：2025 年 H1 数据补齐后做 OOS 验证 源文件 想法 · idea.md 讨论笔记 · notes.md 结论 · conclusion.md 实现 · implementation.md 验证 · validation.md 可复跑脚本 · validate.py 本版本目录（含 artifacts） 本文由 scripts/sync_strategies.py 从 Strategy-Lib 同步生成。\n","permalink":"https://lizhao903.github.io/0xBroleez/posts/2026/05/a%E8%82%A1-etf-%E7%AD%89%E6%9D%83--%E8%B6%8B%E5%8A%BF%E5%80%BE%E6%96%9C-v1/","summary":"❌ \u003cstrong\u003e边际有效但不及预期：搁置（shelved）\u003c/strong\u003e。在 2020-2024 样本期 S5 总收益 +20.26% 优于 S3 (+10.80%) 和 510300 BH (+4.18%)，Sharpe 也更高 (0.28 vs 0.21 vs 0.15)，但\u003cstrong\u003e核心命题「下行保护」未兑现\u003c/strong\u003e——2022 单年 S5 -21.61% 与 BH -21.68% 几乎完全重合，超额主要来自 2024 单年的板块行情。","title":"A股 ETF 等权 + 趋势倾斜 · v1"},{"content":" ✅ shipped · 最终化：2026-05-08 源码：ideas/S3_cn_etf_equal_rebalance/v1 + summaries/S3_cn_etf_equal_rebalance/v1 想法（Why） 一句话概括\n6 只 A股 ETF 等权满仓，定期再平衡回到目标权重，作为 S4/S5 因子倾斜策略的对照基线。\n核心逻辑\n初始 100k RMB 一次性按 1/6 等权买入 6 只 ETF（510300/510500/159915/512100/512880/512170），全程满仓、不持有现金、不投货币基金。每隔 N 个交易日（默认 N=20，约月度）将组合再平衡回等权（1/6）：超配的卖出、低配的买进。期间不做任何择时或因子主动调仓，唯一的主动行为就是「拉回等权」。次日开盘成交，使用基线交易成本（fees=0.00005, slippage=0.0005）。\n假设与依据\n资产间相关性低于 1：等权 + 再平衡对低相关资产组合本身就有「再平衡溢价」（Rebalancing Premium / Shannon\u0026rsquo;s Demon）——本质是高抛低吸的弱版本。 A股板块/宽基轮动显著：6 只 ETF 覆盖大盘（300）、中小盘（500/1000/创业板）、行业（券商、医疗），轮动差异为再平衡提供\u0026quot;卖盈买亏\u0026quot;的素材。 作为 V1 基准的角色：S3 的设计目的不是做最强策略，而是给 S4（动量倾斜）和 S5（趋势倾斜）一个纯被动、无现金缓冲的等权对照——把\u0026quot;主动选择权重\u0026quot;这一项隔离出来评估。 与 S1/S2 的差异点：S1/S2 都有现金/货币基金作为缓冲（持续买入），S3 一次性满仓后只做权重再平衡，因此 S3 在牛市跑赢 S1/S2、在熊市跑输的方向是先验明确的。 标的与周期\n市场：cn_etf 标的池：510300 沪深300 / 510500 中证500 / 159915 创业板 / 512100 中证1000 / 512880 证券 / 512170 医疗 频率（日线/小时/分钟）：日线 1d 数据起止：2020-01-01 ~ 2024-12-31（V1 基准窗口） 一句话结论 6 只 A股 ETF 等权 + 月度再平衡 (period=20)，在 2020-01-01 ~ 2024-12-31 跑出 CAGR 2.37% / Sharpe 0.26 / MaxDD -45.18% / 累计收益 +11.89%，对 510300 BH 有 +7.77% 累计 alpha、IR 0.20，alpha 主要来自 2020-2021 板块轮动期； 作为 S4 (动量倾斜) / S5 (趋势倾斜) 的对照基线，已实现并验证 target_weights 钩子可被子类无侵入继承。\n关键数据 值 样本期 2020-01-01 ~ 2024-12-31（1209 个交易日） 样本外 N/A（基准角色，全窗口） 终值（10 万本金） 111,895 RMB 累计收益 +11.89% 年化收益 (CAGR) 2.37% 年化波动 23.78% Sharpe 0.261 最大回撤 -45.18% Calmar 0.053 年化换手 26.11% 与 510300 BH alpha (累计) +7.77% 信息比率 (vs BH) 0.20 跟踪误差 (vs BH) 9.58% 再平衡次数 61（月度） 在什么情况下有效，什么情况下失效 ✅ 板块轮动 / 中小盘领涨期（如 2020-2021）：分散到 6 个 ETF + 月度再平衡 ≈ 卖高买低，板块间相关性下降时再平衡溢价显现。2021 抱团瓦解年 alpha 高达 +9.5%。 ✅ 风险资产间相关性下降时：等权策略本质赌\u0026quot;分散 ≥ 集中\u0026quot;，相关性 0.6 以下尤甚。 ❌ 单边熊市（如 2022）：无现金缓冲，最大回撤几乎与 BH 同步（-45.2% vs -44.8%）。 S1/S2 (DCA + 511990 货币基金) 在这个窗口对 S3 形成结构性优势。 ❌ 极端集中行情（如 2024 大盘单边领涨）：等权天然把 5/6 仓位放在中小盘， 在 510300 一枝独秀时拉胯（-10.2% 相对 BH）。 ❌ 高摩擦成本场景：年化换手 26%（period=20）量级一般，但若散户成本翻 4 倍， period=5 档（年化换手 41%）会率先变差。 这个策略教会我什么 权重驱动 vs 信号驱动：Portfolio.from_orders 用 size_type=\u0026quot;targetpercent\u0026quot; + cash_sharing=True + call_seq=\u0026quot;auto\u0026quot; 是处理多 asset 目标权重的标准用法； NaN 表示\u0026quot;该 bar 不下单\u0026quot;、0 表示\u0026quot;清仓\u0026quot;，两者不可混淆。 target_weights(date, panel) 钩子设计：S4/S5 可以只覆盖一个方法就接入完整框架， 父类负责再平衡日历、权重校验（非负+和=1）、自动归一化、下单。 再平衡频率的边际效用快速衰减：5 / 10 / 20 / 60 四档 Sharpe 仅 0.249-0.263， CAGR 差 \u0026lt; 0.3%。月度（period=20）是性价比最高的默认值；不要为周度再平衡的\u0026quot;高频感\u0026quot; 付额外换手成本。 「无现金缓冲 vs 有现金缓冲」的本质差异：S3 等权满仓在 2022 熊市最大回撤 与 510300 BH 相同；这是后续 S1/S2 vs S3 / S4 / S5 对比的核心维度。 基准选择要诚实：同窗口、同费率、同初始资金的 BH 才是公平基准；切勿与不计成本的 指数本身比较——会高估策略 0.5%-1% / 年。 关键图表 实现要点 展开完整实现记录 Implementation — Strategy 3：A股 ETF 等权 + 定时再平衡 整体方案 权重驱动策略，与现有 BaseStrategy（信号驱动）并行存在。代码： src/strategy_lib/strategies/cn_etf_equal_rebalance.py → EqualRebalanceStrategy\n核心流程：\nbuild_target_weight_panel(panel)：在共同交易日历上，每隔 rebalance_period 个交易日生成一行目标权重；非触发日为 NaN。 target_weights(date, prices_panel) 钩子：基类返回 {s: 1/n}；S4/S5 子类覆盖此方法实现因子倾斜。 _validate_weights：约束权重非负、和=1（可自动重归一化）、key 覆盖 self.symbols 全集。 run(panel, init_cash, fees, slippage)：调用 vbt.Portfolio.from_orders(size_type=\u0026quot;targetpercent\u0026quot;, group_by=True, cash_sharing=True)。NaN 行自动表示「不下单」。 因子清单 Factor 类 文件 参数 方向 是新增还是复用 N/A — — — 本策略不使用因子（恒等权） S4 / S5 会在子类中引入因子，但 S3 本身保持纯被动。\n新增因子（如有） 无。\n策略配置 配置文件：configs/cn_etf_equal_rebalance.yaml 类型：weight_based（自定义；非现有 single_threshold / cs_rank） 关键参数： rebalance_period: 20（交易日，约月度。候选 5/10/20/60） drift_threshold: null（纯日历再平衡。可选 0.03 / 0.05 / 0.10） 标的池：6 只 V1 基线 ETF（510300/510500/159915/512100/512880/512170） 回测参数：100k / fees=0.00005 / slippage=0.0005（V1 共享基线） 数据 标的池来源：手工列（V1 基线，与 docs/benchmark_suite_v1.md 同步） 数据范围：2020-01-01 ~ 2024-12-31（日线，前复权 qfq） 数据预处理：build_target_weight_panel 内部对 6 只 ETF 取交易日历交集，避免某只 ETF 上市晚导致的对齐问题 target_weights 钩子接口契约（S4 / S5 必读） 这一节是 S4 (cn_etf_momentum_tilt) 和 S5 (cn_etf_trend_tilt) 的扩展契约。 S4/S5 应只覆盖 target_weights 一个方法，不应改父类的下单/再平衡日历/权重校验逻辑。\n签名 def target_weights( self, date: pd.Timestamp, prices_panel: dict[str, pd.DataFrame], ) -\u0026gt; dict[str, float]: ... 调用时机 父类 build_target_weight_panel(panel) 在每个 rebalance 候选日调用一次。 候选日序列由 _rebalance_calendar(common_idx) 生成：common_idx[0], common_idx[period], common_idx[2*period], ...（即 T0 + 每 N 个交易日）。 设了 drift_threshold 时，候选日权重会先和\u0026quot;上一次实际权重\u0026quot;比较，未超阈值则不写入 panel（但 target_weights 仍被调用以判断是否触发）。 参数 参数 类型 说明 date pd.Timestamp 当前再平衡触发日。子类只能使用 date 当日及之前的数据（避免 lookahead）。父类不主动切片，子类自行 prices_panel[s].loc[:date]。 prices_panel dict[str, pd.DataFrame] OHLCV panel。每个 DataFrame 至少包含 close 列，索引为 DatetimeIndex。S4/S5 通常用 close.loc[:date] 计算动量/趋势。 返回值 dict[str, float]，键为 symbol、值为目标权重。约束：\n# 约束 违反时父类行为 1 keys 是 self.symbols 的子集；缺失视为 0 自动补齐为 0 2 所有 weight \u0026gt;= 0（不允许做空） 抛 ValueError 3 权重和 ≈ 1.0（误差 \u0026lt; 1e-6） 自动重归一化（容忍未归一返回） 4 权重和 \u0026gt; 0（不可全 0） 抛 ValueError 子类最小示例（动量倾斜的简化版） class MomentumTiltStrategy(EqualRebalanceStrategy): def __init__(self, *, lookback: int = 60, top_k: int = 3, **kw): super().__init__(**kw) self.lookback = lookback self.top_k = top_k def target_weights(self, date, prices_panel): # 计算每只 ETF 截至 `date` 的 lookback 日动量 rets: dict[str, float] = {} for s in self.symbols: close = prices_panel[s][\u0026#34;close\u0026#34;].loc[:date] if len(close) \u0026lt; self.lookback + 1: rets[s] = 0.0 continue rets[s] = close.iloc[-1] / close.iloc[-self.lookback - 1] - 1.0 # Top-K 等权（其余 0） ranked = sorted(self.symbols, key=lambda s: rets[s], reverse=True) winners = ranked[: self.top_k] return {s: (1.0 / self.top_k if s in winners else 0.0) for s in self.symbols} 返回的权重会被父类校验（自动重归一化、违反约束报错）后写入目标权重 panel。\n关键不变量（S4 / S5 不要破坏） 永远满仓：父类校验权重和 = 1，S3/S4/S5 都不持有现金缓冲（这是与 S1/S2 的本质差异）。 次日成交防未来函数：vbt.from_orders 在下一根 bar 成交，配合 target_weights 只读 date 及之前的数据 → 无 lookahead。子类绝对不要在 target_weights 内访问 prices_panel[s].loc[date+1日:]。 下单时机由父类管：子类不要自己调 vectorbt API。 踩过的坑 Python 3.13 + importlib spec 加载 dataclass：在 smoke test 中通过 importlib.util.spec_from_file_location 直接加载模块时，必须把 module 注册进 sys.modules 之后再 exec_module，否则 @dataclass 装饰器解析 type 时会拿 cls.__module__ 在 sys.modules 里找不到模块、抛 AttributeError。 vectorbt from_orders 的 size 语义：用 size_type=\u0026quot;targetpercent\u0026quot; 时，NaN 表示\u0026quot;该 bar 不下单\u0026quot;，0 表示\u0026quot;清仓该资产\u0026quot;，两者不可混淆。本实现里非触发日全部为 NaN。 6 只 ETF 上市时间不同：512100（中证1000 ETF）、512170（医疗 ETF）等部分 ETF 在 2014-2016 才上市，但 V1 窗口从 2020-01-01 起，已经全部存在；build_target_weight_panel 用交易日历交集兜底。 未来函数边界：target_weights(date, ...) 中 date 是用于产生权重的\u0026quot;参考日\u0026quot;，vectorbt 的 from_orders 默认在当前 bar 用 close 价成交。要做到次日成交，可在传入 size 时用 weights_df.shift(1)——本实现暂未 shift（等 validation 阶段确认 vectorbt 的语义后再决定，注释保留）。 相关 commits 实现：\u0026lt;待提交\u0026gt; 调参：N/A（V1 默认参数即可） 验证过程 展开完整验证记录 Validation — Strategy 3：A股 ETF 等权 + 定时再平衡 每次新一轮回测/验证就追加一个 ## YYYY-MM-DD \u0026lt;轮次主题\u0026gt; 小节，不要覆盖。\n2026-05-08 初版 smoke test（合成数据） 配置 \u0026amp; 数据 配置：configs/cn_etf_equal_rebalance.yaml @ commit \u0026lt;未提交\u0026gt; 数据范围：合成 panel，250 个交易日，6 个 symbol（覆盖 V1 基线代码） 训练/样本外切分：N/A（仅 smoke test） 因子层（IC 分析） 不适用——本策略不使用因子，目标权重恒为 1/6。\n分位数分组 不适用。\n回测绩效 未跑真实回测。本轮仅验证：\n测试项 结果 等权目标权重 panel 形状正确（n_days × 6） PASS 触发日权重 = 1/6、和 = 1.0、非负 PASS 非触发日权重为 NaN（from_orders 跳过） PASS 触发次数 = ceil(n_days / period) = 13 PASS drift_threshold 模式触发次数 ≤ 纯日历 PASS 子类覆盖 target_weights 钩子（S4/S5 契约） PASS 子类返回未归一权重时基类自动重归一化 PASS 子类返回负权重时抛 ValueError PASS 5/5 通过。详见 validate.py。\n关键图表 待真实回测后追加。导出到 artifacts/。\nartifacts/equity_curve.png —— 待生成 artifacts/rebalance_period_sensitivity.png —— 敏感性扫描，待生成 artifacts/yearly_returns.csv —— 分年度收益，待生成 解读 \u0026amp; 问题 target_weights 钩子契约可正常被子类覆盖——这是 S4/S5 能复用本策略框架的关键证据。 未来函数边界：当前实现没有对 weights_df 做 shift(1)，依赖 vectorbt from_orders 的\u0026quot;当 bar close 成交\u0026quot;语义；validation 阶段需要确认是否需要显式 shift（V1 共享基线要求\u0026quot;次日开盘成交\u0026quot;）。 drift_threshold 在合成数据上的效果有限：6 只 ETF 漂移幅度在 0.05 阈值下大多数候选日不触发。真实数据上效果待测。 下一步 跑真实数据回测：2020-01-01 ~ 2024-12-31，6 只 ETF 敏感性扫描：rebalance_period ∈ {5, 10, 20, 60}，drift_threshold ∈ {None, 0.03, 0.05, 0.10} 与 510300 BH 基准对比：超额收益、信息比率、跟踪误差 分年度收益分解（2020/2021/2022/2023/2024） 验证未来函数：手动 shift 一日 vs 不 shift，对比净值差 2026-05-08 真实数据回测 配置 \u0026amp; 数据 配置：configs/cn_etf_equal_rebalance.yaml @ commit \u0026lt;未提交\u0026gt;；运行入口 summaries/cn_etf_equal_rebalance/validate.py real 数据范围：2020-01-01 ~ 2024-12-31（V1 共享窗口；含疫情、抱团、22 熊市、23-24 震荡） 风险标的池：510300 / 510500 / 159915 / 512100 / 512880 / 512170 共 6 只 ETF（不持有 511990，S3 满仓） 基准：510300 单 ETF buy-and-hold，与策略同窗口、同费率（fees=5e-5、slippage=5e-4）、同初始资金 100k 引擎：vbt.Portfolio.from_orders(size_type=\u0026quot;targetpercent\u0026quot;, group_by=True, cash_sharing=True, call_seq=\u0026quot;auto\u0026quot;, freq=\u0026quot;1D\u0026quot;) 6 只 ETF 共同交易日历：1209 个交易日 主结果（rebalance_period = 20，约月度） 指标 S3 EqualRebalance 510300 BH 差异 终值（10 万本金） 111,895 104,125 +7,770 累计收益 11.89% 4.13% +7.76% CAGR (年化) 2.37% 0.85% +1.52% 年化波动 23.78% 21.81% +1.97% Sharpe 0.26 0.18 +0.08 最大回撤 -45.18% -44.75% -0.43%(更深) Calmar 0.053 0.019 +0.034 年化换手 26.11% 9.81% +16.30% 信息比率 (vs BH) — — 0.20 跟踪误差 (vs BH) — — 9.58% 累计 alpha — — +7.77% 再平衡次数 61 1 — 关键观察：S3 在 2020-2024 这个尤其偏弱的窗口仅以 7.77% 累计 alpha、IR=0.20 跑赢 510300 BH。Sharpe 都很低（\u0026lt;0.3）说明这是个普涨期之外整体仓位结构都不讨好的窗口。\n分年度收益 年 S3 (period=20) 510300 BH 超额 2020 +41.64% +31.11% +10.53% 2021 +4.23% -5.24% +9.47% 2022 -22.42% -21.37% -1.05% 2023 -10.88% -10.71% -0.17% 2024 +9.90% +20.11% -10.21% 2020 / 2021 是 alpha 主要贡献年：分散到中证 500 / 创业板 / 证券 / 医疗的等权配置在板块轮动里吃到了 510300 之外的红利。 2022 熊市：S3 与 BH 几乎同步（-22.4% vs -21.4%）。等权满仓没有现金缓冲，回撤完全暴露——这是 S3 与 S1/S2 (DCA 系) 的本质差异点。 2023 震荡：S3 几乎完全跟随 BH（差 0.2%），等权分散在普跌环境下没用。 2024 拖累严重：BH 的 510300 在 2024 跑出 +20.1%（受益于\u0026quot;国家队\u0026quot;对沪深 300 的护盘），但 S3 把 5/6 仓位放在中小盘（500/创业板/1000/证券/医疗），全部跑输 → -10% 反向 alpha。 跨年看：S3 的 alpha 几乎全部来自 2020-2021，2022-2024 三年累计是反向贡献。 rebalance_period 敏感性 rebalance_period n_rebalances 终值 CAGR Sharpe MaxDD 年化换手 5（周度） 242 111,621 2.32% 0.258 -45.20% 41.11% 10（双周） 121 112,069 2.40% 0.263 -45.13% 33.92% 20（月度，默认） 61 111,895 2.37% 0.261 -45.18% 26.11% 60（季度） 21 110,678 2.14% 0.249 -45.42% 17.54% 最佳档：period=10（双周再平衡），但 5/10/20 三档差异极小（CAGR 差 \u0026lt; 0.1%、Sharpe 差 \u0026lt; 0.005）。 最差档：period=60（季度），CAGR 比最佳低 0.26%、回撤略深，因为板块轮动期等了太久才再平衡。 换手率随 period 递减：5 → 60 时年化换手从 41% 降到 18%，但绩效提升不显著——说明本组 ETF 在这个窗口里相关性较高，再平衡溢价微弱（年化 ~25 bps 量级）。 若用更现实的成本（如散户万 2.5 + 滑点万 10，约我们当前 4×），period=5 会率先变成最差档。 关键图表 artifacts/equity_curve.png —— S3 (period=20) vs 510300 BH 净值曲线 artifacts/drawdown.png —— 双方水下曲线对比 artifacts/rebalance_dates.png —— 61 次再平衡日期分布 + 每次再平衡前实际权重相对 1/6 的偏离条形图（前 12 次样本） artifacts/weight_drift_heatmap.png —— 6 只 ETF 实际持仓占比的时间序列热图（应在 1/6 ≈ 16.7% 附近） artifacts/yearly_returns.csv —— 分年度收益数据 artifacts/rebalance_period_sensitivity.csv —— 敏感性扫描数据 解读 2022 熊市暴露问题：等权满仓 = BH 的回撤。S3 与 BH 的 MaxDD 几乎相同（-45% 量级），月度再平衡完全无法对冲单边下跌——任何想给\u0026quot;再平衡能减回撤\u0026quot;打高分的，看这个窗口都会失望。S1/S2 (DCA + 511990 现金缓冲) 应该在这个窗口对 S3 形成结构性优势。 2021 抱团瓦解期是 S3 最大功臣：BH (大盘 510300) -5%，S3 +4%，alpha +9.5%。等权分散到中证 500 / 创业板 / 证券，正好赶上 2021 中小盘领涨；这是教科书式的\u0026quot;分散 + 再平衡 = 卖高买低\u0026quot;案例。 2024 集中行情是 S3 最大软肋：BH 受益于 510300 单股集中行情 (+20%)，等权配置必然拉胯（-10% 相对）；这告诉我们等权天然不适合极端集中行情。 再平衡频率敏感性弱：5/10/20/60 四档 Sharpe 仅在 0.249~0.263 之间。结论是月度再平衡是最理性的默认值：周/双周再平衡换手翻倍但收益相同，季度再平衡虽省成本但跟丢板块轮动。 给 S4 / S5 的\u0026quot;超额阈值\u0026quot;建议 S3 已经把\u0026quot;分散等权 + 月度再平衡\u0026quot;这条 baseline 跑出来：5 年累计 alpha = +7.77%、IR = 0.20、CAGR alpha = +1.52%。S4 (动量倾斜) / S5 (趋势倾斜) 想证明因子有效，需要至少做到：\n维度 S3 baseline S4/S5 「因子有效」最低门槛 「因子显著有效」目标 CAGR alpha (vs S3) 0 ≥ +1.5% / 年 ≥ +3.0% / 年 累计 5y alpha (vs S3) 0 ≥ +7.5% ≥ +15% Sharpe（vs S3 = 0.261） 0.261 ≥ 0.35 ≥ 0.50 MaxDD（vs S3 = -45%） -45% ≤ -45%（不应更深） ≤ -38% IR (vs S3) — ≥ 0.30 ≥ 0.50 2024 year（S3 痛点） -10.2% (vs BH) ≥ S3 + 5% 转正 阈值理由：因子带来的额外换手会消耗成本；S3 已经吃掉了\u0026quot;分散 + 再平衡溢价\u0026quot;，S4/S5 必须额外提供 \u0026gt;= 1.5% 年化 才能覆盖（信号噪声 + 摩擦成本）。低于此值很难区分是因子真有效还是窗口运气。\n已发现的策略类问题 from_orders 没有显式 shift(1)：当前实现里 weights_df 索引就是触发日，配合 vectorbt 默认\u0026quot;当 bar 收盘价成交\u0026quot;语义，等价于\u0026quot;信号当日盘后即按收盘价成交\u0026quot;——理论上有 lookahead 风险（信号是基于当日收盘的\u0026quot;价格\u0026quot;判断，又用收盘价立即成交）。S3 等权策略本身不依赖价格信号，所以对 S3 不构成实质未来函数；但 S4 / S5 用动量/趋势信号时必须显式 shift(1) 或在 target_weights 里用 loc[:date - 1d]。已在 implementation.md \u0026ldquo;踩过的坑\u0026rdquo; 里留注释。 没有发现任何代码 bug：build_target_weight_panel、_validate_weights、run 都按预期工作；6 只 ETF 上市时间不一交集兜底正常；权重恒等于 1/6 在 sensitivity 扫描里全对。 下一步（更新） 真实数据回测 ✅ rebalance_period 敏感性 ✅ 与 510300 BH 对比 + 分年度收益 ✅ drift_threshold 敏感性扫描（{None, 0.03, 0.05, 0.10}）—— 等 S4/S5 也能复用 shift(1) 与不 shift 的净值差对比——为 S4/S5 提供\u0026quot;是否需要显式 shift\u0026quot;的量化证据 S4 (cn_etf_momentum_tilt) 的实现 —— 用同一份 panel 跑 vs S3 alpha 对比 S5 (cn_etf_trend_tilt) 同上 源文件 想法 · idea.md 讨论笔记 · notes.md 结论 · conclusion.md 实现 · implementation.md 验证 · validation.md 可复跑脚本 · validate.py 本版本目录（含 artifacts） 本文由 scripts/sync_strategies.py 从 Strategy-Lib 同步生成。\n","permalink":"https://lizhao903.github.io/0xBroleez/posts/2026/05/a%E8%82%A1-etf-%E7%AD%89%E6%9D%83--%E5%AE%9A%E6%97%B6%E5%86%8D%E5%B9%B3%E8%A1%A1/","summary":"✅ 6 只 A股 ETF 等权 + 月度再平衡 (period=20)，在 2020-01-01 ~ 2024-12-31 跑出","title":"A股 ETF 等权 + 定时再平衡"},{"content":" ❌ shelved · 最终化：2026-05-08 源码：ideas/S4_cn_etf_momentum_tilt/v1 + summaries/S4_cn_etf_momentum_tilt/v1 本策略的其他版本 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） 一句话概括\n在 6 只 ETF 等权满仓的基础上，按动量得分把权重从 1/N 微幅倾斜到强势资产。\n核心逻辑\n标的池固定为 Benchmark Suite V1 共享的 6 只风险 ETF（沪深300/中证500/创业板/中证1000/证券/医疗），起点权重均为 1/6。 每个再平衡日（默认每 20 个交易日）计算各标的的动量得分（MomentumReturn(lookback=20)，可叠加 MomentumReturn(lookback=60)）。 把动量得分横截面 z-score 化，按 z-score linear tilt 公式生成目标权重：w_i = 1/N + α * z_i / N。 应用上下限：w_i ∈ [0.05, 0.40]，再归一化使 sum(w) == 1。 仍然 100% 满仓、无现金缓冲，Rebalance 周期与 S3 默认一致。 假设与依据\nA股板块指数中期存在动量延续（典型半年度反转之外的 1–3 个月窗口）。Asness 等的截面动量、ETF 动量轮动文献有支持。 倾斜而不是「全仓 top-K」的好处：在 6 只池子里用 top-N 噪音太大、换手太高；轻倾斜既保留分散，又不至于完全无视截面信息。 上下限保证最坏情况下任一资产权重不会过度集中（≤40%）或被清仓（≥5%），避免单点风险。 标的与周期\n市场：A股 ETF (market: cn_etf) 标的池：510300, 510500, 159915, 512100, 512880, 512170 频率：日线（1d） 数据起止：2020-01-01 ~ 2024-12-31 一句话结论 搁置 v1。在 V1 共享基线（6 只宽基/行业 ETF、2020-2024、20 日 lookback、α=1.0、月频再平衡）上，横截面动量倾斜相对 S3 等权基线产生 -2.56% 年化超额、IR=-0.49、t-stat=-1.08，统计上未达到显著拒绝零假设，但所有尝试过的 α 与 lookback 组合方向上一致为负——属于经济意义上的系统性反向，不是噪音。\n在什么情况下有效，什么情况下失效 ❌ 失效（已观测）：单边普涨年（2020：S4-S3 = -20.3%）、单边熊市（2022：-3.6%）、风格分化但池内重叠高的年份。 ✅ 看似有效但不稳健：2024 一年 +7.7% vs S3，主要靠抓住下半年证券（512880）反弹；这是单一年度单一资产驱动的「样本内胜利」，不能视为可重复的 alpha。 三个 lookback (10/20/60) × 五个 α (0/0.5/1/2/5) 全部 vs S3 IR 为负 → 不是参数问题，是信号本身在这个池子上失效。 这个策略教会我什么 池子分散度比因子参数更重要。6 只 A股 ETF 的横截面 σ 太小，z-score 的信息密度被相关性吃掉了。下次做 cross-section 动量先看池内 pairwise correlation。 A股 ETF 上短期反转盖过中期动量。lookback=20 是最差的一档，符合传统经验中 A股一个月内的均值回复倾向。下次试动量先 skip=21。 t-stat 不显著但方向稳定也是有用信号。|t|=1.08 \u0026lt; 2 不能拒绝 0，但 8/8 (5α + 3lb) 实验全输等权 → 该结论有 8 次独立的「方向一致」证据，足以判定 v1 配置不可上线。 「目标权重恒定」策略的 turnover 不能用目标权重差衡量。S3 目标周转=0 但实际有漂移再平衡，下次跨策略 turnover 比较直接读 vbt pf.trades.records。 事前指标预期值是有用的。事前 IR 估计 0.2~0.5，实测 -0.49 → 偏离 ~1σ 以上 → 直接说明「我们对这个市场有错误先验」，比单看绝对收益更易识别问题。 关键图表 实现要点 展开完整实现记录 Implementation — A股 ETF 等权 + 动量倾斜 整体方案 派生自 Strategy 3 的 EqualRebalanceStrategy，仅覆盖 target_weights(date, prices_panel) 钩子。整体仍是「6 只 ETF 满仓 + 每 20 个交易日再平衡」，本策略的差异完全集中在「目标权重怎么算」：\n在 rebalance 日，对面板里每个 symbol 计算 MomentumReturn(lookback=20) 在 date 当天的值。 横截面 z-score 标准化（NaN 当作 0）。 线性倾斜：w_raw_i = 1/N + alpha * z_i / N。 用「水位线迭代」处理 [w_min=0.05, w_max=0.40] 上下限：每轮按 sum=remaining 缩放未钉死资产，识别最严重的越界并钉死到边界，重复直至收敛或 free 集为空。 输出权重严格满足 sum(w)=1、w_i ∈ [w_min, w_max]、非负。 可选：启用 secondary_lookback=60，主+次动量各自横截面 z-score 后等权融合。\n因子清单 Factor 类 文件 参数 方向 是新增还是复用 MomentumReturn src/strategy_lib/factors/momentum.py lookback=20 +1 复用 MomentumReturn（可选） src/strategy_lib/factors/momentum.py lookback=60 +1 复用 新增因子（如有） 无。notes.md 中提议了 MomentumRiskAdjusted（动量/波动率），暂不实现。\n策略配置 配置文件：configs/cn_etf_momentum_tilt.yaml 类型：momentum_tilt（自定义；与 cs_rank 等内置类型并列） 关键参数：tilt.method=zscore_linear、tilt.alpha=1.0、tilt.w_min=0.05、tilt.w_max=0.40、rebalance=20 与 S3 的关系：strategy.parent: cn_etf_equal_rebalance，明确派生关系；MomentumTiltStrategy(EqualRebalanceStrategy) 类签名 class MomentumTiltStrategy(EqualRebalanceStrategy): def __init__( self, *args, lookback: int = 20, secondary_lookback: int | None = None, alpha: float = 1.0, w_min: float = 0.05, w_max: float = 0.40, **kwargs, ): ... def target_weights( self, date: pd.Timestamp | datetime.date, prices_panel: dict[str, pd.DataFrame], ) -\u0026gt; dict[str, float]: ... 数据 标的池来源：手工 6 只 ETF（与 Benchmark Suite V1 共享基线） 数据范围：2020-01-01 ~ 2024-12-31 数据预处理：依赖 data/cn_etf loader 的前复权（akshare qfq） 与 S3 接口契约的差异（v1） S3 的 subagent 与本任务并行运行；写本策略时 src/strategy_lib/strategies/cn_etf_equal_rebalance.py 还未落地。本实现按以下假设契约推进：\nclass EqualRebalanceStrategy: def target_weights(self, date, prices_panel) -\u0026gt; dict[str, float]: # 返回非负、和=1 的权重字典 ... MomentumTiltStrategy 仅覆盖 target_weights，其余初始化签名通过 *args, **kwargs 透传到父类。如果 S3 的真实实现签名不一致（比如返回 pd.Series 而非 dict、或者钩子叫别的名字），按要求优先采用本假设契约，必要时在 S3 落地后做一次小适配（修改 target_weights 的返回类型即可），其它逻辑无需改动。\n源码里的 try/except ImportError 兜底是为了在 S3 模块缺失时本模块仍可被 import；正式回测必须以 S3 真实落地为前提。\n踩过的坑 第一版 _tilt_weights 用「单轮 clip + 归一化」，在 alpha 过大时所有资产都打到上限，归一化后又退化为等权（信号丢失）。改为水位线迭代后稳定。 z-score 计算要把 NaN 当 0（中性），否则少数资产没历史数据会污染整个截面。 compute_panel(panel) 返回 wide DataFrame，要按 date 取一行；如果 date 不在 index 里，回退到 \u0026lt;= date 的最后一行（典型场景：周末/停牌）。 相关 commits 实现：\u0026lt;sha\u0026gt;（待提交） 调参：未发生 验证过程 展开完整验证记录 Validation — A股 ETF 等权 + 动量倾斜 每次新一轮回测/验证就追加一个 ## YYYY-MM-DD \u0026lt;轮次主题\u0026gt; 小节，不要覆盖。\n2026-05-08 Smoke test（合成数据） 配置 \u0026amp; 数据 配置：configs/cn_etf_momentum_tilt.yaml @ commit \u0026lt;待提交\u0026gt; 数据：合成 panel，6 个 symbol、250 个交易日（B 频率）、不同 drift（-0.10‰ ~ +2.00‰/天） 父类：EqualRebalanceStrategy stub（S3 subagent 并行落地中，以保证本策略可独立验证） 入口脚本：summaries/cn_etf_momentum_tilt/validate.py（python3 summaries/cn_etf_momentum_tilt/validate.py） 结果 Case 场景 期望 结果 1 默认 alpha=1.0、6 资产 sum=1，min≥0.05，max≤0.40，最高 drift 资产权重最高 sum=1.000000，min=0.0500，max=0.3704；最高 drift 512170 最高 (0.3704) 2 alpha=0 退化为等权 (1/6=0.1667) 全部 0.1667 3 alpha=10（极端倾斜） 上下限收住，top-2 顶到 0.40 top 2 个都达到 0.40，bottom 4 个达到 0.05 4 secondary_lookback=60 sum=1、上下限生效 sum=1，max=0.3966，min=0.05 5 数据不足 5 行（动量算不出） 降级为等权 全部 1/6 6 单 symbol panel 100% {\u0026quot;510300\u0026quot;: 1.0} 7 空 panel {} {} 代表性权重（Case 1，6 资产、合成数据 drift 单调递增）：\n512170: 0.3704 512880: 0.2809 159915: 0.1362 510500: 0.0973 512100: 0.0653 510300: 0.0500 关键观察 倾斜方向正确（高动量 → 高权重），上下限严格生效，归一化无误差。 极端 alpha 下退化为「上限组 vs 下限组」的二分配置，与设计意图一致（极端不是 bug）。 数据不足/空 panel 降级路径平稳。 解读 \u0026amp; 问题 v1 没有真实数据，只能确认数学正确性；alpha=1.0、w_min/w_max 选择需要真实数据复盘。 动量 lookback 选择尚未在真实数据上比较，notes.md 已记录候选。 下一步 等 S3 (cn_etf_equal_rebalance) 落地 → 替换 stub → 跑联合 import smoke test 接 data.get_loader(\u0026quot;cn_etf\u0026quot;) 拉真实数据，跑 2020-2024 完整回测 与 S3 等权基线做差异指标分析（见下节） 与 S3 等权基线的预期对比指标（事前估计） 由于本策略只在「权重分配」上偏离 S3，用以下指标量化倾斜带来的择时贡献：\n指标 定义 预期值（合成假设） 解读 信息比率 (IR) mean(r_strat - r_s3) / std(r_strat - r_s3) * sqrt(252) 0.2 ~ 0.5 中等水平。如果 IR \u0026gt; 0.5 说明动量倾斜有显著择时；\u0026lt; 0.1 说明信号被成本/噪音吃掉 跟踪误差 (TE) std(r_strat - r_s3) * sqrt(252) 1% ~ 3% 年化 过低（\u0026lt;1%）说明 alpha 太小、退化为 S3；过高（\u0026gt;5%）说明倾斜过强、和 S3 相差太远不再是「派生」 超额收益 (alpha) CAGR(strat) - CAGR(s3) -1% ~ +3% 动量年（2020/2021/2023H2）应贡献正 alpha；2022 单边熊市可能贡献负 alpha 主动权重均值 |Δw| mean over rebal dates of mean_i abs(w_strat_i - 1/N) 5% ~ 12% 验证倾斜幅度：太低说明 alpha=1 不够强；太高说明撞到边界饱和 换手率差额 turnover(strat) - turnover(s3) +30% ~ +80%/年 倾斜引入额外换手；对比成本敏感度 命中率 rebalance 区间内「正 Δw 资产平均收益 \u0026gt; 负 Δw 资产平均收益」的比例 50% ~ 60% 动量倾斜的「方向准确率」；趋近 50% 说明动量信号无效 择时贡献分解 把 r_strat - r_s3 拆成 Σ_i Δw_i * r_i 按资产/年度归因 — 看哪个 ETF / 哪个年度最贡献 alpha；若集中在 1 个资产说明分散度不足 判定标准（草案）：\n入选（继续推进）：年化 IR ≥ 0.3、TE ∈ [1%, 4%]、超额 ≥ 0%、命中率 \u0026gt; 52% 搁置（回到 S3）：IR \u0026lt; 0.1 或 alpha 长期 \u0026lt; 0、且换手率成本 \u0026gt; 净超额收益 注：这些数值是事前估计，等真实数据回测出来后会在新一轮 ## YYYY-MM-DD 小节里用真实数对照修正。\n2026-05-08 真实数据回测（2020-01-01 ~ 2024-12-31） 配置 \u0026amp; 数据 入口：python summaries/cn_etf_momentum_tilt/validate.py real（主结果） / ... sweep（敏感性） 数据：data.get_loader(\u0026quot;cn_etf\u0026quot;).load_many(...)，6 只 ETF（不含货币 511990），akshare qfq 前复权，缓存命中 510300, 510500, 159915, 512100, 512880, 512170 每只 ~1211 个交易日（2020-01-02 ~ 2024-12-31） 父类：S3 真实落地的 EqualRebalanceStrategy（不再是 stub），直接 import 跑 baseline，参数对齐 rebalance_period=20 绩效计算：以策略 NAV 的日收益序列为口径，年化基数 252；总收益 = (1+r).cumprod()-1（与 vbt total_return 略有差异，因前者不扣初始建仓的佣金/滑点） 共享基线：100k RMB 起步，fees 万 0.5，slippage 万 5（V1 标准） 主结果（α=1.0, lookback=20, rebal=20） 策略 Final NAV Total Ret CAGR Vol Sharpe MaxDD Calmar S4 momentum_tilt 0.986 -1.42% -0.30% 24.16% 0.108 -48.73% -0.006 S3 equal_rebal (basis) 1.120 +11.96% 2.38% 23.77% 0.217 -45.18% 0.053 510300 BH 1.064 +6.39% 1.30% 21.74% 0.168 -42.78% 0.030 S4 显著输给 S3 等权基线，输的幅度也接近 510300 BH 的水平。\n与 S3 等权基线的相对绩效（因子有效性核心） 指标 值 解读 年化超额（CAGR S4 − S3） -2.56% 动量倾斜每年烧掉 ~2.5% 信息比率 (IR) -0.49 显著为负；事前预期 0.2~0.5，反向 跟踪误差 (TE) 5.18% 在合理范围（事前预期 1%~3%，略偏高） 超额收益 t-stat -1.08 (n=1209) |t|\u0026lt;2，统计上未达到 5% 显著拒绝 0；但方向稳定为负 平均主动权重 mean|Δw| 0.092 ≈ 9.2% 仓位偏离 1/N，倾斜幅度合适，不是因为信号太弱才没效果 年化目标权重周转（target turnover） 4.26 (S4) vs 0.00 (S3) S3 目标权重恒为 1/N 因此目标周转 0；S4 ~426% 年化（每月单边换 ~35%） 与 510300 BH 的对比（绝对基准） 指标 值 年化超额 -1.05% IR -0.09 TE 11.18% 超额 t-stat -0.21 (n=1209) S4 的绝对水平比 510300 BH 略差（-1% 年化），但波动/回撤都更高；从「绝对基准」看也没有跑赢。\n分年度收益（2020 ~ 2024） 年份 S4 momentum_tilt S3 equal_rebal 510300 BH S4 − S3 S4 − BH 备注 2020 +21.32% +41.64% +31.11% -20.32% -9.79% 单边大牛市；S3 全押满仓占便宜，S4 倾斜反而踏不上 2021 +6.13% +5.89% -2.89% +0.25% +9.02% 中证500/创业板抱团年；S3/S4 都靠 510500/159915 跑赢；S4 未额外贡献 2022 -26.96% -23.34% -21.20% -3.62% -5.75% 单边熊市；S4 因倾斜方向错被双杀，符合事前担忧 2023 -9.84% -10.33% -10.43% +0.49% +0.60% 震荡下跌；S4 微胜 2024 +16.24% +8.58% +18.39% +7.66% -2.15% 9-10 月快速反弹叠加证券板块（512880）爆发；S4 倾斜抓住部分 关键观察：S4 的「正贡献年份」是 2024（+7.7% vs S3），但被 2020 一年的 -20% 完全压垮。20% 的年度 alpha 缺口几乎全部来自 2020 一年——彼时 6 只 ETF 全面普涨且分散度小，动量信号容易在板块轮动中买在高位、卖在低位。\nAlpha 敏感性（lookback=20, rebal=20） α CAGR Sharpe MaxDD vs S3 α_ann vs S3 IR vs S3 t mean|Δw| 目标周转 0.0 2.38% 0.217 -45.18% -0.00% -0.10 -0.23 0.000 0.00 0.5 0.54% 0.141 -47.48% -1.79% -0.48 -1.06 0.061 2.98 1.0 -0.30% 0.108 -48.73% -2.56% -0.49 -1.08 0.092 4.26 2.0 -0.51% 0.100 -49.04% -2.72% -0.49 -1.07 0.106 4.59 5.0 -0.36% 0.107 -48.77% -2.54% -0.44 -0.97 0.112 4.65 α=0 是最优档位，意思是直接用 S3 等权——任何非零的动量倾斜都把 Sharpe 从 0.217 拖到 ~0.10。CAGR 与 IR 关于 α 单调递减并在 α≈2 触底，更大的 α 反而被边界 clip 救回一点，但仍显著落后等权。\nLookback 敏感性（α=1.0, rebal=20） lookback CAGR Sharpe MaxDD vs S3 α_ann vs S3 IR vs S3 t mean|Δw| 10 +0.89% 0.157 -44.97% -1.34% -0.27 -0.58 0.089 20 -0.30% 0.108 -48.73% -2.56% -0.49 -1.08 0.092 60 +1.62% 0.187 -48.53% -0.67% -0.14 -0.30 0.087 三档 lookback 全部 vs S3 IR 为负。最差的是 20 天（中期动量 + 短期反转交叠），10/60 天稍好但仍未超 S3。没有任何 lookback 使动量倾斜对等权产生正 alpha。\n关键图表 artifacts/equity_curve.png：三条线，S4 蓝 vs S3 绿 vs BH 红。可见 S3 全程在 S4 之上，分歧主要发生在 2020Q3-Q4 与 2024H2 artifacts/drawdown.png：S4 在 2022 熊市与 2020 国庆后双双比 S3 多 ~3-5pp 回撤 artifacts/weight_evolution.png：堆积面积图显示证券板块（512880）经常顶到 0.40 上限，医疗（512170）2021 后期顶到下限 0.05，符合「赌赛道」直观印象 artifacts/tilt_strength.png：z-score 分布近似中心化，活跃权重 |Δw| 集中在 0~0.25（被 [0.05, 0.40] 边界拘束） artifacts/alpha_sweep.csv / lookback_sweep.csv：敏感性原始数据 解读：动量在 A股 ETF 上是否有效 结论：在本组 6 只宽基/行业 ETF 池 + 2020-2024 窗口上，横截面 20 日动量倾斜在统计与经济意义上都不成立。\n方向不稳定：5 个年度只有 2024 一个明显正贡献年份，2020 一年抹掉所有 alpha。 统计未显著但持续负：t-stat ≈ -1.08，经典阈值（|t|\u0026gt;2）下不能拒绝 0，但 5/5 个 α、3/3 个 lookback 全部输 → 不是「随机噪声」而是系统性的反向。 池内分散度太低：6 只 ETF 都是 A股宽基/行业，相关性本就高（截面 σ 小、z-score 信息量有限），加上单边市场（2020、2022）下 6 只齐涨齐跌，「相对动量」几乎等价于过去几周的 noise。 短期反转在 A股 ETF 上盖过中期动量：lookback=20 是最差的一档，10 天/60 天稍好——和 A股「短期均值回复 + 中长期趋势」的传统经验吻合。skip=21（1 个月）的「跳过最近一个月」改进未在本轮启用，是潜在补救方向。 目标 turnover 426%/年的成本侵蚀：fees+slippage = 万 5.5，单边换手 4.26 倍 → 每年 ~0.23% 显式成本，不算大头但累计 5 年 ~1.2%，把 -1.05% 的 BH 缺口的一部分填回，但信号本身就是负的，省成本救不回来。 解读：Bug / 问题 未发现源码 bug。S4 的 target_weights 输出严格非负、和=1、上下限生效（已经过 smoke 与 real 双重验证）。 目标周转的口径选择：S3 因为目标权重恒为 1/N，\u0026ldquo;目标周转\u0026rdquo;=0；但 S3 实际仍因漂移触发再平衡换手——更精确的 turnover 需要从 vbt pf.trades 抽实际成交。当前指标只用于 S4 自身的相对比较是合理的，跨策略对照需谨慎。 回测口径：(1+r).cumprod() 总收益与 vbt pf.total_return() 在第一日 NAV 点的处理上略有差异（自洽即可，三条线统一口径）。 下一步 试 MomentumReturn(skip=21)（跳过最近 1 月，避开短期反转）后再跑一轮 试「波动率调整动量」(mom / vol，risk-adjusted)，可能比纯动量更稳健 把池子扩到 ≥ 12 只 ETF（加风格/行业）以提高横截面分散度 考虑把 S4 改成「动量 × 趋势过滤」的二元信号——只在大盘月度均线之上倾斜，跌破时退化为 S3 等权。这是 v2 的方向 当前结论：v1 配置（α=1.0、lookback=20）→ 搁置；以等权 S3 为 baseline，等更稳健的因子/信号再继续 源文件 想法 · idea.md 讨论笔记 · notes.md 结论 · conclusion.md 实现 · implementation.md 验证 · validation.md 可复跑脚本 · validate.py 本版本目录（含 artifacts） 本文由 scripts/sync_strategies.py 从 Strategy-Lib 同步生成。\n","permalink":"https://lizhao903.github.io/0xBroleez/posts/2026/05/a%E8%82%A1-etf-%E7%AD%89%E6%9D%83--%E5%8A%A8%E9%87%8F%E5%80%BE%E6%96%9C-v1/","summary":"❌ \u003cstrong\u003e搁置 v1\u003c/strong\u003e。在 V1 共享基线（6 只宽基/行业 ETF、2020-2024、20 日 lookback、α=1.0、月频再平衡）上，横截面动量倾斜相对 S3 等权基线产生 \u003cstrong\u003e-2.56% 年化超额、IR=-0.49、t-stat=-1.08\u003c/strong\u003e，统计上未达到显著拒绝零假设，但\u003cstrong\u003e所有尝试过的 α 与 lookback 组合方向上一致为负\u003c/strong\u003e——属于经济意义上的系统性反向，不是噪音。","title":"A股 ETF 等权 + 动量倾斜 · v1"},{"content":" ❌ shelved · 最终化：2026-05-08 (preliminary) 源码：ideas/S2_cn_etf_dca_swing/v1 + summaries/S2_cn_etf_dca_swing/v1 本策略的其他版本 v1 本文 ❌ — S2 在 2020-2024 跑出 +1.11% 年化 alpha、信息比率 0.11、最大回撤优于 BH 7.6 pct， v2 ❌ — 「停 DCA 灌入」无法治愈 v1 的高抛/低吸不对称——根因是 5 年里 6 只 ETF 整体偏多头 + 资金长期占在风险池， 想法（Why） 一句话概括\n月度定投打底 + 偏离目标权重触发的「高抛低吸」窄带刷单，捕捉震荡市的均值回归红利。\n核心逻辑\n月度 DCA（与 S1 一致）：每月第一个交易日把 5,000 RMB 从货币基金 511990 等额转入 6 只风险 ETF。 阈值触发再平衡（做T）：每个交易日收盘检查持仓（决策日），若某只 ETF 当前权重相对其目标权重偏离超过阈值，则在次日开盘部分调仓回归目标： 超阈值上限（+rel_band，默认 +20%）→ 卖出超额的 50% 回到 511990（高抛） 超阈值下限（-rel_band，默认 -20%）→ 从 511990 加买相同比例（低吸） 频率防抖：同一标的触发再平衡后，强制冷却 5 个交易日不再次触发，避免锯齿震荡里来回打。 假设与依据\nA股 ETF 在 2020-2024 期间大体是宽幅震荡（除 2021 年抱团和 2022 单边熊），个股/板块横向均值回归特征显著。 等权 + 现金缓冲使得任何单只 ETF 暴涨暴跌都会拉开权重 → 自然地高抛低吸。 DCA 提供持续买入流量，降低择时压力；阈值再平衡在不放弃 DCA 纪律的前提下增加一个无前瞻的反周期信号。 文献：Markowitz/Perold「Constant proportion portfolio」、Bernstein《The Intelligent Asset Allocator》关于 rebalancing premium 的论述。 标的与周期\n市场：A股 ETF（market: cn_etf） 标的池：511990（货基）+ 510300 / 510500 / 159915 / 512100 / 512880 / 512170（风险池，等权） 频率：日线（1d） 数据起止：2020-01-01 ~ 2024-12-31（共享基线） 一句话结论 S2 在 2020-2024 跑出 +1.11% 年化 alpha、信息比率 0.11、最大回撤优于 BH 7.6 pct， alpha 主要由 2021/2022/2023 三个震荡或熊市贡献，2024 单边反弹年明显跑输 BH 11.7 pct。 做 T 的「对称性」在 DCA 净流入下被结构性破坏（高抛 11.8× 低吸），这是设计问题不是实现问题。\n在什么情况下有效，什么情况下失效 ✅ 有效（已被本轮回测确认）： 单边熊（2022）：现金缓冲 30% 直接降低 beta，跑赢 BH +3.66pct 切换震荡（2021）：抱团→均衡切换中，频繁高抛锁定领涨利润，+9.17pct 震荡下行（2023）：低波动 + 现金缓冲，+2.33pct ❌ 失效（已被本轮回测确认）： 单边强反弹（2024）：高抛过早离场 + 6 只权重分散，跑输 BH -11.70pct V 反结尾（2020 Q4）：与 BH 接近，alpha 被换手成本吃掉 这个策略教会我什么 DCA + 阈值再平衡在「净流入」资金模式下不对称。资金净流入会持续推高风险权重，导致触发上沿的频率远高于下沿。如果想做对称的「做T」，要么改成「无净流入 + 等权再平衡」（参见 S3），要么调成「上沿阈值 \u0026gt; 下沿阈值」让低吸更敏感。 现金缓冲（30%）是 alpha 的主要来源，不是高抛节奏。光看 BH 跑赢的年份分布即可推断：弱市 / 震荡赢，强单边输。这与权益多头基金「Beta 决定一切」一致。 cooldown=5d 在 rel_band=20% 时拦截率约 24%——这是 V1 默认值的工作区间。如果 cooldown=1d，预期换手会翻倍但 alpha 不一定增加（因为同一波动会被多次切片）。 vbt 的 from_orders 跑通并不等于结果可信：本仓库已经先用 simulate 算 NAV 再喂 vbt，vbt 主要用来做后续 trade analyzer，而非真值来源。 关键图表 实现要点 展开完整实现记录 Implementation — A股 ETF DCA + 阈值再平衡（做T） 整体方案 idea.md 中的「DCA + 阈值触发再平衡」落到代码上是一个自包含的 weight-based 策略类： src/strategy_lib/strategies/cn_etf_dca_swing.py::DCASwingStrategy。\n不复用 BaseStrategy（信号驱动 entries/exits），原因：\n本策略依赖多资产权重快照做触发判定，不是单资产 long-only 信号。 月度 DCA 是「净流入+等额买入」，不是「entries 切换」。 货基 511990 同时充当现金等价物 + 实际 ETF（T+0、有真实净值），无法用 vbt 的 init_cash 模拟。 实现路径：\nDCASwingStrategy.simulate() — 纯 numpy/pandas 单循环，逐日： 执行上一日决策的订单（DCA / swing），按 T+1 open + slippage 成交 按 T 日 close 估值并计算每只权重 判定是否需要月度 DCA（看下一个交易日是否月初） 扫描 6 只 ETF 的相对偏离，超阈值且 cooldown 已过则进 pending 队列 DCASwingStrategy.run() — 包一层，惰性 import vectorbt 构造 Portfolio.from_orders（依赖未装时降级，仅返回 simulate 结果）。 因子清单 本策略不依赖因子层，纯权重规则。\nFactor 类 文件 参数 方向 是新增还是复用 —— —— —— —— 不使用 新增因子 无。\n策略配置 配置文件：configs/cn_etf_dca_swing.yaml 类型：自定义 dca_swing（未注册到 registry，遵守硬约束） 加载方式：由 summaries/cn_etf_dca_swing/validate.py 直接实例化 DCASwingStrategy 关键参数： 参数 默认值 说明 risk_target_weight 0.70 风险池目标合计权重 monthly_dca_amount 5000.0 每月 DCA 净流入 RMB rel_band 0.20 相对偏离触发阈值 ±20% adjust_ratio 0.50 单次拉回到目标的比例 cooldown_days 5 同标的触发冷却交易日 fees 0.00005 共享基线 slippage 0.0005 共享基线 init_cash 100000 共享基线 数据 标的池来源：手工列（共享基线 6 只 ETF + 货基） 数据范围：2020-01-01 ~ 2024-12-31 数据预处理： akshare 前复权（qfq） dropna(how='any') 对齐 7 只标的的共有交易日（缺一天就跳一天，避免单边持仓估值漂移） 起始日 T0 把 init_cash 全数买入 511990 货基，形成现金缓冲 关键设计决策 1. 「做T」的实现 = 窄带刷单 + 频率防抖 每日 T 收盘扫描，每只 ETF 按相对偏离判定：\nw_i / w_target \u0026gt; 1 + rel_band → 高抛（卖偏离的 50%） w_i / w_target \u0026lt; 1 - rel_band → 低吸（从货基买偏离的 50%） 同一标的触发后写 next_allowed_idx[s] = t + 1 + cooldown_days，5 个交易日内不再触发。\n2. 防未来函数 触发判定：T 日 close 后 下单价格：T+1 open × (1 ± slippage) 月度 DCA：T 日判定「下一日 index 是月初」，T+1 open 成交 3. 货基的处理 511990 当作真实 ETF 一起喂 OHLCV，但价格几乎线性：合成数据里我们用 close = 1 + 0.02/252 × t 模拟年化 2%。 真实回测时直接用 akshare 的 511990 净值序列。\n4. 现金不足/持仓不足的处理 simulate 在执行 pending 订单时缩单到可用额度，避免负持仓。这意味着极端连续触发时实际下单量小于理论值，会在 diagnostics 里体现为 swing_* 计数比预期低。\n踩过的坑 起初想用 BaseStrategy + entries/exits 表达，发现部分调仓（不是清仓）信号驱动表达不了 → 改自包含 run。 月度 DCA 的「第一个交易日」如果直接用 index.to_period('M').drop_duplicates() 会拿到 period 而非具体日期；正确做法是 groupby(period).min() 取每月 index 的最小值。 佣金和滑点容易重复扣：本实现把 fees 当成「从货基扣两腿手续费」，slippage 直接打在 open price 上；不要在 size 计算里再扣一次。 相关 commits 实现：待提交（2026-05-08 当日批次） 调参：本版固定参数，不调 后续 follow-up 装 vectorbt 后跑通 _run_with_vbt，对比 simulate vs vbt 的 NAV 差距应 \u0026lt; 0.5% 真实数据下重跑 validation 与 S1 横向对比表（等 S1 实现完成后） 验证过程 展开完整验证记录 Validation — A股 ETF DCA + 阈值再平衡（做T） 每次新一轮回测/验证就追加一个 ## YYYY-MM-DD \u0026lt;轮次主题\u0026gt; 小节，不要覆盖。\n2026-05-08 初版 smoke test（合成数据） 配置 \u0026amp; 数据 配置：configs/cn_etf_dca_swing.yaml @ commit \u0026lt;待提交\u0026gt; 实现：src/strategy_lib/strategies/cn_etf_dca_swing.py::DCASwingStrategy 数据：合成 OHLCV — 1 货基（年化 2% 线性）+ 6 ETF（GBM，drift ∈ [-2%, 15%]，vol ∈ [18%, 30%]） 时间窗：2020-01-02 起 504 个交易日（2 年） 训练/样本外切分：本轮仅 smoke，未做切分 因子层（IC 分析） N/A — 本策略不依赖因子层。\n分位数分组 N/A — 本策略不做分位选股。\nSmoke test 断言通过项 ✅ 月度 DCA 触发正确：24 个月 × 6 标的 = 144 笔预期，实际 138 笔（符合「最后两月不一定全触发」的容忍） ✅ NAV 重算恒等：Σ(shares × close) == nav，相对误差 \u0026lt; 1e-9 ✅ cooldown 拦截：所有 swing 订单同标的间隔 ≥ 5 自然日 ✅ 起始建仓：T0 把 100% init_cash 买入 511990 ✅ 终态权重健全：风险权重 0.77，货基权重 0.23，符合「DCA 持续买入抬高风险占比」预期 回测绩效（合成数据，仅供功能验证，不代表真实预期） 指标 值 总收益 11.87% 年化收益 5.78% 年化波动 7.52% Sharpe 0.77 最大回撤 -8.18% Calmar 0.71 交易日数 504 DCA 买入笔数 138 Swing 买入笔数（低吸） 18 Swing 卖出笔数（高抛） 72 关键观察：合成数据 6 只 ETF 的 drift 设得偏正，所以「高抛」次数远多于「低吸」（72 vs 18）。 这本身正是 rebalancing 的预期行为：上涨标的被反复落袋。\n与 S1 对比设计（待真实数据填充） S1 (cn_etf_dca_basic) S2 (cn_etf_dca_swing) Δ 总收益 TBD TBD TBD Sharpe TBD TBD 期望 +0.1~0.3 最大回撤 TBD TBD 期望 ↓（现金缓冲触发） 换手率（年化） ~20% TBD 期望 30%~80% 与 510300 BH 的 alpha TBD TBD TBD 信息比率 TBD TBD TBD 跟踪误差 TBD TBD TBD 2020 年收益 TBD TBD 震荡向上：S2 略优 2021 年收益 TBD TBD 抱团趋势：S2 可能跑输 2022 年收益 TBD TBD 单边熊：S2 抄底蚀本 2023 年收益 TBD TBD 震荡：S2 优 2024 年收益 TBD TBD 震荡反弹：S2 优 关键图表 待真实回测后导出到 artifacts/\n— 待补 — 待补 — 待补 — 待补 解读 \u0026amp; 问题 Smoke test 证明实现层面没有 NAV 漂移、未来函数、cooldown 失灵这三类常见 bug。 合成数据下 swing 买入仅 18 笔（vs 卖出 72 笔），意味着 rel_band=20% 在偏多头序列里几乎不触发低吸 → 真实 2022 熊市预期会反向。 起初担心 cooldown=5d 会让 sharp drop 错过抄底，smoke 数据里没出现这个场景，需要真实数据复盘 2022-01 到 2022-04 的连续阴跌段。 下一步 安装 vectorbt + akshare，跑 2020-01-01 ~ 2024-12-31 真实数据 等 S1 实现就绪后跑 head-to-head（同一份数据，同一份 panel） 输出年度收益分解 + 累计净值曲线 + 持仓权重热力图 敏感性分析（不调参，只是观察）：rel_band ∈ {15%, 20%, 25%}、cooldown ∈ {3, 5, 10} 的 OOS 差异 2026-05-08 真实数据回测 配置 \u0026amp; 数据 实现：src/strategy_lib/strategies/cn_etf_dca_swing.py::DCASwingStrategy（默认参数 = 共享基线） 数据：本地缓存 parquet（akshare qfq 已预拉取） 货基：511990 风险池：510300 / 510500 / 159915 / 512100 / 512880 / 512170 时间窗：2020-01-01 ~ 2024-12-31（1209 个共有交易日，已 inner-join） 共享成本：fees=0.00005（万 0.5）/ slippage=0.0005（万 5）/ init_cash=100,000 关键参数：risk_target_weight=0.70、monthly_dca_amount=5000、rel_band=0.20、adjust_ratio=0.50、cooldown_days=5 vbt Portfolio 已成功构造（Portfolio.from_orders，cash_sharing=True，group_by=True） 绩效（V1 共享指标） 指标 S2 (DCA + swing) 510300 BH 最终净值 113,807.92 104,957.43 总收益 +13.87% +4.18% 年化收益 (CAGR) +2.75% +0.86% 年化波动 18.10% 21.80% Sharpe 0.152 0.039 最大回撤 -37.10% -44.75% Calmar 0.074 0.019 换手率（年化） 153.92% ~0% Alpha（年化） +1.11% — 信息比率 (IR) 0.110 — 跟踪误差（年化） 10.09% — 超额总收益 vs BH +9.69 pct — 注：S2 的「换手率 154%」绝大部分来自月度 DCA（每月 6 笔等额买入），swing 部分仅 192 笔/5 年 ≈ 38 笔/年。如果剔除 DCA，仅看主动调仓的换手会降到 ~30%。\nS2 特有指标 指标 值 说明 高抛次数（swing_sell） 177 风险标的权重 \u0026gt; target × 1.20 触发 低吸次数（swing_buy） 15 风险标的权重 \u0026lt; target × 0.80 触发 高抛 / 低吸比 11.8 : 1 严重不对称（见下面观察） 平均触发偏离（abs） 24.78% 超阈值平均 ~5pct，与 rel_band=20% 自洽 cooldown 命中率 24.11% 24% 的「想触发」事件被 5d cooldown 拦下 DCA 买入笔数 354 59 个月 × 6 标的 ≈ 354 ✓ 总订单数 552 354 DCA + 192 swing + 5 init/cash 流 与 510300 BH 比较 — 各年度 年份 S2 510300 BH Δ 行情 2020 +34.32% +31.11% +3.21pct 疫情后 V 反 2021 +3.93% -5.24% +9.17pct 抱团 → 切换 2022 -17.71% -21.37% +3.66pct 单边熊 2023 -8.38% -10.71% +2.33pct 震荡下行 2024 +8.41% +20.11% -11.70pct 924 行情大反弹 2024 跑输 11.7 pct 是 5 年中唯一明显失利的年份。原因：9.24 大反弹时 510300 单一标的爆发，而 S2 把权重等分给 6 只，并且现金缓冲 30% + 期间累计的高抛仓位拖了后腿。\n与 S1 (cn_etf_dca_basic) 的预期对比（V1 数字未跑，下面是结构性推断） 设 S1 = 同样 DCA 但不做主动再平衡（仅月度等额买入 6 只 ETF + 持有，不高抛低吸）。 基于本轮 S2 的 192 次再平衡且严重偏向「高抛」，可推：\n维度 假设 V1 数字 X S2 相对 X 的预期 本轮 S2 实测 总收益 X = 5%~10%（DCA 偏防御 + 风险池更宽，应略优于 510300 BH 4.18%） 应略低或持平：高抛 11.8× 多于低吸，长期净「锁利」会拉低多头 beta +13.87% Sharpe X = 0.10~0.20（同窗口 DCA 估计） 应略高：现金缓冲 + 阈值调仓压低波动 0.152 ✓ 应处于 X 中段或略上 Max DD X = -38%~-42%（接近 BH） 应略浅：每次回撤过程中触发的「低吸」会延长仓位但「高抛」节奏会先消化部分头寸 -37.10%（明显优于 BH -44.75%） 年化波动 X ≈ 18%~19% 应略低 18.10% 换手率 X ≈ 15%~25%（仅 DCA 现金流） 必然显著高（多 swing 部分） 153.9% 2021 收益 抱团切换：DCA 在分散中受益，X 应正 S2 应再高一些（频繁高抛锁住领涨标的的利润） +3.93%（vs BH -5.24%） 2022 收益 单边熊：X ≈ -19%~-22% S2 应略浅（cash buffer 不参与下跌；但低吸有套牢风险） -17.71% 2024 收益 单边反弹：X ≈ +18%~+22% S2 应跑输（高抛过早离场；权重等分摊薄龙头） +8.41%（确实跑输 BH 11.7pct） 整体推断：S2 的 alpha 主要来自震荡市与切换年（2021/2022/2023），单边趋势市（2024）会因为「过早高抛 + 权重分散」付出明显代价。 这与 idea.md 的事前假设吻合。\n关键观察 — 阈值再平衡在不同行情下的「做T」成效 2020-2023 几乎单向「高抛」：cumulative 5 年 177 卖 vs 15 买，比例 ~12:1。\n原因：DCA 持续从货基净流入风险池 → 风险权重天然向上漂移，触发上沿的概率远高于触发下沿。 这是「DCA + 阈值再平衡」结构的内禀偏差，不是 bug。idea.md 提到的「对称做T」在该资金流模式下不对称。 2024 高抛明显是「过早离场」：风险权重在 9 月反弹前已被多轮高抛压回到目标附近甚至偏低，没能享受 9.24 暴涨的乘数效应。\n现金缓冲 + 高抛=有效的下行保护：4 年中 3 年（21/22/23）跑赢 BH 共 +15 pct，这是 S2 alpha 的来源。Calmar 0.074 也几乎是 BH 0.019 的 4 倍。\ncooldown 拦截 24%「想触发」事件：rel_band=20% / cooldown=5d 的组合让信号实际触发率 ≈ 76%，没有过度抑制（≥70% 算合理工作区间）。\nvbt Portfolio 与 simulate NAV 一致：run() 同时调用 simulate 和 vbt 时未抛错，portfolio 已构建，留作后续 vbt 报表分析使用。\n关键图表 — S2 vs 510300 BH 标准化净值 — 回撤对比 — 高抛/低吸事件叠加在风险池权重曲线上（核心 S2 视图） — 风险权重轨迹 + ±20% 阈值带 附原始数据：artifacts/nav_series.csv、artifacts/orders.csv、artifacts/weights.csv、artifacts/real_backtest_summary.json。\n下一步 等 S1 (cn_etf_dca_basic) 真实回测出炉，做 head-to-head 表（NAV / Sharpe / DD / 分年度差） 敏感性扫描（不调参用，只观察单调性）： rel_band ∈ {0.10, 0.15, 0.20, 0.25, 0.30} — 关注高抛/低吸频次和 2024 跑输幅度 cooldown_days ∈ {1, 3, 5, 10, 20} — 关注 cooldown 命中率与 turnover adjust_ratio ∈ {0.25, 0.50, 0.75, 1.00} — 关注换手率与 alpha 复盘 2024 跑输：是否需要在「单边趋势 + 风险权重低于目标」时降低高抛敏感度？ 复盘 2022 抄底是否提前耗尽现金缓冲（low-buy 仅 15 次说明并未频繁低吸，可能 cash buffer 起到了「不抄底反而更稳」的作用） 源文件 想法 · idea.md 讨论笔记 · notes.md 结论 · conclusion.md 实现 · implementation.md 验证 · validation.md 可复跑脚本 · validate.py 本版本目录（含 artifacts） 本文由 scripts/sync_strategies.py 从 Strategy-Lib 同步生成。\n","permalink":"https://lizhao903.github.io/0xBroleez/posts/2026/05/a%E8%82%A1-etf-dca--%E9%98%88%E5%80%BC%E5%86%8D%E5%B9%B3%E8%A1%A1%E5%81%9At-v1/","summary":"❌ S2 在 2020-2024 跑出 +1.11% 年化 alpha、信息比率 0.11、最大回撤优于 BH 7.6 pct，","title":"A股 ETF DCA + 阈值再平衡（做T） · v1"},{"content":" ⏳ validating-realdata-done · 最终化：TBD 源码：ideas/S1_cn_etf_dca_basic/v1 + summaries/S1_cn_etf_dca_basic/v1 想法（Why） 一句话概括\n把闲钱停在场内货币 ETF，按月等额定投到 6 只风险 ETF，不做主动调仓。\n核心逻辑\n初始 100,000 RMB 全部进入 511990（华宝添益）作为现金池。每月第 1 个交易日，从 511990 卖出 5,000 RMB 等额分配给 6 只风险 ETF（510300/510500/159915/512100/512880/512170）等权买入。 风险池一旦买入持有不动，不做再平衡、不做止盈止损。20 个月后现金池打满进入风险池， 之后只领取 511990 的\u0026quot;利息\u0026quot;（即真实价格上涨）作为现金部分。\n假设与依据\n行为金融：DCA 平滑入场成本，避免一次性高位入场的择时焦虑，是被动投资者最常用基线。 现金管理：场内货币 ETF（511990）T+0、年化 ~2%，作为闲置资金的\u0026quot;无风险\u0026quot;载体优于活期。 基线意义：作为 Benchmark Suite V1 中最朴素的策略，给后续做T/再平衡/因子倾斜策略提供下限对比。 只要后续策略跑不赢这条基线，说明主动操作没有创造 alpha。 标的与周期\n市场：A 股 ETF (cn_etf) 标的池： 现金池：511990 华宝添益 风险池：510300 沪深300 / 510500 中证500 / 159915 创业板 / 512100 中证1000 / 512880 证券 / 512170 医疗（共 6 只，等权） 频率：日线，每月第 1 个交易日触发 DCA 数据起止：2020-01-01 ~ 2024-12-31（与 Benchmark Suite V1 共享窗口） 一句话结论 在 2020-01 ~ 2024-12 的 A股 ETF 真实样本上，基础 DCA 跑输 510300 BH（CAGR -2.25% vs +0.86%，超额 -14.54%）； 现金缓冲仅覆盖前 21 个月（2021-09 耗尽），既错过 2020 的疫情反弹、又没赶上 2022 熊市的对冲价值， 此后策略退化为 6 ETF 静态等权组合，与基准的差异完全由池子构成决定，而非 DCA 节奏。\n关键数据 DCA basic 510300 BH 样本期 2020-01-02 ~ 2024-12-31 同左 样本外 无（DCA 规则无参数拟合，整段即样本外） 同左 最终 NAV (RMB) 89,647.22 104,275.01 总收益 -10.35% +4.28% 年化收益 (CAGR) -2.25% +0.86% 年化波动 20.24% 21.76% Sharpe -0.012 +0.148 最大回撤 -45.13% -44.75% Calmar -0.050 +0.019 年化换手率 21.33% 0% 超额总收益 -14.54% —— 信息比率 -0.278 —— 跟踪误差 12.44% —— 分年度：\n年份 DCA BH 差 2020 +13.35% +31.11% -17.76% 2021 +4.72% -4.32% +9.03% 2022 -23.78% -21.68% -2.10% 2023 -9.90% -10.43% +0.54% 2024 +9.98% +18.39% -8.41% 在什么情况下有效，什么情况下失效 ✅ 2021 类行情有效：现金未耗尽 + 风险池中小盘 / 行业 ETF 跑赢沪深 300 主板时，DCA 能产生 +9pct 的超额。 ❌ 2020 类牛市无效：现金缓冲在牛市初期是机会成本，本轮代价为 -17.76pct。 ❌ 现金耗尽后失效：第 21 个月起策略退化为 6 ETF 静态等权，不再具备\u0026quot;慢入场对冲下行\u0026quot;属性， 2022 熊市反而比 BH 多跌 2.10pct（因池子里小盘 / 医疗权重高于 510300）。 ❌ 池子构成决定 2024 反弹的迟钝度：医疗 (512170)、中证 1000 (512100) 在 2024 跑输沪深 300， 而非 DCA 节奏导致。 这个策略教会我什么 DCA 的现金缓冲长度必须和回测窗口匹配：init_cash / (dca_amount * 12) 决定缓冲月数， 超过该窗口后 DCA 节奏失效，策略退化为静态权重。基线 100k / 5k = 20 月，对 5 年回测显然不够。 风险池构成 ≫ 入场节奏：现金耗尽后 alpha 全部由池子里 6 只 ETF 与基准的相对走势决定。 DCA 类策略的横向对比应控制池子一致，单独研究节奏。 \u0026ldquo;DCA 在熊市保护下行\u0026rdquo; 的常见叙事：只在 缓冲未耗尽 的窗口内成立。 要让叙事通用，得引入 swing / rebalance 把现金重新生成出来——即 Strategy 2。 关键图表 实现要点 展开完整实现记录 Implementation — 基础 DCA（cn_etf_dca_basic） 整体方案 完全规则驱动，不依赖任何因子。核心循环：\n初始：100,000 RMB 全部折算成 511990 货币 ETF 股数（开户入金，不收手续费）。 遍历每日 close： 若该日是当月第 1 个交易日（DCA 触发日），从货币池卖出 dca_amount=5000， 按 risk_allocation（默认等权）分配给风险池 6 只 ETF 买入； 卖/买都按 fees + slippage 共享基线扣费。 当日收盘簿记：cash = 货币 ETF 股数 × close；risk = ∑ 风险股数 × close；equity = cash + risk。 不再平衡：风险池一旦买入就持有到回测结束。 因子清单 Factor 类 文件 参数 方向 是新增还是复用 —— —— —— —— 本策略不使用任何 Factor 理由：DCA 是时间触发 + 等权规则，无需横截面/时序排名。\n新增因子（如有） 无。\n策略配置 配置文件：configs/cn_etf_dca_basic.yaml 类型：dca_basic（自定义类型，类路径 strategy_lib.strategies.cn_etf_dca_basic.DCABasicStrategy） 关键参数： dca_amount: 5000（每月转入金额） dca_frequency: \u0026quot;M\u0026quot;（月度触发） risk_allocation: \u0026quot;equal\u0026quot;（风险池内部等权） 数据 标的池来源：手工列（与 Benchmark Suite V1 共享基线一致，6 只风险 ETF + 1 只货币 ETF） 数据范围：2020-01-01 ~ 2024-12-31（akshare fund_etf_hist_em，前复权 qfq） 数据预处理： close 前向填充（停牌 / 早期数据缺失时维持上一日价格） 货币 ETF 511990 起始价格异常时直接抛错（不做软兜底，避免无声错误） 关键代码思路 class DCABasicStrategy: def run(self, panel, *, init_cash, fees, slippage, since, until) -\u0026gt; DCAResult: # 1. 拼宽表 close = panel[cash + risk_syms][close] # 2. 找出每月第 1 个交易日 dca_dates = idx.groupby([year, month]).first() # 3. cash_units = init_cash / close[cash, t0] # 货币 ETF 股数 # 4. for date in idx: # if date in dca_dates: # spend = min(dca_amount, cash_units * cash_price) # cash_units -= spend / cash_price # for sym, w in target_weights(date, prices).items(): # buy_px = price * (1 + slippage) # units = (spend * w * (1 - fees)) / buy_px # risk_units[sym] += units # equity[t] = cash_units*cash_price + sum(risk_units * risk_prices) 设计选择：\n不继承 BaseStrategy：base 是 entries/exits 信号驱动，DCA 用不上，硬塞会让代码更乱。 不调 vectorbt：DCA + 等权 + 无再平衡足够简单，纯 numpy 模拟反而清晰；当前环境也未装 vectorbt。 target_weights(date, prices) 暴露：方便后续策略（Swing/Tilt）继承相同接口的\u0026quot;DCA 内部权重\u0026quot;语义。 货币池用真实 511990 价格：而非 2% 年化简化。事后可在 validation 中和 2% 年化做对比。 踩过的坑 （首版实现，待真实回测后追加）\n⚠️ 货币 ETF 价格语义：511990 在 akshare fund_etf_hist_em 返回的 close 通常在 100 附近微涨， 需确认前复权后是否产生不连续跳变。Smoke test 用合成数据无法暴露此问题，留待真实回测核对。 ⚠️ 月初定位：用 idx.groupby([year, month]).first() 而非 MonthBegin offset， 避免月初遇周末时漏触发。 相关 commits 实现：（待提交） 验证过程 展开完整验证记录 Validation — 基础 DCA（cn_etf_dca_basic） 每次新一轮回测/验证就追加一个 ## YYYY-MM-DD \u0026lt;轮次主题\u0026gt; 小节，不要覆盖。\n2026-05-08 Smoke Test（合成数据） 配置 \u0026amp; 数据 配置：configs/cn_etf_dca_basic.yaml（参数 dca_amount=5000, dca_frequency=M, risk_allocation=equal） 数据：合成 7 个 symbol（1 现金 + 6 风险）的 OHLCV，~500 个交易日（2022-01-03 起，B 频率） 511990 模拟年化 ~2% 低波动 6 只风险 ETF 模拟年化漂移 ~8%、日波动 ~1.8% 目的：验证策略类机械正确，不验证策略 alpha。 因子层（IC 分析） 本策略不使用任何 Factor，跳过 IC / 分组分析。\nSmoke 回测绩效 指标 值 起始净值 99,997.00 最终净值 116,672.32 总收益 +16.67% CAGR +8.08% 年化波动 7.62% Sharpe 1.058 最大回撤 -8.44% Calmar 0.957 年化换手率 48.28% DCA 触发次数 21 次 总交易笔数 147 笔（21 卖现金 + 21×6 买风险） 机械正确性检查 ✅ result.metrics 是 dict，包含全部约定字段（total_return / cagr / sharpe / max_drawdown / calmar / annual_turnover / n_trades / ann_vol） ✅ result.equity 全程无 NaN，起始值 ≈ init_cash（100k） ✅ result.holdings 列数 = 6（6 只风险 ETF） ✅ DCA 触发频率正确：500 天 ≈ 24 个月，触发 21 次（窗口未跨整年，部分月份共享） ✅ 单次 DCA 一致性：首次 DCA（2022-01-03）6 只风险 ETF 累计买入金额 = 4999.75 ≈ 5000 - 卖货币池佣金，符合预期 ✅ 累计买入金额 (101,559) \u0026lt; 初始资金 + 货币池利息（约 105k 上限），且 \u0026lt; 21 × 5000 = 105,000， 最后一次 DCA 被余额限制部分成交 → 兜底逻辑 spend = min(dca_amount, cash_value) 工作正常 关键图表 真实数据回测才生成，本轮无图表。\n解读 \u0026amp; 问题 初版有一个潜在 bug 已被 smoke 测出并修复： _dca_trigger_dates 原本用 pd.DatetimeIndex(sorted(g.values)) 还原触发日， 但 groupby(...).first().values 会丢掉 tz 信息，导致 date in dca_dates 永远 False、 从未触发 DCA（n_trades=0）。改为按 位置（integer index）分组取首个，再用 idx[positions] 还原， 保留原 Timestamp 与 tz。 预期问题：合成数据下 Sharpe ~1.0、回撤 -8% 是\u0026quot;假\u0026quot;的（合成正漂移使然），不能用于评估真实 alpha。仅用于验证机械流程。 下一步（真实数据待执行清单） 安装依赖：pip install akshare loguru vectorbt matplotlib pandas （或仓库 lock 文件） 运行 python summaries/cn_etf_dca_basic/validate.py（默认会先跑 smoke 再跑真实） 检查 511990 真实价格序列是否连续（akshare fund_etf_hist_em qfq）；如异常则切换为 2% 年化模拟 输出指标到本文件新追加的 ## YYYY-MM-DD 真实数据回测 小节，需含： 总收益 / CAGR / Sharpe / 最大回撤 / Calmar / 年化换手 与基准 510300 BH 的：超额收益 / 信息比率 / 跟踪误差 分年度收益（2020/2021/2022/2023/2024） 出图保存至 artifacts/： equity_curve.png —— DCA 净值 vs 510300 BH weights_stack.png —— 7 个仓位（含现金）的权重堆叠时序 与 Strategy 2 (cn_etf_dca_swing)、Strategy 3 (cn_etf_equal_rebalance) 横向对比， 评估\u0026quot;DCA 慢入场\u0026quot;在 2020/2021 牛市中的代价 2026-05-08 真实数据回测 配置 \u0026amp; 数据 代码版本：本仓库工作树（尚未首个提交，git log 为空），运行点：summaries/cn_etf_dca_basic/validate.py::run_real() 策略参数（DCABasicStrategy() 默认值）： cash_symbol=511990、risk_pool=(510300,510500,159915,512100,512880,512170)、 dca_amount=5000、dca_frequency=M、risk_allocation=equal 资金/成本（与 docs/benchmark_suite_v1.md 共享基线一致）： init_cash=100,000、fees=0.00005、slippage=0.0005、复权 = qfq 回测窗口：2020-01-02 ~ 2024-12-31，共 1212 个交易日 数据来源：data/raw/cn_etf/{511990,510300,510500,159915,512100,512880,512170}_1d.parquet （cache hit，未触发 akshare 网络请求）。loader 索引为 tz-aware UTC，run_real() 在传入策略前已 tz_convert(None) 转 naive 以兼容策略内部切片。 因子层（IC 分析） 本策略不使用任何 Factor，跳过 IC / 分组分析。\n回测绩效（策略 vs 510300 BH） 指标 DCA basic 510300 BH 差值 最终 NAV (RMB) 89,647.22 104,275.01 -14,627.79 总收益 -10.35% +4.28% -14.63% 年化收益 (CAGR) -2.25% +0.86% -3.11% 年化波动 20.24% 21.76% -1.52% Sharpe（rf=0） -0.012 +0.148 -0.160 最大回撤 -45.13% -44.75% -0.39% Calmar -0.050 +0.019 —— 年化换手率 21.33% 0% —— DCA 触发月份 60（其中第 21 个月起仅减额成交） —— —— 总交易笔数 147 笔 —— —— 跟踪指标（日频对数差/算术差）：\n超额总收益：-14.54% 信息比率：-0.278 跟踪误差（年化）：12.44% 分年度收益 年份 DCA basic 510300 BH 差值 2020 +13.35% +31.11% -17.76% 2021 +4.72% -4.32% +9.03% 2022 -23.78% -21.68% -2.10% 2023 -9.90% -10.43% +0.54% 2024 +9.98% +18.39% -8.41% 关键图表 净值曲线：artifacts/equity_curve.png 回撤曲线：artifacts/drawdown.png 现金 vs 风险占比时序：artifacts/cash_vs_risk.png 各 ETF 权重堆叠（含 cash）：artifacts/weights_stack.png 解读 现金缓冲在 2021-09-01 耗尽：100k 起始 ÷ 5k/月 ≈ 20 个月，验算精确（2020-01 起首次 DCA，第 21 次触发余额降至 0， 随后每月 DCA 按 spend = min(5000, cash) 兜底为 0，仅日常货币池利息可分摊，量级可忽略）。 这意味着 2021-09 之后策略本质是 6 ETF 静态等额组合，不再有\u0026quot;慢入场\u0026quot;的对冲效果。 2020 的代价：BH (510300) +31.11%、策略仅 +13.35%。原因：2020 全年现金占比 35–100%，错过了上半年的疫情反弹。 这是 DCA 在牛市的结构性成本，与设计预期一致。 2021 的红利：BH -4.32%、策略 +4.72%。原因：2020 末仓位才到 65%，2021 仍在持续买入，6 ETF 等权组合（含 512880 证券、 512170 医疗）在中证 500 / 中证 1000 板块上跑赢沪深 300。这是 DCA 唯一实质跑赢基准的年份，价值约 +9pct。 2022 熊市表现劣于 BH：DCA -23.78% vs BH -21.68%。理由：现金缓冲已在 2021-09 耗尽，进入熊市时与等权组合无差异； 6 ETF 池中创业板 (159915) 和医疗 (512170) 权重过半暴露在小盘 / 成长跌幅最深的板块，反而比 510300 更糟。 → DCA \u0026ldquo;在熊市保护下行\u0026rdquo; 的叙事，仅在现金未耗尽时成立。 2024 反弹的\u0026quot;迟钝度\u0026quot;：BH +18.39%、策略 +9.98%。关键原因不再是现金拖累（2024 年初现金=0）， 而是 6 ETF 等权组合中 512170 医疗、512100 中证 1000 等在 2024 行情中跑输沪深 300 主升浪。 策略迟钝度本质来自池子构成 + 等权，而非 DCA 节奏。 最大回撤几乎相同（-45% vs -45%）：因为 2021-09 之后两者都是 \u0026ldquo;买入并持有\u0026rdquo; 状态，回撤主要发生在 2021–2024 区间， 此时策略与 BH 的 beta 已经接近 1。 一句话核心观察：现金缓冲只覆盖了前 21 个月，恰好错过 2020 的上涨、又没赶上 2022 的下跌前夜； 2021-09 之后策略退化为 6 ETF 静态等权组合，与 510300 BH 的差异完全由池子构成决定，而非 DCA 节奏。\n已知 bug / 边界 tz mismatch 临时绕过：DCABasicStrategy.run(..., since=, until=) 内部用 tz-naive pd.Timestamp(since) 与 panel 索引比较；loader 默认产出 tz-aware UTC 索引，会 raise TypeError: Invalid comparison between dtype=datetime64[us, UTC] and Timestamp。 当前 validate.py::run_real() 通过传入 panel 前 tz_convert(None) 绕过； 根因修复方案有两个，留待后续： run() 内部把 pd.Timestamp(since) 改为根据 close.index.tz 携带 tz； loader 在 _normalize 之外再提供一个 tz-naive 的 view。 首个 commit 尚未创建：git log 为空，本次回测对应代码版本= 工作树快照。 下次跑前请先 commit，validation 可记录 commit hash。 下一步（敏感性 / 横向对比清单） 敏感性：dca_amount ∈ {2k, 5k, 10k}，看现金耗尽时点对 5 年 NAV 的影响 敏感性：dca_frequency ∈ {M, W}，看高频小额 DCA 是否平滑回撤 敏感性：risk_allocation = inverse_price 在 2022 熊市的低吸效果 池子构成消融：去掉 512170/512100，仅留 4 只宽基；对比 alpha 是否回正 与 Strategy 2 (cn_etf_dca_swing) 横向对比：阈值再平衡能否在 2022 实现负相关 alpha 与 Strategy 3 (cn_etf_equal_rebalance) 横向对比：等权满仓 + 季度再平衡 在 2020 是否能保住 BH 的牛市收益 修复上文 tz 边界，再做一次 commit-trackable 复跑 源文件 想法 · idea.md 讨论笔记 · notes.md 结论 · conclusion.md 实现 · implementation.md 验证 · validation.md 可复跑脚本 · validate.py 本版本目录（含 artifacts） 本文由 scripts/sync_strategies.py 从 Strategy-Lib 同步生成。\n","permalink":"https://lizhao903.github.io/0xBroleez/posts/2026/05/%E5%9F%BA%E7%A1%80-dca%E8%B4%A7%E5%B8%81%E5%9F%BA%E9%87%91--%E9%A3%8E%E9%99%A9%E8%B5%84%E4%BA%A7%E6%B1%A0%E7%AD%89%E9%A2%9D%E5%AE%9A%E6%8A%95/","summary":"⏳ 在 2020-01 ~ 2024-12 的 A股 ETF 真实样本上，基础 DCA 跑输 510300 BH（CAGR -2.25% vs +0.86%，超额 -14.54%）；","title":"基础 DCA（货币基金 + 风险资产池等额定投）"},{"content":"为什么写这篇 这是这个站点的第一篇文章。本身也是一次完整的「项目实施」——选型、动手、复盘——所以适合作为示范，把这套流程的形态固定下来。\n选型上的几个关键判断 为什么不是 Notion / 语雀 托管型工具的写作体验不错，但有两个硬伤：\n数据不在自己手里。文章是 markdown 文件，本来就该是可移植的纯文本。 无法版本化。git log 是博客最自然的修订史。 为什么不是 Quartz / Obsidian Publish 我的 Obsidian 用法是「散点笔记」，里面 80% 的内容并不打算公开。如果直接发布整个 vault，需要花精力做可见性管理。把博客和笔记本物理隔开，反而更省心：\nObsidian 留给自己看的散点笔记 博客仓库 是经过整理、明确要发布的内容 为什么是 Hugo + PaperMod 维度 选择理由 构建速度 Go 写的，毫秒级，写得多了不会退化 中文社区 中文文档完整，主题选择多 部署 GitHub Actions 一键搞定，免费 写作 标准 markdown，无任何专有语法 图片 Page Bundle 模式支持文章+图片同目录 PaperMod 是技术博客最常见的主题之一：暗色模式、首页时间线、标签、归档、搜索都开箱即用。\n目录设计的取舍 最关键的决定是：每篇文章是一个文件夹（Hugo 称之为 Page Bundle）。\ncontent/posts/ └── 2026-05-08-博客搭建过程/ ├── index.md ← 正文 ├── 架构图.png ← 同目录引用 └── 部署流程.png 这样做的好处：\n一篇文章的所有资源（图片、附件、子页面）都在自己的目录里，移动/删除文章不会留下孤儿图片 Markdown 里直接用 ![](架构图.png) 引用，Obsidian 和 Hugo 都能正确解析 在 Obsidian 里把这个文件夹当作 vault 打开，所见即所得 与 Obsidian 的衔接方式 我没有把整个仓库当 vault，而是按需把要写的那一篇文章的目录用 Obsidian 打开。这样：\n写作时在 Obsidian 里专注一篇 图片直接拖进 Obsidian，会保存到当前目录 写完 commit、push，GitHub Actions 自动部署 部署链路 本地写作 (Obsidian/任意编辑器) │ ▼ git commit + git push origin main │ ▼ GitHub Actions: hugo --minify │ ▼ GitHub Pages: lizhao903.github.io/0xBroleez/ 整个链路无人工介入，从 push 到上线大约 1 分钟。\n接下来想做什么 写第二篇真正的项目复盘 配置评论系统（giscus 用 GitHub Discussions 当后端，零运维） 加个统计：访问量、阅读时长 小结 技术决策的关键不是「哪个最先进」，而是「哪个最匹配我的工作流」。对我来说：\n我用 Obsidian 写 markdown → 就用支持 page bundle 的框架 我代码托管在 GitHub → 就用 GitHub Pages 我想专注写作不想运维 → 就让 Actions 自动部署 每个决策都顺着已有习惯走，阻力最小。\n","permalink":"https://lizhao903.github.io/0xBroleez/posts/2026/05/%E7%94%A8-hugo--github-pages-%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2%E7%9A%84%E8%BF%87%E7%A8%8B%E5%A4%8D%E7%9B%98/","summary":"记录从空仓库到上线第一篇博客的完整过程：技术选型、目录设计、与 Obsidian 写作流的衔接，以及 GitHub Actions 自动部署。","title":"用 Hugo + GitHub Pages 搭建个人博客的过程复盘"},{"content":"2026 年 4 月 19 日，2026 北京银行北京城市副中心马拉松（暨\u0026quot;通北\u0026quot;马拉松）。\n净计时 5:06:20，号码 C18902，总排 5528 / 35-39 年龄段 1177。\n分段配速 分段 用时 累计 配速 0–5K 28:09 0:28:09 5:38/K 5–10K 27:16 0:55:25 5:27/K 10–15K 26:56 1:22:21 5:23/K 15–20K 28:49 1:51:10 5:46/K 半程 6:32 1:57:42 5:57/K 20–25K 25:43 2:23:25 5:09/K 25–30K 37:54 3:01:19 7:35/K 30–35K 43:28 3:44:47 8:42/K 35–40K 59:21 4:44:08 11:52/K 40K–终点 22:12 5:06:20 10:07/K 前 25K 配速稳定在 5:30/K 左右，30K 后明显掉速 — 经典撞墙节奏，留给下次的功课。\n","permalink":"https://lizhao903.github.io/0xBroleez/gallery/2026-04-19-%E5%89%AF%E4%B8%AD%E5%BF%83%E9%A9%AC%E6%8B%89%E6%9D%BE/","summary":"5:06:20 完赛 · 号码 C18902 · 总排 5528 / 35-39 年龄段 1177","title":"2026 北京副中心马拉松"},{"content":" 实施策略和因子验证过程中常用的术语速查。Ctrl-F 搜索最快。\n策略与组合管理 DCA · Dollar-Cost Averaging · 定额定投 按固定时间间隔（通常是月）从现金池投入固定金额到风险资产池，不择时。 平滑入场成本，避免一次性高位入场的择时焦虑。是被动投资者最常用基线。\nBuy-and-Hold (BH) · 买入并持有 最朴素的基线：一次性满仓买入持有到回测结束，期间不做任何操作。 所有主动策略都至少要跑得过 BH 才算「创造 alpha」。\nRebalance · 再平衡 按预设规则把组合权重调回目标权重。常见频率：\n定时再平衡：每月/每季度执行一次（不管偏离多少） 阈值再平衡：权重偏离目标超过 ±X% 才触发 Equal-Weight · 等权 所有标的占组合相同比例（如 6 只 ETF 各 1/6）。最朴素的分散方式。\nTilt · 因子倾斜 在等权基线上，按某个因子（动量、趋势、价值…）的强弱线性加减权重。 公式通常类似 weight_i = base_weight * (1 + α * z_score_i)，再 clip 到 [min, max]。\nValue Averaging (VA) · 价值平均法 DCA 的进阶版：不固定投入金额，而固定目标净值增长。市场跌了多投，市场涨了少投甚至赎回。 理论上比 DCA 多一层 mean-reversion 信号。\nThreshold Swing · 阈值做T DCA + 阈值再平衡的组合：每天检查权重，超阈值上沿（高估）卖出超额的一部分（高抛）， 低于阈值下沿（低估）从现金池补买（低吸）。冷却期防抖。\nRisk-on / Risk-off 市场状态二元判定。常见判据：大盘指数 \u0026gt; MA200 → risk-on（满仓风险池）；\u0026lt; MA200 → risk-off（撤回现金/债券）。\n业绩与风险指标 NAV · Net Asset Value · 净值 组合当前市值。回测起始 NAV = 初始资金（如 100,000）。\nTotal Return · 总收益 NAV_end / NAV_start - 1，整段回测的累计收益。\nCAGR · Compound Annual Growth Rate · 年化收益 (NAV_end / NAV_start) ^ (1/years) - 1。把总收益换算成年化，方便跨周期比较。\nAnnualized Volatility · 年化波动 日收益率标准差 × √252（A股交易日数）。\nSharpe Ratio · 夏普比率 (annual_return - rf) / annual_vol，单位风险换来的超额收益。 通常 \u0026gt; 1 算优秀，\u0026gt; 2 罕见，\u0026lt; 0 是负预期。\nMaxDD · Maximum Drawdown · 最大回撤 从历史最高净值跌到的最低点的跌幅。-30% 表示组合一度亏掉本金的 30%。 比 Sharpe 更直观体现\u0026quot;扛得住吗\u0026quot;。\nCalmar Ratio · 卡玛比率 CAGR / |MaxDD|。每承担 1% 的回撤换来多少收益。MaxDD 视角下的 Sharpe。\nExcess Return · 超额收益 策略收益 - 基准收益。基准通常是 510300 BH。\nAlpha · 超额收益（年化） 通常等于年化的 excess return（vs 基准）。S2 v1 vs BH 的 +1.89%/yr 就是 alpha。\nBeta · β 策略收益对基准收益的敏感度（回归系数）。β=1 跟基准同步，β\u0026gt;1 放大波动。\nIR · Information Ratio · 信息比率 Alpha / Tracking Error。每单位\u0026quot;主动暴露的风险\u0026quot;换来多少 alpha。 跨策略横向比较 alpha 含金量的最公允指标。\nTracking Error · 跟踪误差 策略 vs 基准超额收益序列的年化标准差。\u0026ldquo;主动管理偏离基准的程度\u0026rdquo;。\nTurnover · 换手率 年化交易额 / 平均组合规模。100% = 平均每年把组合卖一遍买一遍。 高换手意味着费用蚕食 alpha。\n因子与信号 Factor · 因子 对资产做出区分的可观测量：动量、价值、低波、规模、质量… 策略通过买入因子值高的、卖出因子值低的资产来获取 alpha。\nIC · Information Coefficient · 信息系数 某日所有标的的因子值 vs 它们次日（或 N 日后）收益的截面相关系数。\nIC 长期均值显著 ≠ 0 → 因子有预测力 |IC 均值| \u0026gt; 0.05 通常算可用，\u0026gt; 0.10 罕见且珍贵 IC IR IC 序列均值 / IC 序列标准差。因子稳定性指标。\nZ-score · 标准化分数 (x - cross_section_mean) / cross_section_std。截面标准化，让因子值\u0026quot;无量纲\u0026quot;， 方便跨标的比较和做线性 tilt。\nLookback · 回看窗口 计算因子时使用的历史天数，如 momentum_60d = 过去 60 个交易日的累计收益。 窗口越长越平滑、越短越敏感，是关键超参。\nVol-adjust · 波动率调整 把因子除以滚动波动率，让低波资产权重更高、高波资产权重更低，等价于 risk parity tilt。\nVol-target · 目标波动率 反向调整仓位让组合年化波动率接近某个目标值（如 12%）。 高波时降仓位，低波时加仓位。\nTime-Series Momentum (TSM) 单标的的\u0026quot;自身动量\u0026quot;：过去 N 天累计收益 \u0026gt; 0 才持有。S5 趋势倾斜本质就是 TSM。\nCross-Sectional Momentum 同一截面上\u0026quot;标的之间的动量排序\u0026quot;，按 z-score 倾斜。S4 动量倾斜用的就是这个。\nMA · Moving Average · 移动平均 价格的移动均值。常用 MA60 / MA120 / MA200 判断趋势。 价格 \u0026gt; MA → 上升趋势；价格 \u0026lt; MA → 下行趋势。\nDonchian Channel · 唐奇安通道 N 日最高价 / 最低价构成的通道。突破上轨 = 多头信号，跌破下轨 = 空头信号。\n验证方法 Smoke Test · 烟雾测试 用合成数据跑一遍策略代码，验证机械正确性（接口对、字段全、无 NaN、无未来函数）。 不验证 alpha，只验证\u0026quot;代码跑得通\u0026quot;。\nReal-data Backtest · 真实数据回测 用真实历史数据跑回测，输出 NAV / Sharpe / MaxDD 等核心指标。\nLookahead Bias · 未来函数 用了\u0026quot;未来才能知道\u0026quot;的信息做今天的决策（典型 bug：用当日收盘价决定当日开盘买入）。 任何策略代码都必须严格无前瞻。常见 fix：信号 shift(1) 一日。\nSample / Out-of-sample (IS / OOS) · 样本内 / 样本外 IS：用来调参的数据段 OOS：未参与调参、用来\u0026quot;打脸验证\u0026quot;的数据段 优秀策略的 OOS 表现应该和 IS 接近；OOS 显著退化 = 过拟合 Walk-Forward 滑动窗口的 IS-OOS 框架：在前 N 年调参 → 用第 N+1 年验证 → 滑动一年重复。 最严格的稳健性测试。\nAblation · 消融实验 逐个关闭策略的组件（如：只留 DCA、关闭再平衡、关闭因子倾斜）看哪部分真正贡献 alpha。 回答\u0026quot;alpha 来自哪里\u0026quot;的核心方法。\nSensitivity · 参数敏感性 扫描超参（lookback、阈值、α）的取值，看绩效是否稳定。 \u0026ldquo;参数稍微一动就崩盘\u0026rdquo; = 过拟合，不能上线。\nPool Ablation · 池子消融 固定策略、改变标的池（如 6 只 vs 11 只 ETF），分离\u0026quot;标的选择\u0026quot;和\u0026quot;策略本身\u0026quot;的贡献。 S4 v2 通过这个发现 alpha 主要来自扩池而非动量信号。\n标的与数据 A股 ETF · 中国 A 股 ETF 在 A 股市场（上交所/深交所）交易的指数 ETF。本博客策略主要在 6-11 只主流 A 股 ETF 池中做实验。\n510300 · 沪深300 ETF 华泰柏瑞沪深 300，本博客所有策略的默认基准。\n510500 · 中证 500 ETF 覆盖中盘股。\n159915 · 创业板 ETF 易方达创业板，成长股代表。\n512100 · 中证 1000 ETF 覆盖小盘股。\n512880 · 证券 ETF 证券行业，β 高、波动大。\n512170 · 医疗 ETF 医疗行业。\n511990 · 华宝添益（场内货币 ETF） T+0 货币基金 ETF。年化 ~2%，本博客的\u0026quot;现金池\u0026quot;载体。\n跨资产 / 跨市场池 A 股 ETF + 海外 ETF（如 513100 纳指、513500 标普）+ 商品（如 518880 黄金）+ 债券（如 511260 国债）。 S4 v2 用 11 只跨资产 ETF 做轮动。\nqfq / hfq · 前复权 / 后复权 处理拆分/分红后的价格序列。回测必须用复权价，否则除权日会出现\u0026quot;虚假大跌\u0026quot;。\n工程与实现 Strategy-Lib 本博客内容的源仓库 github.com/lizhao903/Strategy-Lib， 按 ideas/Sn_xxx/vN/ + summaries/Sn_xxx/vN/ 双目录维护策略全周期记录。\nPage Bundle Hugo 中\u0026quot;一篇文章是一个文件夹\u0026quot;的组织方式：index.md + 同目录的图片资源。 本博客的 content/posts/strategies/Sn_xxx_vN/ 都是 Page Bundles。\nFrontmatter markdown 文件顶部三个 --- 之间的 YAML 元数据块（title/date/tags/\u0026hellip;）。\nBenchmark Suite 一组共享数据窗口和成本假设的对照策略集合，确保横向对比的公平性。 本博客的 V1 = S1-S5 在同一时段、同一池、同一手续费下跑出的对照组。\n缺词条？直接在 content/glossary.md 文件里追加，文件结构是按主题分组 + 字母（或主题）排序的。\n","permalink":"https://lizhao903.github.io/0xBroleez/glossary/","summary":"\u003cblockquote\u003e\n\u003cp\u003e实施策略和因子验证过程中常用的术语速查。\u003ccode\u003eCtrl-F\u003c/code\u003e 搜索最快。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"策略与组合管理\"\u003e策略与组合管理\u003c/h2\u003e\n\u003ch3 id=\"dca--dollar-cost-averaging--定额定投\"\u003eDCA · Dollar-Cost Averaging · 定额定投\u003c/h3\u003e\n\u003cp\u003e按固定时间间隔（通常是月）从现金池投入固定金额到风险资产池，\u003cstrong\u003e不择时\u003c/strong\u003e。\n平滑入场成本，避免一次性高位入场的择时焦虑。是被动投资者最常用基线。\u003c/p\u003e\n\u003ch3 id=\"buy-and-hold-bh--买入并持有\"\u003eBuy-and-Hold (BH) · 买入并持有\u003c/h3\u003e\n\u003cp\u003e最朴素的基线：一次性满仓买入持有到回测结束，期间不做任何操作。\n所有主动策略都至少要跑得过 BH 才算「创造 alpha」。\u003c/p\u003e\n\u003ch3 id=\"rebalance--再平衡\"\u003eRebalance · 再平衡\u003c/h3\u003e\n\u003cp\u003e按预设规则把组合权重调回目标权重。常见频率：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e定时再平衡\u003c/strong\u003e：每月/每季度执行一次（不管偏离多少）\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e阈值再平衡\u003c/strong\u003e：权重偏离目标超过 ±X% 才触发\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"equal-weight--等权\"\u003eEqual-Weight · 等权\u003c/h3\u003e\n\u003cp\u003e所有标的占组合相同比例（如 6 只 ETF 各 1/6）。最朴素的分散方式。\u003c/p\u003e\n\u003ch3 id=\"tilt--因子倾斜\"\u003eTilt · 因子倾斜\u003c/h3\u003e\n\u003cp\u003e在等权基线上，按某个因子（动量、趋势、价值…）的强弱\u003cstrong\u003e线性\u003c/strong\u003e加减权重。\n公式通常类似 \u003ccode\u003eweight_i = base_weight * (1 + α * z_score_i)\u003c/code\u003e，再 clip 到 [min, max]。\u003c/p\u003e\n\u003ch3 id=\"value-averaging-va--价值平均法\"\u003eValue Averaging (VA) · 价值平均法\u003c/h3\u003e\n\u003cp\u003eDCA 的进阶版：不固定\u003cstrong\u003e投入金额\u003c/strong\u003e，而固定\u003cstrong\u003e目标净值增长\u003c/strong\u003e。市场跌了多投，市场涨了少投甚至赎回。\n理论上比 DCA 多一层 mean-reversion 信号。\u003c/p\u003e\n\u003ch3 id=\"threshold-swing--阈值做t\"\u003eThreshold Swing · 阈值做T\u003c/h3\u003e\n\u003cp\u003eDCA + 阈值再平衡的组合：每天检查权重，超阈值上沿（高估）卖出超额的一部分（高抛），\n低于阈值下沿（低估）从现金池补买（低吸）。冷却期防抖。\u003c/p\u003e","title":"术语"}]