← 文档库
📄 文档learn/择时策略/2026-08-11-三层优化落地行动计划.md
🌱 创建于2026-08-20 10:30
✏️ 修改于2026-08-20 10:30
👁️ 浏览量—
🔗 短链接/~learn/s/fe08d80c
🔐 可见性

三层择时优化 · 落地行动计划(v2 · 回测驱动)

> 2026-08-11 · v2 重大调整:回测是第一公民。v1 把回测弱化成两个"验证任务",v2 改为回测驱动开发(Backtest-Driven Development)——先量化现状差距(基线回测)→ 实现 → 回测对比 → 不达标不进下一阶段。 > > 配套探讨文档:2026-08-11-牛熊判断与牛市仓位优化探讨.md(v3,讲"为什么这么设计") > > 本文回答三个问题: > 1. 迁移成本有多大? —— 零迁移成本,纯加法 overlay(详见第一章)。 > 2. 回测怎么做? —— 这是本文重头戏:基线回测先行、Walk-Forward 防过拟合、分市场状态拆解、图表化报告(详见第二章)。 > 3. 怎么一步步落地? —— 四阶段 15 任务,每个阶段以"回测达标"为进入下一阶段的硬门槛(详见第三章)。

---

一、迁移成本评估(结论不变:零迁移成本)

1.1 现有数据流(已核实)

timing_config.py (硬编码注册30+策略)
    ↓
show_marker.py L2263  for code in codes_to_process:
    └─ load_mult_market_strategy(...)            # L2278 ★ 唯一核心入口
         ├─ calculate_portfolio_metrics()         # 逐策略信号→加权日收益
         ├─ calculate_average_holding_ratio()     # ★ position% = Σ(stock_value_ratio × weight),0~100
         ├─ 存 last_timing_positions[code]        # L161-185 {predicted/today/yesterday}
         └─ 返回推送文本
    ↓
推送消息 L2297~2951 / 网页"今日仓位" / 持久化DB / 回测图表  ← 全部读 last_timing_positions

1.2 改动范围

模块 改动
------
30+ 策略池 / Strategy 基类 / timing_config.py ❌ 零改动
截面选基 / 推送链路 / 每日调度(run_pushd.sh) ❌ 零改动
MarketAnalysis.load_mult_market_strategy ⚠️ 插入 1 个受开关保护的调用(默认关)
新增 timing_overlay/ 包 🆕 L1/L2/L3/合成器 + 回测引擎 + 报告生成
测试 🆕 unittest(项目现状),补齐策略零测试空白

---

二、回测工作流(本文核心)

2.1 为什么回测必须是第一公民

这套优化要回答的是数据问题,不是代码问题:

不先做基线回测,就没有"改之前的真相";不对比回测,就没有"改之后的有效证明"。

2.2 数据就绪性(已实测核查 ✅)

data_cache/ 里是全历史日线缓存(run_data_fetcher.py 每日 08:45/23:10 全量刷新),不是近期窗口:

品种 缓存行数 约覆盖
---------
399102.SZ 创业板综 3935 行 ≈2010 至今(16 年)
510880 红利ETF 4745 行 ≈2007 至今(19 年)
其余 23 个已配置品种 各有 tushare/adata 双源 多数覆盖 2010+

结论:Walk-Forward 回测所需 2015-01~2026-08 数据已就位,无需新拉数据(走 DataCacheManager.load_best,见 data_cache.py)。阶段三涉及的 QQQ/GLD(alpha 源)在数据准备任务中单独核查。

2.3 回测口径(防未来函数)

项 口径
------
数据 DataCacheManager.load_best(code, start, end),与生产同一缓存
策略 timing_config.build_allocation_manager() 现成策略池,参数不动(只验证 overlay,不重调策略参数)
信号 直接用现有 position 列(内部已有 signal_offset shift 机制,避免未来函数),overlay 只改这一列
overlay 逐日状态机:apply_overlay_series(code, df, pos_raw) 每日用"当日及以前"数据更新状态
品种 全部 23 个已配置品种(含 short- 空头变体)
周期 2015-01 ~ 2026-08(11.5 年,覆盖 2019-21 牛市 / 2022 熊市 / 2023-24 震荡 / 2025-26 牛市)

2.4 回测指标体系(对比维度全,才叫"详细回测")

每品种、每窗口、现状 vs overlay 两套,输出同一套指标:

