Files
xquant-beginner/book/q8-iteration.md
T

536 lines
36 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 第八章:让策略活着:监控、诊断、迭代
第七章你搭好了从回测到实盘的桥梁——订单生成、成交价压力测试、模拟交易、执行报告。策略终于跑起来了。
**然后你开始每天看净值。**
上个月赚了 3%,开心。这个月亏了 5%,慌了。盯着那条往下走的曲线,你脑子里全是问号——策略是不是坏了?要不要停?要不要改?还是再等等?
别慌。这一章教你三件事:怎么看(监控)、怎么查(诊断)、怎么改(迭代)。
**飞轮转回起点了。** 这一章你站在飞轮的“归因层”——但和第五章不同,第五章你“看”的是离线的回测;这一章你“看”的是活着的实盘。
本书口号“做要规则、看要指标、还要怀疑你的指标”——做(监控规则)、看(仪表盘指标)、疑(诊断三问),三个动作在这一章被你**第二次**走了一遍。
### 路线图
**选什么标的(第二章)→ 每个买多少(第三章)→ 什么时候买卖(第四章)→ 怎么验证有效(第五章)→ 如何避免自欺欺人(第六章)→ 如何真正执行(第七章)→ 如何持续改进(第八章本章)**
本章从“盯着净值焦虑”的状态出发,用三个步骤建立系统性的迭代能力,三个步骤的问题与方法对照如表 8-1 所示。
**表 8-1 第八章三步迭代框架——问题与方法对照**
| 步骤 | 问题 | 方法 |
|------|------|------|
| 第 1 步 | 策略是不是坏了? | 监控仪表盘——用指标替代焦虑 |
| 第 2 步 | 问题出在哪? | 诊断三问——由外到内定位原因 |
| 第 3 步 | 怎么改? | 假设驱动的对照实验 |
(操作流程见前言“怎么使用这本书”。)
---
## 8.1 策略是不是坏了?
上个月赚了 3%,这个月亏了 5%。你盯着净值曲线,心里发慌。
但“净值在跌”这个信息太粗糙了——就像你身体不舒服,光说“不舒服”没用,得量体温、测血压、做检查。策略也一样,需要一个“仪表盘”,用具体的指标告诉你:现在到底是正常波动,还是真的出了问题。
我们用三个指标构成监控仪表盘,三者覆盖的角度如表 8-2 所示。
**表 8-2 监控仪表盘的三个核心指标**
| 指标 | 含义 | 类比 |
|------|------|------|
| **滚动夏普**(最近 3 个月) | 最近 3 个月的风险调整收益 | 体温计——正常在 0 以上,跌破 0 说明在“发烧” |
| **滚动回撤** | 从近期高点到现在跌了多少 | 水位线——持续下降说明“水在退” |
| **对比沪深 300 多赚了多少**(你 - 沪深 300) | 同期你的策略和沪深 300 指数的差额 | 跑步比赛——持续落后说明跑法有问题 |
### 动手实验 1:监控仪表盘
这是本章第一份“运营 spec”,和前七章那种“建一个新功能”不同——这里我们要写的是**怎么周期性地看一个已经在跑的策略**。我们一起把这份 spec 写出来。这次重点看两件新东西:**心智切换**——上下文段第一句话就要告诉 AI“这不是建造,是体检”;以及**周期性运营 spec 的四要素**——触发条件 / 频率 / 阈值 / 输出形态全部显式。
#### 起草上下文 + 任务
上下文段开头那句“用具体指标替代焦虑”是整个第八章的总动机。运营章 spec 的上下文不必交代环境怎么搭,要交代**学员现在的情绪状态和场景**:
> **上下文**:学员跑完第七章,策略终于在实盘运行。但上个月赚了 3%,这个月亏了 5%——是正常波动还是策略出了问题?需要一个“仪表盘”,用具体指标替代焦虑。
>
> **任务**:在 notebook 中跑基准回测后,用 `StrategyMonitor` 构建监控仪表盘,画三个滚动指标的时间序列图,并自动检测恶化时段。
> 📌 **要点**:运营 spec 的上下文要交代“心智切换”。前七章的 spec 是“建一个新功能”,这一章开始是“看一个已有功能的健康状态”。学员如果带着建造者的姿态去做运维,看了 30 个图也不知道在干嘛——上下文一句话点明“用指标替代焦虑”,就是给学员(也给 AI)定调子:这次的产出不是新代码,是健康判断。
#### 起草要求:周期性运营 spec 的四要素
要求段是这份 spec 的主体——重点是把“什么时候触发监控、多久跑一次、什么数值算异常、异常了怎么输出”四件事**全部显式写明**:
> **要求**
>
> 1. 跑基准回测(`SimBroker` + 风险平价 + 21 天调仓 + 10% 止损),拿到 `result_base`
> 2. 用 `StrategyMonitor(result_base, benchmark="510300.SS", roll_window=63)` 获取滚动指标
> - `roll_window=63` ≈ 3 个月交易日(21 天/月 × 3),太短(<20)每天报警、太长(>120)警报来时已经亏了半年
> 3. 画四行子图(figsize 14×16,共享 x 轴):净值曲线 / 滚动夏普(标零线和 1 线)/ 滚动回撤 / 相对沪深300超额收益,红色填充异常区域
> 4. 自动检测恶化时段——告警规则显式声明:
> - **触发条件**:滚动夏普 < 0 且持续 ≥ 20 个交易日
> - **频率**:每月例行检查一次;第 1 步发现恶化时立即触发第 2 步诊断
> - **阈值**`sharpe_threshold=0.0, min_days=20`(在 `StrategyMonitor` 构造时锁定)
> - **输出形态**:遍历 `monitor.bad_periods`,每段一行打印起止日期、持续天数、平均夏普
> 5. 打印结尾过渡句:“2022 年集中出现恶化,原因是什么?”——把读者推向第 2 步
> 📌 **要点**:周期性运营 spec 的告警四要素——触发条件 / 频率 / 阈值 / 输出形态——必须全部显式。建造类 spec 的“成功标志”是“代码跑通、图渲染出来”;运营类 spec 的“成功标志”是“在该报警的时候报了警、不该报的时候没报”。**任何一个要素藏在 oxq 库默认参数里、没写进 spec,半年后阈值变了你也不知道**——这是运营 spec 区别于 q1-q7 建造 spec 的核心方法点。
> 📌 **要点**:关键常量旁边要写“为什么是这个数”。`roll_window=63` 不是随便选的——它是行业惯例(约 3 个月交易日),理由也要进 spec。运营场景里的每一个数字都是阈值,阈值的合理性比数值本身更重要。
#### 起草结果呈现
> **结果呈现**:四行仪表盘图正常渲染 + 恶化时段列表非空(2022 年应有多个时段)+ `equity`、`daily_ret`、`result_base`、`monitor` 变量已定义(后续 spec 接续使用)。
完整 spec 在 [`specs/spec-01-monitoring.md`](https://github.com/xingwudao/xquant-learning/blob/main/q8-iteration/specs/spec-01-monitoring.md)——复制给 AI,弹窗选「允许」。
AI 助手执行完毕后,你的 notebook 里应该出现了基准回测结果和一张四行的监控仪表盘图。
这个实验做了什么?先用 第四、七章 同款的风险平价策略(沪深300ETF + 纳指100ETF + 黄金ETF21 天调仓 + 10% 止损)跑一次从 2021 年到现在的完整回测,拿到净值曲线。然后把净值喂给 `StrategyMonitor`——它就是那个“体检中心”,自动计算三个滚动健康指标(滚动夏普、滚动回撤、相对基准超额收益),并标注出指标持续恶化的时段。
### 运行结果
先看基准回测:累计收益 86.45%,年化 12.50%,最大回撤 -16.62%,夏普 1.17,共 184 笔交易。
监控仪表盘的整体效果如图 8-1 所示。
![监控仪表盘——四个指标同时恶化的时段就是“危险区”](images/q8-iteration/01-monitoring-dashboard.png)
图 8-1 第一行是净值曲线,整体向上但中间有几段明显的下跌。第二行是**滚动夏普**——红色填充区域就是夏普跌破 0 的时段,说明最近 3 个月策略在亏钱。第三行是回撤,持续加深说明策略在“失血”。第四行是相对沪深300的超额收益,为负说明还不如直接买指数。
仪表盘自动检测到的恶化时段如表 8-3 所示。
**表 8-3 监控仪表盘自动检测到的恶化时段**
| 起始日期 | 结束日期 | 持续天数 | 平均夏普 |
|----------|----------|----------|----------|
| 2022-02-07 | 2022-04-29 | 58 天 | -1.78 |
| 2022-05-05 | 2022-08-10 | 68 天 | -2.25 |
| 2022-08-29 | 2022-09-30 | 23 天 | -1.13 |
| 2022-10-10 | 2022-12-01 | 39 天 | -1.02 |
| 2023-10-18 | 2023-11-14 | 20 天 | -0.84 |
| 2023-12-05 | 2024-01-03 | 21 天 | -0.91 |
2022 年集中出现了 4 段恶化,最长的一段持续了 68 天,平均夏普 -2.25。
### 结果解读
仪表盘让你从“盯着净值焦虑”变成“看指标判断”。
滚动夏普跌破 0,说明最近 3 个月策略在亏钱——这是最直接的“发烧”信号。回撤持续加深说明策略在“失血”。相对沪深300的超额收益为负,说明你还不如直接买指数。
三个指标同时恶化的时段就是需要关注的“危险区”。2022 年出现了多段集中恶化——但**发现问题不等于知道原因**。
是执行出了问题?市场变了?还是策略本身有缺陷?
---
## 8.2 问题出在哪?
第 1 步告诉你“最近不对劲”——但不对劲有很多种原因。
在开始诊断之前,先认识一个本节的关键概念:
**市场状态(regime** ——市场不是一成不变的。有时候波动大、几乎所有资产一起剧烈起伏(“高波动”),有时候很平静(“低波动 / 正常”)。就像天气有晴天和台风天,同一件衣服在不同天气穿感受完全不同。策略也一样——一个在正常市场表现不错的策略,到了高波动状态可能完全失灵。**诊断三步里有两步都和它相关**——所以我们前置介绍它。
就像身体不舒服,你不会直接吃药。先量体温排除感冒,再查血排除感染,最后做 CT 看看有没有更深的问题。策略诊断也一样,我们由外到内排查三层:
1. **是执行的问题吗?** —— 回测和实盘之间的差距有没有异常放大
2. **是市场状态(regime)变了吗?** —— 市场的波动水平有没有发生大的变化
3. **是策略本身的设计问题吗?** —— 参数在不同时段的表现是否稳定
### 动手实验 2:诊断三问
我们一起把这份 spec 写出来。这次重点看两件新东西:**一份 spec 包多步诊断的取舍**——前七章每份 spec 只做一件事,这份一次塞进三步诊断;以及**阈值事前锁定**——什么样的数字算“通过”、什么样算“失败”,必须在跑诊断之前定好,不能跑完了再看图说话。
#### 起草上下文 + 任务
接续型 spec 链的上下文要写明前置 spec 留下了哪些变量、本份 spec 在叙事里的位置:
> **上下文**:在 `q8-iteration.ipynb` 中已有第 1 步监控仪表盘和恶化时段检测。第 1 步发现了多段恶化时段(2022 年集中出现),现在需要搞清楚原因。由外到内排查三层:执行 → 市场 → 策略。
>
> **任务**:完成三步诊断——排查执行落差、检测市场状态变化、检查参数稳定性。这三步是不可拆分的方法论,必须按“由外到内”的顺序执行。
> 📌 **要点**q1-q7 的写法是“一份 spec 一件事”,这份是“一份 spec 三件事”——叙事完整性优先于任务原子性。诊断方法论本身就是一个不可拆分的整体,拆成三份 spec 学员会丢掉“先排外因再查内因”的节奏。但代价是 spec 体积变大,所以**必须在任务段第一句就明示“这三步必须按顺序”**——给 AI 也给学员定下“这是一个流程,不是三个独立任务”的预期。
#### 起草要求:阈值事前锁定
要求段是这份 spec 的主体——三步诊断每一步都要把“通过 / 失败”的判定阈值事前写明,绝不能“看图说话”:
> **要求**
>
> 1. **诊断 1——执行落差排查**:
> - 跑“理想回测”(收盘价 + 佣金,无滑点)和“模拟实盘”(次日开盘 + 佣金 + 千分之一滑点)
> - 计算两条净值曲线的滚动执行落差(63 日滚动均值)
> - **判定阈值**:落差走势平稳(每 63 日变化标准差 < 0.5%)→ 排除执行问题;若突然放大则结论改为“需进一步检查滑点 / 佣金模型”
> - 把落差放到学员能理解的语境中:N 年赚了 X%,其中 Y% 被执行成本吃掉
>
> 2. **诊断 2——市场状态检测**:
> - 用 `MarketStateDetector(result_base)` 检测市场状态
> - 分状态打印策略表现(年化收益、夏普、天数)
> - **判定阈值**:高波动状态下夏普 < 0 且与第 1 步恶化时段重叠 ≥ 70% → 结论“市场状态变了,不是策略 bug”
> - 画两行子图:净值曲线 + 高波动红色阴影 / 市场波动率 + 中位数线 + 高波动阈值线
> - `detector`、`high_vol_mask`、`market_vol`、`HIGH_VOL_LINE` 变量必须已定义(第 3 步接续使用)
>
> 3. **诊断 3——参数稳定性检查**:
> - 把数据分成前后两半(按交易日中点切分)
> - 对 5 种调仓频率(10/15/21/42/63 天)分别在两半上跑回测
> - **判定阈值**:用 `scipy.stats.kendalltau` 计算前后两半夏普排序的 tau(Kendall tau 是衡量两组排序一致性的统计量,取值 -1 到 1,越接近 1 说明前后两段的最优参数排序越像);tau ≥ 0.7 → 排序稳定,参数没过拟合;tau < 0.7 → 参数对时段敏感
> - 打印对比表:频率、前半段年化、前半段夏普、后半段年化、后半段夏普
> 📌 **要点**:诊断 spec 的核心是“事前锁定阈值”——绝不能“看图说话”。建造类 spec 的失败长什么样很明显(代码报错、图渲染不出来);诊断类 spec 的失败是隐式的——你跑完看到一个图,凭直觉说“看起来没问题”。**只要阈值不事前锁定,诊断就退化成 confirmation bias**——你想看到什么结论就能从图里读出什么结论。每一步诊断都必须配一个“通过 X、失败 Y”的数字阈值。
> 📌 **要点**:spec 不能给 AI“二选一”。如果你写“用 Kendall tau 或简单比较都行”,AI 选哪种你就拿到哪种结果,下次重跑可能换一种实现,结果不可比。**spec 是契约不是协商**——能确定一种方案就锁死一种,留 AI 自由发挥的余地越小,诊断越可复现。
#### 起草结果呈现
> **结果呈现**:三步诊断各一组图 + 数字结论;`detector`、`high_vol_mask`、`market_vol` 变量已定义供第 3 步使用。
完整 spec 在 [`specs/spec-02-diagnostics.md`](https://github.com/xingwudao/xquant-learning/blob/main/q8-iteration/specs/spec-02-diagnostics.md)——复制给 AI,弹窗选「允许」。
AI 助手执行完毕后,你的 notebook 里应该出现了三步诊断的完整结果。
这个实验做了什么?由外到内排查三层原因:
- **诊断 1(执行)**:跑两次回测——一次用“理想条件”(收盘价成交、只有佣金),一次用“模拟实盘”(次日开盘价 + 佣金 + 滑点),对比两条净值曲线的差距是否在正常范围内。
- **诊断 2(市场)**:用 `MarketStateDetector` 自动检测市场的波动率水平,把每一天标记为“高波动”或“正常”,然后分别统计策略在两种状态下的表现。
- **诊断 3(参数)**:把整段回测数据按时间中点切成前后两半(前半段大致覆盖 2021-2023 上半年的高波动市场,后半段覆盖 2023 下半年至今的恢复期),在两半上分别测试 5 种调仓频率,看最优参数在不同时段是否稳定。
### 运行结果
按“由外到内”的顺序看三步诊断的输出。
#### 诊断 1 — 执行落差排查
理想回测收益 92.13%,模拟实盘收益 83.54%,执行落差累计 8.59%,每年约 1.72%。
换个方式理解:策略 5 年累计赚了 92%,其中 8.6% 被佣金、滑点等执行成本吃掉了。相当于每年的执行成本约 1.7%——对于月度调仓的策略来说,这是正常水平。
更重要的是执行落差的走势是平稳的,没有突然放大——说明执行层面没有异常。如果这条线突然变陡(落差加速扩大),才说明执行出了问题。
排除执行问题。
#### 诊断 2 — 市场状态检测
回顾 8.2 节首讲过的**市场状态(regime)**——这一步我们用 `MarketStateDetector` 把每一天自动标记为高波动 / 正常 / 低波动,再看策略在不同状态下的表现差异。结果三档分得很开——表 8-4 一目了然。
策略在不同市场状态下的表现差异如表 8-4 所示。
**表 8-4 策略在不同市场状态下的表现**
| 市场状态 | 年化收益 | 夏普 | 天数 |
|----------|----------|------|------|
| 高波动 | -1.44% | -0.08 | 260 天 |
| 正常 | 16.62% | 1.80 | 940 天 |
| 低波动 | 34.72% | 6.32 | 36 天 |
策略净值与高波动时段的叠加如图 8-2 所示。
![策略净值 + 高波动时段——恶化和高波动高度吻合](images/q8-iteration/02-market-state.png)
图 8-2 的红色阴影区域就是高波动时段。和第 1 步检测到的恶化时段一对比,高度吻合——策略在高波动环境下年化收益 -1.44%,夏普 -0.08,在亏钱。而正常时段年化 16.62%,夏普 1.80,表现优秀。
恶化的原因是市场状态变了,不是策略的 bug。
#### 诊断 3 — 参数稳定性检查
把数据按时间中点切成前后两半,对 5 种调仓频率分别跑回测,结果如表 8-5 所示。
**表 8-5 5 种调仓频率在前后两半段的表现**
| 频率 | 前半段年化 | 前半段夏普 | 后半段年化 | 后半段夏普 |
|------|-----------|-----------|-----------|-----------|
| 10 天 | +3.30% | 0.40 | +25.78% | 2.17 |
| 15 天 | +1.91% | 0.25 | +23.76% | 2.02 |
| 21 天 | +2.18% | 0.28 | +23.13% | 1.89 |
| 42 天 | +2.88% | 0.37 | +24.82% | 1.95 |
| 63 天 | +5.34% | 0.65 | +24.56% | 1.87 |
不同时段的年化收益差异很大(前半段 2-5%,后半段 23-26%),说明策略在不同市场环境下表现差异大。但参数排序有变化——前半段 63 天最优,后半段 10 天最优。这意味着:策略的框架是对的,但对某些市场状态没有应对方案。
### 结果解读
三步诊断的结论汇总如表 8-6 所示。
**表 8-6 三步诊断的结论汇总**
| 诊断 | 问题 | 结论 |
|------|------|------|
| 执行 | 回测和实盘的差距有没有异常放大? | 没有。执行落差稳定,排除 |
| 市场 | 市场波动水平有没有大的变化? | 有。恶化时段和高波动高度吻合 |
| 策略 | 参数在不同时段是否稳定? | 排序有变化,但策略框架没问题,对某些市场状态没有应对方案 |
问题定位清楚了——不是执行的问题,不是参数过拟合,而是**策略对高波动的市场状态没有应对方案**。
把这套诊断流程压成一句以后能脱口而出的口诀——
> **🔧 归因 SOP 口诀:先排执行落差(外),再查市场状态(中),最后看策略本身(内)。**
这个由外到内的顺序不能反过来。先动策略本身,你大概率会“为了一个市场状态变化”而推翻整个框架,下一轮市场恢复正常你又要再推翻一次——这正是缺乏纪律的迭代最常见的死循环。三步走完之后再决定改什么、改哪一层,每一步都有数据支撑。
那怎么改?推翻重来?还是在现有策略上做小步改进?
---
## 8.3 怎么改?
诊断结果出来了——执行没问题,是市场状态变了,策略对高波动没有应对。
你可能想直接调参数试试。但改策略不是拍脑袋——你需要像做实验一样:**先提出假设和判断标准,再做对照实验,最后看结果确认还是推翻假设。**
就像医生看病——不是看到头疼就开止疼药,而是先做检查确认病因,再开药。
每次迭代都走这个闭环:
```
观察(看到了什么)—— 假设(猜原因)—— 验证标准(怎么判断对错)—— 实验(改一个变量)—— 结论(确认/推翻)
```
### 动手实验 3:假设驱动的对照实验
我们一起把这份 spec 写出来。这次重点看两件新东西:**让“假设”跟着 Strategy 跑**——open-xquant 的 `Strategy` 类在设计时就内置了 `hypothesis``objectives` 两个空,创建 Strategy 时填两个空(假设、判定标准),跑出来的 Strategy 自己背着这张假设卡;以及**验证标准事前锁定具体数字**——绝不写“看情况调整”这种橡皮条款。
#### 起草上下文 + 任务
> **上下文**:在 `q8-iteration.ipynb` 中已有第 1 步监控 + 第 2 步诊断的完整变量。诊断结论:策略对高波动的市场状态没有应对方案。
>
> **任务**:完成两轮迭代实验(调仓频率 + 波动率过滤),用 Strategy 的 `hypothesis`/`objectives` 字段展示完整的实验工作流,最后用 `ExperimentLog` 记录迭代历史。
#### 起草要求:假设跟着 strategy 跑 + 验证标准事前锁定
要求段是这份 spec 的灵魂——把“假设 → 验证标准 → 实验 → 结论”的方法论挂到 Strategy 自己身上的两个空(`Strategy.hypothesis``Strategy.objectives`),让 strategy 跑到哪、假设卡跟到哪:
> **要求**
>
> 1. **迭代 1——调仓频率对照实验**(注定被推翻的一轮):
> - 创建 Strategy 时填写 `hypothesis="缩短调仓频率能改善高波动期表现"`
> - 同时填写 `objectives={"high_vol_sharpe": "above_baseline", "max_dd_limit": dd_limit}`——验证标准事前锁定
> - 对 6 种频率(5/10/15/21/42/63 天)分别跑回测
> - 计算每种频率在高波动时段的夏普(用 `high_vol_mask` 筛选)
> - 假设验证:对比基准(21 天)和最优频率的高波动夏普,判断假设确认还是推翻
>
> 2. **迭代 2——波动率过滤对照实验**(注定被确认的一轮):
> - 创建 Strategy 时填写 `hypothesis="高波动时降仓位能显著降低回撤"`
> - 验证标准事前锁定:`objectives={"dd_improvement": ">=30%", "sharpe": ">=1.0"}`
> - 用 `VolFilteredOptimizer` 包装 `RiskParityOptimizer`:高波动期权重缩放 0.5
> - 测试 4 种配置:无过滤、阈值 10% / 15% / 20%
> - **“最佳配置”选择函数显式锁定**`best_label = argmax(dd_improvement)`——不能让 AI 揣摩“最佳”的含义
> - 假设验证:检查最佳配置是否同时满足两个事前标准
>
> 3. **迭代记录表**
> - 用 `ExperimentLog(name="第八章迭代实验")` 创建记录
> - 用 `log.add()` 添加两轮迭代,每行字段值字符级显式:`name` / `observation` / `hypothesis` / `criteria` / `result` / `conclusion` / `notes`
> - 用 `log.to_dataframe().to_string(index=False)` 打印记录表
> 📌 **要点**:让 hypothesis 跟着 strategy 跑——`hypothesis` 和 `objectives` 这两个空是 open-xquant 在 `Strategy` 类设计时就内置好的。spec 要求“创建 Strategy 时填写”这两个空,意思是把“假设驱动”从写在注释里、口头讲讲的方法论,挂到 Strategy 自己身上——跑出来的 Strategy 对象自带 `.hypothesis` 标签,`ExperimentLog` 自动取走填进表格。**以前你的假设写在心里、写在备忘录里——现在 Strategy 自己背着这张假设卡。**
> 📌 **要点**:验证标准必须事前锁定到具体数字。“高波动夏普优于基准”是数字、“回撤改善 ≥ 30% 且夏普 ≥ 1.0”是数字——绝不写“看情况调整”“效果好就采纳”这种橡皮条款。**事后改标准就是过拟合**——你跑完看到 25% 改善,临时把标准从 30% 改到 20%,那不是验证假设,是在为已有结果找说辞。事前锁定阈值是迭代 spec 区别于“调参 spec”的本质差异。
> 📌 **要点**:注意章节的节奏——迭代 1 故意安排成“假设被推翻”,迭代 2 是“假设被确认”。这是教学设计:先让学员体会“推翻不是失败、推翻是省下了实盘的学费”,再示范一个被确认的假设长什么样。spec 也要配合这个节奏——迭代 1 不要写得“温柔一点让它更容易通过”,要让结果如实推翻。
#### 起草结果呈现
> **结果呈现**:两轮指标表 + 净值对比图 + 假设验证结论 + 2 行迭代记录表(iter1 rejected + iter2 confirmed+ Strategy 的 hypothesis / objectives 字段值在输出中可见。
完整 spec 在 [`specs/spec-03-iteration.md`](https://github.com/xingwudao/xquant-learning/blob/main/q8-iteration/specs/spec-03-iteration.md)——复制给 AI,弹窗选「允许」。
AI 助手执行完毕后,你的 notebook 里应该出现了两轮对照实验的完整结果和迭代记录表。
这个实验做了什么?两轮对照实验,每轮都遵循“假设 —— 验证标准 —— 实验 —— 结论”的流程:
- **迭代 1(调仓频率)**:假设“缩短调仓频率能改善高波动期表现”。保持策略逻辑不变,只改调仓周期——分别测试 5/10/15/21/42/63 天六种频率,重点比较各频率在高波动时段(第 2 步检测出的那些红色阴影区域)的夏普。
- **迭代 2(波动率过滤)**:假设“高波动时降低仓位能显著减少回撤”。保持调仓频率不变,用 `VolFilteredOptimizer` 包装 `RiskParityOptimizer`——当市场波动率超过阈值时,自动将权重缩放 0.5(仓位减半)。分别测试无过滤、阈值 10%/15%/20% 四种配置,看回撤改善了多少、付出了多少收益代价。
open-xquant 的 `Strategy` 支持在创建时填写 `hypothesis`(假设)和 `objectives`(验证目标),让“假设驱动”从口号变成 Strategy 自己背着的两个标签。`ExperimentLog` 则帮你自动记录每次迭代的假设、标准、结果和结论。
### 运行结果
两轮迭代的产出按“先被推翻、后被确认”的节奏展开。
#### 迭代 1 — 调仓频率
观察:高波动时段策略表现最差。假设:21 天调仓太慢,缩短频率能改善。验证标准:高波动时段夏普优于基准。
6 种调仓频率的全时段与高波动指标对比如表 8-7 所示。
**表 8-7 迭代 1——6 种调仓频率的指标对比**
| 频率 | 年化收益 | 最大回撤 | 夏普比率 | 高波动夏普 |
|------|----------|----------|----------|------------|
| 5 天 | +13.01% | -14.47% | 1.25 | 0.22 |
| 10 天 | +14.55% | -12.57% | 1.39 | 0.56 |
| 15 天 | +13.29% | -14.14% | 1.26 | 0.09 |
| 21 天(基准) | +12.50% | -16.62% | 1.17 | -0.08 |
| 42 天 | +14.09% | -15.98% | 1.29 | 0.44 |
| 63 天 | +14.71% | -11.49% | 1.31 | 0.84 |
不同调仓频率的净值曲线对比如图 8-3 所示。
![迭代 1:不同调仓频率的净值对比——图例中是高波动时段的夏普](images/q8-iteration/03-iter1-rebal-freq.png)
注意看表 8-7 最后一列——“高波动夏普”,这才是我们验证假设的指标。全时段夏普最高的是 10 天(1.39),但那不是我们的验证目标。高波动时段表现最好的是 63 天(0.84)——**更长而非更短**。
**假设被推翻。** 缩短调仓频率并没有改善高波动期表现。
这告诉我们什么?改“多久做一次决策”没有用——因为每次做的决策(风险平价满仓分配)没变。问题不在于你多久看一次天气预报,而在于看到暴雨预警后有没有带伞。
#### 迭代 2 — 波动率过滤
观察:频率不是关键,真正的问题是高波动时策略依然满仓暴露在风险中。假设:高波动时降仓位,能显著降低回撤。验证标准:回撤改善 ≥ 30%,夏普 ≥ 1.0。
4 种波动率过滤配置的指标对比如表 8-8 所示。
**表 8-8 迭代 2——4 种波动率过滤配置的指标对比**
| 配置 | 年化收益 | 最大回撤 | 夏普比率 | 回撤改善 |
|------|----------|----------|----------|----------|
| 无过滤(基准) | +12.50% | -16.62% | 1.17 | — |
| 过滤>10% | +6.57% | -8.70% | 1.18 | 47.6% |
| 过滤>15% | +10.44% | -9.62% | 1.43 | 42.1% |
| 过滤>20% | +12.89% | -12.43% | 1.28 | 25.2% |
不同配置的净值曲线对比如图 8-4 所示。
![迭代 2:波动率过滤的净值对比——高波动时仓位减半](images/q8-iteration/04-iter2-vol-filter.png)
最佳配置是“过滤>10%”:回撤从 -16.62% 改善到 -8.70%(改善 47.6%),夏普 1.18。两个验证标准都达标。
**假设被确认。** 波动率过滤有效。
但年化收益从 +12.50% 降到 +6.57%,代价 5.93%。你用 5.9% 的收益换来了 48% 的回撤改善——这就是**取舍(trade-off)**。没有“最好”的参数,只有“适合你风险承受能力”的选择。
### 结果解读
两轮迭代的关键认知汇总如表 8-9 所示。
**表 8-9 两轮迭代的核心认知**
| | 改了什么 | 学到了什么 |
|---|---|---|
| 迭代 1 | 执行参数(多久做一次) | 问题不在频率——策略对市场状态没有反应 |
| 迭代 2 | 策略逻辑(做什么) | 让策略感知市场状态并调整行为才有效 |
假设被推翻不是失败——这是最有价值的学习。如果你凭直觉觉得“调快点应该更好”,然后不做验证就上线,可能越改越差。**对照实验的意义就在于此:用数据而不是直觉做决策。**
**“执行参数”和“策略逻辑”是两件事。** 调仓频率是“多久行动一次”,但每次行动的内容(满仓风险平价)没变。如果问题出在行动的内容上,改频率没用——你需要改策略的行为本身。
---
## 8.4 回头看:你刚才做了什么?
三步实验,回答了同一个核心问题——**策略跑起来之后怎么办?**
第 1 步:建监控,发现“最近不对劲”(滚动夏普跌破 0)。第 2 步:做诊断,定位“是市场状态变了”(由外到内三层排查)。第 3 步:做迭代,改频率没用(推翻),改逻辑有效(确认)。
从今以后,策略出了问题你都可以套这套 SOP 应对,三步对照清单如表 8-10 所示。
**表 8-10 监控/诊断/迭代 SOP 速查**
| 步骤 | 怎么做 | 好的信号 | 坏的信号 |
|------|--------|----------|----------|
| 监控 | 看滚动夏普、回撤、超额 | 指标波动但整体健康 | 多个指标同时持续恶化 |
| 诊断 | 由外到内:执行、市场、策略 | 能定位到具体原因 | 三层都有问题(可能需要重新设计) |
| 迭代 | 假设、验证标准、实验、结论 | 假设被确认或被有价值地推翻 | 没有假设就开始改参数 |
---
## 8.5 本章总结
### 策略进化路径
第七章把策略推上了实盘,第八章把“上线之后怎么办”补完——三步进化路径如表 8-11 所示。
**表 8-11 第八章三步进化路径**
| 步骤 | 做了什么 | 关键发现 |
|------|---------|---------|
| 第 1 步 监控 | 滚动指标 + 自动标恶化 | 2022 年集中出现 4 段恶化 |
| 第 2 步 诊断 | 由外到内三层排查 | 不是 bug,是市场状态变了 |
| 第 3 步 迭代 | 两轮假设驱动对照 | 改频率没用,改逻辑才有效 |
第七章产出“从回测到实盘的完整闭环”,第八章在它之上多走一步——产出“策略迭代的完整方法论”加上“用数据做决策”的认知。
### 概念速查表
本章涉及的核心概念汇总如表 8-12 所示,方便随时回查。
**表 8-12 第八章核心概念速查**
| 概念 | 含义 | 类比 |
|------|------|------|
| 滚动夏普 | 最近一段时间(如 3 个月)的风险调整收益,衡量策略“现在”的健康度 | 体温计——数值正常说明没发烧 |
| 市场状态(regime) | 市场的波动水平和整体环境,不同时期差异很大 | 天气——晴天和台风天出门穿的衣服不一样 |
| 假设驱动迭代 | 先提出可验证的猜想和判断标准,再做实验,而不是凭感觉改 | 医生看病——先检查确认病因,再开药 |
| 对照实验 | 只改一个变量、其他不变,看效果 | 同一道菜只改盐的量,尝尝区别 |
| 执行参数 vs 策略逻辑 | “多久做一次”和“做什么”是两件事 | 多久看一次天气预报 vs 看到暴雨预警后是否带伞 |
| 迭代记录表 | 每次改动的假设、标准、结果、结论,形成可追溯的改进历史 | 病历本——记录每次看病的诊断和治疗 |
### 飞轮第一圈完整闭环
**到这里,你完成了第一圈完整的飞轮——本章的结论会反过来喂给下一圈飞轮的“候选 → 组合 → 执行”。** 候选层(第二章)→ 组合层(第三、四章)→ 执行层(第七章)→ 归因层(第五、六、八章)→ 反哺起点。三字要点(做 / 看 / 疑)也走了完整一遍:做 = 把每一层都写成可重复执行的规则;看 = 用指标判断好坏;疑 = 不止信回测,也质疑实盘的每一段。
下一圈飞轮要让它转得更快——这就是第九章的事。
### 本章核心认知
走完三步之后,本章最值得带走的九条认知如表 8-13 所示。
**表 8-13 第八章核心认知**
| 认知 | 来源 |
|------|------|
| 策略亏钱不一定是坏了,可能是正常波动 | 第 1 步 |
| 持续监控比偶尔看一眼净值重要得多 | 第 1 步 |
| 诊断要由外到内——先排执行,再看市场,最后看策略 | 第 2 步 |
| 市场状态会变,策略在不同状态下表现不同是正常的 | 第 2 步 |
| 改策略前先定好验证标准,不能事后改规则 | 第 3 步 |
| 改“多久行动”和改“怎么行动”是两件事 | 第 3 步 迭代 1 |
| 假设被推翻是好事——省下了实盘亏钱的学费 | 第 3 步 迭代 1 |
| 让策略感知市场状态并调整行为,才是有效的改进 | 第 3 步 迭代 2 |
| 每次改进都是取舍——用收益换安全,或反过来 | 第 3 步 迭代 2 |
### 带走的问题
我们已经走完了从选标的(第二章)到持续改进(第八章)的完整闭环。你现在掌握了监控、诊断和迭代的方法论。
但你可能已经感受到:每次迭代都要构造假设、改参数、跑回测、对比指标、记录结论……这个过程不算轻松。
策略不完美没关系,碰到问题也没关系——只要迭代得够快,从错误和失败中学习得够快,你就可以保持进化。**迭代速度本身就是竞争力。**
有没有办法让这个循环转得更快?—— 第九章
> 本章所有代码的可运行版本见 `notebooks/q8-iteration.ipynb`
---
## 8.6 拓展阅读
本章正文聚焦“监控 / 诊断 / 迭代”三个策略层面的动作。下面这段是动手之外的“软功”——读完正文后回头看,决定你能不能把这套方法长期跑下去。
### 迭代自己:耐心 / 纪律 / 记录
前面三步解决的是策略层面的问题,但还有一层问题藏在你自己身上。
**耐心(对应第 1 步 监控)。** 策略有波动是常态。不要每天盯着净值焦虑——你已经有了监控仪表盘,定期检查就够了。如果你发现自己每小时都在看净值,那不是在监控,是在焦虑。仪表盘的意义不只是发现问题,也是让你在没有问题的时候安心。
**纪律(对应第 3 步 验证标准)。** 定好了验证标准就不要改。如果实验结果不好就改标准,这和第六章讲的过拟合是一回事——你在“事后”调整规则来让结果看起来好,而不是真的在改进。假设被推翻了?接受它,记录下来,想下一个假设。
**记录(对应第 3 步 迭代记录表)。** 每次迭代都填迭代记录表。三个月后回头看,你会发现大部分假设都被推翻了——但你从每次推翻中学到的东西比确认更多。这些记录就是你的成长轨迹。
**一句话:** 策略层面的方法论你已经有了;接下来三年里能不能跑通,取决于你“耐心 / 纪律 / 记录”这三条软纪律守得有多严。这是带得走的“心态地图”。
---
## 反馈与读者群
如果你发现本章有错别字、概念不清或实验结果不一致,欢迎
[提交 GitHub Issue](https://github.com/xingwudao/xquant-beginner/issues/new/choose)。
课程、正式书和后续更新见
[xquant.shop/courses](https://xquant.shop/courses)。
添加作者微信 `xquanter` 进入读者群。请备注:`XQuant 读者`