类别 指标
------
收益 总收益、年化收益、超额收益(vs 大盘)
风险 最大回撤、平均回撤、最长回撤持续期(关键!验证低回撤特质)
效率 夏普、索提诺、卡玛(收益/最大回撤)
择时 择时增值(策略收益−大盘收益×平均仓位)、平均仓位、牛市参与度(牛市期平均仓位)
行为 状态切换次数/年(验证"震荡市不被磨"的核心证据)、trailing stop 触发次数、触发后 30 日恢复率
市场状态拆解 分牛市/熊市/震荡三态的收益与回撤(直接检验质疑 1、2)

2.5 回测报告产物(图表化,入文档库)

每阶段回测生成一份报告,放在 learn/投资复盘/(与实盘复盘报告同级,自动收录文档库):

格式沿用实盘复盘报告风格(深色主题、内嵌 SVG、可分享截图)。

---

三、行动项清单(四阶段 · 15 任务)

> 节奏:每阶段 = 先建回测基线 → 再实现 → 回测对比 → 达标才进下一阶段。所有任务 TDD(先写失败测试→实现→跑通→commit),unittest 风格。

---

阶段零:回测基础设施 + 现状基线(改任何代码之前先量化差距)

Task 0.1:回测引擎骨架(数据加载 + 指标计算)

Files:

Step 1: 写失败测试(指标计算的纯函数)

# tests/adata_test/quant/test_backtest_metrics.py
import unittest
import numpy as np
from adata.aohan.quant.timing_overlay.backtest.metrics import (
    compute_drawdown, sharpe, sortino, calmar, timing_value_added)

class TestMetrics(unittest.TestCase):
    def test_drawdown(self):
        # 净值 1.0 → 0.9 → 1.0:最大回撤 10%
        nav = np.array([1.0, 1.1, 0.99, 1.05])   # 高点1.1 → 低点0.99 = 10%
        dd = compute_drawdown(nav)
        self.assertAlmostEqual(dd.max(), 0.10, places=2)

    def test_sharpe_annualized(self):
        # 每日收益率恒定 0.001,年化夏普应 > 0
        daily_ret = np.full(252, 0.001)
        s = sharpe(daily_ret)
        self.assertGreater(s, 0)
        # 无波动率时应返回 0(不炸)
        self.assertEqual(sharpe(np.zeros(100)), 0.0)

    def test_timing_value_added(self):
        # 策略收益 10%,大盘 15%,平均仓位 50% → 择时增值 = 10-7.5 = 2.5
        v = timing_value_added(strat_ret=0.10, bench_ret=0.15, avg_pos=0.5)
        self.assertAlmostEqual(v, 0.025)

Step 2: 跑失败 — env -u PYTHONHOME -u PYTHONPATH .venv/bin/python -m unittest tests.adata_test.quant.test_backtest_metrics -v Expected: FAIL(ModuleNotFoundError) Step 3: 实现 — metrics.py:drawdown(rolling max)、sharpe(日收益×√252)、sortino(下行波动)、calmar(年化/最大回撤)、timing_value_added(择时增值定义);engine.py:load_backtest_df(code, start, end) 封装 DataCacheManager.load_best + run_baseline(code) 调 load_mult_market_strategy 取 processor.df['position']。 Step 4: 跑通过 Step 5: Commit — git commit -m "feat: 回测引擎骨架与指标计算"

---

Task 0.2:现状基线回测(10 年 Walk-Forward · 改代码前先量化差距)

Files:

Step 1: 写失败测试(walk_forward runner:给定窗口列表,输出每窗口指标 dict)

# tests/adata_test/quant/test_walk_forward.py
import unittest
from adata.aohan.quant.timing_overlay.backtest.walk_forward import (
    WINDOWS, build_windows)

class TestWalkForward(unittest.TestCase):
    def test_seven_windows(self):
        self.assertEqual(len(build_windows()), 7)
        # 每窗口:训练集早于验证集
        for w in build_windows():
            self.assertLess(w['train_end'], w['valid_start'])

Step 2: 跑失败 → Step 3: 实现 — 7 窗口(2015-18 训练/2019 验证 … 2015-24 训练/2025-26 验证,见探讨文档第八节);对 23 品种 × 7 窗口跑 run_baseline,输出每窗口的指标表。 Step 4: 跑通 — 性能注意:23 品种 × 30+ 策略 × 10 年,单次全库可能数分钟到数十分钟;用 ThreadPoolExecutor(max_workers=6)(参考 fetch_data_batch)并行品种,指标可先只跑 3 个代表品种(399102 创业板/510880 红利/510300 沪深300)快速迭代,全量最后跑。 Step 5: Commit — git commit -m "feat: 现状基线Walk-Forward回测(7窗口×23品种)"

---

Task 0.3:基线回测报告(量化差距 · 里程碑 M0)

Files:

Step 1: 报告生成器(净值曲线/回撤曲线/分市场柱状图/指标表,复用实盘复盘报告的内嵌 SVG 风格)。 Step 2: 跑全量基线,产出基线报告。 Step 3: 分析并量化三个差距: - 牛市踏空幅度:23 品种 10 年牛市期平均仓位 vs 大盘收益; - 震荡磨损量级:状态切换次数/年 + 震荡期择时增值; - 低回撤特质:最大回撤/平均回撤 vs 大盘(应与实盘复盘结论一致)。 Step 4: Commit — git commit -m "docs: 择时现状基线回测报告(10年Walk-Forward)"

> 里程碑 M0(硬门槛):基线报告产出,三个差距量化成数字。不达标不写 overlay 一行代码。

---

阶段一:L1 + L3 实现(纯价格,零外部依赖)

Task 1.1:timing_overlay 包骨架 + 开关配置
(同 v1 Task 1,内容不变:config.py 三层开关默认全关、参数表)

Task 1.2:L1 趋势状态机(组合层 ADX + MA60 + 滞回 + 共识度)
(同 v1 Task 2:layer1_trend_state.py,含 TrendStateMachine.update(df, consensus, rs_pass) 与 compute_consensus)

Task 1.3:L3 双触发 trailing stop + 再进入规则
(同 v1 Task 3:layer3_drawdown_protect.py,含指数 ATR 触发 + 净值硬止损 + 分 3 日再建仓)

Task 1.4:overlay 合成器(主开关 + min 封顶 + 事件驱动)
(同 v1 Task 4:position_overlay.py 的 apply_overlay,公式见探讨文档第六节)

Task 1.5:overlay 逐日序列版(回测用)—— 回测与实盘共用同一逻辑

Files:

Step 1: 写失败测试(序列版与逐点版等价:同一输入序列,逐日推进状态机,输出与手动逐点调用一致;特别测:状态一旦进入 attack,回调 5% 不出局(滞回),回撤 8% 触发减半等)

class TestOverlaySeries(unittest.TestCase):
    def test_series_equals_pointwise(self):
        # 同一 df 上,apply_overlay_series 的输出 == 手动循环 apply_overlay
        ...

    def test_hysteresis_keeps_attack_on_5pct_pullback(self):
        # 进攻态下回调5%(<缓冲带),状态不变
        ...

Step 2: 跑失败 → Step 3: 实现(apply_overlay_series:遍历 df 行,维护 L1/L3 状态对象,逐日调用 apply_overlay,返回新 position 序列)→ Step 4: 跑通 Step 5: Commit — git commit -m "feat: overlay逐日序列版(回测/实盘共用)"

Task 1.6:L1+L3 回测对比(vs 基线 · 里程碑 M1)

Files:

Step 1: 打开 L1+L3 开关,7 窗口 × 23 品种重跑。 Step 2: 对比基线报告,检查硬指标:

指标 硬门槛
------
状态切换次数/年 ↓ 60%+(震荡市不被磨的实证)
最大回撤(vs 基线) ≤ 基线(不能为加仓牺牲低回撤)
牛市参与度(牛市期平均仓位) 40% → 60~80%
择时增值(每窗口) 不得比基线差(至少持平)
trailing 触发后 30 日恢复率 > 70%
分市场拆解 震荡市择时增值 ≥ 基线(磨耗下降)

Step 3: 生成 learn/投资复盘/2026-08-11-择时L1L3回测对比.html,与基线报告并排对比。 Step 4: Commit — git commit -m "docs: L1+L3回测对比报告"

> 里程碑 M1(硬门槛):切换次数 -60% 且最大回撤不恶化。不达标→回到 Task 1.2/1.3 调参,禁止进入阶段二。

Task 1.7:接入生产链路(受开关保护,默认关)+ 实盘 A/B

Files:

Step 1: 写失败测试(回归:开关全关时 load_mult_market_strategy 行为与改动前逐字节一致) Step 2: 实现(受 config.set_overlay_config 环境变量控制) Step 3: 跑通回归测试 Step 4: 手动 AH_OVERLAY=1 bash run_push.sh,对照"消息推送.txt"今日仓位差异。 Step 5: Commit — git commit -m "feat: overlay接入生产链路(默认关,回归安全)+A/B旁路"

---

阶段二:L2 估值刹车

Task 2.1:L2 估值档位(PE/PB 分位 + 品种类型适配 + 5 年滚动分位修正)

Files:

Step 1: 写失败测试

Step 2: 跑失败 → Step 3: 实现(数据走 ValuationFetcher.fetch(code) 的 pe_pct/pb_pct;注意缓存文件已有 399006.SZ_valuation_1d_valuation.csv、000300.SH_valuation_1d_valuation.csv 等)→ Step 4: 跑通 Step 5: Commit — git commit -m "feat: L2估值档位(品种适配+5年滚动分位)"

Task 2.2:接入 overlay 合成器(GLD/QQQ 禁用、红利放宽)

Files:

Step 1: 失败测试(L2 返回 None → 不封顶;红利 → 80% 上限) Step 2~4: 实现 + 跑通 Step 5: Commit — git commit -m "feat: L2接入overlay合成器"

Task 2.3:三层全开 Walk-Forward 复测 + 报告(里程碑 M2)

Files:

Step 1: 三层全开重跑 7 窗口。 Step 2: 检查硬指标:

指标 硬门槛
------
牛市品种超额收益 收敛到 -5pp 内(vs 基线 -15~-20pp)
最大回撤 ≤ 基线
夏普 牛市品种翻倍,其他不下降
参数稳定性 7 窗口选出的参数不跳变(K_ATR/估值档位阈值)
择时增值 每窗口为正(稳定性)

Step 3: 生成三层对比报告,与基线/L1L3 报告三线并排。 Step 4: Commit — git commit -m "docs: 三层全开Walk-Forward复测报告"

> 里程碑 M2(硬门槛):牛市超额收敛到 -5pp 且回撤不恶化。达标后开启生产 L2 开关进行实盘 A/B。

---

阶段三:增强项(需要新建数据管道)

Task 3.1:跨品种相对强度(RS)过滤

Task 3.2:情绪/宽度数据管道

Task 3.3:Breadth 预警 + 海外 ETF 估值补全

---

四、回测达标门槛总表(不达标不进下一阶段)

里程碑 内容 硬门槛 卡口
------------
M0 基线回测报告 三个差距量化成数字 未量化不写 overlay
M1 L1+L3 回测 切换次数 -60% 且回撤 ≤ 基线 未达标调 L1/L3 参数
M2 三层全开回测 牛市超额 -5pp 内 + 回撤 ≤ 基线 + 参数稳定 未达标调 L2 参数/口径
M3 增强项复测 各增强项相对三层不恶化 + 有量化收益 未达标砍掉该项

回测是门槛,不是装饰:每阶段回测报告 + 指标对比表都要落到 learn/投资复盘/,成为可追溯的决策依据。

---

五、依赖与风险

依赖/风险 说明 缓解
---------
回测耗时 23 品种×30+策略×10年×7窗口 先 3 代表品种快速迭代;ThreadPool 并行;指标可缓存
策略参数是否要在回测中重调 不重调!只调 overlay 参数 overlay 与策略解耦,才能归因"改进来自 overlay"
valuation_fetcher 分位是全历史 探讨文档要求 5 年滚动 Task 2.1 加 rolling_window 参数,默认行为不变
QQQ/GLD 历史估值 阶段三才需要 阶段一/二 L2 对它们禁用,不阻塞
show_marker.py 3181 行 阶段三 RS 需改主循环 只加"算排名传参"几行,不重构
项目无 pytest unittest 现状 新测试全 unittest,与 tests/ 一致

---

六、里程碑与产物一览

里程碑 产物 位置
---------
M0 基线回测报告 learn/投资复盘/2026-08-11-择时基线回测.html
M1 L1+L3 对比报告 learn/投资复盘/2026-08-11-择时L1L3回测对比.html
M2 三层对比报告 learn/投资复盘/2026-08-11-择时三层回测对比.html
M3 增强项复测 追加到三层报告
生产 三层 overlay 接入(开关默认关) timing_overlay/ + MarketAnalysis.py
配套 探讨文档(决策依据) learn/择时策略/2026-08-11-牛熊判断与牛市仓位优化探讨.md

▶ 📜 变更历史(1 次提交)

e34e9f82026-08-20 10:30docs: 提交learn目录新增文档
🍃quant 知识库
by 伊甸园和枫

🔊 播放设置

勾选音源后调节配比,总和自动归一化
已选 0 个 · 合计:0%
合成中...
00:00/00:00
1.0x

分享图片

生成中...