docs: sync xquant tutorial updates

This commit is contained in:
xingwudao
2026-06-21 23:17:19 +08:00
parent a0d05d966f
commit c1a6decc56
25 changed files with 1955 additions and 2214 deletions
+10 -10
View File
@@ -20,16 +20,16 @@ export default defineConfig({
items: [
{ text: '项目首页', link: '/' },
{ text: '前言', link: '/book/preface' },
{ text: '开始动手之前', link: '/book/getting-started' },
{ text: '第 1 章:第一次跑通一个完整策略', link: '/book/q1-how-to-profit' },
{ text: '第 2 章:选出你的投资宇宙', link: '/book/q2-what-to-buy' },
{ text: '第 3 章:给 ETF 分钱', link: '/book/q3-how-much' },
{ text: '第 4 章:给策略加规则', link: '/book/q4-when-to-trade' },
{ text: '第 5 章:策略好不好', link: '/book/q5-how-to-validate' },
{ text: '第 6 章:看清好看的回测', link: '/book/q6-avoid-overfitting' },
{ text: '第 7 章:真的下单', link: '/book/q7-execution' },
{ text: '第 8 章:策略活着', link: '/book/q8-iteration' },
{ text: '第 9 章:持续寻找盈利机会', link: '/book/q9-daily-work' },
{ text: '准备工作', link: '/book/getting-started' },
{ text: '第 1 章:跑通一个策略', link: '/book/q1-how-to-profit' },
{ text: '第 2 章:先选买什么:从 3 只 ETF 开始', link: '/book/q2-what-to-buy' },
{ text: '第 3 章:决定各买多少:3 种分法实测', link: '/book/q3-how-much' },
{ text: '第 4 章:决定何时买卖:再平衡、止损、止盈', link: '/book/q4-when-to-trade' },
{ text: '第 5 章:判断策略好不好4 个关键视角', link: '/book/q5-how-to-validate' },
{ text: '第 6 章:别高兴太早:防止过拟合', link: '/book/q6-avoid-overfitting' },
{ text: '第 7 章:从理想到现实:执行交易', link: '/book/q7-execution' },
{ text: '第 8 章:策略跑起来以后:监控、诊断、迭代', link: '/book/q8-iteration' },
{ text: '第 9 章:持续寻找机会:因子研究入门', link: '/book/q9-daily-work' },
{ text: '反馈与读者群', link: '/book/feedback' }
]
}
+6
View File
@@ -2,6 +2,12 @@
本文件记录开源书稿的重要修订。小的错别字和格式修复可能合并记录。
## 2026-06-21
-`xquant-tutorial@c7b9d4c` 同步前言、准备工作和第 1-9 章书稿
- 同步章节配图,新增 TRAE 模型配置截图和前言学习地图
- 更新 GitBook 目录与 VitePress 侧边栏章节标题
## 2026-06-09
- 发布 `xquant-beginner` 开源书稿仓库
+10 -10
View File
@@ -2,14 +2,14 @@
* [项目首页](README.md)
* [前言](book/preface.md)
* [开始动手之前](book/getting-started.md)
* [第 1 章:第一次跑通一个完整策略](book/q1-how-to-profit.md)
* [第 2 章:选出你的投资宇宙](book/q2-what-to-buy.md)
* [第 3 章:给 ETF 分钱](book/q3-how-much.md)
* [第 4 章:给策略加规则](book/q4-when-to-trade.md)
* [第 5 章:策略好不好](book/q5-how-to-validate.md)
* [第 6 章:看清好看的回测](book/q6-avoid-overfitting.md)
* [第 7 章:真的下单](book/q7-execution.md)
* [第 8 章:策略活着](book/q8-iteration.md)
* [第 9 章:持续寻找盈利机会](book/q9-daily-work.md)
* [准备工作](book/getting-started.md)
* [第 1 章:跑通一个策略](book/q1-how-to-profit.md)
* [第 2 章:先选买什么:从 3 只 ETF 开始](book/q2-what-to-buy.md)
* [第 3 章:决定各买多少:3 种分法实测](book/q3-how-much.md)
* [第 4 章:决定何时买卖:再平衡、止损、止盈](book/q4-when-to-trade.md)
* [第 5 章:判断策略好不好4 个关键视角](book/q5-how-to-validate.md)
* [第 6 章:别高兴太早:防止过拟合](book/q6-avoid-overfitting.md)
* [第 7 章:从理想到现实:执行交易](book/q7-execution.md)
* [第 8 章:策略跑起来以后:监控、诊断、迭代](book/q8-iteration.md)
* [第 9 章:持续寻找机会:因子研究入门](book/q9-daily-work.md)
* [反馈与读者群](book/feedback.md)
+219 -181
View File
@@ -1,300 +1,338 @@
# 开始动手之前
# 准备工作
这一节是**动手之前的一次性准备**工作,一共做两件事:把 AI 编程工具和 Python 环境装好;② 顺便学习写一份对 AI 友好的 spec
前言讲完了学习地图,这一章先把工作台搭起来。你要做两件事:第一,把 AI 编程工具和 Python 环境准备好;第二,用“装环境”这件事,完整练一次写 spec。
## 准备工具
这一章先不要求你学会编程,也不要求你记住每一条命令。你只需要了解:把任务说明白是你的工作;让 AI 按说明执行;拿到结果后再检查对不对。本章会提供检查工具,帮你判断环境是否真的准备好了。准备工作做好了,从第 1 章开始,我们就可以直接跑量化实验。
做量化交易需要的工具其实很少。入门阶段你只需要一个基础环境,本书会用三样东西搭出来:① 数据源(拿什么做实验);② 编程环境(在哪跑代码);③ AI 编程工具(替你写代码)。下面分别介绍。
## 先认识要用的工具
### 数据源:策略的“原材料”
做量化实验需要的工具并不多。入门阶段先认识三类就够了:数据源、编程环境、AI 编程工具。先把这三类说清楚,后面安装时你就知道每一步在准备什么。
数据是策略的原材料”——没有数据,回测无从谈起。本书用两个免费的数据源就够了,对比如表 II-1 所示。
### 数据源:策略的原材料
数据是量化策略实验的原材料。没有历史价格、成交量这些基础数据,就没有根据提出策略,也无法检验策略是否有效。本书主要用两个免费数据源,如表 II-1 所示。
**表 II-1 本书使用的数据源**
| 数据源 | 覆盖范围 | 费用 |
|--------------|--------------------------|------|
| **akshare** | A 股为主,数据全、更新快 | 免费 |
| **yfinance** | 全球股票,接口简单 | 免费 |
| 数据源 | 覆盖范围 | 费用 |
|--------|----------|------|
| **akshare** | A 股为主,数据全、更新快 | 免费 |
| **yfinance** | 全球股票,接口简单 | 免费 |
本书用 akshareA 股)+ yfinance(美股 / 全球)两个免费源就够了。等你把整个流程跑通、确定要认真做了,再考虑付费数据源
你不需要提前下载一大包数据文件。后面运行代码时,程序会自己从网上获取数据。数据很重要:没有数据做不了实验;数据错了、缺失了、处理得不对,都会影响结论,进而影响最终判断
数据有几个坑要提前知道:**复权方式**(前复权、后复权、不复权,用错了结果完全不同)、**停牌处理**(停牌期间的价格怎么填充)、**幸存者偏差**(只用当前在市的股票回测会高估收益)。这些问题在后续章节遇到时会展开
数据中的问题有非常多细节,略举几例,暂时听不懂没关系,比如复权、停牌、幸存者偏差,后面遇到真实实验时再讲
### 编程环境:跑策略的“工作台
### 编程环境:跑代码的工作台
有了数据,还需要一个可以运行策略的工作台。你不需要复杂的工程系统,只需要一组基础工具,对照如表 II-2 所示
有了数据,还需要一个能运行程序的环境。程序写好了,要让它自动执行,需要安装一些“看不见”的软件,这在编程里叫“环境”。你可以把环境理解成厨房:数据是食材,环境是锅、灶、刀具和调料。没有这些东西,有食材也做不出饭
我们需要的环境,是运行 Python 程序的一组工具,比如 Python 解释器、Jupyter Notebook 等。你不用先记住这些名字,后面我们会让 AI 帮你把它们装好。你不需要 GPU、云服务器或数据库,一台普通电脑就够了,操作系统是 Windows、macOS 或 Linux 都可以。
本书用到的基础环境如表 II-2 所示。
**表 II-2 本书使用的编程环境**
| 工具 | 用途 |
|----------------------|----------------------------------|
| **Python 3.12** | 编程语言本体 |
| **Jupyter Notebook** | 交互式编程环境,可以边写边看结果 |
| **常用库** | pandas、NumPy、Matplotlib |
| 工具 | 用途 |
|------|------|
| **Python 3.12** | 运行代码的语言 |
| **Jupyter Notebook** | 边写边看结果的交互式环境 |
| **常用库** | pandas、NumPy、Matplotlib 等现成工具 |
不需要 GPU、不需要云服务器、不需要数据库——一台普通电脑就够了
表里的“库”,可以理解为别人已经写好的工具包。比如 pandas 负责处理表格数据,Matplotlib 负责画图。你不需要详细学习这些工具,只要把它们装好,AI 懂得用就行
> 第 2 章起,本书会用到一个叫 **open-xquant** 的开源框架——它的设计理念和“为什么需要它”会在第 2 章章首详细介绍。本节只列你**现在**要装的工具
第 2 章起,本书会用到一个叫 **open-xquant** 的开源量化框架,它是我们自己专门为 AI 写的一个框架,等到第 2 章正式介绍它。这一章只负责把它先装进环境里
## 安装 TRAE
### AI 编程工具:把 spec 变成代码
工具列出来后,开始装。先装 TRAE(本书用于演示 AI 编程的AI 编程工具),再让 TRAE 按一份 spec 帮你装 Python 环境
前言里说过,你不需要先学会编程再学量化,但你必须学会把策略描述清楚,把它写成策略规格说明书,简称 spec。AI 编程工具的作用,就是把你写清楚的 spec 翻译成 Python 代码,再帮你运行和修改
### 为什么选 TRAE
本书以 TRAE 为演示工具。如果你已经在用 Cursor、Claude Code、Codex 或其他 AI 编程工具,完全可以继续使用——操作方式相同:在对话框用自然语言结构化写下想法(即spec),AI 将其翻译成 python 程序,执行即可。
主流的 AI 编程工具不少,对比如表 II-3 所示。
主流 AI 编程工具不少,如表 II-3 所示。
**表 II-3 主流 AI 编程工具对比**
| 工具 | 简介 |
|---------------------|---------------------------------------|
| VS Code + Copilot | 老牌编辑器,加装 AI 插件 |
| Cursor | AI 原生编辑器,海外用户多 |
| Claude Code / Codex | 命令行工具,专业开发者常用 |
| **TRAE** | 字节跳动出品,AI-first 设计,中文界面 |
| 工具 | 简介 |
|------|------|
| VS Code + Copilot | 老牌编辑器,加装 AI 插件 |
| Cursor | 使用广泛的 AI 编程工具 |
| Claude Code | Anthropic 出品的 AI 编程工具 |
| Codex | OpenAI 出品,专业开发者常用 |
| OpenCode/OpenClaw | 开源的 AI 编程工具 |
| TRAE | 字节跳动出品,AI-first 设计,中文界面 |
本书选择 TRAE 的原因:
这里要区分两个容易混淆的概念:编程工具和编程模型。表 II-3 里的都是编程工具,是你直接操作的界面;编程模型则是工具背后的能力来源。很多编程工具都可以切换或配置不同模型。具体哪个模型更适合写代码,会随着产品迭代不断变化,你不需要在这里记住某个固定名单。
- **AI-firstSOLO 模式**:整个界面围绕“和 AI 对话”设计,不需要你懂编辑器怎么操作
- **模型实力强**:字节有自己的大模型(如豆包),能力不输一线
- **免费可用**:个人开发者免费使用,还能免费接入自定义模型
- **对中国用户友好**:中文界面,国内直连
本书以 TRAE 为演示工具,原因很简单:中文界面、上手门槛低、SOLO 模式围绕“和 AI 对话”设计。对零基础读者来说,这比先学一个复杂编辑器更友好。TRAE 还在不断迭代,也许你看到这本书时,产品界面已经不一样了,我相信,这些 AI 编程工具只会越来越容易使用,而不是越来越难。
一句话:TRAE 是目前对零基础、中国用户最友好的 AI 编程工具
如果你已经有自己熟悉的 AI 编程工具,我鼓励你继续使用自己的工具,因为本书并不是教你学会一个静态的工具,而是学会一种思维模式。后面的关键不是某个按钮在哪里,也不是要记住某个具体命令,而是学会使用 AI 来实现自己的投资想法、检验自己的投资想法,这比学习工具本身重要得多
### 四步装好 TRAE
本书截图主要覆盖 macOS 和 Windows。如果你使用 Linux,书中的思路仍然适用,但个别安装命令、界面位置和系统权限提示可能需要按你的发行版调整。
#### 1. 下载 TRAE
接下来的安装顺序会和上面的介绍略有不同:我们先装 AI 编程工具 TRAE,再让 TRAE 帮你装 Python 环境和这些库。这样做的目的,是让 AI 从第一步开始就参与进来。
访问 TRAE 官网 trae.cn / trae.ai,根据你的操作系统(macOS 或 Windows)下载安装包,下载页面如图 II-1 所示。
## 安装并设置 TRAE
我们这一步只做三件事:下载并安装 TRAE,切到适合本书的 SOLO 模式,再打开一个学习目录。
### 下载并安装 TRAE
访问 TRAE 官网 `trae.cn``trae.ai`,根据你的操作系统下载安装包。下载页面如图 II-1 所示。
![TRAE 官网下载页面](images/getting-started/trae-download-page.png)
#### 2. 安装 TRAE
运行下载的安装包,按照向导提示完成安装。安装过程很简单,一路点“下一步”即可(Windows 安装向导见图 II-2macOS 见图 II-3)。
下载完成后运行安装包。Windows 用户按安装向导提示继续,安装界面如图 II-2 所示;macOS 用户把 TRAE 拖入应用程序,安装界面如图 II-3 所示。
![TRAE Windows 安装向导](images/getting-started/trae-install-wizard-windows.png)
![TRAE macOS 安装向导](images/getting-started/trae-install-wizard-mac.png)
#### 3. 打开 TRAE,认识界面
安装完成后打开 TRAE。默认是 IDE 模式(如图 II-4 所示),但本书要用的是 SOLO 模式——它对 AI 编程更友好,整个界面围绕“和 AI 对话”设计。切换到 SOLO 模式后的界面如图 II-5 所示。
### 切到 SOLO 模式
安装完成后打开 TRAE。默认界面可能是 IDE 模式,如图 II-4 所示;本书使用的是 SOLO 模式,它更像“和 AI 一起完成任务”的工作台。
![TRAE 默认的 IDE 模式界面](images/getting-started/trae-main-interface-ide.png)
切到 SOLO 模式后,界面会变成图 II-5 这样。你最需要关注的是下方的 AI 对话框:后面所有 spec 都会粘贴到这里,让 AI 执行。
![TRAE SOLO 模式界面](images/getting-started/trae-main-interface.png)
SOLO 模式下你只需要关注一个区域:**AI 助手对话框**。后续所有操作都是在这个对话框里完成的。
### 选择编程模型
#### 4. 创建学习目录
TRAE 已经内置了多个编程模型,入门阶段直接使用内置模型即可,不需要一开始就购买或配置额外模型。模型选择界面如图 II-6 所示。
在你的桌面上新建一个文件夹(书中示例文件夹叫 `xquant` )。然后在 TRAE 中打开它:菜单 → 文件 → 打开文件夹 → 选择 `xquant`,操作过程如图 II-6 所示。
![TRAE 内置的编程模型](images/getting-started/trae-model-selection.png)
![在 TRAE 中打开 xquant 文件夹](images/getting-started/trae-open-folder.png)
如果你已经有某个模型厂商的 API Key,也可以把它配置到所选的编程工具中。例如,你在 DeepSeek 的 API 平台购买了额度,就可以在后台创建 API Key,再填入 TRAE 的自定义模型配置。TRAE 的自定义模型配置界面如图 II-7 所示。
打开后,你会在左侧文件管理器中看到 `xquant` 这个文件夹名称。现在它是空的,很快 AI 会帮你把需要的文件都创建好。
![TRAE 自定义模型](images/getting-started/trae-model-provider.png)
## 一起写第一份 spec:让 AI 装 Python 环境
这里要搞清楚订阅账号和 API 账号的区别。ChatGPT Plus、Claude Max 这类订阅账号,通常是给用户本人在官方产品里直接使用的,不能直接填进“API Key”输入框。AI 编程工具如果要调用外部模型,通常需要 API 账号生成的 API Key;也有一些工具会提供官方登录或订阅集成,具体以工具自己的说明为准。入门阶段先用 TRAE 内置模型即可,后面确实需要更换模型时,再理解 API Key 也不迟。
到这一步 TRAE 装好了,但 Python 还没装。我们的目标是装 Python 3.12 + 7 个依赖包,并且自动验证每个组件都装对了。
最直接的做法是直接告诉 TRAE:“给我装一下 Python 环境。”
### 创建并打开学习目录
不行——AI 不知道你要哪个版本、用什么工具、要哪些包、跑通的标志是什么。它会按自己的偏好装一套,你的同学按他的电脑装另一套,半年后你们的环境完全不同
在电脑上新建一个文件夹,作为本书的学习目录。建议使用英文路径,比如桌面上的 `xquant-learning` 文件夹,避免中文路径、空格或特殊符号带来额外问题
正确的做法是写一份 **spec**——前言里讲过的「任务说明书」,把“做什么 + 做成什么样”清清楚楚交给 AI。这份 spec 我们已经写好放在仓库里了——但与其让你直接抄一遍,不如一起从零把骨架走一遍,等你看明白每一段为什么这么写,再去打开真实文件对照。下面我们一段段写
然后在 TRAE 中打开它:菜单 → 文件 → 打开文件夹 → 选择 `xquant-learning`。操作过程如图 II-8 所示
### spec 长什么样:四段骨架
![在 TRAE 中打开 xquant-learning 文件夹](images/getting-started/trae-open-folder.png)
一份能让 AI 稳定执行的 spec,通常有四段:
打开后,你会在左侧文件管理器中看到 `xquant-learning` 这个文件夹。现在它还是空的,下一步 AI 会帮你把需要的环境和文件创建出来。
## 让 TRAE 帮你搭建环境
到这一步,AI 编程工具准备好了,但 Python 环境还没准备好。我们的目标是把环境搭建好:装好 Python 3.12,装好本书要用的 7 个库,并用一个检查脚本确认环境真的可用。
最直接的想法,是告诉 TRAE:“给我装一下 Python 环境。”
这样不够。这里先约定一下:后面为了行文简洁,我们会把 AI 编程工具简称为 AI。它不知道你要哪个 Python 版本,用什么工具装,要装哪些库,也不知道怎样才算装对。你可能也不知道这些细节,这很正常;刚开始我会帮你把它们写清楚。
前言里讲过,spec 本身泛指一切要交给 AI 执行的任务说明书。这一章,这个任务就是搭建环境,以后的内容就专门指策略规格说明书了。本章的 spec 要说清楚的是:做什么、怎么做、做到什么程度算成功。
这份完整 spec 已经放在配套仓库里。你可以直接复制运行,但我们也从现在就在这个例子中看懂它为什么这样写。下面我们先看骨架,再一段段拆解。
### 写安装 spec
我们可以先想象一下,如果有人给我们布置一项任务,那么我们希望他怎么描述这项任务,才能让我们一次搞定的概率最高?同样的,我们把 AI 也看做一个人,一份能让 AI 高概率一次性完成的 spec,通常要回答四个问题:
> 📌 **spec 的四段骨架**
>
> 1\. **上下文** — 在什么前提下做这件事(AI 不知道你之前做了什么,要明示)
> 1. **上下文** —— 所有跟这个任务有关的信息、背景知识。
>
> 2\. **任务** — 一句话点明本次要做什么,不用写怎么做。本书中示例
> 2. **任务描述** — 任务具体要做什么,具体而明确。
>
> 3\. **要求** — 具体步骤,AI 自由度越小越可复现
> 3. **任务要求** —— 任务要遵守的规范,满足的约束,遵循的原则等。
>
> 4\. **结果呈现** — 怎么算成功(最容易被忽略也最重要的部分)
> 4. **验收标准** — 任务完成时要交付的清单,要满足的标准,清晰可检验。
四段是 spec 必须回答的四个问题:**前提是什么 / 做什么 / 怎么做 / 算不算做对了**——哪一段都不能少
注意第四段“验收标准”不是让你提前知道运行结果,而是先把成功标准写清楚。以这次环境搭建为例,“看到 `11/11 全部通过`,并且有 `.venv/``check_env.py`”,这就比“装好了”清楚得多
我们一段段把“装环境”这个任务写成 spec
下面四段都以 macOS 为例。Windows 用户不用担心,这次要搭建的环境是跨平台的,而且 spec 的四段骨架完全相同,只需要在上下文中把 macOS 换成 Windows,把 mac 命令换成 PowerShell 命令即可。具体对应关系已经写在 `specs/spec-01-env-setup-windows.md`
### 第一段:上下文
#### 第一段:上下文
我们的状况很简单:刚装好 TRAE,打开了一个空目录,Python 一切还没装。这一段写出来
上下文段告诉 AI:现在是什么状态。我们刚装好 TRAE,打开了一个空目录,Python 和课程要用的库都还没装。这一段可以这样写:
> **上下文**:我(学员)刚在 TRAE 中打开了一个空目录作为课程根目录。本机是 macOS,尚未安装 Python 或任何依赖。这是课程的第一份 spec——后续spec 都依赖这一份装出的环境。
> **上下文**:我刚在 TRAE 中打开了一个空目录作为量化课程学习实践的根目录。本机是 macOS,尚未安装 Python 或课程要用的库。这是课程的第一份 spec后续 spec 都依赖这一份装出的环境。
本节后续四段 blockquote 全部以 macOS 为例展示。Windows 用户按相同骨架,把「macOS」替换成「Windows」、把 mac 命令替换成 PowerShell 命令即可——具体替换在 `specs/spec-01-env-setup-windows.md` 文件里逐行对照,本节无需重复。
这一段示范的是 spec 写作的第一条要点,下面我把它单列出来——本节后续每写完一段 spec,都会附一条对应的「📌 要点」。这是全书第一次出现这种样式块,它和正文并列、可以反复回查。
> 📌 **要点 1:能一句话说清的事就不要写两句** AI 一次能处理的信息有限。你写得啰嗦,最关键的指令容易被它忽略。写 spec 的过程是反复“还能不能再短一点”的过程。
### 第二段:任务
任务段要具体到一句话能说清。“装好 Python 环境”不够具体——什么版本?用什么工具?哪些包?
> **任务**:在当前目录下完成 macOS 量化课程编程环境配置——装 uv → 用 uv 创建 Python 3.12 虚拟环境 → 装 7 个依赖包 → 创建并跑通 check_env.py 自检脚本。
> 📌 **要点 1:能一句话说清,就不要写两句**
>
> 📌 **要点 2:关键事物要具体到 ID 或版本号** “Python” 不够,要 “Python 3.12”;“包管理工具”不够,要 “uv”;“装一些库”不够,要 “7 个具体的库”。任何 spec 中的关键名词都要具体到不能再具体的程度,AI 才不会按自己的偏好随便选一个
> AI 关注的是指令,不是字数。写得越啰嗦,关键要求越容易被淹没。写 spec 的过程,本质上是在删掉多余的话,只保留真正重要的信息
### 第段:要求
####段:任务描述
要求段是 spec 的主体——把“怎么做”拆成步骤。环境配置一共 6 步:确认目录、装 uv、建虚拟环境、装依赖、写 check_env.py、跑 check。以下要求比较具体,是因为编程需要让环境前后一致,避免引起不必要的问题,实际工作中我们不必告诉 AI 应该怎么做,所以你可以完全复制这一段:
任务描述段要一句话说明目标,不要把所有步骤塞进去。这里先补一个小词:`uv` 是一个管理 Python 环境的工具,它可以帮我们安装 Python、创建虚拟环境、安装库。
> **要求**: 1. **确认当前目录**——在终端运行 `pwd`,记录路径,后续操作都在这个目录下。 2. **安装 uv**——运行 `curl -LsSf https://astral.sh/uv/install.sh | sh`,再 `source $HOME/.local/bin/env`,最后 `uv --version` 应输出版本号。 3. ……(共 6 步,完整内容见 `specs/spec-01-env-setup-mac.md`
这次任务可以这样写:
完整 6 步加上每一步的命令、check_env.py 全文都在 spec 文件里。这里只挑两个特别值得讲的细节
> **任务描述**:在当前目录下完成 macOS 量化课程编程环境配置:安装 uv,用 uv 创建 Python 3.12 虚拟环境,安装 jupyter、pandas、NumPy、Matplotlib、akshare、yfinance、open-xquant,并创建、运行 `check_env.py` 检查脚本
**装依赖时为什么要锁版本号?** 比较两种写法:
`uv pip install pandas numpy matplotlib``uv pip install pandas==3.0.* numpy==2.4.* matplotlib==3.10.*`
不锁版本的话,半年后你重新跑 spec,pandas 升到了 3.1,某个 API 改了,书上的图就跑不出来。锁到 `==X.Y.*` 既允许 patch 升级(修 bug 的小版本)又不会跨 minor(可能改 API 的版本),最稳。
> 📌 **要点 3:能锁的都锁住——版本号、日期、随机种子** 量化交易的命脉是“可复现”。你今天跑出来的图,明天还得跑得出来;你电脑上跑的回测,老师电脑上的结果一样;半年后回头检查,依然能复现。任何会随时间漂移的东西(库版本、日期窗口、随机数)都要锁。
环境配置这件事**没有图表可看**——某个包没装好,当下不会报错,等到第 1 章跑实验时才暴露问题。所以验证必须是机械的、固定的、可重复的。所以我们把检查脚本写进了 spec,这就是告诉 AI 使用指定的工具完成某些任务,从而提高 AI 任务的确定性。AI 只能照着抄一份一字不差的,每个学员跑出的报告完全一致——失败时哪一项 FAIL 就是哪一项装错了。
### 第四段:结果呈现
最后一段是“成功长什么样”。一份 spec 没有这一段,AI 跑完你只能凭感觉判断对错。
> **结果呈现** 1. `python check_env.py` 最末两行输出 `结果: 11/11 全部通过!` + `环境配置完成,可以开始课程。` 2. 命令退出码为 0 3. 当前目录下存在 `.venv/` 与 `check_env.py`
> 📌 **要点 2:关键对象要点名**
>
> 📌 **要点 4:成功的标志要可机械验证** “跑通了” / “看起来对” / “应该没问题”——这些都是不合格的成功标志。一份好 spec 的结果呈现段要让 AI 自己能说“对了”或“错了”,不需要学员凭感觉判断。assert 语句、具体的数字(11/11)、具体的文件名、退出码——这些就是机械验证
> “装 Python”不够,要写成“Python 3.12”;“装一些库”不够,要把 7 个库的名字列出来;“写个检查脚本”不够,要写出文件名 `check_env.py`。关键对象越明确,AI 越不容易按自己的偏好补全
四段拼起来就是一份完整 spec。我们刚才在书里写过的骨架——上下文 / 任务 / 要求 / 结果呈现——和 `specs/spec-01-env-setup-mac.md`(或 `spec-01-env-setup-windows.md`,根据你的系统)里的真实文件一一对应;区别只在于真实文件把 6 步和 check_env.py 全文都写齐了。下一节我们把它交给 TRAE。
#### 第三段:任务要求
任务要求段是 spec 的主体。它把任务拆成可以执行的步骤。环境配置一共 6 步:确认目录、安装 uv、创建虚拟环境、安装库、创建检查脚本、运行检查脚本。
完整命令和 `check_env.py` 全文都在配套仓库的 `specs/spec-01-env-setup-mac.md` 里。书里先摘出开头几步,让你看清写法:
> **任务要求**
>
> 1. **确认当前目录**:运行 `pwd`,记录路径,后续所有操作都在这个目录下进行。
>
> 2. **安装 uv**:运行 `curl -LsSf https://astral.sh/uv/install.sh | sh`,再运行 `source $HOME/.local/bin/env`,最后用 `uv --version` 检查是否安装成功。
>
> 3. **创建 Python 3.12 虚拟环境**:运行 `uv venv --python 3.12`,再激活虚拟环境,并用 `python --version` 确认版本。
>
> 4. **安装 7 个库**:按 spec 文件中的版本号安装 jupyter、pandas、NumPy、Matplotlib、akshare、yfinance 和 open-xquant。
>
> 5. **创建 `check_env.py`**:把 spec 文件中的检查脚本写入当前目录。
>
> 6. **运行检查脚本**:执行 `python check_env.py`,查看检查结果。
这里有个细节很重要:安装库时要锁版本号。比如不要写:
```bash
uv pip install pandas numpy matplotlib
```
而要写成:
```bash
uv pip install pandas==3.0.* numpy==2.4.* matplotlib==3.10.*
```
不锁版本,半年后某个库升级,书里的代码可能就跑不出来。锁到 `==X.Y.*`,意思是小修小补可以升级,但大版本变化先不要碰。
> 📌 **要点 3:会变化的东西要锁住**
>
> 库版本会变,日期窗口会变,随机数也可能变。量化实验最怕今天能跑、明天不能复现。凡是会随时间漂移的关键条件,都要尽量写死。
#### 第四段:验收标准
最后一段写成功标准。你还没有运行,怎么知道成功长什么样?答案是:你不用猜最终数据,只要约定检查脚本应该给出哪些信号。
比如这次我们要求看到 `11/11 全部通过`,要求命令退出码为 0,要求目录里出现 `.venv/``check_env.py`。这些都是 AI 和你都能核对的信号。
> **验收标准**
>
> 1. `python check_env.py` 最末两行输出:
>
> ```text
> 结果: 11/11 全部通过!
> 环境配置完成,可以开始课程。
> ```
>
> 2. 命令退出码为 0。
>
> 3. 当前目录下存在 `.venv/` 目录与 `check_env.py` 文件。
> 📌 **要点 4:成功标准要能检查**
>
> “看起来没问题”不算成功标准。更好的写法是给出具体数字、具体文件名、具体输出文字。这样 AI 做完后,你不用凭感觉判断,对照检查就行。
四段拼起来,就是一份完整安装 spec。下一节我们把真实文件交给 TRAE 执行。
## 让 TRAE 跑这份 spec
上一节我们把这份 spec 在书里一段段写完了,现在把它交给 TRAE 真正执行。在打开 spec 文件之前,先看下面这条提示——等下 TRAE 弹窗时你就不会被吓到:
上一节我们已经看懂了安装 spec 的结构,现在要让 TRAE 来执行这一份任务说明书。打开文件之前,先看一个提示,等下看到弹窗就不会慌。
> **💡 提示:关于“沙箱”**
>
> TRAE 帮你装环境时会弹窗询问是否允许“在沙箱外运行”。别紧张:AI 平时是在一个受限的小房间(沙箱)里活动,但现在它要帮你布置整个工作台,需要你给它开一下门。遇到这个提示,直接点“是”“允许”即可过程示意如图 II-7 所示。
>
> 如果你打开 spec 文件第一行也写了“所有命令在沙箱外运行”——这就是 TRAE 弹窗问你的原因;不是出错,是预期里的一步。
> TRAE 帮你装环境时,可能会询问是否允许“在沙箱外运行”。所谓“沙箱”,就是给 AI 的操作设置一个受限制的运行范围,用来降低误操作影响真实文件和系统环境的风险。但这次要安装的 Python 环境需要访问你的真实终端和学习目录,所以通常需要允许它在终端中运行。遇到这个提示,选择“是”“允许”或“在终端中运行”即可过程示意如图 II-9 所示。
![TRAE 沙箱提示示意](images/getting-started/trae-solo-sandbox.png)
读完提示,再打开你系统对应的 spec 文件——
现在打开你系统对应的 spec 文件
- **macOS 用户**:打开 `specs/spec-01-env-setup-mac.md`
- **Windows 用户**:打开 `specs/spec-01-env-setup-windows.md`
把 spec 全部内容复制到 TRAE 的 AI 对话框中,发送。
把 spec 全部内容复制到 TRAE SOLO 模式下方的 AI 对话框中,发送。
AI 会自动完成 6 个步骤:装 uv、建虚拟环境、装 7 个依赖包(含 open-xquant 框架)、创建 check_env.py、跑自检。整个过程几分钟。你不需要理解每一步细节,只需要看最后的检查结果。
TRAE 会按 spec 自动完成 6 个步骤:装 uv、建虚拟环境、装 7 个、创建 `check_env.py`、运行检查脚本。整个过程通常只需要几分钟。你不需要理解每一条命令,只需要看最后的检查结果。
### 验证结果
如果一切顺利,你会在 TRAE 的终端中看到类似这样的输出:
==================================================
XQuant 课程环境检查
==================================================
```text
==================================================
XQuant 课程环境检查
==================================================
[OK ] Python 3.12.x
[OK ] 虚拟环境已激活
[OK ] jupyter
[OK ] pandas
[OK ] numpy
[OK ] matplotlib
[OK ] akshare
[OK ] yfinance
[OK ] open-xquant
[OK ] jupyter 命令可用
[OK ] akshare 数据源连通(获取到 5847 条数据)
[OK ] Python 3.12.x
[OK ] 虚拟环境已激活
[OK ] jupyter
[OK ] pandas
[OK ] numpy
[OK ] matplotlib
[OK ] akshare
[OK ] yfinance
[OK ] open-xquant
[OK ] jupyter 命令可用
[OK ] akshare 数据源连通(获取到 5847 条数据)
--------------------------------------------------
结果: 11/11 全部通过!
环境配置完成,可以开始课程。
--------------------------------------------------
结果: 11/11 全部通过!
环境配置完成,可以开始课程。
```
实际终端输出如图 II-8 所示。
实际终端输出如图 II-10 所示。
![环境检查全部通过](images/getting-started/env-check-success.png)
如果有任何项目显示 `[FAIL]`,**不要紧张,也不要自己改命令重试**——把完整的输出结果复制给 TRAE 的 AI 助手,告诉它“环境检查有失败项,请帮我修复”。AI 会照着 spec 里“故障恢复”那一节的对照表逐项排查
其中 `akshare 数据源连通` 后面的条数可能和书里不同,这不影响结果。关键是这一项显示 `[OK]`,并且最后出现 `11/11 全部通过`
> **💡 关于错误处理的原则**
如果有任何项目显示 `[FAIL]`,不要自己改命令重试。把完整输出复制给 TRAE 的 AI 助手,告诉它:“环境检查有失败项,请帮我修复。”AI 会按照 spec 里的故障恢复说明逐项排查。
> **💡 关于错误处理**
>
> 整本书你都不必去**啃** Python 报错——遇到任何报错,**把完整信息复制给 AI 助手**就好。AI 排查报错的能力远比让初学者读 stack trace 强。这是本书的承诺:你只写 spec、看结果调试交给 AI。
> 整本书你都不必去 Python 报错。遇到报错,把完整信息复制给 AI 助手就好。你的工作是写清楚任务、看结果调试细节先交给 AI。
## 你刚刚学到了什么
### 你做了什么
刚才这个过程包含两件事。
刚才的过程其实包含两件事:
第一,环境准备好了。这件事做完后,后面章节就可以直接跑实验。
**环境装好了**——这件一次性的事做完了,往后不用再碰。 ② **你看到了一份 spec 是怎么从零写出来的**——四段骨架在你眼前展开了一遍,四条核心要点各自示范了一次
第二件事比第一件重要。
第二,你看到了一份 spec 是怎写出来的。它不是一段模糊、碎片、随意的说法,而是由上下文、任务描述、任务要求、验收标准四段组成。你把任务说清楚,AI 才能稳定执行
### 7 条 spec 自查清单
刚才本节示范了 4 条要点(写在每段 spec 之后的「📌 要点 1-4」)。把这 4 条加上 3 条通用补充,就构成下面这份“写完 spec 过一遍”的自查清单——任何一条没做到都是问题:
把本章的 4 条要点,再加上后面会反复用到的 3 条补充,就得到一份 spec 自查清单。以后每写完一份 spec,都可以用它快速扫一遍。
| \# | 自查项 |
|----|----|
| 1 | **四段都齐了?** 上下文 / 任务 / 要求 / 结果呈现,一段都不能少 —— 结构骨架 |
| 2 | **任务一句话能说清吗?** “做一下数据分析” ❌;“获取沪深 300ETF 历史数据并可视化” ✅ —— 来自要点 1 |
| 3 | **关键事物具体到 ID 了吗?** 标的代码、库版本、文件名、参数名都点名 —— 来自要点 2 |
| 4 | **能锁的都锁了吗?** 库版本、日期窗口、随机种子——任何会随时间漂移的都要锁 —— 来自要点 3 |
| 5 | **结果呈现有“机器能判断的标志”吗?** assert、具体数字、文件名、退出码 —— 来自要点 4 |
| 6 | **写的是“做 X”而不是“不要 Y”吗?** 正向指令比否定句对 AI 更稳定 —— 通用补充(首次出现) |
| 7 | **长度合理吗?** 大多数 spec 不超过 80 行(环境配置这种特殊情况除外) —— 通用补充(首次出现) |
**表 II-4 spec 自查清单**
第 6、7 两条本节没有专门演示,会在第 1 章后续 spec 写作中遇到时再补讲。这 7 条会跟着你整本书——每写一份新 spec,最后都过一遍这个清单。
| 序号 | 自查项 |
|------|--------|
| 1 | **四段都齐了吗?** 上下文、任务描述、任务要求、验收标准,一段都不能少 |
| 2 | **任务能一句话说清吗?** 先讲目标,不要把所有步骤塞进任务描述段 |
| 3 | **关键对象准确吗?** 如版本号、库名、文件名等,都要写清楚 |
| 4 | **会变化的条件确定了吗?** 库版本、日期范围、随机种子,都要写清楚,不要变 |
| 5 | **成功标准能检验吗?** 给出具体输出、具体文件、具体数字 |
| 6 | **指令是正向的吗?** 多写“请做 X”,少写“不要 Y” |
| 7 | **长度是否合理?** 不要因为详细,就写得啰嗦,都写必要而准确的话 |
### 你需要做什么
后续每一章都会让你写或改一份 spec。开始时书里会带你一段一段写(像本节这样),后面会渐渐放手——到第 6、7 章你应该能自己写完整的 spec,到第 9 章你应该能根据问题写新 spec。
> **💡 spec 写作能力的成长曲线**
>
> 抄 → 改 → 拼 → 写。第 1 章基本是“抄 + 看懂”;第 2-3 章是“改其中几个参数”;第 4-6 章是“把多个 spec 拼成新流程”;第 7-9 章是“看到一个量化问题,自己写一份新 spec”。每一步都不用着急。
写 spec 不需要死记硬背,多写多实验,多和 AI 交互自然能掌握。模型越强,越能对 spec 容错,但把 spec 写清楚,仍然能减少返工,也能让你更容易判断 AI 有没有做对。后面进入策略实验时,open-xquant 也会帮你检查策略规格说明书里常见的遗漏。
## 准备就绪
恭喜,你现在已经具备了开始量化交易学习的全部条件
前言和这一章涉及的核心概念,汇总在表 II-5 里
### 速查表
**表 II-5 准备工作核心概念速查**
前言和这一节涉及的核心概念汇总在表 II-4 里。
| 概念 | 含义 | 这一章怎么用 |
|------|------|--------------|
| spec(任务说明书) | 给 AI 的结构化指令 | 用装环境练第一份 spec |
| spec 四段骨架 | 上下文 / 任务描述 / 任务要求 / 验收标准 | 按四段写清楚任务 |
| TRAE | 本书演示用的 AI 编程工具 | 把 spec 交给它执行 |
| Python 环境 | 跑代码需要的语言和库 | 用检查脚本确认可用 |
| 四个阶段 | 确定候选 / 制定规则 / 执行交易 / 评估归因 | 第 1 章开始正式进入 |
| 做 / 看 / 疑 | 写成规格 / 读懂结果 / 检验可信 | 本章先练“做”:把任务写清楚 |
**表 II-4 动手前的核心概念速查**
到这里,你已经具备了开始学习量化交易的基本条件:有 AI 编程工具,有 Python 环境,也知道一份 spec 应该怎样写。
| 概念 | 含义 | 类比 |
|----|----|----|
| spec(任务说明书) | 给 AI 的结构化指令 | 你说清楚要做什么,AI 负责执行 |
| spec 四段骨架 | 上下文 / 任务 / 要求 / 结果呈现 | 一份 spec 必须回答的四个问题 |
| TRAE | 本书演示用的 AI 编程工具 | 也可以换成 Cursor / Claude Code 等 |
| 量化交易 | 用数据和规则做交易决策 | 把“我觉得”变成“数据显示” |
| 量化飞轮 | 候选 / 组合 / 执行 / 归因,循环往复 | 越转越聪明的轮子 |
| 三字要点 | 飞轮每一格都要走的三步:做 / 看 / 疑 | 想法要规则、结果要指标、指标要被怀疑 |
### 下一步
**第 1 章**,我们会用一个真实的 ETF 带你**走完整个飞轮一圈**——从最简单的定投开始(做),看它能不能赚钱(看),尝试让它“聪明”一些(再做、再看),最后亲眼看到“回测时间段里收益率优美、换段时间就亏损”这种典型情况(疑)。 准备好了吗?翻到下一章,开始你的第一次飞轮转动。
---
## 反馈与读者群
如果你发现本章有错别字、概念不清或实验结果不一致,欢迎
[提交 GitHub Issue](https://github.com/xingwudao/xquant-beginner/issues/new/choose)。
课程、正式书和后续更新见
[xquant.shop/courses](https://xquant.shop/courses)。
添加作者微信 `xquanter` 进入读者群。请备注:`XQuant 读者`
第 1 章,我们会用一个真实 ETF 走一遍完整流程:写规则、跑实验、看结果,以及让你产生第一次怀疑,为什么看到实验结果漂亮不要高兴得太早。准备好了吗?翻到下一章,开始第一次量化实验。
Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 440 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 470 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 754 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 639 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 135 KiB

After

Width:  |  Height:  |  Size: 138 KiB

+118 -139
View File
@@ -1,188 +1,167 @@
# 前言
你不是来背量化术语的,也不是来硬学编程的。你来学会一件事:把一个投资想法,借助 AI 做成实验,再用数据判断它是否站得住。这就是本书想带你完成的事。
你可能没有金融基础,不知道 ETF、回测、最大回撤是什么意思;你可能没有编程基础,看到 Python、代码、报错就头疼;你也可能没有 AI 使用经验,不知道怎样把自己的想法讲给 AI。没关系,这本书就是为这样的读者写的。
过去,量化投资听起来像少数专业人士的工具。它和数据、模型、程序、交易系统绑在一起,普通投资者很容易觉得:这东西很好,但离我太远。现在,AI 编程工具正在改变这件事。
你不必一开始就成为程序员。你可以先用自然语言说明自己的想法,让 AI 协助你写代码、跑实验、画图表、排查错误。你真正要学的,不是背下每一行代码,而是学会提出清楚的问题,设计可验证的规则,看懂实验结果,并对结果继续追问。这也是量化投资最重要的能力。
## 为什么写这本书
你打开这本书,大概率是因为你听说过“量化交易”或者“量化投资”。多年以来,媒体都在传递一类观点:量化投资具有卓越的投资业绩表现。然后,如此卓越的量化投资通常只服务于高净值用户。普通人常常因为净值门槛被阻碍在量化投资的门外。既然如此,普通个人投资者如何才能应用量化投资呢?既然无法借助他人而使用量化投资,那么唯一的途径就是去自学量化投资。中国目前有2.4亿个人投资者,按照千分之一的人想学习量化投资而言,也至少有24万人正走在学习量化投资的路上。但这些尝试学习量化投资的人在学习的路上走得并不算顺利。究其原因,与量化投资相关的晦涩的金融术语、复杂的数学公式和天书般的程序代码阻挡了普通投资者的量化投资学习之路。有少数人能迈过以上几个门槛,可是新的阻碍又重新出现。学完了量化投资内容,在面对真实市场时,如何把量化投资和真实市场结合,如何抓住量化投资应用到金融市场的重点之处,如何把握把时间花在量化投资的哪些环节上?要解决这些问题,只有建立一套科学、完整、可执行的量化体系才行,否则,面对波诡云谲的金融市场,我们无法持续构建出有效的量化策略,也就无法真正享受量化投资的红利
很多人第一次听到量化投资,都会有类似的感觉:它听起来很厉害,但离自己很遥远
我自己是算法工程师出身,过去十多年一直做 AI 和算法相关的工作,具备了数学和编程基础。我接触量化投资的方式,是从业余时间跟着教程跑过几个简单的量化策略 demo开始。之后我发现工程思维里“拆问题、提假设、用数据验证”这一套方法在量化投资领域也成立。这不过,这里要解决的问题是金融领域的问题。三个拼图,我已经有了两块,只要补足金融领域的知识短板,我就可以继续做量化投资了。我决定深入下去,直到在这条量化投资路上找到了「一硕」——一位有十几年量化交易经验的搭档。他正在尝试用 AI 来实现量化投资,他是统计学和财务出身,具备数学和金融的基础,我们两个的能力互补。之后,他把自己开发的量化投资策略毫无保留地分享给我,我们一起把它拆开:搞清楚每一个想法是怎么产生的、怎么用数据验证、验证之后如何在工程层面真正执行,分析执行时又会遇到哪些大大小小的问题。整个拆解的过程,让我获益匪浅,这也是本书的缘起
你也很可能已经在互联网上看过一些量化内容。有人让你先学 Python,有人让你研究因子,有人让你理解回测框架;有人让你装回测软件、交易软件,名字一个比一个陌生,比如 QMT、pTrade;又有人告诉你数据源最重要。他们说每一个重要,每一个都必须学
这本书能写出来,还有一个 2025 年才成熟的关键基础——**AI 编程能力的跃迁**。在 2024 年之前,“用 AI 替你写量化代码”还是不靠谱的:AI 写出来的代码经常跑不通、逻辑反复出错、库版本对不上。但从 2025 年下半年开始,全世界的大模型在编程方面的能力突飞猛进,以 Cursor、TRAE、Claude Code 为代表的 AI 编程工具也在持续迭代,两者叠加,AI 编程发生了一轮明显的能力跃迁——能读懂自然语言写的需求、能把复杂任务拆成小步骤、能自己调试、能记住上下文连续工作数十小时。
什么都要学的压力,再加上不知道先后顺序的混乱,让很多人停在门口张望,迈不进量化的门槛。你不是没有兴趣,也不是没有投资想法,而是不知道怎样把想法变成一次可以验证的实验。更具体一点,常见的卡点有三个:
**这意味着什么?这意味着阻挡大家尽早动起来的门槛降低了。这也是本书提供的方法——“你用自然语言描写想法、AI 替你写代码做实验”——在 2025 年才真正可行。** 我们等到这扇窗开了,才动笔把这套从想法到落地的完整过程写成书
- 不知道从哪里开始,只能在教程和名词之间来回打转
- 想测试一个策略,却被代码、环境、报错挡住。
- 看过很多单个策略,却始终搭不出一套可以持续改进的方法。
## 这本书要解决什么问题
其实,我认为,初学者并不怕要学的内容多,真正让人踌躇不前的,是三个疑问:这些东西之间有什么关系?第一步做什么,才能先找到手感?最后怎样把零散知识组装成一套系统?
本书要解决的,是量化投资初学者可能正在遇到的三个主要问题——
就要分享一下我的故事了。我是算法工程师出身,过去十多年一直做 AI 和算法相关工作,有数学和编程基础。刚接触量化投资时,我也是从业余时间跟着教程跑几个简单策略开始。后来我发现,算法工程里常用的“拆问题、提假设、用数据验证”这一套方法,在投资里同样成立。不同的是,这里要解决的是金融问题。
**问题 1:想做量化,但卡在“不会编程”,迟迟迈不出第一步。** 你有想法,甚至有过策略雏形,但一想到要写代码、搭环境、调库,就放弃了。这本书让你**用自然语言**表达策略思路,写代码、跑程序、画图表全部交给 AI 助手。你只需要清晰地说明“要做什么、做成什么样”
也就是说,量化投资至少需要三块能力:数学、编程、金融。我已经有前两块,还差金融这块。后来我认识了「一硕」——一位有十几年量化交易经验的搭档。他有统计学和财务背景,也长期在真实市场里打磨策略。我们把他做过的策略一层层拆解:一个想法从哪里来,怎样变成规则,怎样用数据检验,怎样落到真实买卖,怎么迭代策略
**问题 2:看了很多量化内容,专有名词高大上,要么看不懂、要么落地难。** 各种指标、术语、模型铺天盖地,但不知道哪些是关键、哪些只是“看起来很专业”。这本书帮你回到最底层:用**数据和逻辑**来重新理解投资,这就是量化思维
随着拆解的策略越来越多,我渐渐意识到,这些疑问背后其实是同一个问题:初学者真正缺的,不是又一个“神奇策略”,而是一条能走通的路径。于是,我和一硕决定把这条反复拆解、验证、改进的路径写出来
**问题 3:尽管学会了很多量化策略,但始终没有搭建出随着市场变化而进化的系统。** 你可能看过教程、读过文章,但内容都是碎片:这里学一点策略,那里学一点工具,没有一根主线把它们串起来。这本书带你从头做一遍,**亲手搭出一个成体系、可迭代的量化交易系统**
此外,这本书能写出来,还有一个背景:从 2025 年到 2026 年,AI 编程能力突飞猛进。在 2024 年之前,“让 AI 替你写量化代码”还不太稳定,代码经常跑不通,逻辑也容易出错。但从 2025 年下半年开始,以 Claude Code、Codex、TRAE、Cursor 为代表的 AI 编程工具和编程大模型一起在持续进步,已经能比较稳定地读懂自然语言需求,拆解任务,写代码,调试错误,并连续处理较长的上下文
**这本书适合两类人**:① **有兴趣 + 没实操**:刚刚对量化产生兴趣、想试试又不知道从哪开始的人;② **有尝试 + 无系统**:读过几本量化书、试过别人的示例代码,但始终没做出过一套自己的策略的人
这意味着,普通投资者不必先花很长时间学编程,才有资格做第一轮量化实验。你可以把精力先放在更重要的问题上:想法是否清楚,规则是否能执行,结果是否可信,策略是否还能继续改
**要花多少时间?** 如果你按“周末半天 + 工作日两个晚上各 1.5 小时”的节奏走(每周大约 5-6 小时),**2-3 个月**可以走完全书。当然你也可以加快——重点不是速度,而是每章动手做完,能完整解释自己做了什么
这就是我们写作本书的初心:让一个没有编程基础、没有系统量化经验的读者,也能从第一章开始动手,逐步搭出自己的量化交易系统
走完这条路,首先你会亲手指挥 AI 实现一份**完整的策略**——一份能告诉你“什么时候买、什么时候卖、买多少”的可执行规则,消除了动手的门槛;其次你建立了一个完整的量化交易体系,以后再实践和学习都会有的放矢。
## 这本书为谁解决什么问题
至于具体会带你走过哪些事——下一节先给你一张地图,再带你走。
这本书主要写给两类人:
## 这本书的整体框架
- **有兴趣,但没实操**:刚刚对量化产生兴趣,想试试,却不知道从哪开始。
- **有尝试,但没系统**:读过量化书、看过教程、跑过别人的示例代码,但始终没有做出过一套自己的系统。
在带你做实验之前,先把全书的骨架立起来——这样后面 9 章动手做的每一步,你都知道自己在地图的哪个位置
本书会按一条清楚的成长路径,帮你解决三个问题
量化交易听起来复杂,但骨架其实很简单:**它只是 4 件事循环做、每件事走 3 步**。看明白这“4×3”,你就有了一张全书都能用的地图。
**问题 1:想学量化,却不知道从哪里开始。**
### 量化飞轮:候选 → 组合 → 执行 → 归因
网上内容很多:Python、因子、回测、策略、数据源……每个方向似乎都重要,但初学者最需要的不是一次性学完所有知识,而是知道第一步先做什么。本书会从一个最小可运行的策略开始,让你先把完整流程跑通。
任何一个量化策略,都是在循环回答四个问题:
**问题 2:想做量化,却被编程门槛挡在门外。**
- **候选层 · 选什么** —— 从几千上万只股票/ETF 里**先有鱼塘,再捞鱼**——圈出一小撮值得长期跟踪的
- **组合层 · 买多少 / 何时买卖** —— 从池子里挑几只,决定每只买多少、什么时候买卖,这是依据数据做决策的关键步骤。
- **执行层 · 真的下单** —— 把“数据决策”变成“真实持仓”,把组合变成真实的买卖订单,并处理价格滑动、成本、风险。
- **归因层 · 对比和打分** —— 钱到底是赚了还是亏了?和更简单的做法相比值不值?是靠本事、还是运气在赚钱?靠运气赚的钱迟早得还给市场。
传统量化需要写代码、处理数据、搭回测框架、调接口。很多人不是不会思考投资,而是不会把想法写成程序。本书借助 AI,让你用自然语言描述策略,把更多精力放在策略思考和结果判断上,而不是卡在代码实现上
这四层构成了一个完整的量化体系,也是一个**飞轮**——归因层得出的结论,会反过来修改下一轮的候选和组合,飞轮越转越准(如图 I-1 所示)。
**问题 3:学了很多策略,却始终搭不出自己的量化系统。**
![量化飞轮:候选 → 组合 → 执行 → 归因 → 反哺候选](images/preface/fig-quant-flywheel.png)
常见的公开量化内容都在讲一个技术指标、一个单策略、一个交易技巧。这些的确吸引眼球,尤其是再配上一个半真半假的实盘收益,很多人就蜂拥而至。但真正能长期发挥作用的,不是某个单点技巧,而是一套能持续迭代的流程。本书会带你走过数据获取、策略生成、回测验证、风险控制、交易执行和持续优化全过程,最终形成属于自己的量化交易系统。
> **📌 小词典:什么是飞轮(Flywheel)?**
>
> 飞轮就是一个能持续转动的轮子——一开始要使劲推,一旦转起来,靠惯性自己就能加速。在量化里,“飞轮”意味着:策略不是“做一遍就完事”,而是每转一圈都比上一圈更聪明一点。
### 三字要点:做 / 看 / 疑
四层构成了一个完整的量化体系,在构建每一层时,还要遵循一套动作,才是真正的量化交易系统。比如说,同样是“做组合层”,你可以凭感觉拍脑袋,也可以用数据说了算,结果当然天差地别。所以**每一层**里,都得走完同样的 3 步:
- **做** —— 把动作写成程序,成为可重复执行的规则。不允许“我感觉茅台不错”,要写成“近 60 日均价上穿当日收盘价就买”
- **看** —— 把目标和结果都要定义出可比较的指标。不允许“感觉”,不允许“看起来还行”,要写成“年化 12%、最大回撤 18%”这种带数字的判断。只要数字上没达成目标,就不能靠“再等等看”或“应该没问题”改变结论。
- **疑** —— 永远对指标保持质疑,防止自己被指标欺骗。不允许“回测说赚了就信”,要问:“换一段时间还行吗?参数稍微调一下还稳定吗?”
这三个动作是**层层递进**的:先**做**出来,才有结果可**看**;**看**到数字了,才有东西可**疑**。
> **📌 小词典:为什么“疑”也是量化的一部分?**
>
> 量化交易中,最严重的不是错过了收益,而是乐观导致亏损。所以始终对回测结果保持怀疑就非常有必要。一个回测能跑出年化 50%——但只在那 5 年的特定数据上跑得出来,换一段时间年化就掉到接近 0。**会做、会看,但不会疑,就会被自己的策略欺骗,就会在某些时候把赚到的钱加倍还回去。** 这就是为什么“疑”和“做”“看”同等重要,甚至更重要。全书第 6 章会专门用一整章训练这个动作。
把四层飞轮和三字要点叠起来,就是完整的量化体系,也是这本书要在你脑子里建起来的整张地图(如图 I-2 所示)。
![飞轮的每一格都要走“做、看、疑”三步](images/preface/fig-flywheel-with-three-actions.png)
### 这本书的 9 站旅程
我们将用 9 个站点走完这张地图的全程。接下来的 9 章,就是带你在这张地图上走 9 站。每一站对应飞轮的哪个位置、主要练哪个动作,列在表 I-1 里。
**表 I-1 9 章主题与飞轮位置对应**
| 章节 | 问题 | 飞轮位置 | 主练动作 |
|---------|--------------------------------|----------------------|--------------|
| 第 1 章 | 第一次跑通一个完整策略 | 走一遍完整流程 | 做 + 看 + 疑 |
| 第 2 章 | 选出你的投资宇宙:3 只 ETF | 候选层(选什么) | 做 |
| 第 3 章 | 给 ETF 分钱:3 种分法实测 | 组合层(买多少) | 做 |
| 第 4 章 | 给策略加规则:调仓、止损、止盈 | 组合层(何时买卖) | 做 |
| 第 5 章 | 策略好不好?4 个评估角度 | 归因层(对比和打分) | 看 |
| 第 6 章 | 看清好看的回测:别被自己骗了 | 怀疑你的回测 | 疑 |
| 第 7 章 | 真的下单:跨到实盘 | 执行层(真下单) | 做 + 看 |
| 第 8 章 | 让策略活着:监控、诊断、迭代 | 评估并反哺起点 | 疑 + 做 |
| 第 9 章 | 持续寻找盈利机会:因子研究入门 | 找下一个赚钱机会 | 做 + 看 |
注意第 1 章——它一上来就带你**走完飞轮一圈**,而且故意让你在终点遇到一种典型陷阱:**回测里看着赚钱,换一段时间就不行了**。这不是设计失误,是**设计意图**:让你在第 1 章就亲身经历什么叫“被自己的回测骗”,从此真正记住“疑”这个动作的分量。
九站走完,飞轮的 4 格你都站过、做/看/疑 3 个动作你都练过。这就是你**带得走**的量化思维。
## 你能获得什么能力
读完这本书,你学会的不是具体的策略,而是一套不断找到新策略的方法。总结一下,你会建立起四类核心能力:
- **会用 AI 编程**:学会用自然语言结构化描述任务,让 AI 准确地翻译成代码——你将学会和 AI 协作做事,这是 AI 时代的核心能力。
- **养成量化思维**:把模糊的想法、抽象的概念转化成定义明确的指标和清晰无误的规则,用数据检验假设是否成立。
- **会做策略实验**:在 4×3 的量化框架下,你将从数据获取、回测、对比基准、参数扫描,到样本外验证,全都亲手跑一遍。
- **知道如何迭代**:你不仅有了量化体系和做 / 看 / 疑 三个动作,你还会知道这个体系以后会遇到哪些坑,从而长期迭代下去。
如果你按“周末半天 + 工作日两个晚上各 1.5 小时”的节奏走,每周大约 5-6 小时,2-3 个月可以走完全书。当然,你也可以更快。重点不是速度,而是每章都动手做完,并且能解释自己做了什么、为什么这样做、结果说明了什么。
## 本书的核心理念
跟着全书走完,你会发现,本书自始至终在贯彻三个理念:
在正式进入学习地图之前,先说明本书的三个设计原则。
**第一问题驱动。** 量化交易本质上是做研究——就现象提出问题,就问题提出假设,用实验验证假设。每一章都一个大问题,引出若干小问题,回答了这些问题再引出一个新的大问题,来到新一章。跟随这些问题,我们将走完完整的 4×3 量化地图
**第一问题驱动。** 量化交易不是背概念,而是做研究:先提出问题,再提出可以检验的假设,然后用实验验证。全书每一章都一个具体问题开始,问题解决后,再自然引出下一章
**第二先动手,再认知。** 量化交易光看不动手绝学不会——你必须亲自走一遍。书中在一个接一个问题驱动下,都有对应的实验来解答这些问题;实验结果出来了,我们才会开始引入一些概念建立认知
**第二先动手,再认知。** 很多概念只有在你亲手跑过一次之后,才会真正理解。书里不会先堆一大串术语,而是先让你做一个实验,看到结果,再解释为什么这样做
**第三0 编程。** 全书都采用 AI 编程的方式做实验,因此我们需要把想法详细描述给 AI——这称之为任务说明书(specification,简称 spec)。全书一步一步告诉你如何写一份好的 spec,并提供 spec 和代码供你在动手时参考
**第三,先把任务说清楚,再让 AI 写代码。** 你不需要先学会自己写程序,但你必须学会把想法说清楚。描述你想让 AI 完成什么、做到什么程度的文字,泛泛地说叫任务说明书,英文是 specification,简称 spec;具体到量化策略实验里,就是策略规格说明书
> **📌 小词典:什么是 spec?**
>
> spec 是 specification 的简写,可以理解为「任务说明书」。你不需要掌握任何编程语法,只需要用大白话把两件事说清楚: **要做什么** (在量化交易中就是“你想做什么策略”); **做成什么样** (在量化交易中就是“策略应该如何运行”)
>
> AI 会根据这份说明,自动完成编码、执行和输出
## 学习地图
正式动手之前,我先把全书地图展示出来。这样后面 9 章每做一步,你都知道自己走到了哪里。
量化交易听起来复杂,但可以先记成一句话:**四个阶段,每个阶段三个环节**
四个阶段,是一个策略从想法到复盘通常要经过的过程,也是进化出一个量化交易系统的过程;三个环节,是每个阶段都要走的实验链。看懂这张地图,你后面就不会被单个术语带乱。
### 四个阶段:确定候选 → 制定规则 → 执行交易 → 评估归因
一个量化策略,通常会经过四个阶段:
- **确定候选 · 选什么** —— 先确定一组值得长期跟踪的资产。你不应该在整个市场里乱找,而是先圈出一个观察范围,建候选。
- **制定规则 · 怎么买卖** —— 持续观察这些候选的数据表现,把买什么、买多少、什么时候买、什么时候卖写成清楚规则。
- **执行交易 · 怎样实现** —— 策略规则生成的是理论交易计划,计划还需要变成现实,比如怎么面对交易不成功、成交价不等于理论值、交易本身还有成本等问题。
- **评估归因 · 为什么赚或亏** —— 无论盈亏,原因都应该追踪到每个阶段的指标和逻辑,找到是什么在真正起作用,否则,赚了也无法复制,亏了也无法改进。
这四个阶段不是走完一次就结束。评估归因得到的结论,会反过来改进下一轮确定候选、制定规则和执行交易。
### 三个环节:做 / 看 / 疑
每个阶段都要按同一条实验链推进,才不会变成凭感觉做投资。
这条实验链由三个环节组成,可以简单记成三个字:**做 / 看 / 疑**。
- **做** —— 把模糊的投资想法,写成可验证的策略规格说明书。规则、数据、时间范围、买卖条件、输出结果都要尽量清楚,这样 AI 才能把它翻译成可执行的 Python 程序。不能执行,就无法验证。
- **看** —— 程序用历史数据跑完实验后,会输出一组指标。你要学会看每个指标代表什么,也要判断这些数字处在什么水平。
- **疑** —— 检查实验过程和结果是否可信:数据有没有问题,时间段是不是太特殊,参数是不是碰巧有效……只有结果可信,才能进一步判断它是支持了假说,还是推翻了假说。
这里的“疑”不是情绪,也不是一句口号,而是一组必须做的复查动作。会做、会看,但不会疑,最容易被漂亮结果骗到。
把四个阶段和做 / 看 / 疑叠起来,就是本书要帮你建立的量化地图(如图 I-1 所示)。
![量化学习地图:四个阶段,每个阶段三个环节](images/preface/fig-quant-map.png)
### 9 站旅程
接下来,我们要在这张地图上一共经历 9 站旅程。这 9 章,每一章解决一个问题。
**表 I-1 9 章主题与学习地图对应**
| 章节 | 问题 | 所在阶段 | 主要训练 |
|------|------|----------|----------|
| 第 1 章 | 跑通第一个策略 | 走一遍完整流程 | 做 + 看 + 疑 |
| 第 2 章 | 先选买什么:从 3 只 ETF 开始 | 确定候选 | 做 |
| 第 3 章 | 决定各买多少:3 种分法实测 | 制定规则 | 做 |
| 第 4 章 | 决定何时买卖:再平衡、止损、止盈 | 制定规则 | 做 |
| 第 5 章 | 判断策略好不好:4 个关键视角 | 评估归因 | 看 |
| 第 6 章 | 别高兴太早:防止过拟合 | 评估归因 | 疑 |
| 第 7 章 | 从理想到现实:执行交易 | 执行交易 | 做 + 看 |
| 第 8 章 | 策略跑起来以后:监控、诊断、迭代 | 评估归因 | 疑 + 做 |
| 第 9 章 | 持续寻找机会:因子研究入门 | 评估归因 + 确定候选 | 做 + 看 |
注意第 1 章。它不会先讲一堆概念,而是直接带你走完整流程:拿到数据、写出规则、跑出结果、再发现一个典型问题——回测里看着赚钱,换一段时间就不行。这个安排是故意的:你要从第一章开始就知道,量化不是只看一次漂亮结果,而是要反复检查结果是否可靠。
当这 9 站旅程走完,你就完整经历了这四个阶段,而且做、看、疑三个环节你也都练过。这就是你真正获得的能力。
## 你能获得什么能力
读完这本书,你学到的不是某一个固定策略,而是一套把想法变成实验、再用实验改进想法的方法。具体来说,你会获得三类能力:
- **养成量化思维**:把市场里的现象和自己的投资想法,转成可以验证的假说;再把假说写成规则和指标,即策略规格说明书,而不是停留在“我觉得会涨”。
- **会做量化实验**:把策略规格说明书交给 AI,让它写代码、跑程序、画图表;然后读懂实验结果,判断结果是支持假说,还是推翻假说。
- **会搭建量化交易系统**:不再只看一个指标、一个策略、一个技巧,而是把数据获取、策略生成、回测验证、风险控制、交易执行和持续优化串成一套可迭代流程。
## 怎么使用这本书
这本书不是用来“看完”的,而是用来“跟着做”的。**它的核心学习方式是循环**
这本书不是用来“看完”的,而是用来“跟着做”的。你可以这样使用每一章
> 提出问题 → 动手实验 → 看结果、理解 → 进入下一个问题
1. 阅读本章开头,先知道这一章要解决什么问题。
2. 跟着正文,看我们怎样把问题拆成一份 spec。
3. 把写好的 spec 交给 AI,让它生成代码并尝试运行。一次写不好也没关系,可以继续修改 spec,直到 AI 明白你的要求。
4. 查看实验结果。对照书中的图表和解释,看懂每个结果说明了什么。
5. 试着改一个小条件,比如时间段、参数,再观察结果是否变化。
6. 遇到报错,把错误信息发给 AI,让它帮你排查。
每一章都按这个循环组织
如果你一开始不知道怎么写,也可以直接使用配套仓库中的示例 spec。你的目标不是永远复制示例,而是先照着跑通,再逐步看懂它为什么这样写,最后能改出自己的版本
你可以在一开始,从我们提供的 GitHub 仓库中获得参考的 spec 或者 Python 代码,完成简单实验,然后模仿我们提供的 spec,根据方法编写自己的 spec,或者修改已有 spec,做新的实验。
全书会穿插两类辅助内容:
实验结果,你需要看懂其含义,书中提供了实验结果的解读,你也可以提出自己的解读,和你的 AI 助手讨论。这也有助于建立你的量化思维。鼓励读者对本书方法和解读充满“怀疑”,这正是三字要点要训练的
- **📌 要点** —— 总结写 spec 和做实验时最重要的提醒
- **提示 / 注意** —— 说明容易踩坑、容易误解或需要临时补充的地方。
### 每一节怎么走
每节正文都会**带你一段一段把 spec 写出来**——上下文、任务、要求、结果呈现四段,对应的方法点(📌 要点)嵌在演示过程中讲,不集中讲。完整的 spec 文件在 GitHub 仓库的 `specs/` 目录里,跑实验时直接复制给 AI。
**你的目标不是“复制 spec”,而是看懂每段为什么那样写,逐步学会自己写。** 每节按这五步:
1. 阅读章节正文,理解这一节要解决什么问题
2. 跟着正文一段段写 spec,看每段对应的方法点
3. 打开仓库里完整的 spec 文件,复制给 AI 让它执行
4. 看运行结果(图表、关键数字)+ 章内的解读
5. 遇到错误,把信息发给 AI 让它排查
### 关于“自己写 spec”
第一次听到要“自己写 spec”,你可能有一点顾虑——“我完全不会写 spec,是不是从一开始就跟不上?”
**别担心。** 这本书把 spec 能力分**抄 → 改 → 拼 → 写**四阶段,一级一级铺给你:
- **第 1 章**:抄——跟着书里的段段演示走一遍,把完整 spec 复制给 AI,观察它的执行过程,先体会跑通的手感
- **第 2-3 章**:改——改 spec 的参数(换标的、换时间窗),结构本身不变
- **第 4-6 章**:拼——把多个已有 spec 的片段组合起来,做新的实验
- **第 7-9 章**:写——为自己的投资想法从零写 spec
正式开始之前,**开始动手之前**那一章会带你完整地写一份 spec——通过让 AI 帮你装环境这件事,把 spec 的四段骨架(上下文 / 任务 / 要求 / 结果呈现)演示一遍,并归纳出一份 7 条自查清单,帮你建立基本心智模型。**这份 7 条自查清单从第 1 章「抄」开始就一直陪你走到第 9 章「写」**——只不过你用它的方式会从“对照检查”逐步变成“下意识落笔”。从第 1 章起,每节正文里出现的方法点都用「📌 要点」标出来——你不用刻意记,看到一次记一次,跟着写到第 7-9 章自然就内化了。
### 两类辅助内容
全书出现两类辅助内容:
- **📌 小词典** —— 概念解释,反复使用的术语随时回查
- **📌 要点** —— spec 写作的方法点,嵌在写 spec 演示的段落里,看到一次记一次
### 配套资源
## 配套资源
本书的免费资源包(扫书末二维码即可拿到),包含:
- 所有章节的示例 spec
- 配套的 Jupyter Notebook(含可运行代码 + 完整运行结果)
- 第 2 章起使用的量化框架 open-xquant(详细介绍见第 2 章工具升级一节)
- 配套的 Jupyter Notebook(含可运行代码完整运行结果)
- 第 2 章起使用的量化框架 open-xquant
也可以从 GitHub 直接获取(适合熟悉 git 的读者):
- 课程仓库: https://github.com/xingwudao/xquant-learning
- 课程仓库:https://github.com/xingwudao/xquant-learning
- 量化框架:https://github.com/xingwudao/open-xquant
地图就到这里。下一节“开始动手之前”是一次性准备——把 AI 编程工具和 Python 环境装好,顺便用“装环境”这件事完整地写一份 spec。一次配完,第 1 章我们就直接跑实验。
---
## 反馈与读者群
如果你发现本章有错别字、概念不清或链接问题,欢迎
[提交 GitHub Issue](https://github.com/xingwudao/xquant-beginner/issues/new/choose)。
课程、正式书和后续更新见
[xquant.shop/courses](https://xquant.shop/courses)。
添加作者微信 `xquanter` 进入读者群。请备注:`XQuant 读者`
前言就到这里。下一章“准备工作”会带你把 AI 编程工具和 Python 环境准备好,并用“装环境”这件事,第一次完整练习怎样写 spec。准备好之后,第 1 章我们就直接跑实验。
+202 -305
View File
@@ -1,103 +1,100 @@
# 第 1 章:第一次跑通一个完整策略
# 第 1 章:跑通一个策略
前言那张地图——**飞轮的四层(候选 / 组合 / 执行 / 归因)+ 三字要点(做 / 看 / 疑)**——在这一章会被你**亲手走一圈**。你把“做 + 看 + 疑”三个动作都练一次,飞轮也在你手里转完第一圈
前言那张学习地图,从这一章开始,就会变成一次次的具体实验。在这一章里,你会把一个最简单的投资想法写成规则,交给 AI 跑实验,读懂结果,再亲手验证:漂亮结果到底靠不靠得住
7 个动手实验串成一条主线。先从最简单的定投起步,把动作写成规则(**做**),看它能不能赚钱(**看**)。给它加一条判断让它“聪明”一点,判断有没有让结果更好(**再做、再看**)。最后扫描一圈参数,亲眼看到量化交易最常见的失效场景——**回测里看着赚钱,换一段时间就不行了**(**疑**)。
这就是为什么本书的口号是「做要规则、看要指标、还要怀疑你的指标」 。
这一章有 7 个动手实验。先从最简单的定投起步,把想法写成规则,再让 AI 写成代码**做**),然后看它能不能赚钱(**看**)。接着给它加一条判断让它稍微变“聪明”一点,判断结果有没有好(**再做、再看**)。最后逐个扫描参数,亲眼看到量化交易最常见的失效场景**实验里看着赚钱,换一段时间就不行了**(**疑**)。
**路线图**
本章 5 节实验 + 1 节总结,串成第一次完整飞轮闭环,路线如表 1-1 所示。
本章围绕 5 个问题,安排 7 次动手实验,外加一次总结,带你第一次完整流程,路线如表 1-1 所示。
**表 1-1 第 1 章路线图**
| 节 | 内容 | 实验 |
|----|----|----|
| 1.1 最简单的量化策略长什么样? | 从“笨定投起步——拿到沪深 300ETF 数据,跑一次最朴素的每月固定金额买入回测 | 2 |
| 1.2 我的收益算好还是算差? | 引入“基准”——和“什么都不做的买入持有”对比,理解 Alpha 与 Beta | 1 |
| 1.1 最简单的量化策略长什么样? | 从简单定投起步拿到沪深 300ETF 数据,跑一次最朴素的每月固定金额买入回测 | 2 |
| 1.2 我的收益算好还是算差? | 引入“基准”和“什么都不做的买入持有”对比,看懂收益到底算好还是差 | 1 |
| 1.3 怎么才能赢过市场? | 给定投加一条均线判断,看见“加判断”的双刃剑 | 2 |
| 1.4 找到“最优参数”就能盈利了? | 暴力扫描 24 个均线参数,找历史收益率最高的“冠军” | 1 |
| 1.5 真相时刻 | 把数据切成训练 / 测试,看冠军参数到了新数据上是否真有本事 | 1 |
| 1.6 本章总结 | 把 7 个实验串成一条策略进化路径,带走第一次飞轮闭环 | 0 |
| 拓展阅读 | 量化在金融世界的位置;钱从哪来 | — |
| 1.4 找到“最优参数”就能盈利了? | 逐个扫描 24 个均线参数,找历史收益率最高的“冠军” | 1 |
| 1.5 过去能代表未来吗? | 把数据切成训练 / 测试,看冠军参数到了新数据上是否真有本事 | 1 |
| 1.6 本章总结 | 把 7 个动手操作串成一条策略进化路径,完成第一次做、看、疑的完整流程 | 0 |
## 1.1 最简单的量化策略长什么样?
这一节我们走完飞轮的第一步——**做**把策略写成规则、跑一次回测、看结果
这一节先进入第一个环节:**做**。你会把策略写成规则,并让程序用历史数据跑一次实验。这个过程叫 **回测(Backtest**:用历史数据模拟“如果当时按这套规则交易,结果会怎样”。回测不能预测未来,因为未来可能出现历史里没有出现过的情况。但它能先帮我们排除明显站不住脚的想法,也能让后面的“看”和“疑”有对象
我们从最简单的策略开始:**定投**。
我们从最简单的策略开始:**定投**。没错,就是你可能在银行 App 上见过的那个“定期定额投资”。每个月发了工资,拿出固定的一笔钱买入,不看涨跌、不做判断,机械地重复。
没错,就是你可能在银行 App 上见过的那个“定期定额投资”。每个月发了工资,拿出固定的一笔钱买入,不看涨跌、不做判断,机械地重复。这算量化吗?当然算——“量化”只要满足两个条件就够了:规则明确 + 执行可重复。定投完全够格
定投能量化吗?当然能,“量化”只要满足两个条件就够了:规则明确 + 执行可重复。定投完全可以量化
不过,我们定投的不是某只个股,而**ETFExchange-Traded Fund,交易所交易基金)**。你可以把 ETF 理解成一份“指数套餐”。我们选择的是沪深 300ETF(代码 510300,它把 A 股市场上规模最大的 300 家公司打包在一起——银行、白酒、新能源、互联网各行各业的龙头都在里面。你买一份 ETF,就等于同时持有了这 300 家公司的股票,不需要你自己去挑选哪家公司好、哪家公司差
为什么选 ETF 而不是某只股票?因为选股本身就是一门极深的学问。个股可能暴涨,也可能退市归零。而 ETF 跟踪的是整个市场的平均表现——单家公司出问题,其他 299 家还在。对新手来说,一键买入整个市场,是最省心、也最不容易犯大错的起点。
不过,我们定投的不是某只个股。个股,也就是单家公司自己的股票。我们定投的**ETFExchange-Traded Fund,交易所交易基金)**。你可以把 ETF 理解成一份“指数套餐”。我们选择的是沪深 300ETF,它把 A 股市场上规模最大的 300 家公司打包在一起,其中包括银行、白酒、新能源、互联网各行各业的龙头。
所以我们的定投规则就是:**每月第一个交易日,用固定金额(1000 元)买入沪深 300ETF。** 没有任何判断,没有任何预测,不看新闻,不猜涨跌。就这么简单。
本章 7 个实验都写在同一个 notebook 文件 `q1-strategy.ipynb`——第一份 spec 会让 AI 帮你创建它,后面 6 份直接接着写,变量、函数、图都在同一个 notebook 里累积下来。
本章 7 个实验都写在同一个 notebook 文件 `q1-strategy.ipynb`。notebook 是一种交互式的编程文件,和普通 Python 代码不同,它可以把代码、图表和运行结果按步骤放在一起,方便你看到策略的探索过程。第一份 spec 会让 AI 帮你创建它,后面 6 份直接接着写,变量、函数、图都在同一个 notebook 里累积下来。
### 动手实验 1:获取历史数据
接下来是你的第一个动手实验——拿到沪深 300ETF 5 年的历史数据。我们一起把这份 spec 写出来。
接下来是你的第一个动手实验拿到沪深 300ETF 5 年的历史数据。我们一起把这份 spec 写出来。
#### 起草上下文
#### 第一段:上下文
我们刚装好环境,notebook 是空的,什么都还没做。上下文一句话说清:
> **上下文**:学员已完成环境配置(Python 3.12 + 虚拟环境 + 依赖包已安装)。这是课程的第一个操作步骤。
>
> 📌 **要点**:上下文段交代“前提是什么”。AI 不知道你之前做了什么,要明示——否则它可能假设环境已有现成数据,跳过下载步骤。
> 📌 **要点**:上下文段交代“前提是什么”。AI 不知道你之前做了什么,要明示否则它可能假设环境已有现成数据,跳过下载步骤。
#### 起草任务
#### 第二段:任务描述
任务段要具体到一句话能说清。“获取数据”是反例——什么数据?什么时间?什么形式?`q1-strategy.ipynb` 是这份任务说明的交付物,这个文件就是一个 Python notebook 文件。notebook 文件是一种交互式的编程源代码,区别于非交互式的 Python 代码,我们在这份文件中可以看到策略的探索过程。
任务描述段要具体到一句话能说清。“获取数据”是反例什么数据?什么时间?什么形式?`q1-strategy.ipynb` 是这份任务说明的交付物,是一个 Python notebook 文件。
> **任务**:在当前工作目录创建 `q1-strategy.ipynb`,获取沪深 300ETF510300.SS)最近 5 年的历史日线数据并可视化。
> **任务描述**:在当前工作目录创建 `q1-strategy.ipynb`,获取沪深 300ETF510300.SS)最近 5 年的历史日线数据并可视化。
>
> 📌 **要点**:关键事物要点名到 ID。“沪深 300”不够——A 股有沪深 300 指数(000300.SH)也有跟踪它的 ETF510300.SS、510310.SS、510330.SSAI 不知道你想要哪个。spec 里所有关键名词都要具体到不能再具体。
> 📌 **要点**:关键事物要点名到 ID。“沪深 300”不够,因为 A 股有沪深 300 指数(000300.SH也有跟踪它的 ETF510300.SS、510310.SS、510330.SSAI 不知道你想要哪个。spec 里所有关键名词都要具体到不能再具体。
#### 起草要求
#### 第三段:任务要求
要求段是步骤主体——AI 自由度最低的部分。我们规定四件事:
这一段是步骤主体AI 自由度最低的部分。我们规定四件事:
> **要求**
> **任务要求**
1. 导入 yfinance, pandas, numpy, matplotlib.pyplot
2. 跨平台中文字体 fallback 链:`plt.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'SimHei', 'STHeiti']` + `plt.rcParams['axes.unicode_minus'] = False`
3. yfinance 获取数据:标的 `510300.SS`、起始 `2021-01-01`、参数 `auto_adjust=True, multi_level_index=False`
3. yfinance 获取数据:投资对象 `510300.SS`、起始 `2021-01-01`、参数 `auto_adjust=True, multi_level_index=False`
4. 数据存到变量 `df`,索引为 DatetimeIndex,列名含 Close
> 📌 **要点**:每个非默认参数都要点名。yfinance 默认会自动调整价格、用多级列名——这两个参数不写出来,AI 可能用默认,结果跟书里不一致。**不要让 AI 揣摩你的偏好。**
> 📌 **要点**:每个非默认参数都要点名。yfinance 默认会自动调整价格、用多级列名这两个参数不写出来,AI 可能就会自行发挥,结果就会跟书里不一致。
#### 起草结果呈现
有人看到 spec 里面还是出现了零星代码,担心自己写不到这么细。其实没关系。你以后写自己的量化策略时,可以分两步来:先和 AI 讨论想法,让 AI 帮你起草 spec;再把确认后的 spec 发给 AI,让它实现代码。此外,从第二章开始,我们会采用开源框架 open-xquant,进一步降低 spec 的编写难度。
最后是“成功长什么样”——这是 spec 最容易被忽略也最重要的部分:
#### 第四段:验收标准
> **结果呈现**
最后是“成功长什么样”,这是 spec 最容易被忽略也最重要的部分
> **验收标准**
1. 打印数据的前 5 行
2. 打印数据时间范围(起始日期 到 结束日期)和总行数
3. 画一条收盘价折线图,标题为「沪深 300ETF (510300) 收盘价走势」
> 📌 **要点**结果呈现要让 AI 跑完自己能说“对了 / 错了”。“画一张图”是反例——AI 可能画 K 线、画柱状图、画散点图。要明确到具体的 X/Y 轴、标题文字、变量名。
> 📌 **要点**验收标准要让 AI 跑完自己能说“对了 / 错了”。“画一张图”是反例AI 可能画 K 线、画柱状图、画散点图。要明确到具体的 X/Y 轴、标题文字、变量名。
完整 spec 在 [`specs/spec-01-get-data.md`](https://github.com/xingwudao/xquant-learning/blob/main/q1-how-to-profit/specs/spec-01-get-data.md)——把它复制到 AI 编程工具对话框弹窗选「允许」。spec 文件用 Markdown 写成(`#` 标题`-` 列表反引号代码——都是给 AI 看的格式,完整复制就好)
完整示例 spec 在配套仓库的 [`q1-how-to-profit/specs/spec-01-get-data.md`](https://github.com/xingwudao/xquant-learning/blob/main/q1-how-to-profit/specs/spec-01-get-data.md),你可以参考。把你写好的 spec 复制到 AI 编程工具对话框里;如果出现授权弹窗选「允许」。示例 spec 用 Markdown 语法,比如 `#` 表示标题`-` 表示列表反引号用于标出代码。Markdown 不是必要条件,关键是把想法表达清楚,并且有一定结构
AI 助手执行完毕后,你会在你的文件夹中看到多出一个叫 `q1-strategy.ipynb` 的文件。第一次双击打开时,TRAE 会询问你使用什么 Python 核,意思是:这份 notebook 交给哪个 Python 环境来运行。我们在这里选择在“开始动手之前”这一章中初始化的 Python 3.12 环境,如图 1-1 所示。
AI 执行完毕后,你会在文件夹中看到一个新文件:`q1-strategy.ipynb`。第一次双击打开时,AI 编程工具会询问你使用什么 Python 内核。它的意思是:这份 notebook 交给哪个 Python 环境来运行。这里选择“准备工作”中创建的 Python 3.12 环境,如图 1-1 所示。
![选择 notebook 使用的 Python 核](images/q1-how-to-profit/notebook-python-core.png)
![选择 notebook 使用的 Python ](images/q1-how-to-profit/notebook-python-core.png)
打开 notebook 文件后,源代码是一个个单元格从上向下构成。这个界面中有几个常用按钮,如图 1-2 所示。
打开 notebook 文件后,你会看到代码被分成一个个单元格从上到下排列。这个界面中有几个常用按钮,如图 1-2 所示。
1. 单步运行:每一个单独的代码单元格,可以单独运行这一步。运行结果会在这个单元格下方出现
1. 单步运行:单独运行当前代码单元格。运行结果会出现在这个单元格下方。
2. 全部运行:整个notebook从上向下,逐一全部运行,直到出现错误为止。每个单元格的运行结果会紧接着出现在代码单元格下方,运行错误也在下方。
2. 全部运行:从上到下依次运行整个 notebook,直到全部完成或出现错误。每个单元格的运行结果会紧接着出现在代码单元格下方,错误信息也会显示在下方。
3. 重新启动:重新启动选择的python核。通常,在初始化的python环境中安装了新的python包,或者更新了某个旧的python 包,会重新启动。
3. 重新启动:重新启动当前选择的 Python核。通常,在 Python 环境中安装了新工具,或者更新了某个旧工具之后,需要重新启动。
4. 当前 python 核:显示当前 notebook 在使用哪个 python
4. 当前 Python 核:显示当前 notebook 在使用哪个 Python 环境
![notebook 常用按钮](images/q1-how-to-profit/notebook-buttons.png)
@@ -111,9 +108,9 @@ AI 助手执行完毕后,你会在你的文件夹中看到多出一个叫 `q1-
**先看整体趋势**:价格总体是在涨还是在跌?有没有明显的“大起大落”?你大概率会看到价格经历了几轮上涨和下跌的周期,不是一条直线往上走,也不是一路向下。
**再看波动范围**:最高点和最低点大概差多少?价格在什么区间内震荡?你会发现,波动幅度可能比你想象的大——同一只 ETF,不同时间点买入,结果可能天差地别。
**再看波动范围**:最高点和最低点大概差多少?价格在什么区间内震荡?你会发现,波动幅度可能比你想象的大。这说明,同一只 ETF,不同时间点买入,结果可能天差地别,也正是我们要先用回测看结果的原因
**这说明什么?** 价格有涨有跌,波动是常态。你不可能买在最低点、卖在最高点——没有人能做到。既然择时这么难,那有没有一种方法,可以不依赖“买在最低点”也能参与市场?定投就是这个思路。
**这说明什么?** 价格有涨有跌,波动是常态。你不可能总是准确地买在最低点、卖在最高点没有人能长期稳定做到这一点。那有没有一种方法,可以不依赖“买在最低点”也能参与市场?定投就是这个思路。
有了数据,一个自然的问题就出来了:**如果从几年前开始,每月固定买入一些,到现在是赚了还是亏了?** 我们来用历史数据回答这个问题。
@@ -121,45 +118,51 @@ AI 助手执行完毕后,你会在你的文件夹中看到多出一个叫 `q1-
在真正投钱之前,我们当然想先知道这个策略靠不靠谱。怎么办?用历史数据模拟一遍。
这就是 **回测(Backtest** 的概念——用历史数据模拟“如果当时按某个规则操作,结果会怎样”。就像在脑海中“重播”历史,看看你的策略在过去能不能盈利。回测不能预测未来,但能帮你验证一个想法是否靠谱。
再提醒一次,这就是前面讲到的 **回测(Backtest**,这个过程就像在脑海中“重播”历史,看看你的策略在过去能不能盈利。回测不能预测未来,但能帮你验证一个想法是否靠谱。
我们一起把这份回测 spec 写出来。这次重点看两件新东西:**spec 之间怎么接续**,以及**复用代码怎么封装成函数**。
#### 起草上下文
#### 第一段:上下文
这次的上下文不再是“什么都没有”——上一份 spec 已经留下了 `df` 变量我们直接接着用:
这次的上下文不再是“什么都没有”上一份 spec 已经留下了 `df` 变量我们直接接着用:
> **上下文**:在 `q1-strategy.ipynb` 中已有上一步的代码:使用 yfinance 获取了沪深 300ETF510300.SS)的日线数据,存储在变量 `df` 中,索引为 DatetimeIndex,包含 Close 列。
>
> 📌 **要点**:spec 之间会有“接续关系”——前一份 spec 的输出是后一份的输入。上下文段把这条链条写明(变量名、形状、关键列)。这样 AI 不会重新下载数据也不会假设一个不存在的列。
> 📌 **要点**:spec 之间会有“接续关系”前一份 spec 的输出是后一份的输入。上下文段把这条链条写明(变量名、形状、关键列)。这样 AI 不会重新下载数据也不会假设一个不存在的列。
#### 起草任务 + 要求
#### 第二、三段:任务描述和任务要求
任务一句话:“实现简单定投回测——每月第一个交易日用 1000 元按收盘价买入”。要求段重点在两件事
任务描述一句话就能说清
> **要求**
> **任务描述**:实现简单定投回测,每月第一个交易日用 1000 元按收盘价买入。
任务要求就要详细一点:
> **任务要求**
1. 把回测逻辑封装为函数 `backtest_dca(df, monthly_amount=1000)`,返回 DataFrame(**后续步骤会复用此函数**)
2. 实现:按月分组取每月第一交易日 → 每月份额 = 1000 / 收盘价 → 累计份额逐月累加 → 月末市值 = 累计份额 × 当月最后收盘价 → 累计收益率 = (市值 - 成本) / 成本
3. 返回 DataFrame 含列:`total_cost, total_shares, portfolio_value, return`
> 📌 **要点**:复用型代码必须封装成函数。“后续步骤会复用此函数”这一句话是给 AI 的硬约束——不要写一段一次性脚本,要写一个有清晰入参的函数。后面的实验 5 会基于它派生 `backtest_dca_with_ma`,函数没封装好整条 spec 链就接不上。
> 📌 **要点**:复用型代码必须封装成函数。“后续步骤会复用此函数”这一句话是给 AI 的硬约束不要写一段一次性脚本,要写一个有清晰入参的函数。后面的实验 5 会在它的基础上再写一个类似函数 `backtest_dca_with_ma`,函数没封装好整条 spec 链就接不上。
>
> 📌 **要点**:返回的 DataFrame 列名要点死。这里写的是 `total_cost, total_shares, portfolio_value, return`——四个名字一字不差。后面好几节实验都要从 `result_dca.return` 这种路径取数,列名只要换成 `ret` 或 `cum_return`,后面所有 spec 都得连带改。**返回值的字段名是 spec 链的“API 契约”,锁死才能让后面的 spec 写起来一行不差。**
> 📌 **要点**:返回的 DataFrame 列名要点死。这里写的是 `total_cost, total_shares, portfolio_value, return`四个名字一字不差。后面好几节实验都要从 `result_dca.return` 这种路径取数,列名只要换成 `ret` 或 `cum_return`,后面所有 spec 都得连带改。
#### 起草结果呈现
随着编程模型进步,也随着你自己的策略库越来越丰富,并非所有 spec 都要像这里这样写得如此明确。AI 会在你的策略库基础上,逐渐学会如何帮你推测和补全。但作为学习,一开始我们尽量详细。
> **结果呈现**:打印投资期间 / 投资月数 / 总投入金额 / 最终市值 / 最终收益率。画两张图(上下排列,figsize 12×8):上图持仓市值 vs 累计成本(盈利绿、亏损红),下图累计收益率(零线上下绿/红填充)。
#### 第四段:验收标准
完整 spec 在 [`specs/spec-02-dca-backtest.md`](https://github.com/xingwudao/xquant-learning/blob/main/q1-how-to-profit/specs/spec-02-dca-backtest.md)——复制给 AI,弹窗选「允许」
> **验收标准**:打印投资期间 / 投资月数 / 总投入金额 / 最终市值 / 最终收益率。画两张图(上下排列,figsize 12×8):上图持仓市值 vs 累计成本(盈利绿、亏损红),下图累计收益率(零线上下绿/红填充)
AI 助手执行完毕后,你的 notebook 里会出现一组数字和两张图。AI 帮你做了这些事:按月分组找到每个月的第一个交易日作为定投日,用 1000 元除以当日收盘价算出每月能买多少份,逐月累加持有份额和投入成本,最后计算每月末的持仓市值和累计收益率
完整示例 spec 在配套仓库的 [`q1-how-to-profit/specs/spec-02-dca-backtest.md`](https://github.com/xingwudao/xquant-learning/blob/main/q1-how-to-profit/specs/spec-02-dca-backtest.md),你可以参考。确认自己的 spec 后,把它复制给 AI
AI 翻译成 notebook 后,如果没有自动运行代码,你可以按照前面演示的步骤自己运行,运行完毕后,你的 notebook 里会出现一组数字和两张图。AI 帮你做了这些事:按月分组找到每个月的第一个交易日作为定投日,用 1000 元除以当日收盘价算出每月能买多少份,逐月累加持有份额和投入成本,最后计算每月末的持仓市值和累计收益率。
定投的机制有一个天然的好处:价格低的时候,同样 1000 块钱能买到更多份额;价格高的时候,买到的份额更少。就像去菜市场,菜贵的时候少买点,菜便宜的时候多买点。
AI 助手还画出了上下两张图(如图 1-4 所示):
AI 还画出了上下两张图(如图 1-4 所示):
- **上图「持仓市值 vs 累计成本」**:蓝色实线是你的持仓市值,它会随市场波动上下起伏;另一条线是你的累计投入成本,它稳步上升(毕竟每个月都在投钱)。两条线之间的区域用颜色标注——绿色代表盈利的时段,红色代表亏损的时段。
- **上图「持仓市值 vs 累计成本」**:蓝色实线是你的持仓市值,它会随市场波动上下起伏;另一条线是你的累计投入成本,它稳步上升(毕竟每个月都在投钱)。两条线之间的区域用颜色标注绿色代表盈利的时段,红色代表亏损的时段。
- **下图「累计收益率」**:更直观地看到收益率随时间的变化曲线。零线以上是赚,以下是亏。
![定投回测结果](images/q1-how-to-profit/02-dca-backtest.png)
@@ -168,124 +171,100 @@ AI 助手还画出了上下两张图(如图 1-4 所示):
先看打印出来的数字:总投入是多少,最终市值是多少,收益率是正还是负?
再看两张图。你大概率会发现,收益率在正负之间来回穿越——有一段时间盈利,过一阵又亏回去,然后可能又赚回来。这不是你的策略有问题,而是市场本来就这样。定投不保证盈利,也不保证亏损。它只是一种“分散买入时点”的方法,避免了把所有钱一次性投在最高点的风险,降低了运气的成分。
再看两张图。你大概率会发现,收益率在正负之间来回穿越,这说明:有一段时间盈利,过一阵又亏回去,然后可能又赚回来。这不是你的策略有问题,而是市场本来就这样。没有一个策略能保证盈利不亏损,定投也不例外。它只是一种“分散买入时点”的方法,避免了把所有钱一次性投在最高点的风险,降低了运气的成分。
不管你看到的最终结果是正收益还是负收益,有一件事比结果本身更重要:**怎么知道这个结果算好还是算差?**
不管你看到的最终结果是正收益还是负收益,有一件事比结果本身更重要:**我们怎么知道这个结果算好还是算差?**
赚了 5%,听起来还行?但如果同期银行理财能给你 4%,你承受了股市的波动,只多赚了 1%,值得吗?亏了 3%,听起来很糟?但如果同期大盘跌了 15%,你其实已经大幅跑赢了市场。
赚了 5%,听起来还行?但如果同期银行理财能给你 4%,你承受了股市的波动,整天担惊受怕,才只多赚了 1%,值得吗?亏了 3%,听起来很糟?但如果同期大盘跌了 15%,你其实已经大幅跑赢了市场。
这就像考试拿了 80 分——你得知道全班平均分才能判断自己考得好不好。我们需要一个参照物。
这就像考试拿了 80 分你得知道全班平均分才能判断自己考得好不好。我们需要一个参照物,考多少分很重要,但分数线划在哪则更重要
## 1.2 我的收益算好还是算差?
上一节最后留下了一个问题:一个收益率数字本身不能告诉你任何事。赚了 20% 是好是坏?亏了 5% 是糟糕还是其实还行?这取决于你跟谁比。
一个收益率就是一个数字本身不能告诉你任何事。赚了 20% 是好是坏?亏了 5% 是糟糕还是其实还行?这取决于你跟谁比。
你需要一个 **基准(Benchmark**。基准就是“全班平均分”——你不知道 80 分算好算差,但如果全班平均 60 分,你就知道自己考得不错;如果全班平均 95 分,那 80 分就得反思了。投资也是一样。你的收益率只有跟某个参照物放在一起看,才有意义。
这时候,你需要一个 **基准(Benchmark**。基准就是分数线,也就是“跟谁比”,比如“全班平均分”你不知道 80 分算好算差,但如果全班平均 60 分,你就知道自己考得不错;如果全班平均 95 分,那 80 分就得反思了。投资也是一样。你的收益率只有跟某个参照物放在一起看,才有意义。
跟谁比?其实有三种不同层次的基准:
- **绝对基准**能接受的最低收益率。比如“年化至少 5%,否则我还不如买理财”。这是你的个人底线。
- **相对基准**:市场指数。你的策略是赢了大盘还是输了大盘?赢了说明你有超额收益,输了说明你还不如买指数基金。
- **策略基准**:同类策略中最简单的那个。择时策略要和“买入持有”比——你费心选时机,总得比什么都不做强吧?权重优化策略要和“等权重”比——你费心调配比例,总得比平均分配强吧?
- **绝对基准**自己能接受的最低收益率。比如“年化至少 5%,否则我还不如买理财”。可以选择一个无风险的利率,比如 3 年定存的年化利率。这是你的个人底线。
- **相对基准**市场或行业指数。你的策略是赢了大盘还是输了大盘?赢了说明你比市场更好,输了说明你还不如买指数基金。如果一番操作还不如市场大盘,再算上自己付出的时间和承受的波动,这个策略就很难说有效。
- **策略基准**同类策略中最简单的做法比。如果策略要挑买入时机,就和“买入持有”比你费心选时机,总得比什么都不做强吧?如果策略要分配不同资产的买入比例,就和“平均分配”比,你费心调配比例,总得比平均分配强吧?
在后面的章节中,我们会逐步引入这三种基准。眼下先从最基本的开始。
在后面的章节中,我们会逐步引入这三种基准。眼下先从最基本的开始。
那什么是最自然的基准?想象你在投资的第一天,面前有两个选择:一是按照某个策略每月定投,二是把所有钱一次性全部买入,然后什么都不做,一直拿着到最后一天。第二个选择就是 **买入持有(Buy & Hold**——它代表“什么都不做”的结果。你的策略要是连“什么都不做”都赢不了,那它存在的意义是什么?
先记住一个简单判断:如果一个策略只是跟着市场一起涨跌,它还不能说明自己发现了独特规律。只有放到基准旁边比较,才知道它到底有没有做得更好。
那什么是最自然的基准?想象你在投资的第一天,面前有两个选择:一是按照某个策略每月定投,二是把所有钱一次性全部买入,然后什么都不做,一直拿着到最后一天。第二个选择就是 **买入持有(Buy & Hold**,它代表“什么都不做”的结果。你的策略要是连“什么都不做”都赢不了,那它存在的意义是什么?
所以我们接下来要做的事很简单:算出“什么都不做”能赚多少钱,然后跟定投的结果放在一起比。
### 动手实验 3:和基准对比
我们一起把这份基准对比 spec 写出来。这次重点看一个新东西:**结果呈现段如何写“动态结论”**——不知道跑出来谁赢谁输,怎么提前定义两种结果的解读?
我们一起把这份基准对比 spec 写出来。这次重点看一个新东西:**验收标准如何写“动态结论”**不知道跑出来谁赢谁输,怎么提前定义两种结果的解读?
#### 起草上下文 + 任务
#### 第一、二段:上下文和任务描述
> **上下文**:在 `q1-strategy.ipynb` 中已有沪深 300ETF 日线数据(变量 `df`,含 Close 列)和简单定投回测结果(变量 `result_dca`,含 return 列)。
>
> **任务**:计算「一次性买入持有」作为基准,和定投策略对比,帮助理解收益来源。
> **任务描述**:计算「一次性买入持有」作为基准,和定投策略对比,帮助理解收益来源。
#### 起草要求
#### 第三段:任务要求
> **要求**
> **任务要求**
1. 基准策略(Buy & Hold):起始日全部资金一次性买入,持有到最后一天;基准收益率 = (终点 - 起点) / 起点,存到 `benchmark_return`
2. 计算基准的逐月累计收益率曲线(每月末收盘价相对起始价的涨跌幅)
3. 与定投策略对比
#### 起草结果呈现
#### 第四段:验收标准
这一段是这份 spec 最有意思的地方——我们不知道沪深 300 这 5 年是涨是跌,所以**得提前写好两种结果的对应文案**:
我们不知道沪深 300 这 5 年是涨是跌,所以**得提前写好两种结果的对应文案**:
> **结果呈现**
> **验收标准**
1. 打印起点 / 终点价格、一次性买入收益率、定投收益率、两者差异
2. 根据对比结果**动态选**一句话:
- 如果定投跑赢:「在下跌市中,定投通过分批买入避开了部分高位」
- 如果定投跑输:「在上涨市中,越早全部投入越好」
3. 无论哪种结果,都补充一句共同收束:「但两种策略都没有 Alpha——收益完全取决于市场涨跌,这就是 Beta
- 如果定投跑赢:「在市场整体下跌时,定投通过分批买入避开了部分高位」
- 如果定投跑输:「在市场整体上涨时,越早全部投入越好」
3. 无论哪种结果,都补充一句:「但两种策略都主要跟着市场涨跌,还不能说明有稳定优势
4. 画一张对比图(figsize 12×6):定投 vs 一次性买入两条累计收益率曲线,图例标注名称
> 📌 **要点**:spec 不能预设结果方向。“分批买入避开高位” / “越早全部投入越好”听起来像两个相反的结论,但它们其实是对**同一个现象**的两种表述——市场跌了 / 涨了。把“双分支动态结论”写进 spec,让 AI 跑完**结果会自己讲故事**,无论涨跌都不会出现“硬写但和数据不符”的尴尬。
>
> 📌 **要点**:双分支后面常需要一句“共同收束”。本 spec 的“两种策略都没有 Alpha”是把读者的注意力**从‘谁赢’拉到‘两者本质相同’**——这是教学型 spec 写作的隐藏技巧:分歧的下面藏着共识。
> 📌 **要点**:spec 不能预设结果方向。“分批买入避开高位” / “越早全部投入越好”听起来像两个相反的结论,但它们其实是对**同一个现象**的两种表述市场跌了 / 涨了。把“双分支动态结论”写进 spec,让 AI 跑完后自己根据结果说话,无论涨跌都不会出现“硬写但和数据不符”的尴尬。
完整 spec 在 [`specs/spec-03-benchmark.md`](https://github.com/xingwudao/xquant-learning/blob/main/q1-how-to-profit/specs/spec-03-benchmark.md)——复制给 AI,弹窗选「允许」
完整示例 spec 在配套仓库的 [`q1-how-to-profit/specs/spec-03-benchmark.md`](https://github.com/xingwudao/xquant-learning/blob/main/q1-how-to-profit/specs/spec-03-benchmark.md),你可以参考。确认自己的 spec 后,把它复制给 AI,让它执行试试看
AI 助手执行完毕后,你的 notebook 里会出现一组对比数字和一张双线对比图。AI 帮你做了这些事:计算买入持有的收益率(用最后一天的价格减去第一天的价格,再除以第一天的价格),然后把它和定投的收益率放在一起对比,算出两者的差异。
AI 执行完毕后,你的 notebook 里会出现一组对比数字和一张双线对比图。AI 帮你做了这些事:计算买入持有的收益率(用最后一天的价格减去第一天的价格,再除以第一天的价格),然后把它和定投的收益率放在一起对比,算出两者的差异。
两条收益率曲线画在同一张图上(如图 1-5 所示)。它们之间的距离,就是两种策略在每个月的差异。有时候定投在上面,有时候买入持有在上面——领先和落后是交替出现的。这张图的价值在于:它不只告诉你最终结果谁赢了,还让你看到整个过程中两者的“赛跑”。
两条收益率曲线画在同一张图上(如图 1-5 所示)。它们之间的距离,就是两种策略在每个月的差异。有时候定投在上面,有时候买入持有在上面领先和落后是交替出现的。这张图的价值在于:它不只告诉你最终结果谁赢了,还让你看到整个过程中两者的“赛跑”。
![定投 vs 买入持有](images/q1-how-to-profit/03-dca-vs-buyhold.png)
### 读懂对比结果
先看打印出来的数字。你会发现一个有意思的现象:沪深 300ETF 的起点价格和终点价格可能非常接近。也就是说,如果你在数据起始日一次性买入,持有到现在,可能几乎没赚没亏——市场在这几年里“坐了一趟过山车”,最后又回到了起点附近。
先看打印出来的数字。你会发现一个有意思的现象:沪深 300ETF 的起点价格和终点价格可能非常接近。也就是说,如果你在数据起始日一次性买入,持有到现在,可能几乎没赚没亏市场在这几年里“坐了一趟过山车”,最后又回到了起点附近。
但定投的结果呢?大概率是正收益。为什么“什么都不做”几乎没有收益,而定投反而获得了正收益?
原因不复杂。回想一下定投的机制:价格低的时候,同样 1000 块钱能买到更多份额;价格高的时候,买到的份额更少。过去几年沪深 300 经历了一轮明显的下跌,又有所回升。在下跌过程中,定投不断在低位买入,积累了大量便宜的份额。等到价格回升,这些便宜份额带来的利润弥补了高位买入的亏损。而一次性买入就没有这个“摊低成本”的机会——你在最开始买了,然后只能眼睁睁看着价格跌下去再涨回来。
原因不复杂。回想一下定投的机制:价格低的时候,同样 1000 块钱能买到更多份额;价格高的时候,买到的份额更少。过去几年沪深 300 经历了一轮明显的下跌,又有所回升。在下跌过程中,定投不断在低位买入,积累了大量便宜的份额。等到价格回升,这些便宜份额带来的利润弥补了高位买入的亏损。而一次性买入就没有这个“摊低成本”的机会你在最开始买了,然后只能眼睁睁看着价格跌下去再涨回来。
再看对比图。两条线的交叉和分离,就是两种策略的攻守转换。你会发现,在市场下跌的阶段,定投那条线通常在上面(跌得少);在市场快速上涨的阶段,买入持有可能追上来甚至反超。这也符合直觉——下跌市里,分批买入天然就比一次性买入有优势,因为你避开了在最高点“全仓”的风险。
### 两个收益率背后的大问题
到这里,不管谁赢了,你可能会觉得:差距其实也没那么大嘛。对,你说得对。因为这两种策略做的是同一件事——买入沪深 300ETF 然后持有。区别只是“一次性全买”还是“分批买”。它们的收益来源是完全一样的:市场本身的涨跌。市场涨,两个策略都赚;市场跌,两个策略都亏。
这引出了量化交易中两个最重要的概念。
**Beta(贝塔)** 是你“坐电梯”获得的收益。你走进商场,坐上电梯,电梯往上走,你就到了高处。你做了什么吗?你什么都没做,只是站在电梯里。市场就是那部电梯。市场涨,你的资产就跟着涨;市场跌,你的资产就跟着跌。你承担了市场整体涨跌的风险,市场给你的回报就是 Beta。
定投和买入持有,本质上都是在“坐电梯”。收益完全取决于这部电梯——沪深 300——是往上走还是往下走。你没有做出任何“判断”,没有选择买入时机,没有选择哪只股票,没有在任何地方做出比市场更聪明的决定。赚的钱全是 Beta。
那如果有人不甘心只坐电梯呢?
**Alpha(阿尔法)** 是你“爬楼梯”多走的路。别人坐电梯到了 10 楼,你坐电梯到了 10 楼之后,又自己爬了 5 层到 15 楼——那多出来的 5 层就是你的 Alpha。翻译成投资语言:Alpha = 你的收益 - 市场的收益。如果市场涨了 10%,你赚了 15%,那你有 5% 的 Alpha。如果市场涨了 10%,你只赚了 8%,那你的 Alpha 是 -2%——你不仅没爬楼梯,还在电梯里摔了一跤。
Alpha 需要你做出某种“判断”,并且这个判断要比市场聪明。比如,判断什么时候该买、什么时候该卖、该买什么、不该买什么。这些判断如果正确,就能产生正的 Alpha;如果错误,Alpha 就是负的——你还不如什么都不做。
回到我们的定投策略:它有 Alpha 吗?没有。零。定投不做任何判断,不猜涨跌,不择时。它的收益百分之百来自 Beta。市场好,定投就盈利;市场差,定投就亏损。它只是一种资金分配方式,不是一种“聪明”的策略。
这个结论很重要,值得你记住:**到目前为止,我们的策略没有任何 Alpha。收益完全来自 Beta——市场好就赚,市场差就亏。**
既然定投没有 Alpha,怎样才能赢过市场?我们需要在策略里加入一些“自己的判断”。最简单的判断工具,是一条线。
再看对比图。两条线的交叉和分离,就是两种策略的攻守转换。你会发现,在市场下跌的阶段,定投那条线通常在上面(跌得少);在市场快速上涨的阶段,买入持有可能追上来甚至反超。这也符合直觉:下跌期间,分批买入天然就比一次性买入有优势,因为你避开了在最高点“全仓”的风险。
## 1.3 怎么才能赢过市场?
上一节结尾说了:要产生 Alpha,就得在策略里加入“判断”。但什么叫“判断”?不是你拍脑袋觉得明天会涨就买——那叫赌博。量化的判断,必须是一条可以写成明确规则的逻辑
上一节我们知道了:如果只跟着市场涨跌走,很难证明策略有自己的优势。想做得比基准更好,就要加入一条能写成规则的判断。最简单的判断工具,是一条线,叫做**均线(Moving Average)**。它把过去一段时间的收盘价取平均,每天算一个值,再连成一条曲线
最简单的判断工具,是一条线——**均线(Moving Average**
打个比方,你今天量体温 37.5°C,是不是发烧了?不好说,也许你刚跑完步、也许体温计不太准。但如果你连续 5 天量体温,算个平均值,发现平均体温都偏高,那大概率是真的不舒服了。均线就是这个“平均体温”。它取过去 N 天收盘价的平均值,把每天的随机波动抹平,留下更稳定的趋势方向。单看某一天的价格就像单看某一次体温,可能被噪音干扰;但看一段时间的平均值,信息就可靠多了
均线是什么?打个比方。你今天量体温 37.5°C,是不是发烧了?不好说,也许你刚跑完步、也许体温计不太准。但如果你连续 5 天量体温,算个平均值,发现平均体温都偏高——那大概率是真的不舒服了。均线就是这个“平均体温”。它取过去 N 天收盘价的平均值,把每天的随机波动抹平,留下更稳定的趋势方向。单看某一天的价格就像单看某一次体温,可能被噪音干扰;但看一段时间的平均值,信息就可靠多了
那“价格在均线上方”意味着什么?意味着最近的价格比过去一段时间的平均价格高。换句话说,市场最近在涨,可能正处于上升趋势。注意我用了“可能”——均线是个**滞后指标(Lagging Indicator)**,它看的是过去,不预测未来。就像你说“这个星期天气都不错”,不代表明天一定晴天。但作为一个参考信号,它总比什么都不看要强。
那“价格在均线上方”意味着什么?意味着当前的价格比过去一段时间的平均价格高。换句话说,市场最近在涨,可能正处于上升趋势。注意我用了“可能”,因为均线是个**滞后指标(Lagging Indicator)**,它看的是过去,不预测未来。就像你说“这个星期天气都不错”,不代表明天一定晴天。但作为一个参考信号,它总比什么都不看要强
基于这个逻辑,我们给定投策略加一条规则:每月定投日,**仅当收盘价高于 N 日均线时才买入**,否则这个月跳过。就这么简单。不买的月份,钱留在手里,份额和成本都不变。
这就是**择时(Market Timing**——不再闭着眼睛每月都买,而是先看看“天气”再决定出不出门。市场趋势向上的时候跟着买入,趋势不明朗的时候按兵不动。听上去很合理,对吧?
这就是**择时(Market Timing**不再闭着眼睛每月都买,而是先看看“天气”再决定出不出门。市场趋势向上的时候跟着买入,趋势不明朗的时候按兵不动。听上去很合理,对吧?
但这里有一个问题:N 取多少?过去 10 天的平均值?20 天?还是 60 天?不同的 N 就是不同的“回看窗口”:
- **MA10**(10 天,约 2 周):短期均线。反应灵敏,价格稍微涨几天它就跟着翘头,但也容易被短期波动骗到——价格随便抖一抖,信号就来回翻转。
- **MA10**(10 天,约 2 周):短期均线。反应灵敏,价格稍微涨几天它就跟着翘头,但也容易被短期波动骗到价格随便抖一抖,信号就来回翻转。
- **MA20**(20 天,约 1 个月):中期均线。过滤掉了大部分日常噪音,是比较常用的参数。
- **MA60**(60 天,约 3 个月):长期均线。非常稳定,但反应迟钝。市场可能已经涨了一大截,它才慢悠悠地确认“嗯,确实在涨”。
@@ -293,19 +272,19 @@ Alpha 需要你做出某种“判断”,并且这个判断要比市场聪明
### 动手实验 4:看见均线
这份“画图” spec 比前面几份更挑细节——它要把图怎么画**全部锁死**到颜色、线型、标记形状。我们一起把它写出来,看看“视觉契约”要写多细才够。
这份“画图” spec 比前面几份有更多细节,要把图怎么画,规定到颜色、线型、标记形状这个粒度上。我们一起把它写出来,看看“视觉要求”要写多细才够。
#### 起草上下文 + 任务
#### 第一、二段:上下文和任务描述
> **上下文**:在 `q1-strategy.ipynb` 中已有沪深 300ETF 日线数据(变量 `df`,含 Close 列)。上一节引入了均线的概念——过去 N 天收盘价的平均值。
> **上下文**:在 `q1-strategy.ipynb` 中已有沪深 300ETF 日线数据(变量 `df`,含 Close 列)。上一节引入了均线的概念过去 N 天收盘价的平均值。
>
> **任务**:画出收盘价与三条均线(MA10 / MA20 / MA60)的对比图,并标记 MA20 的上穿 / 下穿点。
> **任务描述**:画出收盘价与三条均线(MA10 / MA20 / MA60)的对比图,并标记 MA20 的上穿 / 下穿点。
#### 起草要求:视觉契约
#### 第三段:任务要求
要求段是这份 spec 的灵魂——**视觉细节全部锁死**:
这部分要规定到颜色、线型、标记形状这个粒度上。
> **要求**
> **任务要求**
- 截最近 1 年数据:`df_recent = df.loc[df.index[-1] - pd.DateOffset(years=1):].copy()`
- 三条均线:`rolling(10/20/60).mean()`
@@ -316,17 +295,17 @@ Alpha 需要你做出某种“判断”,并且这个判断要比市场聪明
- 下穿点:红色向下三角(`marker='v'`, markersize=10
- 标题「沪深 300ETF:收盘价与均线(最近 1 年)」+ 图例 + 网格
> 📌 **要点**:可视化 spec 必须把视觉契约写到颜色、线型、标记形状级别。“画一张对比图”是反例——AI 可能给你 default 蓝色细线、不带 marker、不带网格的图,跟书里截图完全对不上。**每一个视觉决策都要进 spec:颜色用什么 hex / linestyle 用“–”还是“:”/ marker 多大 / figsize 多少。** 这是【精确】原则在视觉场景的具体落点
> 📌 **要点**:可视化 spec 必须把视觉要求写到颜色、线型、标记形状级别。只说“画一张对比图”不够,AI 可能给你默认蓝色细线、不带标记、不带网格的图,跟书里截图完全对不上。每一个视觉决策都要进 spec:颜色、线型、标记大小、图形尺寸。你不一定需要指明颜色代码,但要用自然语言说明
>
> 📌 **要点**:截窗口要带 `.copy()`。要求里的 `df.loc[...].copy()` 不是装饰——pandas 里 `.copy()` 直接切片是“视图”,后面再往这个视图上加列(MA10/MA20/MA60)会触发 `SettingWithCopyWarning`,有时甚至改不到原数据。**spec 里写明** `.copy()` **比写一句“注意切片”更稳**——AI 看到 `.copy()` 就知道你要的是独立副本。
> 📌 **要点**:截窗口要带 `.copy()`。要求里的 `df.loc[...].copy()` 不是装饰pandas 里不带 `.copy()` 的切片常常是“视图”,后面再往这个视图上加列(MA10/MA20/MA60)会触发 `SettingWithCopyWarning`,有时甚至改不到原数据。spec 里写明 `.copy()` 比写一句“注意切片”更稳AI 看到 `.copy()` 就知道你要的是独立副本。这一点不需要一开始就知道,我也是第一遍输出不满意之后,经过 AI 提醒才补上的。你要学的是持续迭代,不是第一次就写完美。
#### 起草结果呈现
#### 第四段:验收标准
> **结果呈现**:价格与均线对比图(含交叉点标记)+ 打印交叉统计「上穿 N 次 / 下穿 N 次」。
> **验收标准**:价格与均线对比图(含交叉点标记)+ 打印交叉统计「上穿 N 次 / 下穿 N 次」。
完整 spec 在 [`specs/spec-04-ma-visual.md`](https://github.com/xingwudao/xquant-learning/blob/main/q1-how-to-profit/specs/spec-04-ma-visual.md)——复制给 AI,弹窗选「允许」。
完整示例 spec 在配套仓库的 [`q1-how-to-profit/specs/spec-04-ma-visual.md`](https://github.com/xingwudao/xquant-learning/blob/main/q1-how-to-profit/specs/spec-04-ma-visual.md),你可以参考。确认自己的 spec 后,把它复制给 AI,弹窗选「允许」。
AI 助手执行完毕后,你的 notebook 里会出现一张价格与均线的对比图,如图 1-6 所示。
AI 执行完毕后,你的 notebook 里会出现一张价格与均线的对比图,如图 1-6 所示。
![收盘价与均线](images/q1-how-to-profit/04-ma-visual.png)
@@ -334,49 +313,49 @@ AI 助手执行完毕后,你的 notebook 里会出现一张价格与均线的
现在你亲眼看到了均线和交叉信号。几个值得注意的地方:
**三条均线的“性格”不同。** 绿色的 10 日均线紧贴价格,几乎同步波动——价格稍微涨几天它就跟着翘头,跌几天它就立刻掉头向下。蓝色的 20 日均线稍微平滑一些,过滤掉了一些日常噪音。红色的 60 日均线最“淡定”,价格上下翻飞它才慢慢跟。这正是前面说的“回看窗口”的差异:窗口越短,越灵敏也越容易被骗;窗口越长,越稳定也越迟钝。
**三条均线的“性格”不同。** 绿色的 10 日均线紧贴价格,几乎同步波动价格稍微涨几天它就跟着翘头,跌几天它就立刻掉头向下。蓝色的 20 日均线稍微平滑一些,过滤掉了一些日常噪音。红色的 60 日均线最“淡定”,价格上下翻飞它才慢慢跟。这正是前面说的“回看窗口”的差异:窗口越短,越灵敏也越容易被骗;窗口越长,越稳定也越迟钝。
**交叉点就是信号。** 绿色三角标记的是“上穿”——收盘价从 MA20 下方穿到上方,意味着短期价格超过了最近一个月的平均水平,趋势可能在转好。红色三角标记的是“下穿”——收盘价跌破了 MA20,趋势可能在转弱。
**交叉点就是信号。** 绿色三角标记的是“上穿”收盘价从 MA20 下方穿到上方,意味着短期价格超过了最近一个月的平均水平,趋势可能在转好。红色三角标记的是“下穿”收盘价跌破了 MA20,趋势可能在转弱。
**信号不少,但不是每次都“对”。** 仔细看图:有些上穿之后确实涨了一段,信号是“对”的;但也有些上穿之后价格又很快跌回去——刚穿上去就掉下来,这就是“假信号”。均线越短,假信号越多;均线越长,信号越少但更迟钝。没有哪条均线能百分百准确。
**信号不少,但不是每次都“对”。** 仔细看图:有些上穿之后确实涨了一段,信号是“对”的;但也有些上穿之后价格又很快跌回去刚穿上去就掉下来,这就是“假信号”。均线越短,假信号越多;均线越长,信号越少但更迟钝。没有哪条均线能百分百准确。
有了这个直觉,我们来试试:如果真的按这些信号来决定买不买,结果会怎样?
### 动手实验 5:加入均线择时
这份均线择时 spec 多了一件事——把可能爆炸的边界 case 列出来。均线初期没值、累计成本为 0 时除法会,这些都要在 spec 里点名。我们一起把它写出来。
这份均线择时 spec 多了一件事,要把可能出错的边界情况提前列出来。因为均线初期没值、累计成本为 0 时除法会出问题,这些都要在 spec 里点名。我们一起把它写出来。
#### 起草上下文 + 任务
#### 第一、二段:上下文和任务描述
> **上下文**:在 `q1-strategy.ipynb` 中已有沪深 300ETF 日线数据(`df`)、简单定投回测函数 `backtest_dca` 和结果(`result_dca`)、基准对比代码。
>
> **任务**:在定投基础上加入均线择时——每月第一个交易日,仅当收盘价 \> N 日均线才买入。
> **任务描述**:在定投基础上加入均线择时每月第一个交易日,仅当收盘价 \> N 日均线才买入。
#### 起草要求:边界 case 显式
#### 第三段:任务要求
> **要求**
> **任务要求**
1. 函数 `backtest_dca_with_ma(df, monthly_amount=1000, ma_period=20)`,返回格式同 `result_dca`,额外含 signal 列(布尔值)。**后续步骤会复用此函数**
2. 三个边界 case 必须显式处理
2. 三个边界情况必须写明处理方式
- 均线计算初期(前 N 天)没均线值,这些月份不买入
- 跳过的月份,累计成本和份额保持不变
- 累计成本为 0 时(尚未发生任何买入),收益率直接设为 0,**不要用除法**,避免除零警告
3. 分别跑 MA10、MA20、MA60 三个参数
> 📌 **要点**:边界 case 必须显式写进 spec。“每月有买入条件”听起来简单,但隐藏着三个炸点:① 前 N 天没均线 ② 跳过月份的状态怎么传递 ③ 累计成本为 0 时除法会 NaN。**只要 spec 不点名这三件事,AI 写出的代码 60% 概率会在某一个炸点上出 warning 或错值。** 你不需要让 AI 自己发现边界——你列清楚,它照办
> 📌 **要点**:边界情况必须写进 spec。“每月有买入条件”听起来简单,但隐藏着三个容易出错的地方:① 前 N 天没均线 ② 跳过月份的状态怎么传递 ③ 累计成本为 0 时除法会 NaN。如果 spec 不写清楚这三件事,AI 就有可能写出有 bug 的代码
>
> 📌 **要点**派生函数的入参命名要和原函数对齐。`backtest_dca_with_ma(df, monthly_amount=1000, ma_period=20)`前两个入参和实验 2 的 `backtest_dca(df, monthly_amount=1000)` 完全一致——只在末尾加 `ma_period`。这不是形式主义:spec 链越往后,函数会被反复调用,入参命名一致才能让“两个回测”的对比代码读起来对称。**新加参数追在已有参数后面不打乱原顺序, spec 链可读性的隐藏前提。**
> 📌 **要点**这里增加了一个回测函数 `backtest_dca_with_ma(df, monthly_amount=1000, ma_period=20)`,它前两个入参和实验 2 的 `backtest_dca(df, monthly_amount=1000)` 完全一致只在末尾加 `ma_period`。新加参数追在已有参数后面不打乱原顺序,才能保证 spec 链可读性
#### 起草结果呈现
#### 第四段:验收标准
> **结果呈现**
> **验收标准**
1. 打印每个参数:`MA10: 收益率 X.XX%, 买入 N/M 次` + 简单定投作为对照
2. 画对比图(figsize 12×6):简单定投灰虚线 + MA10/20/60 三色实线,图例 + 网格 + 零线
完整 spec 在 [`specs/spec-05-ma-timing.md`](https://github.com/xingwudao/xquant-learning/blob/main/q1-how-to-profit/specs/spec-05-ma-timing.md)——复制给 AI,弹窗选「允许」。
完整示例 spec 在配套仓库的 [`q1-how-to-profit/specs/spec-05-ma-timing.md`](https://github.com/xingwudao/xquant-learning/blob/main/q1-how-to-profit/specs/spec-05-ma-timing.md),你可以参考。确认自己的 spec 后,把它复制给 AI,弹窗选「允许」。
AI 助手执行完毕后,你的 notebook 里会出现一组对比数据和一张多线对比图。AI 帮你做了这些事:在每个交易日计算过去 N 天收盘价的平均值(均线),然后在每月定投日比较当天收盘价和均线值——价格在均线上方就买入,在下方就跳过。和简单定投的区别就在这里:简单定投每个月都买,均线策略有些月份会选择“不出手”。AI 分别用 MA10、MA20、MA60 三个参数各跑了一遍回测,然后画了一张对比图(如图 1-7 所示):灰色虚线是简单定投作为基准,三条有色实线是不同均线参数的策略,四条线放在一起一眼就能看出谁在什么时候领先、什么时候落后。
AI 执行完毕后,你的 notebook 里会出现一组对比数据和一张多线对比图。AI 帮你做了这些事:在每个交易日计算过去 N 天收盘价的平均值(均线),然后在每月定投日比较当天收盘价和均线值价格在均线上方就买入,在下方就跳过。和简单定投的区别就在这里:简单定投每个月都买,均线策略有些月份会选择“不出手”。AI 分别用 MA10、MA20、MA60 三个参数各跑了一遍回测,然后画了一张对比图(如图 1-7 所示):灰色虚线是简单定投作为基准,三条有色实线是不同均线参数的策略,四条线放在一起一眼就能看出谁在什么时候领先、什么时候落后。
![均线择时对比](images/q1-how-to-profit/05-ma-timing.png)
@@ -384,113 +363,101 @@ AI 助手执行完毕后,你的 notebook 里会出现一组对比数据和一
先看打印出来的数字。每一行告诉你两件事:最终收益率和买入次数。
注意买入次数的变化。简单定投是每个月都买,均线策略则会跳过一些月份。MA10 跳过得少一些,因为短期均线容易被价格快速穿越;MA60 跳过得多一些,因为长期均线更“迟钝”,价格要涨很久才能站到它上面。换句话说,均线周期越长,策略越“挑剔”——不是每个月都觉得“可以买”。
注意买入次数的变化。简单定投是每个月都买,均线策略则会跳过一些月份。MA10 跳过得少一些,因为短期均线容易被价格快速穿越;MA60 跳过得多一些,因为长期均线更“迟钝”,价格要涨很久才能站到它上面。换句话说,均线周期越长,策略越“挑剔”不是每个月都觉得“可以买”。
再看收益率。三个参数的收益率各不相同——有的可能比简单定投好,有的可能更差。这是第一个值得注意的现象:**加了均线判断,不一定收益更高。**
再看收益率。三个参数的收益率各不相同有的可能比简单定投好,有的可能更差。这是第一个值得注意的现象:**加了均线判断,不一定收益更高。**
为什么会这样?因为均线择时是一把双刃剑。它帮你跳过了一些“价格在下跌通道”的月份,避免了在下跌中继续投钱,这是好事。但它也可能让你错过一些“刚开始反弹”的月份——价格刚从底部起来,还没站上均线,你就没买到最便宜的那几次。少买的月份如果后来涨了,你就少赚了。
为什么会这样?因为均线择时是一把双刃剑。它帮你跳过了一些“价格在下跌通道”的月份,避免了在下跌中继续投钱,这是好事。但它也可能让你错过一些“刚开始反弹”的月份价格刚从底部起来,还没站上均线,你就没买到最便宜的那几次。少买的月份如果后来涨了,你就少赚了。
现在看对比图。灰色虚线是简单定投,有色实线是三条均线策略。注意它们什么时候分开、什么时候靠近。你可能会发现:在市场大幅下跌的阶段,均线策略的曲线有时比简单定投下跌得少——因为它跳过了一些下跌月份的买入。但在市场持续上涨的阶段,均线策略可能跟不上简单定投——因为有些月份它“犹豫”了,没有买入。
现在看对比图。灰色虚线是简单定投,有色实线是三条均线策略。注意它们什么时候分开、什么时候靠近。你可能会发现:在市场大幅下跌的阶段,均线策略的曲线有时比简单定投下跌得少因为它跳过了一些下跌月份的买入。但在市场持续上涨的阶段,均线策略可能跟不上简单定投因为有些月份它“犹豫”了,没有买入。
这里有一个关键观察,请你记住:**加入判断条件确实改变了结果,但改变的方向不确定。它不是一个“只赚不赔”的改进,而是一种取舍——用“可能错过上涨”换“可能避开下跌”。**
这里有一个关键观察,请你记住:**加入判断条件确实改变了结果,但改变的方向不一定是你期望的方向。它不是一个“只赚不赔”的改进,而是一种取舍用“可能错过上涨”换“可能避开下跌”。** 这样的取舍会贯穿后面的章节:每一条规则都在换取某种收益,也承担某种代价。
而且三个参数的效果不一样。MA10、MA20、MA60,谁最好?在你手上这段数据里,可能 MA20 赢了,也可能 MA60 更好——这取决于市场在过去几年具体走了什么样的路径。
你还会注意到,这三个参数的效果不一样。MA10、MA20、MA60,谁最好?在你手上这段数据里,可能 MA20 赢了,也可能 MA60 更好这取决于市场在过去几年具体走了什么样的路径。
三个参数效果不同——MA10、MA20、MA60,到底哪个最好?为什么不干脆把所有可能的参数都试一遍呢?从 MA5 到 MA120,每隔 5 天试一个,总有一个是最优的吧?
三个参数MA10、MA20、MA60,到底哪个最好?为什么不干脆把所有可能的参数都试一遍呢?从 MA5 到 MA120,每隔 5 天试一个,总有一个是最优的吧?没错,这个做法就叫“参数扫描”,目的是找到“最优参数”。
## 1.4 找到“最优参数”就能盈利了?
上一节我们试了三个参数,效果各不相同。你心里可能已经在想:才三个参数就想找到最优解?太不严谨了。干脆全试一遍——从 MA5 到 MA120,每隔 5 天取一个,一共 24 个参数。跑完之后,选收益率最高的那个。听起来是不是很“科学”、很“严谨”?毕竟我们没有拍脑袋猜,而是用数据说话、穷举搜索。
上一节我们试了三个参数,效果各不相同。你心里可能已经在想:才三个参数就想找到最优解?太不严谨了。干脆全试一遍从 MA5 到 MA120,每隔 5 天取一个,一共 24 个参数。跑完之后,选收益率最高的那个。听起来是不是很“科学”、很“严谨”?毕竟我们没有拍脑袋猜,而是用数据说话、穷举搜索。
为什么是 5 到 120 这个范围?5 天太短,几乎等于在看每周的波动,全是噪音;120 天约等于半年,再长的话均线几乎不动,策略基本不会发出买入信号。5 到 120 覆盖了实务中常用的所有均线周期——短期、中期、长期都包含在内。每隔 5 天取一个是为了效率:不需要逐天试,大趋势的差异在 5 天级别就能体现出来,MA15 和 MA16 的结果不会有质的不同。
为什么是 5 到 120 这个范围?5 天太短,几乎等于在看每周的波动,全是噪音;120 天约等于半年,再长的话均线变化越来越缓慢,策略基本不会发出买入信号。5 到 120 覆盖了实务中常用的所有均线周期短期、中期、长期都包含在内。每隔 5 天取一个是为了效率:不需要逐天试,大趋势的差异在 5 天级别就能体现出来,MA15 和 MA16 的结果不会有质的不同。
不过在看结果之前,你可以先想一个问题——如果朝墙上射了 24 支箭,总有一支是最接近靶心的。但这说明箭术好吗?先把这个问题放在心里,我们来看看实验结果。
不过在看结果之前,你可以先想一个问题如果朝墙上射了 24 支箭,总有一支是最接近靶心的。但这说明箭术好吗?如果射 2400 支箭呢?先把这个问题放在心里,我们来看看实验结果。
### 动手实验 6:参数扫描
这份参数扫描 spec 有点反常——它**故意保持 naive**:不警告“过拟合”,不分训练 / 测试集,就让你“找最优”。我们一起把它写出来,先经历一次“找到漂亮数字”的过程,下一节再回头看看那个数字到底是什么。
这份参数扫描 spec 有点**朴素**,所谓朴素,就是暂时不管什么是“过拟合”,不分训练 / 测试集,这些概念我们留到以后再说,这次就单纯“找最优”。我们一起把它写出来,先经历一次“找到漂亮数字”的过程,下一节再回头看看那个数字到底是什么。
#### 起草上下文 + 任务
#### 第一、二段:上下文和任务描述
> **上下文**:在 `q1-strategy.ipynb` 中已有 `df`、`result_dca`、带均线条件的回测函数 `backtest_dca_with_ma`。
>
> **任务**:扫描所有均线参数(MA5 到 MA120,步长 5),找到历史收益率最高的「最优参数」并可视化。
> **任务描述**:扫描所有均线参数(MA5 到 MA120,步长 5),找到历史收益率最高的「最优参数」并可视化。
#### 起草要求 + 结果呈现
#### 第三、四段:任务要求和验收标准
> **要求**:扫描范围 MA5 到 MA120 步长 5(共 24 个参数)→ 调用已有的 `backtest_dca_with_ma` 跑一遍 → 记录每个参数的最终收益率 → 找出最高那个。
> **任务要求**:扫描范围 MA5 到 MA120 步长 5(共 24 个参数)→ 调用已有的 `backtest_dca_with_ma` 跑一遍 → 记录每个参数的最终收益率 → 找出最高那个。
>
> **结果呈现**:打印最优参数 / 最优收益率 / 简单定投对照 / 提升幅度;画柱状图(figsize 12×5),正收益绿、负收益红、最优金色高亮黑边框、灰虚线标定投基线、黑实线标零线。
>
> 📌 **要点**:教学型 spec 有时要“刻意保持 naive”。这份 spec 的 spec-writer 是知道过拟合存在的——但 spec **不能**写“小心过拟合”或者“加一个 train/test split”。如果这里就警告了,下一节“真相时刻”的冲击力就消失了。**让学员先经历“找到漂亮数字”的兴奋,下一份 spec 再让那个数字在新数据上原形毕露,认知冲击才有教学价值。** 学员自己写“研究探索型 spec”时也是同理——先写一个 naive 版本看看会怎样,再升级。
>
> 📌 **要点**:spec 的“语气”也是设计选择。本 spec 的任务段写“找到历史收益率最高的最优参数”——用了“最优”这个词。如果 spec 写“扫描参数看看分布”,学员就不会有“啊我找到秘诀了”的错觉,后面再讲过拟合就没冲击力了。**spec 的措辞要服务于章节的节奏。**
> **验收标准**:打印最优参数 / 最优收益率 / 简单定投对照 / 提升幅度;画柱状图(figsize 12×5),正收益绿、负收益红、最优金色高亮黑边框、灰虚线标出简单定投水平线、黑实线标零线。
完整 spec 在 [`specs/spec-06-param-scan.md`](https://github.com/xingwudao/xquant-learning/blob/main/q1-how-to-profit/specs/spec-06-param-scan.md)——复制给 AI,弹窗选「允许」。
完整示例 spec 在配套仓库的 [`q1-how-to-profit/specs/spec-06-param-scan.md`](https://github.com/xingwudao/xquant-learning/blob/main/q1-how-to-profit/specs/spec-06-param-scan.md),你可以参考。确认自己的 spec 后,把它复制给 AI,弹窗选「允许」。
AI 助手执行完毕后,你的 notebook 里会出现一组数字和一张柱状图。AI 帮你做了这些事:从 MA5 到 MA120,每隔 5 天取一个参数,逐个运行均线回测并记录收益率,一共 24 次——就像考试出了 24 套卷子,让同一个策略换不同的均线参数去做,看哪套得分最高。然后从中找出收益率最高的“冠军参数”,并画成柱状图。
AI 执行完毕后,你的 notebook 里会出现一组数字和一张柱状图。AI 帮你做了这些事:从 MA5 到 MA120,每隔 5 天取一个参数,逐个运行均线回测并记录收益率,一共 24 次就像考试出了 24 套卷子,让同一个策略换不同的均线参数去做,看哪套得分最高。然后从中找出收益率最高的“冠军参数”,并画成柱状图。
参数扫描结果如图 1-8 所示。柱状图中每根柱子代表一个参数的收益率。绿色柱子 = 正收益,红色柱子 = 负收益,金色高亮那根就是“冠军”——24 个参数里收益率最高的。灰色虚线是简单定投的水平线,让你一眼看出哪些参数跑赢了简单定投,哪些跑输了。黑色实线是零线——在它下面的参数连本都没保住。
参数扫描结果如图 1-8 所示。柱状图中每根柱子代表一个参数的收益率。绿色柱子 = 正收益,红色柱子 = 负收益,金色高亮那根就是“冠军”24 个参数里收益率最高的。灰色虚线是简单定投的水平线,让你一眼看出哪些参数跑赢了简单定投,哪些跑输了。黑色实线是零线在它下面的参数连本都没保住。
![参数扫描结果](images/q1-how-to-profit/06-param-scan.png)
### 读懂参数扫描的结果
先看打印出来的数字。最优参数的收益率是多少?和简单定投比,提升了多少?你大概率会看到一个相当漂亮的数字——看起来我们真的“找到了盈利的秘诀”。
先看打印出来的数字。最优参数的收益率是多少?和简单定投比,提升了多少?你大概率会看到一个相当漂亮的数字看起来我们真的“找到了盈利的秘诀”。
但是——请你仔细看那张柱状图的形状。
但是请你仔细看那张柱状图的形状。
如果真的存在某个“最优均线周期”的内在规律,你会期望柱状图呈现一个“山峰”形状:最优参数附近的参数表现应该也不差,离最优越远表现越差,像一座钟形山丘,顶点就是最优值。这说明“最优参数附近就是甜蜜区”,你稍微偏一点也没关系。
但实际的柱状图是这样吗?大概率不是。你看到的更像是高高低低、毫无规律的锯齿形状。MA35 可能很好,MA40 突然变差,MA45 又回来了。相邻的两个参数——仅仅差 5 天——表现可能天差地别。金色那根“冠军”柱子的左右邻居,可能一个正收益一个负收益。
但实际的柱状图是这样吗?大概率不是。你看到的更像是高高低低、毫无规律的锯齿形状。MA35 可能很好,MA40 突然变差,MA45 又回来了。相邻的两个参数仅仅差 5 天表现可能天差地别。金色那根“冠军”柱子的左右邻居,可能一个正收益一个负收益。
这说明什么?
说明这个“最优参数”的“好”,很可能只是碰巧。它恰好在这段特定的历史数据上表现最好,而不是因为它抓住了什么深层规律。如果真的有深层规律,相邻参数不应该差这么多——MA35 和 MA40 只差 5 天的回看窗口,它们看到的趋势信息几乎一样,凭什么一个盈利一个亏损?唯一的解释是:结果对参数极度敏感,而这种敏感性来自数据中的随机波动,不是来自某种稳定的市场规律。
说明这个“最优参数”的“好”,很可能只是碰巧。它恰好在这段特定的历史数据上表现最好,而不是因为它抓住了什么深层规律。如果真的有深层规律,相邻参数不应该差这么多MA35 和 MA40 只差 5 天的回看窗口,它们看到的趋势信息几乎一样,凭什么一个盈利一个亏损?唯一的解释是:结果对参数极度敏感,而这种敏感性来自数据中的随机波动,不是来自某种稳定的市场规律。
现在回到开头那个问题:你朝墙上射了 24 支箭,然后走到离某支箭最近的地方画了一个靶心。“百发百中”?当然不是——你只是射得够多罢了。24 个参数里,总有一个碰巧在这段时间表现最好,这是统计学的必然,不是你发现了什么秘密。就像你抛 24 次硬币,总有一次正面连续出现最多——你不会因此断定那枚硬币有“特异功能”。
现在回到开头那个问题:你朝墙上射了 24 支箭,然后走到离某支箭最近的地方画了一个靶心。“百发百中”?当然不是你只是射得够多罢了。24 个参数里,总有一个碰巧在这段时间表现最好,这是统计学的必然,不是你发现了什么秘密。就像你抛 24 次硬币,总有一次正面连续出现最多你不会因此断定那枚硬币有“特异功能”。
这个现象我们暂时叫它“锯齿现象”——参数和参数之间的差异不是平滑过渡的山峰,而是高低跳动的锯齿。它有一个更正式的名字,但在给它正式命名之前,我们需要一个更严格的方法去验证:这个“冠军”到底是真本事,还是只是 24 次掷骰里运气最好的那一次。
这个现象我们暂时叫它“锯齿现象”参数和参数之间的差异不是平滑过渡的山峰,而是高低跳动的锯齿。所以,我们需要一个更严格的方法去验证:这个“冠军”到底是真本事,还是只是 24 次掷骰里运气最好的那一次。
方法很简单——用这个参数从未见过的新数据来测试它。下一节就做这件事。
方法很简单用这个参数从未见过的新数据来测试它。下一节就做这件事。
## 1.5 真相时刻
## 1.5 过去能代表未来吗?
怎样才算一场“公平考试”
回测就是用过去的数据来检验策略,无论考分高低,我们都应该问一句:过去能代表未来吗
不能拿学生复习过的原题来考他——他全都见过,考满分不说明任何问题。要测出真实水平,得一张他从没做过的卷子。投资策略也一样。上一节我们用全部 5 年的历史数据做参数扫描,找到了“冠军参数”。但这个冠军是在“开卷考”里拿的第一名——它见过所有数据,当然能挑到最好的答案。这不算本事。
既然我们无法真正预测未来,也不能穿越到未来,我们只能想办法设计考试,让考试中得到高分的策略在未来也更可能表现好。因此,不能拿学生复习过的原题来考他他全都见过,考满分不说明任何问题。要测出真实水平,得一张他从没做过的卷子。投资策略也一样。上一节我们用全部 5 年的历史数据做参数扫描,找到了“冠军参数”。但这个冠军是在“开卷考”里拿的第一名它见过所有数据,当然能挑到最好的答案。这不算本事。
要给它一场闭卷考,方法是把数据分成两段。
要给它一场闭卷考,方法是把数据分成两段。
你手上有大约 5 年的历史数据。我们把前 60% 拿出来——大约 3 年——作为**训练集(Training Set**,用来“复习”,也就是做参数扫描、找最优参数。剩下的后 40%——大约 2 年——作为**测试集(Test Set)**,这是“考试卷”。训练集里发生的事,测试集完全不知道;测试集里发生的事,训练的时候完全没看过。两段数据,互不相见。
你手上有大约 5 年的历史数据。我们把前 60% 拿出来大约 3 年,这部分叫**训练集(Training Set**,用来“刷题”,也就是做参数扫描、找最优参数。剩下的后 40%大约 2 年,叫**测试集(Test Set)**,这是“考试卷”。训练集里发生的事,测试集完全不知道;测试集里发生的事,训练的时候完全没看过。两段数据,互不相见。这是这次实验最重要的约束:两段数据不能交叉,否则就又变成开卷考。
考试的类比可以帮你记住这个逻辑:你手上有 5 年的真题。你用前 3 年的真题来复习——反复做、反复总结、找到最顺手的解题方法。然后你用后 2 年的真题来“考试”——这 2 年的题你在复习的时候完全没碰过。如果你是真的理解了规律,换一张新卷子也能考好。如果你只是背了前 3 年的答案,新卷子就会露馅。
考试的类比可以帮你记住这个逻辑:你手上有 5 年的真题。你用前 3 年的真题来复习反复做、反复总结、找到最顺手的解题方法。然后你用后 2 年的真题来“考试”这 2 年的题你在复习的时候完全没碰过。如果你是真的理解了规律,换一张新卷子也能考好。如果你只是背了前 3 年的答案,新卷子就会露馅。
> 📌 小词典:什么是训练集 / 测试集?
>
> **训练集(Training Set)**:用来“复习”的那段数据。
>
> **测试集(Test Set)**:用来“考试”的那段新数据,模型训练时完全没见过。
>
> 核心约束:两段数据互不交叉。否则就是开卷考。
为什么是 60/40?没有什么神奇数字。训练集不能太少,否则“复习”不充分,找到的参数不可靠;测试集也不能太少,否则“考试”的题量不够,结果说明不了问题。60/40 是一个常见的折中:前面大约 3 年够做一轮完整的参数扫描,后面大约 2 年够看出趋势。这不是完美的方法,真正的未来当然不可知,我们只是用“后来发生的事”来模拟未来。但它是我们能做到的最公平的模拟。
为什么是 60/40?没有什么魔法数字。训练集不能太少,否则“复习”不充分,找到的参数不可靠;测试集也不能太少,否则“考试”的题量不够,结果说明不了问题。60/40 是一个常见的折中:前面大约 3 年够做一轮完整的参数扫描,后面大约 2 年够看出趋势。这不是完美的方法——真正的未来当然不可知,我们只是用“后来发生的事”来假装未来。但它是我们能做到的最公平的模拟。
接下来的实验就是这个思路:在训练集上重新做参数扫描,找到训练集的“冠军参数”。然后拿着这个冠军参数去测试集上跑一遍——看看它能不能通过“考试”。同时我们在测试集上也跑一遍简单定投作为对照。为什么要对照?因为简单定投不做任何参数优化,它就是“什么都不想”的基线。如果你精心优化的参数连“什么都不想”都赢不了,那优化的意义何在?
接下来的实验就是这个思路:在训练集上重新做参数扫描,找到训练集的“冠军参数”。然后拿着这个冠军参数去测试集上跑一遍,看看它能不能通过“考试”。同时我们在测试集上也跑一遍简单定投作为对照。为什么要对照?因为简单定投不做任何参数优化,它就是“什么都不想”的参照物。如果你精心优化的参数连“什么都不想”都赢不了,那优化的意义何在?
### 动手实验 7:样本外测试
这份样本外测试 spec 是本章压轴的一份——它把数据按时间切成两段,让上一节找到的“冠军参数”在没见过的新数据上重新考一次。我们一起把它写出来,重点是**数据 split 的可复现写法**和**怎么把三个数字摆在一起做对比**。
这份样本外测试 spec 把数据按时间切成两段,让上一节找到的“冠军参数”在没见过的新数据上重新考一次。我们一起把它写出来,重点是**数据切分的可复现写法**和**怎么把三个数字摆在一起做对比**。
#### 起草上下文 + 任务
#### 第一、二段:上下文和任务描述
> **上下文**:在 `q1-strategy.ipynb` 中已有 `df`、`backtest_dca`、`backtest_dca_with_ma`、参数扫描代码(MA5 到 MA120)。
>
> **任务**:把数据按时间前 60% / 后 40% 分为训练集 / 测试集,验证「最优参数」在未见过的新数据上是否仍然有效。
> **任务描述**:把数据按时间前 60% / 后 40% 分为训练集 / 测试集,验证「最优参数」在未见过的新数据上是否仍然有效。
#### 起草要求:split 可复现 + 三栏对比
#### 第三段:任务要求
> **要求**
> **任务要求**
1. 数据分割:**按时间前 60% 为训练集,后 40% 为测试集**(按时间顺序切,不打乱)
2. 在训练集上扫描参数(MA5 到 MA120 步长 5),找到训练集上的最优参数
@@ -498,69 +465,69 @@ AI 助手执行完毕后,你的 notebook 里会出现一组数字和一张柱
4. 同时在**测试集**上跑简单定投作为对照
5. 对比三个收益率:训练集最优 / 测试集该参数 / 测试集简单定投
> 📌 **要点**:数据 split 的写法直接决定可复现。“按时间前 60%”是确定的,“随机分 60/40”是不确定的——后者每次跑会得到不同 split,结果就不能 1:1 对照了。**任何切分操作都要写明:是按时间还是随机?切点在哪?随机种子?锁住了你的 spec 才能跨次复现。**
> 📌 **要点**:数据切分的写法直接决定可复现。“按时间前 60%”是确定的,“随机分 60/40”是不确定的后者每次跑会得到不同切分结果,结果就不能 1:1 对照了。任何切分操作都要写明:是按时间还是随机?切点在哪?随机种子?影响切分结果的要素确定写在你的 spec 中,实验结果才能复现。
>
> 📌 **要点**:当一份 spec 要核对前一份 spec 的结论时,固定套路是把三个数字摆在一起对比——前一份给出“看似最优”,本份给出“测试集上的真实成绩”+“什么都不做的成绩”。三者一对比,前一份的“最优”到底是真本事还是巧合,一目了然。三个数字不是随便凑的:训练集最优是“你以为”,测试集该参数是“其实”,测试集简单定投是“什么都不做”——三者放一起,过拟合就藏不住了
> 📌 **要点**:当一份 spec 要核对前一份 spec 的结论时,固定套路是把三个数字摆在一起对比前一份给出“看似最优”,本份给出“测试集上的真实成绩”+“全选 C 的成绩”。三者一对比,前一份的“最优”到底是真本事还是巧合,一目了然。三个数字不是随便凑的:训练集最优是“自我感觉”,测试集该参数是“真实检验”,测试集简单定投就好比“全选 C”,三者放一起对比,隐藏风险就更容易暴露
#### 起草结果呈现
#### 第四段:验收标准
> **结果呈现**
> **验收标准**
1. 打印训练集 / 测试集时间范围和条数
2. 三个数字并排打印:训练集最优参数 + 收益率 / 测试集上该参数的收益率 / 测试集上简单定投的收益率
3. 对比结论双分支:
- 最优参数在测试集上比简单定投还差:打印「这就是过拟合——你在历史数据上找到的是巧合,不是规律」
- 最优参数在测试集上比简单定投还差:打印「这就是过拟合你在历史数据上找到的是巧合,不是规律」
- 否则:打印「结果看起来还行,但仍需谨慎,样本量有限」
完整 spec 在 [`specs/spec-07-overfitting.md`](https://github.com/xingwudao/xquant-learning/blob/main/q1-how-to-profit/specs/spec-07-overfitting.md)——复制给 AI,弹窗选「允许」。
完整示例 spec 在配套仓库的 [`q1-how-to-profit/specs/spec-07-overfitting.md`](https://github.com/xingwudao/xquant-learning/blob/main/q1-how-to-profit/specs/spec-07-overfitting.md),你可以参考。确认自己的 spec 后,把它复制给 AI,弹窗选「允许」。
AI 助手执行完毕后,你的 notebook 里会出现一组关键数字。这可能是本章最重要的一组数字——请你做好心理准备。
AI 执行完毕后,你的 notebook 里会出现一组关键数字。这可能是本章最重要的一组数字请你做好心理准备。
AI 帮你做了这些事:把数据按时间顺序在第 60% 的位置一刀切——前面是训练集(“复习用的真题”),后面是测试集(“考试当天才看到的新题”)。注意数据是按时间顺序切的,不是随机打乱的,因为在现实中你只能用过去的数据做决策,未来的数据还没发生。然后在训练集上重新做参数扫描找到冠军参数,拿着这个冠军参数去测试集上跑一遍(“正式考试”),同时在测试集上也跑一遍简单定投作为对照。
AI 帮你做了这些事:把数据按时间顺序在第 60% 的位置切下一刀,前面是训练集(“复习用的真题”),后面是测试集(“考试当天才看到的新题”)。注意数据是按时间顺序切的,不是随机打乱的,因为在现实中你只能用过去的数据做决策,未来的数据还没发生。然后在训练集上重新做参数扫描找到冠军参数,拿着这个冠军参数去测试集上跑一遍(“正式考试”),同时在测试集上也跑一遍简单定投作为对照。
### 读懂样本外测试的结果
现在看看这三个数字。
第一个数字:训练集上最优参数的收益率。这个数字大概率很漂亮——毕竟它是 24 个参数里挑出来的冠军,在训练集上当然表现最好。记住,这是“开卷考”的成绩。
第一个数字:训练集上最优参数的收益率。这个数字大概率很漂亮毕竟它是 24 个参数里挑出来的冠军,在训练集上当然表现最好。记住,这是“开卷考”的成绩。
第二个数字:测试集上用同一个参数的收益率。这是“闭卷考”的成绩。
第三个数字:测试集上简单定投的收益率。这是“什么都不想”的成绩。
第三个数字:测试集上简单定投的收益率。这是“全选 C”的成绩。
把第二个数字和第三个数字放在一起看。
大概率你会看到这样的结果:冠军参数在测试集上的收益率,比简单定投还差。
请你再读一遍这句话,确认你没有看错。你花了大量精力做参数扫描,从 MA5 到 MA120 穷举了 24 个参数,在训练集上找到了表现最好的那个。然后你拿着这个精心挑选的冠军参数去面对“未来”——它不仅没有比简单定投更好,反而更差了
请你再读一遍这句话,确认你没有看错。你花了大量精力做参数扫描,从 MA5 到 MA120 穷举了 24 个参数,在训练集上找到了表现最好的那个。然后你拿着这个精心挑选的冠军参数去面对“未来”,它还不如简单定投。折腾了一圈,最后还不如“全选 C”
换句话说:**你精心优化的策略,还不如什么都不做。**
也就是说:**你精心优化的策略,还不如什么都不做。**
这不是你的操作有误,也不是代码有 bug。这就是过拟合的真实面目
这不是你的操作有误,也不是代码有 bug。这就是过拟合”(Overfitting)。通俗一点说,就是策略把见过的题背得太熟,却没有学到真正规律;一遇到波动变化,就失效
在训练集上,你的冠军参数看起来无懈可击——它是 24 个候选人里的第一名,收益率比第二名还高出不少。但到了测试集上,它差到几乎像换了一策略。它在训练集上的好成绩不是因为它抓住了市场的某种深层规律,而是因为它恰好适配了那段特定数据的特定走势。换一段数据,这种适配就不存在了。
在训练集上,你的冠军参数看起来无懈可击它是 24 个候选人里的第一名,收益率比第二名还高出不少。但到了测试集上,它差到几乎像换了一策略。它在训练集上的好成绩不是因为它抓住了市场的某种深层规律,而是因为它恰好适配了那段特定数据的特定走势。换一段数据,这种适配就不存在了。
为什么会这样?我从三个角度帮你理解。
**从统计的角度看。** 24 个参数里挑最好的那个,就像在教室里随便拉 24 个人,选最高的那个。他身高 185cm——但这能代表这 24 个人的平均身高吗?当然不能。他只是碰巧最高的那个。你选的参数越多,“碰巧最好”的那个看起来越好——因为你给了随机波动更多的机会来制造“看起来很厉害”的假象。这种现象叫选择偏差(Selection Bias):当你从一堆结果里挑最好的那个,你挑到的往往不是“真正最好”的,而是“运气最好”的。
**从统计的角度看。** 24 个参数里挑最好的那个,就像在教室里随便拉 24 个人,选最高的那个。他身高 185cm但这能代表这 24 个人的平均身高吗?当然不能。他只是碰巧最高的那个。你选的参数越多,“碰巧最好”的那个看起来越好因为你给了随机波动更多的机会来制造“看起来很厉害”的假象。这种现象叫选择偏差(Selection Bias):当你从一堆结果里挑最好的那个,你挑到的往往不是“真正最好”的,而是“运气最好”的。
**从市场的角度看。** 前 3 年和后 2 年的市场环境不同。也许前 3 年是一轮漫长的熊市,价格一路下跌;后 2 年是触底反弹,价格逐步回升。或者反过来。一个参数在熊市中表现好——比如 MA60,因为它足够迟钝,帮你躲过了大部分下跌——但到了牛市,这种迟钝反而成了累赘:市场都涨起来了,MA60 还在犹豫要不要买入,白白错过了一大段涨幅。市场不是一成不变的。一件衣服不可能四季都合适——冬天穿着暖和的羽绒服,夏天穿就热死人。你在前 3 年找到的“最合身的衣服”,到后 2 年换了季节,哪里都不对。
**从市场的角度看。** 前 3 年和后 2 年的市场环境不同。也许前 3 年价格一路下跌;后 2 年价格从低点慢慢涨回来。或者反过来。一个参数在价格下跌时表现好比如 MA60,因为它足够迟钝,帮你躲过了大部分下跌但到了价格上涨时,这种迟钝反而成了累赘:市场都涨起来了,MA60 还在犹豫要不要买入,白白错过了一大段涨幅。市场不是一成不变的。一件衣服不可能四季都合适冬天穿着暖和的羽绒服,夏天穿就热死人。你在前 3 年找到的“最合身的衣服”,到后 2 年换了季节,哪里都不对。
**从直觉的角度看。** 最优参数就是为那段训练数据“量体裁衣”的产物。量体裁衣做出来的西装确实完美合身——但那是为你一个人做的。换一个身材不同的人穿,哪里都不对劲。你的冠军参数也是一样:它是为 2021 年到 2023 年某段特定行情“量身定做”的,每一个均线周期、每一次买入跳过,都恰好踩在了那段数据上。换一段数据——哪怕只是后面紧接着的两年——整套策略就不再合身。
**从直觉的角度看。** 最优参数就是为那段训练数据“量体裁衣”的产物。量体裁衣做出来的西装确实完美合身但那是为你一个人做的。换一个身材不同的人穿,哪里都不对劲。你的冠军参数也是一样:它是为 2021 年到 2023 年某段特定市场走势“量身定做”的,每一个均线周期、每一次买入跳过,都恰好踩在了那段数据上。换一段数据哪怕只是后面紧接着的两年整套策略就不再合身。
这就是过拟合——量化交易最大的敌人。
这就是过拟合,它是量化中最危险的敌人,也是最会伪装的敌人。
它不是一个小 bug,不是代码写错了一行,修一修就好。它是一种系统性的思维陷阱。只要你在历史数据上做优化,过拟合的幽灵就始终在你身边。你测试的参数越多,优化的维度越多,它就越危险——因为你给了“碰巧”更多的生存空间。
它不是 bug,不是因为代码写错了一行引起的,它是一种系统性的思维陷阱。只要你在历史数据上做优化,过拟合的风险就始终在。你测试的参数越多,优化的维度越多,它就越危险因为你给了“碰巧”更多的生存空间。
每一个做量化的人,不管是刚入门的新手还是华尔街的资深研究员,都必须时刻警惕它。在历史数据上看到的任何“好结果”,在被样本外测试验证之前,都不值得相信。训练集上的冠军可能只是考试原题背得最熟的那个学生,真正的考试一来就露馅了。
每一个做量化的人,不管是刚入门的新手还是资深研究员,都必须时刻警惕它。在历史数据上看到的任何“好结果”,在被样本外测试验证之前,都不值得相信。训练集上的冠军可能只是考试原题背得最熟的那个学生,真正的考试一来就露馅了。
你在本章走过的路——从简单定投、到加入均线判断、到参数扫描找最优、再到样本外测试看清真相——正是每一个量化新手都会经历的完整旅程。区别只在于:有些人在“找到最优参数”那一步就停下来了,兴冲冲地拿去实盘交易,然后亏了真金白银才明白过拟合是什么。而你,在投入一分钱之前,就已经看到了真相。
你在本章走过的路从简单定投、到加入均线判断、到参数扫描找最优、再到样本外测试看清真相,正是很多量化新手都会经历的完整旅程。区别只在于:有些人在“找到最优参数”那一步就停下来了,兴冲冲地拿去实盘交易,等到真正亏钱之后才明白过拟合是什么。而你,在投入一分钱之前,就已经看到了真相。
记住这个教训:**历史数据上的“最优”,不等于未来的“可用”。** 在量化交易的世界里,怀疑永远比自信更值钱
记住这个教训:**历史数据上的“最优”,不等于未来的“可用”。** 在量化交易的世界里,保持怀疑会让你走得更稳
## 1.6 本章总结
回头看看我们走过的路。从最简单的定投开始,到加入均线判断做择时,到参数扫描寻找“最优解”,再到样本外测试看着精挑的参数全线衰减——7 个实验,一步一步,你经历了一次完整的认知升级。这不只是学了几个策略,而是亲眼见证了“看起来有效”和“真的有效”之间的巨大鸿沟。
回头看看我们走过的路。从最简单的定投开始,到加入均线判断做择时,到参数扫描寻找“最优解”,再到样本外测试看着精挑出来的参数失效,7 个动手实验,一步一步,你经历了一次完整的认知升级。这不只是学了几个策略,而是亲眼见证了“看起来有效”和“真的有效”之间的巨大鸿沟。
### 概念速查表
@@ -572,8 +539,6 @@ AI 帮你做了这些事:把数据按时间顺序在第 60% 的位置一刀切
|----|----|----|
| 回测(Backtest) | 用历史数据模拟策略执行 | 在脑海中“重播”历史 |
| 基准(Benchmark) | 用来比较的参照物 | 全班的平均分 |
| Beta(贝塔) | 承担市场风险获得的收益 | 坐电梯——市场涨你跟着涨 |
| Alpha(阿尔法) | 超越市场的那部分收益 | 爬楼梯——靠自己的本事往上走 |
| 均线(Moving Average | 过去 N 天价格的平均值 | 连续几天的平均体温 |
| 过拟合(Overfitting) | 把数据中的噪音当成规律 | 背答案 vs 理解规律 |
| 训练集(Training Set) | 用来“复习”和找参数的那段数据 | 复习用的真题 |
@@ -587,99 +552,31 @@ AI 帮你做了这些事:把数据按时间顺序在第 60% 的位置一刀切
| 版本 | 策略 | 教训 |
|------|-----------------|----------------------------------------------|
| v0.1 | 简单定投 | 纯 Beta 暴露,收益完全随市场涨跌 |
| v0.2 | 定投 + 均线择时 | 加入判断是双刃剑,不保证比无脑定投更好 |
| v0.1 | 简单定投 | 收益主要跟随市场涨跌 |
| v0.2 | 定投 + 均线择时 | 加入判断是双刃剑,不保证比简单定投更好 |
| v0.3 | 参数扫描优化 | 历史最优不等于未来可用,过拟合的风险就在面前 |
### 飞轮第一:你刚刚走完的路
### 学习地图第一:你刚刚走完的路
回头看:你已经把飞轮的四层都各自踩过一次。**候选**——你选了沪深 300ETF**组合层**——你定了“每月买一份”或“上穿均线再买”的规则;**执行层**——回测引擎替你按规则下单**归因**——你比了三种策略的累计收益对照了样本内/外。
回头看:你已经把四个阶段都接触了一次。**确定候选**你选了沪深 300ETF**制定规则**你定了“每月买一份”或“上穿均线再买”的规则;**执行交易**:程序在回测里按规则模拟买入**评估归因**你比了三种策略的累计收益,也对照了样本内和样本外。
更重要的是:**做、看、疑**三个动作你都练了一遍。前言里那句口号——“**做要规则、看要指标、还要怀疑你的指标**”——你不是听别人讲,而是亲眼看见冠军参数在测试集上现原形之后才真正信的。**这就是为什么本书把“疑”和“做”“看”放在同等位置**:会做、会看,但不会疑,最容易在某些时候把赚到的钱加倍还给市场
更重要的是:**做、看、疑**三个环节你都经历了一遍。前言里那句话,“**做要规则、看要指标、还要怀疑你的指标**”你不是听别人讲,而是亲眼看见冠军参数在测试集上现原形之后才真正信的。**这就是为什么本书把“疑”和“做”“看”放在同等位置**:会做、会看,但不会怀疑,最容易被漂亮结果骗到
### 本章最重要的收获
说实话,我们并没有学会一个能稳定盈利的策略。定投跑不赢市场,加了均线也未必更好,参数优化更是一脚踩进了过拟合的坑。如果你期待的是“学完第 1 章就能盈利”,那确实要让你失望了。
说实话,我们并没有学会一个能稳定盈利的策略。定投跑不赢市场,加了均线也未必更好,参数优化更是一脚踩进了过拟合的坑。如果你期待的是“学完第 1 章就能找到盈利的策略”,那确实要让你失望了。
但你学会了一样比任何策略都值钱的东西——**怀疑**。面对任何一条漂亮的回测曲线,你现在知道第一个要问的问题是:“这是规律,还是巧合?”这种本能的警觉,会让你在未来少走很多弯路,少很多真金白银
但你学会了一样比任何策略都值钱的东西**怀疑**。面对任何一条漂亮的回测曲线,你现在知道第一个要问的问题是:“这是规律,还是巧合?”这种本能的警觉,会让你在未来少走很多弯路,少很多代价
### 带走的问题
本章带你跑通了第一个完整策略,但也留下了一些更深的问题。带着它们继续往下走:
- **只买沪深 300 够吗?** 本章 7 个实验全在拿沪深 300 做样本,但真正投资时不会只买一只。下一章我们要选 3 只 ETF组成一个真正的“投资宇宙”。——第 2 章会解答
- **怎么区分运气和能力?** 一个策略盈利了,到底是真有水平还是瞎猫碰上死耗子?——第 5 章、第 6 章会解答。
- **有没有不容易过拟合的策略?** 既然参数优化这条路走不通,还有别的路吗?——第 4 章、第 6 章会解答。
- **怎么知道策略在未来还能盈利?** 样本外测试只是第一步,还有更严格的检验方法吗?——第 5 章会解答。
- **只买沪深 300 够吗?** 本章 7 个实验全在拿沪深 300 做样本,但真正投资时不会只买一只。第 2 章会先选 3 只 ETF建立一个更像样的候选范围
- **怎么区分运气和能力?** 一个策略盈利了,到底是真有水平还是刚好碰上了一段适合它的市场走势?第 5 章、第 6 章会解答。
- **有没有不容易过拟合的策略?** 既然参数优化这条路走不通,还有别的路吗?第 4 章、第 6 章会解答。
- **怎么知道策略在未来还能盈利?** 样本外测试只是第一步,还有更严格的检验方法吗?第 5 章会解答。
答案都在后面。继续。
答案都在后面。我们下一章继续。
> 💡 **提示**:本章正文聚焦“亲手跑通最小闭环”。「拓展阅读」给了两段世界观铺垫——读不读都不影响主线,但读完会让后面几章吸收得更快
>
> 💡 **提示**:本章所有代码的可运行版本见 `notebooks/q1-strategy.ipynb`。
## 拓展阅读
本章正文聚焦“亲手跑通最小闭环”。下面两节是动手之前可以省略的视野铺垫——读完正文后回头看,反而吸收得更快。
### 你做的是哪种交易?
“量化”、“程序化”、“算法”、“高频”——这四个词常被混用,其实是层层包含的关系,如图 1-9 所示。
![四个概念层层包含](images/q1-how-to-profit/quant-concepts-nested.png)
四个概念的具体对照如表 1-4 所示。
**表 1-4 量化 / 程序化 / 算法 / 高频四个概念对照**
| 概念 | 干什么 |
|----|----|
| **量化交易**(最外层) | 用数据和模型做决策(对面是靠经验直觉的“主观交易”) |
| **程序化交易** | 用程序自动执行——你完全可以做量化但手动执行,很多个人量化交易者就这么做 |
| **算法交易** | 解决“怎么买”——把大单拆小单降低市场冲击 |
| **高频交易**(最内层) | 毫秒级速度,专用机房和光纤,门槛千万美元起 |
**“投资”与“交易”也是两件事**:投资买的是公司、赚的是公司成长的钱;交易买的是筹码、赚的是别人犯错的钱。本课程偏交易,不分析公司成长。
**本课程做的是中低频量化交易,手动或半自动执行**——持仓周期周到月级别,普通电脑就能跑。你的对手不是 Citadel 这种顶级机构,而是没有系统、靠感觉交易的散户。在这个层面上,纪律严明的量化策略已经是巨大优势。
### 钱从哪来,为什么大多数人赚不到
不管你买什么,赚到的钱本质上**只来自两类**——前言里我们用过同样的分法:**资产创造**(你拿到的是资产本身在帮你赚钱)和**价格变化**(你赚的是别人愿意付的差价)。两类的具体来源如表 1-5 所示。
**表 1-5 投资收益的两大类来源**
| 大类 | 具体来源 | 核心本质 | 关键特征 |
|----|----|----|----|
| **资产创造** | 分红 | 公司把利润分给股东 | 不依赖市场情绪(股票、ETF) |
| **资产创造** | 利息 | 借出资金的报酬 | 不依赖市场情绪(债券、国债) |
| **价格变化** | 价差 | 低买高卖的差额 | 赌价格变化方向(股票、ETF、期货) |
| **价格变化** | 套利 | 不同市场间的瞬时差价 | 几乎不预测方向,门槛高(跨市套利) |
一句话总结:**分红和利息赚的是“现金流”,价差赚的是“共识差”,套利赚的是“空间差”。** 本课程的策略主要赚价差。
**短期负和、长期正和。** 每笔交易都要付佣金、印花税、买卖价差——频繁交易等于把自己放进一个“整体必输”的游戏。但长期持有指数级资产能分享企业增长,蛋糕在变大、大家一起赚。这就是格雷厄姆那句“**短期是投票机,长期是称重机**”。本课程定位“中频”——既捕捉规律,又不被成本吃掉。
**三个差距,量化的三种对抗。** 虽然市场整体可以赚钱,但大多数散户没赚到——核心是三个“差”,恰好对应量化的三种优势(如表 1-6 所示):
**表 1-6 三个差距与量化的对应优势**
| 普通人的劣势 | 体现 | 量化的对应优势 |
|----|----|----|
| 信息差 | 你看到新闻时,机构早已先动 | 同时扫描几千标的找统计规律 |
| 情绪差 | 涨了就追买、跌了就吓得抛掉,本能在股市害人 | 规则写好机器执行,无情绪干扰 |
| 成本差 | 频繁操作累积成本,可能吃掉一半本金 | 明确规则约束,不频繁操作 |
量化不能保证赚钱,但能让你在更公平的位置上竞争。
---
## 反馈与读者群
如果你发现本章有错别字、概念不清或实验结果不一致,欢迎
[提交 GitHub Issue](https://github.com/xingwudao/xquant-beginner/issues/new/choose)。
课程、正式书和后续更新见
[xquant.shop/courses](https://xquant.shop/courses)。
添加作者微信 `xquanter` 进入读者群。请备注:`XQuant 读者`
> 💡 **提示**:本章所有代码的可运行版本见配套仓库的 [`q1-how-to-profit/notebooks/q1-strategy.ipynb`](https://github.com/xingwudao/xquant-learning/blob/main/q1-how-to-profit/notebooks/q1-strategy.ipynb)
+176 -235
View File
@@ -1,307 +1,285 @@
# 第二章:选出你的投资宇宙:3 只 ETF
# 第 2 章:先选买什么:从 3 只 ETF 开始
章我们用一只沪深300ETF 体验了量化策略的完整闭环——从下载数据、定投回测、基准对比,到均线择时、参数扫描、样本外测试。一路走下来,你已经亲手验证了一个策略从“看起来有效”到“撞上过拟合”的全过程。
1 章我们用一只沪深 300ETF 体验了量化策略的完整流程:从下载数据、定投回测、基准对比,到均线择时、参数扫描、样本外测试。一路走下来,你已经亲手验证了一个策略从“看起来有效”到“过拟合”的全过程。
但有个问题我们始终没碰:只买一只 ETF 够吗?直觉告诉你,把鸡蛋放在一个篮子里不够安全,应该广撒网、分散风险。这个直觉对不对?“分散”到底该怎么分?随便多买几只就算分散了吗?这些问题,就是本章要回答的。
不过在动手之前,我们要先把“市场上到底有什么可以买”这个最基础的问题搞清楚——第一章直接给了你一只沪深300ETF,但你还没正式见过投资市场的全图。
直觉告诉我们,如果策略只围绕一只资产运行,风险会很集中。把鸡蛋放在一个篮子里不够安全,应该分散一点。这个直觉对不对?“分散”到底该怎么分?随便多买几只就算分散了吗?这些问题,就是本章要回答的。
### 路线图
从第二章开始,我们要把第一章的闭环拆开,一步步做扎实:
本章按“准备 → 实验 → 收尾”三段组织,对照如表 2-1 所示,一共要动手做 3 次实验。
**选什么资产(第二章 本章)→ 每个买多少(第三章)→ 什么时候买卖(第四章)→ 怎么验证有效(第五章)**
**表 2-1 第 2 章路线图**
本章七节按“准备 → 实验 → 收尾”三段组织,对照如表 2-1 所示。
**表 2-1 第二章七节的三段组织**
| 节 | 内容 | 类型 |
| 节 | 内容 | 实验 |
|----|------|------|
| 2.1 - 2.2 | 你到底能买什么 → 工具升级(引入 open-xquant | 准备 |
| 2.3 - 2.5 | 选什么类型 → 选哪个地区 → 怎么保护组合 | 实验 |
| 2.6 - 2.7 | 回头看 + 本章总结 | 收尾 |
| 2.1 - 2.2 | 为什么要圈定一个候选名单 | 0 |
| 2.3 - 2.5 | 怎么圈定一个候选名单:类型、地区、风险分散 | 3 |
| 2.6 - 2.7 | 回头看 + 本章总结 | 0 |
---
## 2.1 你到底能买什么?
## 2.1 先圈定可选范围
直接给你答案:**新手不要买个股,从 ETF 开始。** 等你通过 ETF 理解了市场运作,再去研究其他资产也不迟。
第 1 章只研究一只沪深 300ETF,所以你不用思考“市场上还有什么”。从这一章开始,问题变了:策略到底允许考虑哪些投资对象?
为什么?新手的典型特点:不懂选股、资金有限、时间有限、心态不稳。而 ETF 恰好能解决这些问题(如表 2-2 所示):
不能只看一只。只看一只,风险集中,机会也少。它不适合的时候,策略可能长时间只能拿着现金。也不能什么都看。把整个市场都塞进来,看似选择更多,其实没有重点。数据质量、成交活跃程度、资产类型、交易规则都混在一起,后面的实验很容易变乱。所以我们要先圈定一个有限的候选范围。这个范围在量化里叫**标的池(Universe)**。所谓“标的”,就是投资对象;标的池,就是策略允许考虑的候选名单。第 2 章先确定名单,第 3 章再讨论名单里的资产各买多少。
**表 2-2 ETF 解决新手四大痛点**
你可以把标的池想成一块白板。白板上先写下策略允许观察的名字。后面做信号、算权重、执行交易,都只能在这块白板里进行。白板之外的资产,再好也先不看。
| 新手痛点 | ETF 如何解决 |
构建标的池时,应该先提出自己的假设,再把假设变成可以计算的变量。在量化里,有些变量会被称为**因子(Factor)**。你现在可以先把因子理解成:把投资直觉翻译成数字后的可计算变量。它可以用来描述资产或市场的特征,也可以用来解释风险和收益、预测未来表现,或者辅助投资决策。比如“经济体量更大的市场更值得优先考虑”,就可以落成 GDP 总量这个简化宏观因子。
本章先不展开复杂的因子研究。这里你只需要记住:GDP 总量是一个简化的因子例子;相关性不是因子本身,而是用来判断资产涨跌是否同步的统计指标。更完整的因子研究放到第 9 章再讲。
如果你还没有金融基础,那么在开始动手实验前,先简单看一眼市场上常见的投资对象:
- **股票(Stock)**:买一家公司的一小块所有权。公司赚钱了你分红,公司涨价了你的股份也值钱了。风险:单个公司可能经营不善甚至倒闭。
- **ETF(交易所交易基金)**:一篮子股票的打包组合。比如沪深 300ETF(510300)打包了 A 股市值最大的 300 家公司。买一份 ETF = 同时持有 300 家公司,风险分散。
- **债券(Bond)**:就是一张“借条”。你买国债 = 借钱给国家,到期还本付息。风险低,收益也相对低。
- **期货 / 期权**:“衍生品”,自带杠杆,风险极高,可能亏损超过本金。**新手先别碰。**
这些种类,风险和收益各不相同,本书作者的建议是:**新手学习不要买卖股票,从 ETF 开始。** 等你通过 ETF 理解了市场运作,再去研究其他资产也不迟。原因很简单,ETF 本身就是一篮子资产。用 ETF 构建第一个标的池,可以先避开单家公司新闻、财报、公司高管变化这些干扰,先把注意力放在量化策略本身:怎么提出假设,怎么写成 spec,怎么用数据验证。
表 2-2 总结了 ETF 适合新手学习量化的四个理由。
**表 2-2 ETF 适合新手学习量化的理由**
| 新手特点 | ETF 适合理由 |
|---------|-------------|
| 不懂选股 | 已经帮你选好了一篮子股票 |
| 资金有限 | 一手 ETF 可能只要几百块 |
| 时间有限 | 不用研究单个公司,买指数 = 买这个国家的整体经济 |
| 心态不稳 | 分散持有降低波动,不会因一只股票暴雷而血本无归 |
| 心态不稳 | 分散持有降低波动,不会因为一家公司出大问题就亏得很惨 |
一个无奈的现实:**大多数散户的长期收益低于指数。** 既然如此,为什么不直接买指数?本章接下来的实验,就是带你看清“为什么不要买个股、怎么用 ETF 构建投资组合”
> ETF 之外还有股票、债券、期货期权;A 股之外有美股、港股;具体可交易的资产清单、各类资产的赚钱方式、券商怎么选——见本章末「拓展阅读」。
本章要教你用 ETF 构建第一个标的池:沪深 300ETF、纳指 100ETF、黄金 ETF
---
## 2.2 工具升级:从手写脚本到利用框架
## 2.2 我们要用的研究工具
2.1 把“为什么不要买个股、怎么用 ETF”的结论给了你。下一节就要动手实验。但在动手之前,先认识一下接下来三个实验都会用到的工具:open-xquant。
第 1 章手写脚本跑通了一个均线策略。那足够用来入门,但从这一章开始不够了。量化研究需要反复做同一类事情:写 spec、下载数据、跑实验、看指标、保存结果、检查结果是否可信。如果每一步都临时写代码,很容易出现这些问题:
第一章手写循环跑通了一个均线策略——AI 帮你写了几十行代码就实现了。能跑,但稍有量化经验的人都知道:**真正的量化策略不是这么写的。**
- 同一个想法,今天跑出来一个结果,明天换一段代码又跑出另一个结果。
- 图画出来了,但数据来源、时间范围、交易假设没有记录清楚。
- 收益看起来很好,却没有审计,也没有稳健性检查。
### 你需要的不是更多代码,而是一套经过实战检验的体系
AI 是概率模型。同一份 spec 发给它,前后两次生成的程序细节可能不一样。量化实验不能接受这种飘忽:同一个想法今天一个结果,明天另一个结果,就无法比较、复盘和审计。所以,从第 2 章开始,我们的实验会引入一个开源框架 **open-xquant**(下文简称 `oxq`[github.com/xingwudao/open-xquant](https://github.com/xingwudao/open-xquant))。它不是本书的主角,也不是你要专门学习的编程框架;它只是帮你把实验流程固定下来,约束 AI 的输出。
技术指标网上一搜就有,让 AI 抄一份代码也不难。但**怎么把指标变成一个真正可上线的策略**——怎么定义信号、怎么组合规则、怎么管仓位、怎么样本外验证、怎么避开常见陷阱——这套体系是多年自营交易桌上**踩过坑、亏过钱、改过的纪律**,看几本书内化不了。
AI 接入 open-xquant 后,主要做三件事:
新手自己摸索,要走的弯路三年起步。但用对的框架,等于让一个老手在背后**约束你的每一步**
1. **让想法变得清晰,成为可执行的实验**:你写清楚“做什么”,形成 spec,它先检查 spec 是否可行,再生成对应的程序
2. **让实验的结果完全可复现,可审计**:spec 中记录了影响实验结果的所有变量,只要没有变化,就会得到同一个结果;一旦有改变,就会被记录下来供事后审计。
3. **让一次次的实验变成研究资产**:实验结束后,原始 spec、实验代码、原始和中间数据、过程图表、研究报告,全都被记录下来,成为研究资产和知识库。
前提是:这个框架真的来自一个老手。
open-xquant 的核心流程可以概括为:
### quantstrat:业界公认的方法论参考
`编写策略规格说明书 → 验证策略规格说明书 → 根据策略规格说明书生成程序 → 执行程序回测 → 审计回测过程 → 检查回测实验稳健性 → 生成回测实验报告`
R 语言生态里有一个叫 **quantstrat** 的开源框架——它在量化策略开发领域久负盛名。主要作者 Brian Peterson 是一位有十几年自营交易实战经验的量化从业者,他公开发布的白皮书《Developing & Backtesting Systematic Trading Strategies》([strat_dev_process.pdf](http://braverock.com/brian/strat_dev_process.pdf))描述了一套从“想法”到“可上线策略”的完整流程,是业界教学和入职培训的常见教材
一句话:**quantstrat 凝结了一位资深量化从业者多年的实战经验。**
### open-xquant:用 Python 致敬 quantstrat 的方法论
quantstrat 体系完整,但作为本书的学习工具,它有两个不利因素:
1. **R 语言在 AI 编程生态里是少数派**。主流 AI 编程助手对 Python 的支持远好于 R。你写 spec 让 AI 写代码,它写 Python 流畅,写 R 经常磕碰
2. **R 的指标 / 数据库迭代慢**。Python 这边 pandas / NumPy / scikit-learn / akshare / yfinance 都很活跃,新指标新数据源出来很快就有 Python 实现
直接学 R + quantstrat,方法论地道,但后续扩展、找数据、用 AI 都不顺手。
**open-xquant**[github.com/xingwudao/open-xquant](https://github.com/xingwudao/open-xquant))就是为此存在的:
- **沿用 quantstrat 的体系、理念、流程**——尤其是 Peterson 那份 strat_dev_process.pdf 的方法论。从顶层架构(Strategy / Signal / Rule / Position)到验证流程,都参照 quantstrat 的设计思路
- **用 Python 独立实现**——融入 pandas / NumPy 等数据科学栈,AI 能直接对话
- **Agent-First 设计**——你在 spec 里描述“做等权信号”、“做参数扫描”,AI 就知道调用哪个模块;框架约束 AI “怎么做”,**让结果稳定可复现**
> open-xquant 是 quantstrat 的 Python **致敬实现**——继承的是体系、理念、流程,而非代码(不是逐行 R → Python 翻译)。Brian Peterson 等人的工作以 GPL-3 开源,本书谨此致谢。
### 顺带解决:AI 编程的“不可复现”
前言里说过,AI 编程能力的跃迁是这本书能写出来的关键基础。但 AI 编程用在量化交易上有个绕不过去的问题:**不可复现**。
AI 大模型的底层原理是概率采样。同一份 spec,AI 今天写出来的代码和明天写出来的,细节上可能不一样。写网页、画图表无所谓;量化交易不行——同一个策略跑两遍结果不一样,你根本无法判断它到底有没有效。**不可复现 = 不可信 = 不可交易。**
open-xquant 的 Agent-First 接口把“框架做什么”约束得很死——AI 拼接的是积木,不是从零写代码。这样既享受 AI 编程的便利,又保证两次跑出来的结果一样。
> 你已经在「开始动手之前」的环境配置脚本里装过 open-xquant 了——如果当时漏装,现在补装:
>
> `pip install open-xquant[yfinance,akshare]`
>
> 把这行命令复制给 AI 编程工具,让它在终端里执行。遇到网络报错,把错误信息贴给 AI。
你不需要记住这些步骤背后的命令和代码。AI 会根据框架说明执行这套流程。本章接下来的三个实验,都会用同一个套路:先提出一个直觉假设,再把它写进 spec,让 AI 调用 open-xquant 跑出结果,最后由你来判断结果说明了什么
---
## 2.3 先拿几只股票试试
## 2.3 为什么先用 ETF
第一章用的是沪深300ETF,但你日常听到的投资故事,主角往往是个股——贵州茅台涨了多少倍、中国平安跌了多少、招商银行分红有多高。这些家喻户晓的大公司,能不能直接拿来跑第一章学的均线策略?
我们在第 1 章用的是沪深 300ETF。刚刚提到,本书建议新手先用 ETF 学习量化策略,但你日常听到的投资故事,主角往往是个股贵州茅台、中国平安、招商银行这些家喻户晓的大公司。也许你对这个建议还有疑虑。那就把第 1 章的流程换到几只个股上,看看结果会怎样。
直觉告诉你:这些都是 A 股里最优秀的公司,策略在它们身上应该也能跑出不错的结果。真的吗?我们来试试——先猜后验。
直觉告诉你:这些都是 A 股里最优秀的公司,策略在它们身上应该也能跑出不错的结果。我们来试试,看看这个直觉能不能经得起数据检验。
### 动手实验 1:个股 vs ETF 信号质量对比
我们一起把这份 spec 写出来。这次重点看两件新东西**怎么 AI 一个能查到接口的入口**(让它有据可依地调框架),以及**怎么对照实验的唯一变量”锁死**。
我们一起把这份 spec 写出来。这次重点看两件**怎么 AI 调用稳定工具**,以及**怎么控制对照实验的唯一变量**。
(下文 spec 里把 open-xquant 简写为 oxq——和 numpy 简写为 np 一样的意思。)
#### 第一、二段:上下文和任务描述
#### 起草上下文 + 任务
> **上下文**:本章探讨“该选哪些标的”。第一步——先拿大家熟悉的几只个股试一遍第一章学的均线策略,跟沪深300ETF 摆在一起看效果。
> **上下文**:本章探讨“该选哪些投资对象”。第一步,先拿大家熟悉的几只个股试一遍第 1 章学的均线策略,跟沪深 300ETF 摆在一起看效果。
>
> **任务**:在 notebook `q2-what-to-buy.ipynb` 中新建代码单元格,对 5 只 A 股个股 + 沪深300ETF 跑均线策略,让你直观看到个股信号的嘈杂。
> **任务描述**:在 notebook `q2-what-to-buy.ipynb` 中新建代码单元格,对 5 只 A 股个股 + 沪深 300ETF 跑均线策略,让你直观看到个股信号的嘈杂。
#### 起草要求:给 AI 接口入口 + 锁死唯一变量
#### 第三段:任务要求
要求段是这份 spec 的关键。这是 open-xquant 框架第一次出场,要求段开头给 AI 一个能查到接口的入口(教程链接),而不是把框架的每个接口签名抄进 spec
要求段是这份 spec 的关键。这里不需要把每个函数怎么调用都写进正文,只要把实验目标、数据范围、策略规则和输出要求写清楚。具体接口让 AI 去参考 open-xquant 的示例
> **要求**
> **任务要求**
>
> 1. 数据:沪深300ETF`510300.SS`+ 5 只 A 股(`600519.SS` 茅台 / `000858.SZ` 五粮液 / `601318.SS` 中国平安 / `000001.SZ` 平安银行 / `600036.SS` 招商银行),时间窗 `2023-01-01` 到 `2026-01-01` 共 3 年
> 2. 6 个标的跑同一个策略:均线交叉信号(SMA(1) 上穿 SMA(20))、等权入场、信号反转出场——每个标的都是独立的单资产投资宇宙。具体 oxq 模块(`Engine.run` / `Crossover` / `EqualWeightOptimizer` / `ExitRule`)请参考 [open-xquant 教程](https://github.com/xingwudao/open-xquant/tree/main/examples/tutorials)
> 3. 取每个标的的累计收益率和年化波动率
> 4. 两张横向柱状图(figsize 10×8):上图收益率(沪深300蓝色高亮其余按正负绿/红),下图年化波动率(沪深300蓝色、个股灰色)
> 1. 数据:沪深 300ETF`510300.SS`+ 5 只 A 股(`600519.SS` 茅台 / `000858.SZ` 五粮液 / `601318.SS` 中国平安 / `000001.SZ` 平安银行 / `600036.SS` 招商银行),时间窗 `2023-01-01` 到 `2026-01-01` 共 3 年
> 2. 6 个投资对象分别跑同一个策略:均线交叉信号(SMA(1) 上穿 SMA(20))、等权入场、信号反转出场。每次只放一个投资对象,保证公平对比。具体 oxq 模块(`Engine.run` / `Crossover` / `EqualWeightOptimizer` / `ExitRule`)请参考 [open-xquant 示例](https://github.com/xingwudao/open-xquant/tree/main/examples/modules)
> 3. 取每个投资对象的累计收益率和年化波动率
> 4. 两张横向柱状图(figsize 10×8):上图收益率(沪深 300 蓝色高亮其余按正负绿 / 红),下图年化波动率(沪深 300 蓝色、个股灰色)
oxq 里 `EqualWeightOptimizer` 就是把钱平均分给入选的标的`ExitRule` 就是按设定的反转条件平仓——你不需要钻接口细节,AI 会去查教程
oxq 里 `EqualWeightOptimizer` 就是把钱平均分给入选的投资对象`ExitRule` 就是按设定的反转条件卖出持仓。你不需要钻接口细节,AI 会去查示例
> 📌 **要点**:用到框架时,**给 AI 一个能查到接口的入口**(如教程链接),再让它写代码。不要把 `Engine.run(rules=[...], router=..., receiver=...)` 这种接口签名硬抄进 spec——框架升级一次,硬抄的接口就全部失效;给一个入口让 AI 现查现用,经得起框架升级。
> 📌 **要点**:用到工具时,**给 AI 一个能查到示例的入口**,再让它写代码。不要把 `Engine.run(rules=[...], router=..., receiver=...)` 这种接口签名硬抄进 spec。工具升级一次,硬抄的接口就可能失效;给一个入口让 AI 现查现用,经得起升级。
> 📌 **要点**:对照实验只留一个变量。这份 spec 让 6 个标的跑**完全一样**的策略——同样的均线周期、同样的时间窗、同样的入场出场规则——这样最终柱状图上的差异就只能归因于“标的本身”。spec 里“6 个标的、同一个策略”必须写死,任何“看 AI 心情”的余地都不能留。
> 📌 **要点**:对照实验只留一个变量。这份 spec 让 6 个投资对象跑**完全一样**的策略同样的均线周期、同样的时间窗、同样的入场出场规则这样最终柱状图上的差异就只能归因于“投资对象本身”。spec 里“6 个投资对象、同一个策略”必须写死,任何“看 AI 心情”的余地都不能留。
#### 起草结果呈现
#### 第四段:验收标准
> **结果呈现**:每只标的打印 `代码(中文名): 收益率 X.XX% 年化波动率 X.XX%` + 两张横向柱状图 + 三句结论:「5 只个股结果天差地别」「个股波动率 XX%-XX%、ETF 只有 XX%」「组合能对抗波动,ETF 就是一种组合」。
> **验收标准**:每个投资对象打印 `代码(中文名): 收益率 X.XX% 年化波动率 X.XX%` + 两张横向柱状图 + 三句结论:「5 只个股结果天差地别」「个股波动率 XX%-XX%、ETF 只有 XX%」「一篮子资产能分散波动,ETF 就是一篮子资产」。
完整 spec 在 [`specs/spec-01-signal-quality.md`](https://github.com/xingwudao/xquant-learning/blob/main/q2-what-to-buy/specs/spec-01-signal-quality.md)——复制给 AI,弹窗选「允许」。
完整示例 spec 在配套仓库的 [`q2-what-to-buy/specs/spec-01-signal-quality.md`](https://github.com/xingwudao/xquant-learning/blob/main/q2-what-to-buy/specs/spec-01-signal-quality.md),你可以参考。确认自己的 spec 后,把它复制给 AI,弹窗选「允许」。
AI 助手执行完毕后,你的 notebook 里应该出现了一组数字和一张柱状图。
AI 执行完毕后,你的 notebook 里应该出现了一组数字和一张包含上下两部分的柱状图。
open-xquant 的工作方式和你写 spec 很像:你不用描述 AI 该怎么一步步执行,只需要清楚说明你要什么。比如“用 20 日均线作为信号,上穿就买、下穿就卖”——框架替你处理所有执行细节
这就是 2.2 说的“稳定工具”的作用。你不用在 spec 里描述每一行代码怎么写,只需要清楚说明你要比较什么、时间范围是什么、结果怎么验收
这里还出现了一个新指标:**年化波动率(Annualized Volatility** ——价格上下波动的剧烈程度,数字越大说明越颠簸。就像坐车,波动率高就是过山车,波动率低就是高铁。年化是把每天的波动换算成一年的尺度,方便理解。
这里还出现了一个新指标:**年化波动率(Annualized Volatility**。它表示价格上下波动的剧烈程度,数字越大说明越颠簸。就像坐车,波动率高就是过山车,波动率低就是高铁。年化是把每天的波动换算成一年的尺度,方便理解。
**回到实验设计本身**——6 只标的、同一个策略、同样的参数,唯一的变量是标的本身。结果如图 2-1 所示。
回到实验设计本身6 个投资对象、同一个策略、同样的参数,唯一的变量是投资对象本身。结果如图 2-1 所示。
![个股 vs ETF 信号质量对比](images/q2-what-to-buy/01-signal-quality.png)
### 读懂这张图
先看图 2-1 上半部分(收益率):同样的 20 日均线策略,跑在 5 只个股上的结果反差大得超出直觉——从 +34.59% 到 -7.47%,最好和最差之间差了 40 多个百分点。你选中国平安就赚了,选贵州茅台就亏了。同一个策略、同一个时间段,结果完全取决于你碰巧选了哪只股票。
先看图 2-1 上半部分(收益率):同样的 20 日均线策略,跑在 5 只个股上的结果反差大得超出直觉从 +34.59% 到 -7.47%,最好和最差之间差了 40 多个百分点。你选中国平安就赚了,选贵州茅台就亏了。同一个策略、同一个时间段,结果完全取决于你碰巧选了哪只股票。
再看图 2-1 下半部分(年化波动率):个股年化波动率普遍在 16%-21%,而沪深300ETF 只有 13.61%。这不是巧合——沪深300ETF 包含 300 只股票,个股各自的噪音互相抵消,波动被分散掉了。均线策略在这种“干净”的趋势信号上,才能正常发挥作用。
再看图 2-1 下半部分(年化波动率):个股年化波动率普遍在 16%-21%,而沪深 300ETF 只有 13.61%。这不是巧合沪深 300ETF 包含 300 只股票,个股各自的噪音互相抵消,波动被分散掉了。均线策略在这种“干净”的趋势信号上,才能正常发挥作用。
**数据说了算:单只股票波动大、不可预测,但组合能对抗这种波动。** ETF 就是一种组合。第一章我们说过,ETF 是“一篮子股票的打包组合”——当时的理由是“适合新手”。现在你有了更深的理解:组合不只是省事,它能从根本上降低噪音,让策略信号更干净。
**数据说了算:单只股票波动大、不可预测,但一篮子资产能分散这种波动。** 第 1 章我们说过,ETF 是“一篮子股票的打包组合”当时的理由是“适合新手”。现在你有了更深的理解:ETF 不只是省事,它能从根本上降低噪音,让策略信号更干净。
这个发现让问题一下子简化了——从“在 5000 只个股里选哪只”变成了“在几百只 ETF 里选哪只”。范围缩小了一个数量级。几百只 ETF 还是太多,怎么继续缩小范围?
这个发现让问题一下子简化了从“在 5000 只个股里选哪只”变成了“在几百只 ETF 里选哪只”。范围缩小了一个数量级。这就是为什么推荐新手先拿 ETF 学习量化。而几百只 ETF 还是不少,怎么继续缩小范围?
---
## 2.4 几百只 ETF,选哪几只?
用 ETF 没问题了,但全球 ETF 有几百只,覆盖美国、中国、欧洲、日本、新兴市场……总不能全买。怎么缩小范围?
全球 ETF 有几百只,覆盖美国、中国、欧洲、日本、新兴市场……总不能全买。怎么缩小范围?
直觉的思路**经济最强的国家,资本市场应该最有潜力。** 一个国家的 GDP 越高,说明这个国家创造财富的能力越强,企业整体的盈利能力也应该更强——股市自然有更大的上涨空间。**这只是起手的猜测,不是铁律**——日本九十年代后 GDP 全球前列但股市低迷十年就是反例。但作为“圈池子”的起点,它有数据支撑。下一步不是直接相信它,而是用数据验证。先猜后验,拿数据看看。
直觉的假设是**经济体量大的国家,资本市场更值得优先考虑。** 一个国家的 GDP 越高,说明这个国家创造财富的能力越强,企业整体的盈利能力也可能更强,股市自然可能有更大的上涨空间。
这是一个假设,并不是真理。日本九十年代后 GDP 全球前列但股市长期表现低迷,就是反例。所以我们不能直接相信它,需要验证。下一步要把这个假设变成一个可计算指标:看 GDP 排名。也就是我们要用 GDP 总量这个简化宏观因子来验证假设。
### 动手实验 2:用 GDP 数据锁定投资方向
同样的写法套路,这次重点演示两件没碰过的事:**怎么把宏观数据源接进来**,以及**怎么把“先猜后验”写进 spec 内部的打印语句**。
这一步我们做两件事:**怎么把宏观经济数据源接进来**,以及**怎么检验 GDP 总量这个简化宏观因子**。
#### 起草上下文 + 任务
#### 第一、二段:上下文和任务描述
接续 spec-01——你已经知道“个股嘈杂、用 ETF”,这份 spec 解决“几百只 ETF 怎么缩范围”的开放问题:
接续 spec-01这份 spec 解决“几百只 ETF 怎么缩范围”的开放问题:
> **上下文**:在 `q2-what-to-buy.ipynb` 中已有 spec-01 的“个股 vs ETF”实验代码,读者已理解“用 ETF 替代个股”。当前开放问题:**全球 ETF 几百只,怎么缩小范围?**
>
> **任务**:用全球主要经济体的 GDP 数据支撑“锁定中美”的选择,并下载对应的两只 ETF(沪深300ETF + 纳指100ETF)。
> **任务描述**:用全球主要经济体的 GDP 数据支撑“锁定中美”的选择,并下载对应的两只 ETF(沪深 300ETF + 纳指 100ETF)。
#### 起草要求:宏观数据源 + 两步式接口
#### 第三段:任务要求
宏观数据(GDP)和行情数据(ETF 价格)来自不同源、有不同的更新频率,但都包在 oxq 同一种“先 download → 再 read”的两步式接口里:
宏观数据(GDP)和价格数据(ETF 价格)来自不同源、有不同的更新频率,但都包在 oxq 同一种“先 download → 再 read”的两步式接口里:
> **要求**
> **任务要求**
>
> 1. 用世界银行数据下载器拉全球前 10 大经济体 GDP`["USA", "CHN", "DEU", "JPN", "IND", "GBR", "FRA", "ITA", "BRA", "CAN"]`),时间窗 2020-2024 共 5 年(取最新一年使用,前 4 年留作缓冲、防止当年数据延迟发布)
> 2. 读出数据,取最新一年,单位换算为万亿美元
> 3. 横向柱状图(figsize 10×6):从高到低排列,中红美蓝高亮、其余灰色,柱子右侧标具体数值,标题「全球前 10 大经济体 GDP」
> 4. 用 yfinance 数据下载器拉两只 ETF`510300.SS`(沪深300ETF,代表中国核心资产)+ `513100.SS`(纳指100ETF,A 股上市的美国科技龙头),时间窗 `2021-01-01` 到 `2026-01-01`
> 4. 用 yfinance 数据下载器拉两只 ETF`510300.SS`(沪深 300ETF,代表中国核心资产)+ `513100.SS`(纳指 100ETF,A 股上市的美国科技龙头),时间窗 `2021-01-01` 到 `2026-01-01`
> 5. 读出数据,画归一化走势图(figsize 12×6,起点=100,两色实线,图例标中文名)
>
> 具体 oxq 模块(`WorldBankDownloader` / `read_factor` / `YFinanceDownloader` / `LocalMarketDataProvider.get_bars`请参考 [open-xquant 教程](https://github.com/xingwudao/open-xquant/tree/main/examples/tutorials)。
> 具体 oxq 数据读取方式请参考 [open-xquant 示例](https://github.com/xingwudao/open-xquant/tree/main/examples/modules)。
> 📌 **要点**:用一个数据锚点支撑你的选择。这份 spec 把“数据源锚点 → 选择”三段写顺:先取 GDP 数据(来源),再算“中美遥遥领先”(锚点),最后下载对应 ETF(选择)。三段缺一不可,缺锚点段就成了“凭印象选标的”。
> 📌 **要点**:用一个可计算指标支撑你的选择。这份 spec 把“假设指标 → 选择”三段写顺:先提出“经济体量大的市场值得优先考虑”(假设),再取 GDP 数据(指标),最后下载对应 ETF(选择)。三段缺一不可,缺指标段就成了“凭印象选择投资对象”。
> 📌 **要点**spec 内部就要把“先猜后验”的三段闭环打印出来。结果呈现里**预先写好**「最直觉的思路:经济最强的国家,资本市场应该最有潜力」(猜想)→「数据说了算:锁定中国 + 美国」(验证)→「但中美都是股市,同时下跌怎么办?」(开放下一份 spec 的问题)。让 AI 跑完 notebook,**结果会自己讲故事**——不用你回头补叙事
> 📌 **要点**spec 内部就要把三段过程打印出来。验收标准里**预先写好**「最直觉的思路:经济最强的国家,资本市场应该最有潜力」(猜想)→「数据说了算:锁定中国 + 美国」(验证)→「但中美都是股市,同时下跌怎么办?」。让 AI 跑完 notebook。
#### 起草结果呈现
#### 第四段:验收标准
> **结果呈现**GDP 柱状图 + 两条 ETF 归一化曲线 + 三句打印(猜想/验证/开放问题,见上方要点)。
> **验收标准**GDP 柱状图 + 两条 ETF 归一化曲线 + 三句打印(猜想 / 验证 / 开放问题,见上方要点)。
完整 spec 在 [`specs/spec-02-gdp-anchor.md`](https://github.com/xingwudao/xquant-learning/blob/main/q2-what-to-buy/specs/spec-02-gdp-anchor.md)——复制给 AI,弹窗选「允许」。
完整示例 spec 在配套仓库的 [`q2-what-to-buy/specs/spec-02-gdp-anchor.md`](https://github.com/xingwudao/xquant-learning/blob/main/q2-what-to-buy/specs/spec-02-gdp-anchor.md),你可以参考。确认自己的 spec 后,把它复制给 AI,弹窗选「允许」。
AI 助手执行完毕后,你的 notebook 里应该出现了两张图和一组分析文字。
AI 执行完毕后,你的 notebook 里应该出现了两张图和一组分析文字。
这里用到的 GDP(国内生产总值)是一个国家一年内创造的商品和服务的总价值,是衡量经济规模最常用的指标。GDP 告诉我们中美经济最强,接下来就去拿对应的 ETF 数据。`510300.SS` 是沪深300ETF——跟踪沪深两市市值最大的 300 家公司,代表中国核心资产的整体表现。`513100.SS` 是纳指100ETF(即纳斯达克100ETF——在 A 股上市,跟踪纳斯达克100指数,涵盖苹果、微软、英伟达等美国科技成长龙头。两只 ETF 都在 A 股交易,交易日历完全一致
这里用到的 GDP(国内生产总值)是一个国家一年内创造的商品和服务的总价值,是衡量经济规模最常用的指标。GDP 告诉我们中美经济最强,接下来就去拿对应的 ETF 数据。`510300.SS` 是沪深 300ETF跟踪沪深两市市值最大的 300 家公司,代表中国核心资产的整体表现。`513100.SS` 是纳指 100ETF(即纳斯达克 100ETF在 A 股可以买卖,它跟踪纳斯达克 100 指数,涵盖苹果、微软、英伟达等美国科技成长龙头。两只 ETF 都在 A 股交易。
走势对比图中出现了一个新概念:**归一化(Normalization** ——把不同起点的价格都调整到同一个起跑线(100),这样才能公平比较谁涨得多、谁跌得多。两只 ETF 单价不同,直接放在一张图上没法比。归一化就像让两个选手从同一条起跑线出发——起点都是 100,后面谁高谁低,就是谁涨得多谁跌得多。
走势对比图中出现了一个新概念:**归一化(Normalization**。它的意思是,把不同起点的价格都调整到同一个起跑线(100),这样才能公平比较谁涨得多、谁跌得多。两只 ETF 单价不同,直接放在一张图上没法比。归一化就像让两个选手从同一条起跑线出发起点都是 100,后面谁高谁低,就是谁涨得多谁跌得多。
全球前 10 大经济体 GDP 对比如图 2-2 所示,两只 ETF 的归一化走势如图 2-3 所示。
![全球 GDP 排名](images/q2-what-to-buy/02-gdp-ranking.png)
![沪深300ETF vs 纳指100ETF 归一化走势](images/q2-what-to-buy/03-etf-comparison.png)
![沪深 300ETF vs 纳指 100ETF 归一化走势](images/q2-what-to-buy/03-etf-comparison.png)
### 读懂这两张图
先看图 2-2 GDP 柱状图:美国和中国遥遥领先,合计占全球 GDP 的约 40%,远超第三名的德国。第一梯队和第二梯队之间的差距不是一点半点——美国约 29 万亿美元、中国约 18 万亿美元,而德国只有约 4.5 万亿美元。经济体量决定了资本市场的深度和广度,选 ETF 自然要从最大的两个经济体入手。对应的投资标的也很明确:沪深300ETF 代表中国最大的 300 家上市公司,纳指100ETF 代表美国科技成长的龙头企业。**数据说了算:锁定中美。**
先看图 2-2 GDP 柱状图:美国和中国遥遥领先,合计占全球 GDP 的约 40%,远超第三名的德国。第一梯队和第二梯队之间的差距不是一点半点美国约 29 万亿美元、中国约 18 万亿美元,而德国只有约 4.5 万亿美元。经济体量决定了资本市场的深度和广度,选 ETF 自然要从最大的两个经济体入手。对应的投资对象也很明确:沪深 300ETF 代表中国最大的 300 家上市公司,纳指 100ETF 代表美国科技成长的龙头企业。**数据说了算:锁定中美。**
再看图 2-3 归一化走势:两条线的走势并不完全同步——有时候纳斯达克涨而沪深300在调整,有时候沪深300反弹而纳斯达克在横盘。这其实是好事。如果两个市场完全同步涨跌,那买两只和买一只没有本质区别。正是因为它们的节奏不同,当一个市场表现不好的时候,另一个市场可能在涨,组合整体波动就被平滑。这就是“分散”的真正含义——不是随便多买几个,而是买涨跌节奏不同的标的
再看图 2-3 归一化走势:两条线的走势并不完全同步有时候纳斯达克涨而沪深 300 在调整,有时候沪深 300 反弹而纳斯达克在横盘。这其实是好事。如果两个市场完全同步涨跌,那买两只和买一只没有本质区别。正是因为它们的节奏不同,当一个市场表现不好的时候,另一个市场可能在涨,后面放在一起时,整体波动就可能被平滑。这就是筛选标的池时要注意的地方:不是随便多买几个,而是买涨跌节奏不同的投资对象
但仔细想想,你可能会有一个担忧:中美毕竟都是股票市场。如果遇到全球性的恐慌——比如 2022 年的全球加息周期——两个市场有可能一起跌。图 2-3 里其实也能看到这样的时段。那怎么办?
但仔细想想,你可能会有一个担忧:中美毕竟都是股票市场。如果遇到全球性的恐慌比如 2022 年的全球加息周期两个市场有可能一起跌。图 2-3 里其实也能看到这样的时段。那怎么办?
都是股市,同涨同跌并非不可能。我们需要一个“不同步”的东西来保护组合——一个在股市下跌时不一定跟着跌、甚至可能逆势上涨的资产。这样的东西存在吗?
都是股市,同涨同跌并非不可能。我们需要一个“不同步”的东西来分散风险,一个在股市下跌时不一定跟着跌、甚至可能反过来上涨的资产。这样的东西存在吗?
---
## 2.5 加什么能保护组合
## 2.5 中美一起跌怎么办
我们要找的是一个在股市下跌时不一定跟着跌的东西。债券、现金都是候选,但还有一个更经典的选项——每次股市大跌的新闻里,会提到“资金涌入黄金避险”。黄金常被称为“避险资产”,意思是当股市恐慌的时候,投资者倾向于把钱从股市撤出,转到黄金这种“硬通货”上,推高金价。
既然中美两大经济体的指数可能同时下跌,那么我们要找的是一个在股市下跌时不一定跟着跌的东西。债券、现金都是候选,但还有一个更常见的选项:黄金。每次股市大跌的新闻里,会提到“资金涌入黄金避险”。这句话的意思是,当很多人担心股市继续下跌时,会把一部分钱从股市撤出,转到黄金上,推高金价。
这个说法听起来有道理,但“听起来有道理”不是我们做决策的依据。先猜后验——我们来用数据验证:黄金和股市的涨跌到底有没有关系?如果真的关系不大,加入黄金能给组合带来多大的保护效果
这个说法听起来有道理,但“听起来有道理”不是我们做决策的依据。我们也得用数据验证:黄金和股市的涨跌到底有没有关系?如果真的关系不大,加入黄金能把整体风险降多少
### 动手实验 3:相关性分析与投资宇宙构建
### 动手实验 3:相关性分析与标的池构建
最后一份 spec,难度上一个台阶——重点是**金融指标的计算路径要在 spec 里点名**,以及**怎么用 oxq 的 `StaticUniverse`“投资宇宙”这个抽象概念封装成可调用对象**。
最后一份 spec 更难一些。重点是两件事:**写到金融指标时,要在 spec 里写清楚怎么算**;**用 oxq 的 `StaticUniverse`本章选出的标的池保存成后续代码能直接调用对象**。
#### 起草上下文 + 任务
#### 第一、二段:上下文和任务描述
接续 spec-02 留下的开放问题——中美都是股市,需要找一个不同步的资产来保护组合:
接续 spec-02 留下的开放问题中美都是股市,需要找一个不同步的资产来分散风险。
> **上下文**:在 `q2-what-to-buy.ipynb` 中已有沪深300ETF + 纳指100ETF 数据,读者已通过 GDP 锁定中美。当前开放问题:**中美都是股市,可能同涨同跌,加什么能保护组合**
> **上下文**:在 `q2-what-to-buy.ipynb` 中已有沪深 300ETF + 纳指 100ETF 数据,读者已通过 GDP 锁定中美。当前开放问题:**中美都是股市,可能同涨同跌,加什么能分散风险**
>
> **任务**:引入黄金ETF 作为对冲资产,用相关性矩阵验证三资产的分散效果,最后用 `StaticUniverse` 把整章的“投资宇宙”概念封装成可调用对象。
> **任务描述**:引入黄金 ETF 作为分散风险的资产,用相关性矩阵验证三资产的分散效果,最后用 `StaticUniverse` 把本章选出的标的池保存成后续代码能调用对象。
#### 起草要求:金融指标显式 + 概念落地
#### 第三段:任务要求
要求段有两件难事:① 金融指标(日收益率 / 累计收益 / 年化波动率 / 最大回撤)对 0 金融基础读者是黑箱,必须在 spec 里点名计算路径;② `StaticUniverse` q3-q9 反复用到的核心对象,第一次出场要让读者看清“它是个什么东西”
要求段有两件难事:① 金融指标(日收益率 / 累计收益 / 年化波动率 / 最大回撤)对金融基础读者是黑箱,必须在 spec 里点名计算路径;② `StaticUniverse`第 3 章到第 9 反复用到的核心对象,它保存的是投资候选名单
> **要求**
> **任务要求**
>
> 1. 用 yfinance 数据下载器拉黄金ETF`518880.SS`),时间窗 `2021-01-01` 到 `2026-01-01`,与前两只 ETF 对齐
> 2. 读出三只 ETF 的收盘价,合并成一个 DataFrame,列名改中文(沪深300 / 纳指100 / 黄金)
> 1. 用 yfinance 数据下载器拉黄金 ETF`518880.SS`),时间窗 `2021-01-01` 到 `2026-01-01`,与前两只 ETF 对齐
> 2. 读出三只 ETF 的收盘价,合并成一个 DataFrame,列名改中文(沪深 300 / 纳指 100 / 黄金)
> 3. 计算相关性矩阵:先 `pct_change()` 算日收益率,再 `.corr()` 出 3×3 矩阵
> 4. 相关性热力图(figsize 8×6imshow + coolwarm,每格标 2 位小数,标题「三资产日收益率相关性」)
> 5. 组合 vs 单押对比——按下方“三个金融指标的计算路径”给出的公式实现累计收益率、年化波动率、最大回撤
> 6. 累计收益率对比图(figsize 12×6,单押沪深300 灰色虚线 + 三资产等权 绿色实线,图例标方案名)
> 7. 用 `StaticUniverse(symbols=[...], name="global-macro-etf")` 构建投资宇宙,调用 `get_universe(as_of_date="2026-01-01")` 取出快照
> 5. 三只 ETF 平均持有 vs 只买沪深 300 对比:按下方三条公式实现累计收益率、年化波动率、最大回撤
> 6. 累计收益率对比图(figsize 12×6,只买沪深 300 灰色虚线 + 三资产等权 绿色实线,图例标方案名)
> 7. 用 `StaticUniverse(tuple([...]))` 构建标的池,调用 `get_universe(as_of_date="2026-01-01")` 取出快照
>
> 具体 oxq 模块(`YFinanceDownloader` / `LocalMarketDataProvider.get_bars` / `StaticUniverse` 的 `get_universe(as_of_date=...)` 返回结构)请参考 [open-xquant 教程](https://github.com/xingwudao/open-xquant/tree/main/examples/tutorials)。
> 具体 oxq 模块(`YFinanceDownloader` / `LocalMarketDataProvider.get_bars` / `StaticUniverse` 的 `get_universe(as_of_date=...)` 返回结构)请参考 [open-xquant 示例](https://github.com/xingwudao/open-xquant/tree/main/examples/modules)。
要求 5 用到的三个金融指标,计算路径都要在 spec 里点名——别让 AI 凭感觉写。三条公式如下:
要求 5 用到的三个金融指标,计算公式都要在 spec 里点名别让 AI 凭感觉写。三条公式如下:
- 累计收益率:`(1 + 日收益率).prod() - 1`
- 年化波动率:`日收益率.std() * sqrt(252)`252 是年交易日数)
- 最大回撤:累计净值 / 累计净值的历史最大值 - 1,取最小值
> 📌 **要点**:spec 里写到金融指标,要给出计算路径——要么写出公式(如 `日收益率.std() * sqrt(252)`),要么明确指向 oxq 现成函数。“计算最大回撤”这种自然语言反例,AI 写出的代码 5 个版本可能 5 个公式,对不上。0 金融基础时,公式比“术语 + 凭感觉”靠谱十倍
> 📌 **要点**:spec 里写到金融指标,要么写出公式(如 `日收益率.std() * sqrt(252)`自定义指标,要么明确指向 oxq 现成的指标。“计算最大回撤”这种说法不够准确,反复运行的话,AI 可能写出几个完全不同的公式,前后对不上,不同策略可能也不同,导致无法互为基准,对比效果
> 📌 **要点**:章末 spec 把整章概念封装成可调用对象。`StaticUniverse(name="global-macro-etf")` 把“投资宇宙”从概念变成可命名、可快照、传给下游 spec 的研究对象——q3-q9 的每一份策略 spec 都要从一个 universe 起手。第一次出场就给它一个语义明确的 name,不要写 `name="u1"` 这种凑数的名字
> 📌 **要点**:章末 spec 把整章概念变成后续代码能调用对象。`StaticUniverse(tuple([...]))` 把“标的池”从一个概念变成一个能取快照、传给后续 spec 的研究对象。第 3 章到第 9 的每一份策略 spec都要从一个 `universe` 对象入手
#### 起草结果呈现
#### 第四段:验收标准
> **结果呈现**:相关性热力图 + 两句相关性解读(「沪深300 与 纳指100:X.XX」「沪深300 与 黄金:X.XX」)+ 三指标对比表(用 `pd.DataFrame.to_string` 打印,避免 markdown 表格漂移)+ 累计收益率对比图 + 一句结论「数据说了算:分散风险的关键不是多买几个,而是买涨跌不同步的」+ 投资宇宙构建结果 `「投资宇宙构建完成:{snapshot.symbols}」`。
> **验收标准**:相关性热力图 + 两句相关性解读(「沪深 300 与 纳指 100X.XX」「沪深 300 与 黄金:X.XX」)+ 三指标对比表(用 `pd.DataFrame.to_string` 打印,避免 markdown 表格漂移)+ 累计收益率对比图 + 一句结论「数据说了算:分散风险的关键不是多买几个,而是买涨跌不同步的」+ 标的池构建结果 `「标的池构建完成:{snapshot.symbols}」`。
完整 spec 在 [`specs/spec-03-correlation.md`](https://github.com/xingwudao/xquant-learning/blob/main/q2-what-to-buy/specs/spec-03-correlation.md)——复制给 AI,弹窗选「允许」。
完整示例 spec 在配套仓库的 [`q2-what-to-buy/specs/spec-03-correlation.md`](https://github.com/xingwudao/xquant-learning/blob/main/q2-what-to-buy/specs/spec-03-correlation.md),你可以参考。确认自己的 spec 后,把它复制给 AI,弹窗选「允许」。
AI 助手执行完毕后,你的 notebook 里应该出现了两张图、一组相关性数字和一个对比表格。
`518880.SS` 是黄金ETF,在 A 股上市,买它就等于间接持有黄金。不需要去金店买金条,通过黄金ETF 就能参与黄金市场。三只 ETF 都在 A 股交易,交易日历完全一致。
AI 执行完毕后,你的 notebook 里应该出现了两张图、一组相关性数字和一个对比表格。`518880.SS` 是黄金 ETF,在 A 股上市,买它就等于间接持有黄金。不需要去金店买金条,通过黄金 ETF 就能参与黄金市场。
这个实验引入了两个重要的新概念。
**日收益率(Daily Return** ——今天的价格比昨天涨了还是跌了,涨跌幅度是多少。比如今天收盘价 105,昨天 100,日收益率就是 +5%。为什么要先算日收益率?因为我们关心的不是“今天价格是多少”,而是“今天比昨天变了多少”。价格本身没有可比性——三只 ETF 单价各不相同——但日收益率把它们拉到了同一个尺度上。
**日收益率(Daily Return**今天的价格比昨天涨了还是跌了,涨跌幅度是多少。比如今天收盘价 105,昨天 100,日收益率就是 +5%。为什么要先算日收益率?因为我们关心的不是“今天价格是多少”,而是“今天比昨天变了多少”。三只 ETF 单价各不相同,价格本身没有可比性;日收益率把它们拉到了同一个尺度上。除了这种简单日收益率,还有对数日收益率,实际上用得更多一些,暂时可以不管。
**相关性(Correlation** ——两个东西的涨跌有多“同步”。数值范围从 -1 到 1:1 表示完全同步涨跌,0 表示毫无关系,-1 表示完全反着来。相关性就像两个人走路的默契度——1 是齐步走,0 是各走各的,-1 是你往左他就往右。我们需要的是接近 0 的——各走各的,这样一个跌的时候另一个不一定跟着跌。
**相关性(Correlation**两个东西的涨跌有多“同步”。数值范围从 -1 到 1:1 表示完全同步涨跌,0 表示毫无关系,-1 表示完全反着来。相关性就像两个人走路的默契度1 是齐步走,0 是各走各的,-1 是你往左他就往右。我们需要的是接近 0 的状态:各走各的,这样一个跌的时候另一个不一定跟着跌。
热力图用颜色深浅表示相关程度——颜色越红越“同步”,越蓝越“反着来”,白色或浅色代表“关系不大”。对角线上全是 1.00——这是自己跟自己比,当然完全同步。
热力图用颜色深浅表示相关程度颜色越红越“同步”,越蓝越“反着来”,白色或浅色代表“关系不大”。对角线上全是 1.00,因为这是自己跟自己比,当然完全同步。
实验还设计了两种方案来对比:单押方案(100% 全部买沪深300)和等权组合(沪深300、纳指100、黄金各买 1/3。除了年化波动率,这里还引入了一个新指标:
实验还设计了两种方案来对比:一种是只买沪深 300,也就是 100% 全部买沪深 300;另一种是三只 ETF 平均持有,也就是沪深 300、纳指 100、黄金各买 1/3。三只资产各占同样比例,除了年化波动率,这里还引入了一个新指标:
**最大回撤(Max Drawdown** ——从最高点到最低点跌了多少,衡量“最惨的时候有多惨”。比如你的账户从 100 万涨到 120 万,又跌回 90 万,最大回撤就是 (120-90)/120 = 25%。这个指标直接关系到你晚上能不能睡好觉——回撤越大,心理压力越大。
**最大回撤(Max Drawdown**从最高点到最低点跌了多少,衡量“最惨的时候有多惨”。比如你的账户从 100 万涨到 120 万,又跌回 90 万,跌幅就是 (120-90)/120 = 25%,用收益率表示就是 -25%。这个指标直接关系到你晚上能不能睡好觉回撤越大,心理压力越大。
三资产之间的相关性热力图如图 2-4 所示。
![三资产相关性热力图](images/q2-what-to-buy/04-correlation-heatmap.png)
单押与等权组合的核心指标对比如表 2-3 所示。
只买沪深 300 与等权组合的核心指标对比如表 2-3 所示。
**表 2-3 单押沪深300 vs 三资产等权组合的指标对比**
**表 2-3 只买沪深 300 vs 三资产等权组合的指标对比**
| 指标 | 单押沪深300 | 三资产等权组合 |
| 指标 | 只买沪深 300 | 三资产等权组合 |
|------|------------|---------------|
| 累计收益率 | -1.78% | 89.50% |
| 年化波动率 | 18.11% | 12.36% |
@@ -309,138 +287,101 @@ AI 助手执行完毕后,你的 notebook 里应该出现了两张图、一组
累计收益率走势如图 2-5 所示。
![单押 vs 等权组合累计收益率](images/q2-what-to-buy/05-portfolio-comparison.png)
![只买沪深 300 vs 等权组合累计收益率](images/q2-what-to-buy/05-portfolio-comparison.png)
### 读懂这些结果
先看图 2-4 热力图和相关性数字。沪深300与纳指100的相关性是 0.26——有一定联动,但远不是同步涨跌。黄金与两个股市的相关性分别只有 0.09 和 0.05,几乎接近 0,说明黄金与股市基本没有联动——股市涨的时候黄金不一定涨,股市跌的时候黄金也不一定跌。“避险资产”这个说法,数据给出了支持。
先看图 2-4 热力图和相关性数字。沪深 300 与纳指 100 的相关性是 0.26有一定联动,但远不是同步涨跌。黄金与两个股市的相关性分别只有 0.09 和 0.05,几乎接近 0,说明黄金与股市基本没有联动股市涨的时候黄金不一定涨,股市跌的时候黄金也不一定跌。“黄金可以分散股市风险”这个说法,数据给出了支持。
再看表 2-3 对比。这里的重点不是“等权组合赚了 89.50% 而单押只有 -1.78%”——虽然收益差距确实很大,但不同时间段跑可能结果不同。真正值得关注的是另外两个指标:
再看表 2-3 对比。这里的重点不是“等权组合赚了 89.50% 而只买沪深 300 只有 -1.78%”虽然收益差距确实很大,但不同时间段跑可能结果不同。真正值得关注的是另外两个指标:
- **波动率从 18.11% 降到 12.36%**。颠簸程度降了三分之一。原来坐的是过山车,现在换成了大巴车——同样是在路上走,但体验完全不同。
- **最大回撤从 -42.16% 降到 -16.49%**。最惨的时候,单押方案从最高点跌了 42%——你的 100 万变成了 58 万。而三资产组合最多只跌了 16%——100 万变成 84 万。回撤缩小了六成,晚上能多睡几个小时。
- **波动率从 18.11% 降到 12.36%**。颠簸程度降了三分之一。原来坐的是过山车,现在换成了大巴车同样是在路上走,但体验完全不同。
- **最大回撤从 -42.16% 降到 -16.49%**。最惨的时候,只买沪深 300 的方案从最高点跌了 42%你的 100 万变成了 58 万。而三资产组合最多只跌了 16%100 万变成 84 万。回撤缩小了六成,晚上能多睡几个小时。
图 2-5 的累计收益率对比更直观:灰色虚线(单押沪深300)大起大落,绿色实线(三资产等权组合)走得更稳、更平滑。不是没有波动,但波动的幅度小了很多。
图 2-5 的累计收益率对比更直观:灰色虚线(只买沪深 300)大起大落,绿色实线(三资产等权组合)走得更稳、更平滑。不是没有波动,但波动的幅度小了很多。
**数据说了算:分散风险的关键不是多买几个,而是买涨跌不同步的。**
**数据说了算:分散风险的关键不是多买几个,而是买涨跌不同步的资产**
回顾一下我们走过的三步:个股噪音大,换成 ETF(组合对抗波动);ETF 太多,用 GDP 锚定中美;中美可能同跌,加入低相关的黄金。每一步都是“先猜后验”——先有直觉判断,再用数据验证。三步走完,三只 ETF 选定:**沪深300ETF、纳指100ETF、黄金ETF**。
三步走下来,标的选好了。但别急——回头看看,你刚才到底做了什么?从几千只标的一路筛选到 3 只 ETF,这个过程本身,其实已经是量化交易中一个重要的专业步骤。
回顾一下我们走过的三步:个股噪音大,换成 ETF;ETF 太多,用 GDP 总量这个简化宏观因子锚定中美;中美可能同跌,再用相关性检查黄金是否能分散风险。每一步都是先有直觉假设,再用可计算指标验证。三步走完,三只 ETF 选定:**沪深 300ETF、纳指 100ETF、黄金 ETF**。
---
## 2.6 回头看:你刚才做了什么?
三步筛选下来,你从全市场几千只标的中选出了 3 只 ETF。这个“策略考虑的标的范围”,在量化交易中一个专门的名字:**投资宇宙(Universe)** ——你的策略只在这个范围内寻找机会,范围之外的标的一概不看
三步下来,从几千个投资对象一路筛选到 3 只 ETF。这个过程本身,就是量化交易中一个重要步骤:筛选(Screening)。等你理解了因子的基本方法,还可以继续深入研究筛选;本书面向新手,这里先讲到够用为止
构建投资宇宙不是偷懒,恰恰相反,它让后续每一步都更有效:
这 3 只 ETF 还不是最终持仓方案,只是后续策略允许考虑的候选名单。这个候选名单,就是本章一开始说的**标的池(Universe**。
- **降低噪声**:2.3 的实验验证过,把个股排除后,信号更干净。低质量的标的越早剔除,后面的分析越不容易被干扰。
- **缩小范围**:从几百只 ETF 缩到 3 只,后续做策略研究时计算量可控,每一个标的都值得你认真对待。
- **风控前置**:选标的本身就是第一道风控——流动性差的、数据质量低的、跟你的策略逻辑不相关的,在这一步就被挡在门外了。不用等到回测时才发现“这个标的根本不该出现在我的组合里”。
你刚才完成的是一个简化版流程:
你可能没有意识到,但你刚才做的事情,和华尔街的量化基金经理做的第一步是一样的——先定义投资宇宙,再谈策略
1. **提出假设**:个股噪音大,ETF 更适合新手;经济体量大的市场更值得优先考虑;涨跌不同步的资产更能分散风险
2. **转成指标**:用波动率看信号是否嘈杂,用 GDP 总量看市场规模,用相关性看资产是否同步涨跌。把假设和现象都转换成明确的可计算指标,是从主观到量化的关键一步。
3. **筛出名单**:最后得到沪深 300ETF、纳指 100ETF、黄金 ETF。
这就是构建标的池的基本思路。不是凭感觉列一个清单,而是先有假设,再找可计算指标,最后留下候选名单。
构建标的池非常重要:
- **降低噪声**:2.3 的实验验证过,把个股排除后,信号更干净。低质量的投资对象越早剔除,后面的分析越不容易被干扰。
- **缩小范围**:从几百只 ETF 缩到 3 只,后续做策略研究时计算量可控,每一个投资对象都值得你认真对待。
- **提前控制风险**:选择投资对象本身就是第一道风险控制。成交不活跃的、数据质量低的、跟你的策略逻辑不相关的,在这一步就被挡在门外了。不用等到回测时才发现“这个投资对象根本不该出现在我的组合里”。
你可能没有意识到,你刚才做的事情,和专业量化研究的第一步是一样的:先定义标的池,再谈策略。还有一点要提前说清:本章实验没有推翻假设,是因为我们为了讲解过程,专门挑了一个经过验证的例子。以后你自己做研究,大部分假设很可能会被数据推翻。
---
## 2.7 本章总结
三只 ETF 选定之后,我们把本章的核心概念、策略进化路径和最重要的收获一起收口。**到这里,你完成了飞轮的“候选层”——主练动作仍是“做”:把“选什么”从拍脑袋变成“用 GDP 和相关性两个具体指标筛”,剩下的飞轮三层从下一章开始接力。**
三只 ETF 选定之后,我们把本章的核心概念、策略进化路径和最重要的收获一起收口。到这里,你完成了四个阶段里的第一阶段:**确定候选**。本章的重点是把“选什么”从拍脑袋变成“提出假设,再用 GDP 和相关性这些可计算指标筛选”。后面几章会继续进入制定规则、执行交易、评估归因。
### 概念速查表
本章涉及的核心概念汇总如表 2-4 所示,方便随时回查。
**表 2-4 第章核心概念速查**
**表 2-4 第 2 章核心概念速查**
| 概念 | 含义 | 类比 |
|------|------|------|
| 指标(Indicator) | 从原始价格数据中算出来的衍生数字 | SMA 就是一种指标——均线 |
| 指标(Indicator) | 从原始价格数据中算出来的衍生数字 | SMA 就是一种指标均线 |
| 因子(Factor) | 描述资产或市场特征的可计算变量,可用于解释风险和收益、预测表现或辅助决策 | GDP 总量可以作为本章的简化宏观因子 |
| 归一化(Normalization) | 把不同起点的价格调整到同一起跑线 | 让两个选手从同一条起跑线出发 |
| 日收益率(Daily Return) | 今天比昨天涨跌了多少 | 每天量体重,看涨了还是跌了 |
| 相关性(Correlation) | 两个东西的涨跌有多同步,-1 到 1 | 两个人走路的默契度 |
| 年化波动率(Annualized Volatility | 价格波动的剧烈程度 | 过山车 vs 高铁 |
| 最大回撤(Max Drawdown) | 从最高点到最低点跌了多少 | 最惨的时候有多惨 |
| 投资宇宙Universe | 策略考虑的标的范围 | 白板上钉的名牌 |
| 标的池Universe | 策略考虑的投资对象范围 | 白板上钉的名牌 |
| 筛选(Screening) | 从全市场筛选出符合假设的投资对象 | 从一堆候选里筛出符合条件的对象 |
### 策略进化路径
三步筛选下来,路径一目了然:
全市场几千只标的 → Step 1:个股噪音大,选 ETF(组合对抗波动)→ Step 2GDP 锚定,锁定中美 → Step 3:低相关性,加入黄金 → 投资宇宙:沪深300ETF、纳指100ETF、黄金ETF
全市场几千个投资对象 → 第 1 步:个股噪音大,选 ETF → 第 2 步GDP 锚定,锁定中美 → 第 3 步:低相关性,加入黄金 → 标的池:沪深 300ETF、纳指 100ETF、黄金 ETF
每一步的决策与教训如表 2-5 所示。
**表 2-5 第章策略进化路径**
**表 2-5 第 2 章策略进化路径**
| 步骤 | 决策 | 教训 |
| 步骤 | 假设 | 筛选标准 |
|------|------|------|
| Step 1 | 个股 → ETF | 组合对抗波动,个股噪音太大 |
| Step 2 | 几百只 ETF → 中美 | 选择要有数据支撑的理由 |
| Step 3 | 中美 → 中美+黄金 | 分散风险靠“不同步”,不靠“数量多” |
| 第 1 步 | ETF 比个股更适合新手策略 | 看收益差异和波动率 |
| 第 2 步 | 经济体量大的市场更值得优先考虑 | 看 GDP 排名 |
| 第 3 步 | 加入不同步资产能分散风险 | 看相关性 |
### 本章最重要的收获
三步筛选本身并不复杂——选 ETF、看 GDP、算相关性,每一步你都能理解。但真正值钱的不是这三步的结论,而是贯穿始终的那个思维习惯:**先猜后验,数据说了算。**
三步筛选本身并不复杂选 ETF、看 GDP、算相关性,每一步你都能理解。但真正值钱的不是这三步的结论,而是贯穿始终的那个思维习惯:**先提出假设,再用数据验证。**
每一步我们都是先有一个直觉猜测,然后用数据去验证。猜对了,数据给你信心;猜错了,数据让你及时转向。这个习惯比任何具体的标的选择都重要——市场会变,标的会换,但“用数据验证直觉”这个方法经得起时间检验。
猜对了,数据给你信心;猜错了,数据让你及时转向。这个习惯比任何具体的投资对象选择都重要市场会变,投资对象会换,但“用数据验证假设”这个方法经得起时间检验。
### 带走的问题
标的选好了,但关键问题还没回答:
投资对象选好了,但关键问题还没回答:
- **沪深300、纳指100、黄金,各买多少?** 等分?还是有更聪明的分法? → 第三章
- **什么时候买入、什么时候卖出?** → 第四章
- **怎么验证这个组合确实有效?** → 第五章
- **沪深 300、纳指 100、黄金,各买多少?** 等分?还是有更合适的分法?第 3 章会解答。
- **什么时候买入、什么时候卖出?** 第 4 章会解答。
- **怎么验证这个组合确实有效?** 第 5 章会解答。
答案都在后面。继续。
> 本章所有代码的可运行版本见 `notebooks/q2-what-to-buy.ipynb`
---
## 拓展阅读
本章正文聚焦“为什么从 ETF 开始 + 怎么构建投资宇宙”。下面三节是动手之前可以省略的视野铺垫。
### 投资市场上还有什么可以买?
ETF 不是唯一的资产类型——它只是新手最该先碰的那个。投资市场里还有这些可以买卖的资产:
- **股票(Stock)** —— 买一家公司的一小块所有权。公司赚钱了你分红,公司涨价了你的股份也值钱了。风险:单个公司可能经营不善甚至倒闭。
- **ETF(交易所交易基金)** —— 一篮子股票的打包组合。比如沪深300ETF(510300)打包了 A 股市值最大的 300 家公司。买一份 ETF = 同时持有 300 家公司,风险分散。
- **债券(Bond)** —— 就是一张“借条”。你买国债 = 借钱给国家,到期还本付息。风险低,收益也相对低。
- **期货 / 期权** —— “衍生品”,自带杠杆,风险极高,可能亏损超过本金。**新手先别碰。**
### 在哪里交易?
**交易所(Exchange** 是个巨大的菜市场——只是买卖的不是菜,而是股票、基金、债券。中国有三个主要交易所:上交所(代码以 6 开头)、深交所(代码以 0 或 3 开头)、北交所(代码以 8 开头)。
但你不需要跑去交易所。现代人都通过**券商** App 交易——券商是你和交易所之间的“中介”,常见的有华泰、中信、招商、东方财富等。开户和入金不急,等到第七章确认策略靠谱了再动。
### 市场上的几种交易方式
我们课程用的是趋势交易(均线、动量)。其实市场上的交易方式不止这一种,常见三类如表 2-6 所示。
**表 2-6 市场上的三种主流交易方式**
| 方式 | 通俗理解 | 时间尺度 |
|------|----------|----------|
| **价值投资** | 找到好公司,便宜时买入,持有数年 | 月线、年线 |
| **趋势交易** | 发现涨的趋势就跟上,反转就撤 | 日线、周线 |
| **统计套利** | 两个相关的东西价格差异,赚差价 | 日内~几天 |
价值投资和趋势交易的核心区别在于**时间尺度**——同一只标的,月线看到的是长期价值,日线看到的是短期趋势。同一个世界,不同的视角。不管哪种方式,**先构建投资宇宙、再做分析**这个原则都一样——投资宇宙是所有策略的起点。
---
## 反馈与读者群
如果你发现本章有错别字、概念不清或实验结果不一致,欢迎
[提交 GitHub Issue](https://github.com/xingwudao/xquant-beginner/issues/new/choose)。
课程、正式书和后续更新见
[xquant.shop/courses](https://xquant.shop/courses)。
添加作者微信 `xquanter` 进入读者群。请备注:`XQuant 读者`
> 本章所有代码的可运行版本见配套仓库的 [`q2-what-to-buy/notebooks/q2-what-to-buy.ipynb`](https://github.com/xingwudao/xquant-learning/blob/main/q2-what-to-buy/notebooks/q2-what-to-buy.ipynb)
+154 -170
View File
@@ -1,96 +1,94 @@
# 第三章:给 ETF 分钱3 种分法实测
# 第 3 章:决定各买多少3 种分法实测
章我们从全市场几千标的中,一步步筛选出了三只 ETF——沪深300、纳指100、黄金,构建了自己的投资宇宙。选标的的问题解决了,但紧接着一个新问题摆在面前:三只 ETF,各买多少?
2 章我们从全市场几千标的中,一步步筛选出了三只 ETF沪深 300、纳指 100、黄金,构建了自己的标的池。选资产的问题解决了,但紧接着一个新问题摆在面前:三只 ETF,各买多少?
这听起来像个分钱小问题——平均分一下不就完了吗?别急着下这个结论。同样三只 ETF、同一段历史,**只是分法不同,累计收益能差 1 倍以上、最大回撤能差三分之一**:等权 80%、风险平价 100%、动量排名 180%;回撤从 -16% 到 -12% 又到 -15%。“买什么决定了你站在哪条赛道上,**“买多少决定了赛道上你能不能跑下去**——这就是为什么一整章都解决这个问题。
这听起来像个分钱小问题平均分一下不就完了吗?别急着下这个结论。同样三只 ETF、同一段历史,只是分法不同,本章这段历史数据里的累计收益确实差出 1 倍以上。买什么决定你参与哪些市场;买多少决定每个市场对账户的影响有多大。这就是为什么一整章都解决这个问题。
### 路线图
我们正在走一条完整的策略构建之路:
本章进入四个阶段里的**制定规则**阶段。本章分三步探索三种分钱方式,每节做 1 次实验,最后你来判断哪种更合理。全章一共要动手做 3 次实验。
**选什么标的(第二章 已完成)→ 每个买多少(第三章 本章)→ 什么时候买卖(第四章)→ 怎么验证有效(第五章)**
**表 3-1 第 3 章路线图**
**这一章你在飞轮的“组合层”,主练“做”——把“各买多少”从凭感觉变成有据可依的规则。** 本章解决“买多少”,分三步探索三种分钱方式,最后你来判断哪种更合理。三节分别对应的方法如表 3-1 所示。
**表 3-1 第三章三节问题与方法对照**
| 节 | 问题 | 方法 |
| 节 | 内容 | 实验 |
|----|------|------|
| 3.1 | 最简单的回答:各买三分之一 | 等权 |
| 3.2 | 波动大的少买点,波动小的多买点 | 风险平价 |
| 3.3 | 涨得好的多买点,跌的别买 | 动量排名 |
(操作流程见前言“怎么使用这本书”。每一节按相同流程:阅读 → 复制 spec 给 AI → 看结果与解读。)
| 3.1 | 最简单的回答:各买三分之一 | 1 |
| 3.2 | 波动大的少买点,波动小的多买点 | 1 |
| 3.3 | 涨得好的多买点,跌的别买 | 1 |
---
## 3.1 最简单的回答:各买三分之一
三只 ETF,各买多少?最自然的回答:平均分,每只买三分之一。不偏不倚,公平合理。毕竟,如果你不确定哪只会涨得更好,平均分至少不会犯大错——对吧?
三只 ETF,各买多少?最自然的回答:平均分,每只买三分之一。不偏不倚,公平合理。毕竟,如果你不确定哪只会涨得更好,平均分至少不会犯大错对吧?
我们来验证一下这个直觉。
### 动手实验 1:等权组合回测
这个实验用到 open-xquant 的几个模块:`EqualWeightOptimizer`等权优化器——把钱平均分)、`Engine`(回测引擎——模拟历史买卖)、`SimBroker`(模拟券商)。你不需要了解它们的代码,spec 会告诉 AI 怎么用。
这个实验用到 open-xquant 的几个模块:`EqualWeightOptimizer`(把钱平均分的组合优化器)、`Engine`(回测引擎模拟历史买卖)、`SimBroker`(模拟券商)。你不需要了解它们的代码,spec 会告诉 AI 怎么用。
我们一起把这份 spec 写出来。这次重点看三件新东西:**oxq 模块的“先读后写”**、**权重和=1.0 的关键断言**、**跨市场组合的共同交易日处理**。
我们一起把这份 spec 写出来。这次重点看三件新东西:**oxq 模块的“先读后写”**、**权重和 = 1.0 的关键断言**、**将多只资产的交易日对齐**。
#### 起草上下文 + 任务
#### 第一、二段:上下文和任务描述
这是 q3 的第一份 spec,直接接续第二章——三只 ETF 已经选好了,现在只关心权重。两段都很短:
这是 q3 的第一份 spec,直接接续第 2 章。三只 ETF 已经选好了,现在只关心权重。上下文说前提,任务描述说要做什么,两段都很短:
> **上下文**:第章选好了 3 只 ETF(沪深300、纳指100、黄金),现在要回答:每只各买多少?
> **上下文**:第 2 章选好了 3 只 ETF(沪深 300、纳指 100、黄金),现在要回答:每只各买多少?
>
> **任务**:在 notebook `q3-how-much.ipynb` 中创建代码,跑等权组合回测,并展示三只 ETF 各自的表现差异。
> **任务描述**:在 notebook `q3-how-much.ipynb` 中创建代码,跑等权组合回测,并展示三只 ETF 各自的表现差异。
#### 起草要求
#### 第三段:任务要求
q3 起 open-xquant 框架正式上场。spec 主体的第一件事不是写代码,而是要求 AI 先读源码——这样它调的是真实接口,不会用过时或编造的写法。
spec 主体的第一件事不是写代码,而是要求 AI 先读源码这样它调的是真实接口,不会用过时或编造的写法。
> **要求**
> **任务要求**
>
> 1. 阅读以下 oxq 模块的源码(按用途分四组):
> - 引擎与策略:`oxq.core.Engine` / `Strategy`
> - 数据:`oxq.data.YFinanceDownloader` / `LocalMarketDataProvider`
> - 仓位优化`oxq.portfolio.optimizers.EqualWeightOptimizer`
> - 权重分配`oxq.portfolio.optimizers.EqualWeightOptimizer`
> - 信号、指标、规则:`oxq.signals.Threshold` / `oxq.indicators.RollingVolatility` / `oxq.rules.RebalanceFrequencyRule` / `oxq.universe.StaticUniverse` / `oxq.trade.SimBroker`
> 2. 下载 3 只 ETF`SYMBOLS = ("510300.SS", "513100.SS", "518880.SS")`,起 `2021-01-01`)。
> 3. 计算共同交易日:中美交易日历不同步,要把三只 ETF 的索引取交集,后续所有图都用这对齐索引
> 4. 用 `EqualWeightOptimizer` + `Threshold` 信号(column=close, threshold=0, relationship=gt)组装策略,再平衡 `RebalanceFrequencyRule(interval_days=10)`。
> 3. 计算共同交易日:把三只 ETF 的索引取交集,后续所有图都用这对齐后的日期
> 4. 用 `EqualWeightOptimizer` + `Threshold` 信号(column="close", threshold=0, relationship="gt")组装策略,再平衡 `RebalanceFrequencyRule(interval_days=10)`。
> 📌 **要点**跨市场组合必须显式处理共同交易日。沪深300 走 A 股日历,纳指100 走美股日历,圣诞节美股休市但 A 股开市,反过来春节 A 股休市但美股开市。**spec 不点名,AI 大概率不会想到这一步**——画出来的净值曲线会是锯齿状。这是 q3 起跨市场章节的领域必备配置块,跟 q1 的字体三选一一样,要写进 spec
> 📌 **要点**多只资产放在一起比较时,先要把日期对齐。即使三只 ETF 都在 A 股市场交易,历史数据也可能出现个别缺失日。spec 要求先取共同交易日,是为了让三条净值曲线站在同一组日期上比较,避免图表因为日期不齐而产生误导
#### 起草结果呈现
#### 第四段:验收标准
结果呈现段把权重检查、对比表、净值曲线一次锁死,让 AI 输出可以直接对照书里的图表。
验收标准段把权重检查、对比表、净值曲线一次锁死,让 AI 输出可以直接对照书里的图表。
> **结果呈现**
> **验收标准**
>
> 1. **权重合法性检查**`weights = ew_weights_df.iloc[-1]; assert abs(weights.sum() - 1.0) < 1e-6, f"权重和应为 1.0,实际 {weights.sum()}"; assert (weights >= 0).all(), "等权权重不应为负"`。
> 2. 打印权重分配 + 10 万元换算(每只约 33,333 元)。
> 3. 打印 4 行对比表:沪深300 / 纳指100 / 黄金 / 等权组合,三列指标(累计收益率、年化波动率、最大回撤)。
> 4. 净值曲线图:3 条灰色线(用 `--` / `:` / `-.` 三种线型区分单只持有)+ 1 条蓝色实线加粗(等权组合),归一化到 100,标题「各买三分之一:等权组合 vs 单只持有」。
> 3. 打印 4 行对比表:沪深 300 / 纳指 100 / 黄金 / 等权组合,三列指标(累计收益率、年化波动率、最大回撤)。
> 4. 净值曲线图:3 条灰色线(用 `--` / `:` / `-.` 三种线型区分单只持有)+ 1 条蓝色实线加粗(等权组合),统一从 100 开始,标题「各买三分之一:等权组合 vs 单只持有」。
> 📌 **要点**仓位分配 spec 的**必查**断言是「权重和 = 1.0 ± 1e-6」。这条 assert 在 q1 单标的策略里不需要只有一个标的,要么买要么不买),但只要进入“分钱”场景,这条就必须有。**没有这条断言,AI 输出 0.998 或 1.003 你都会以为正常**——但下游回测会按这个权重直接乘资金,误差会一路传到最后的收益率。三个 spec 都要复用这条模板。
> 📌 **要点**权重分配 spec 里必须写清一个验收标准:「权重和 = 1.0 ± 1e-6」。这在第 1 章那种单资产策略里不需要只有一个资产时,要么买要么不买但只要进入“分钱”场景,这条就必须有。没有这条断言,AI 输出 0.998 或 1.003 你可能都会以为正常下游回测会按这个权重直接乘资金,误差会一路传到最后的收益率。三个 spec 都要复用这条模板。
完整 spec 在 [`specs/spec-01-equal-weight.md`](https://github.com/xingwudao/xquant-learning/blob/main/q3-how-much/specs/spec-01-equal-weight.md)——复制给 AI,弹窗选「允许」
完整示例 spec 在配套仓库的 [`q3-how-much/specs/spec-01-equal-weight.md`](https://github.com/xingwudao/xquant-learning/blob/main/q3-how-much/specs/spec-01-equal-weight.md)。参考它确认自己的 spec 后,再复制给 AI,按提示允许执行
AI 助手执行完毕后,你的 notebook 里应该出现了一组权重数字、一张对比表和一张净值图。我们先认识两个新概念。
AI 执行完毕后,你的 notebook 里应该出现了一组权重数字、一张对比表和一张净值图。我们先认识两个新概念。
**权重(Weight** ——“这笔钱怎么分”,每只标的分到总资金的百分之多少。你有 10 万元,三只 ETF 各 1/3 权重,就是每只买 33,333 元。权重就像分蛋糕——蛋糕总共 100%,每人分多少取决于权重。
**权重(Weight**:回答“每个标的分总资金的多少比例”。每个标的分到总资金的百分之多少,就是它的权重。你有 10 万元,三只 ETF 各 1/3 权重,就是每只买 33,333 元。
**信号(Signal** ——根据指标对市场做出的判断:要不要动、怎么动。信号只表达意愿,不负责执行。第一章的 Crossover 信号说的是“买还是不买”——价格上穿均线就买、下穿就卖。这里的 EqualWeightOptimizer 说的是“各买多少”——三只 ETF 各 1/3。判断的内容升级了,但本质都是信号:观察市场、做出判断。至于判断之后怎么执行——下多少单、什么价格成交——那是后面的事。
**权重分配方法**:回答“按照什么逻辑分配资金”。等权分配就是一种最简单的权重分配方法:不看涨跌,不看波动,三只 ETF 各 1/3。权重分配方法有很多,后面也可以看到不同的分配方式。至于目标权重之后怎么变成真实订单、什么价格成交,那是第 7 章要处理的事。
回测中设定了每 10 个交易日(大约两周)重新调整一次持仓,让实际权重回到目标权重。为什么需要再平衡?因为三只 ETF 涨跌幅不同,持有一段时间后,涨得多的那只实际占比会变大,偏离了最初的 1/3。再平衡就是定期“拨乱反正”
在量化领域,在一起参与分配资金的一组标的,叫做**组合(Portfolio)**。组合不是简单的候选名单。第 2 章选出的标的池,是策略允许考虑的范围,是候选大名单;第 3 章要做的,是从这个大名单里给每只标的分配资金。按规则算出来的配置资金比例,叫**目标权重(Target Weight**
目标权重只是理想资金比例。账户里真正持有的标的和份额数量,叫**持仓(Position)**;这些持仓按市值算出来的比例,才是**实际权重**。即使一开始完全按目标权重买入,后面标的价格也会涨跌,实际权重会慢慢偏离目标权重。因此,过一段时间就要重新调整持仓,让实际权重重新接近目标权重。这个过程叫**再平衡(Rebalancing**。
标的池里的标的,不是每次都一定能分到钱。有些标的的权重可能是 0,极端情况下,所有标的的权重都可能是 0。这时可以把现金(CASH)也视为组合成员,现金获得 100% 权重,意味着账户里暂时不持有任何标的。
### 实验结果
等权就是等,三只各占三分之一,每只买 33,333 元。但等分钱之后跑出来的效果如何?三只 ETF 单独持有与等权组合的对比如表 3-2 所示。
等权就是强制让资产的权重相等,三只各占三分之一,每只买 33,333 元。这种平均分资金的方式,效果如何?三只 ETF 单独持有与等权组合的对比如表 3-2 所示。
**表 3-2 三只 ETF 单独持有 vs 等权组合表现**
| 标的 | 累计收益率 | 年化波动率 | 最大回撤 |
| ETF | 累计收益率 | 年化波动率 | 最大回撤 |
|------|-----------|-----------|---------|
| 沪深300ETF | -1.78% | 18.11% | -42.16% |
| 纳指100ETF | 106.09% | 22.66% | -27.50% |
@@ -103,87 +101,85 @@ AI 助手执行完毕后,你的 notebook 里应该出现了一组权重数字
### 读懂这些结果
先看图 3-1 的大趋势:等权组合那条蓝色实线,稳稳地走在三条灰色线之间——不是最好的,也不是最差的。黄金一路领涨,纳指100先跌后涨、波折不断,沪深300整体低迷。组合把三者的表现“平均”了一下,走出了一条居中的路线。
先看图 3-1 的大趋势:等权组合那条蓝色实线,走在三条灰色线之间。它不是最好的,也不是最差的。黄金一路领涨,纳指 100 先跌后涨、波折不断,沪深 300 整体低迷。组合把三者的表现“平均”了一下,走出了一条居中的路线。
但你可能已经注意到了:黄金单独拿的累计收益率是 188.54%,比等权组合的 80.75% 高了一倍多。那为什么不全买黄金?
看波动率那列。**年化波动率(Annualized Volatility** 衡量的是价格上下波动的剧烈程度——第二章我们认识过这个指标。波动率大意味着你的账户大起大落:今天涨 3%、明天跌 4%,反复折腾。现实中大部分人受不了这种颠簸——涨的时候开心,连跌几天就开始怀疑“是不是该卖了”,一卖可能就错过后面的反弹。**波动率小,意味着你能在低点不慌、在涨中不慌——账户里看着一时少了 5%、10%,你不会马上卖掉。** 不慌,才能真正拿到那段收益。
看波动率那列。**年化波动率(Annualized Volatility** 衡量的是价格上下波动的剧烈程度。第 2 章我们认识过这个指标。波动率大意味着你的账户大起大落:今天涨 3%、明天跌 4%,反复折腾。现实中大部分人受不了这种颠簸涨的时候开心,连跌几天就开始怀疑“是不是该卖了”,一卖可能就错过后面的反弹。**波动率小,意味着你能在低点不慌、在涨中不慌。** 账户里一时少了 5%、10%,你不会马上卖掉。不慌,才能真正拿到那段收益。
再看一个更关键的问题。三只 ETF 的年化波动率从 15.83%(黄金)到 22.66%(纳指100),差了好几个百分点。但等权信号说“不管你多颠簸,都买一样多的钱”。想想这意味着什么波动最大的纳指100,虽然只花了 1/3 的钱,但它每天的涨跌幅度最大,对组合整体的影响也最大——它一跌,组合就被拖着跌;它一涨,组合的涨幅也主要靠它撑着。**平均分钱,并不等于平均分影响力。** 波动大的那只,用同样的钱产生了更大的影响力,**拉着整个组合的涨跌走**
再看一个更关键的问题。三只 ETF 的年化波动率从 15.83%(黄金)到 22.66%(纳指 100),差了好几个百分点。但等权方法说:“不管你多颠簸,都买一样多的钱”这意味着什么波动最大的纳指 100,虽然只花了 1/3 的钱,但它每天的涨跌幅度最大,对组合整体的影响也最大它一跌,组合就被拖着跌;它一涨,组合的涨幅也主要靠它撑着。**每个标的拿一样多的资金,并不等于它造成的波动也一样。** 波动大的那只,用同样的钱产生了更大的影响力,拉着整个组合的涨跌走。
有没有更聪明的分法?比如,波动大的少买点,波动小的多买点——让每只 ETF 对组合的“影响力”真正平均
有没有更合理的分法?比如,波动大的少买点,波动小的多买点让每只 ETF 对组合的“影响力”更接近
---
## 3.2 波动大的少买点,波动小的多买点
上一节的结论很清楚:平均分钱,并不等于平均分影响力。波动大的纳指100,用同样的钱却产生了更大的影响力,把整个组合的涨跌拉着走。那反过来想——既然波动大的影响力太大,能不能给它少分点钱?波动小的那只影响力不够,多分点钱给它?这样三只 ETF 对组合的“拉扯力度”应该更均匀。
上一节的结论很清楚:平均分钱,并不等于平均分影响力。波动大的纳指 100,用同样的钱却产生了更大的影响力,把整个组合的涨跌拉着走。那反过来想既然波动大的影响力太大,能不能给它少分点钱?波动小的那只影响力不够,能不能多分点钱给它?这样三只 ETF 对组合的“拉扯力度”应该更均匀。
这种“按波动率倒数分配权重、让每个资产对组合波动的贡献相等”的方法,有一个正式的名字:**风险平价(Risk Parity** ——“风险”指的是波动率,“平价”指的是让每个资产的风险贡献相等。让每个资产对组合波动的贡献相等——就像把三个发动机的功率调到一样大,整个组合不会被某一个拖着走。桥水基金的“全天候策略”用的就是这个思路。下面我们用一个生活化的类比直接走进它。
这种“按波动率倒数分配权重”的方法,有一个正式的名字:**风险平价(Risk Parity**。本章用的是简化版风险平价:波动大的少买,波动小的多买,让每只标的对组合的影响更均匀。你可以把它想成把三个发动机的功率调到接近,整个组合就不容易被某一个拖着走。
想象你要把一笔钱交给三个人帮你打理。第一个人成熟稳重,赚了不炫亏了不慌,你看着他的账户心里很踏实;第二个人喜怒无常,今天信心爆棚明天就想清仓,你的心跟着他上上下下;第三个人介于两者之间。你会怎么分钱?肯定是稳重的那个多管点,情绪的那个少管点。三只 ETF 也一样——波动率就是它们的“情绪起伏程度”。波动小的稳重,多买;波动大的情绪化,少买。
想象你要把一笔钱交给三个人帮你打理。第一个人成熟稳重,赚了不炫耀,亏了不慌,你看着他的账户心里很踏实;第二个人情绪起伏大,今天信心很足,明天就想清仓;第三个人介于两者之间。你会怎么分钱?大概率会让稳重的那个多管点,情绪起伏大的那个少管点。三只 ETF 也一样波动率就是它们的“情绪起伏程度”。波动小的稳重,多买;波动大的起伏大,少买。
### 动手实验 2:风险平价组合回测
我们一起把这份 spec 写出来。这次重点看两件新东西:**signal.required_indicators 在信号上挂指标**、**差量式 spec 让差异点单点暴露**。
我们一起把这份 spec 写出来。这次重点看两件新东西:**`signal.required_indicators` 在信号上挂指标**、**公共配置复用,只改差异点**。
#### 起草上下文 + 任务
#### 第一、二段:上下文和任务描述
这是 q3 的第二份 spec,接续 spec-01 的等权回测结果。上下文一句话承接,任务一句话点明:
> **上下文**:在 `q3-how-much.ipynb` 中已有等权组合回测结果。学员已看到三只 ETF 波动率差异很大,等权分配让波动大的 ETF 主导了组合涨跌。当前问题:能不能按波动来分——波动大的少买,波动小的多买?
> **上下文**:在 `q3-how-much.ipynb` 中已有等权组合回测结果。读者已看到三只 ETF 波动率差异很大,等权分配让波动大的 ETF 主导了组合涨跌。当前问题:能不能按波动来分波动大的少买,波动小的多买?
>
> **任务**:在 notebook 中新建代码单元格,用风险平价信号运行回测,对比等权组合。
> **任务描述**:在 notebook 中新建代码单元格,用风险平价权重分配方法运行回测,对比等权组合。
#### 起草要求
#### 第三段:任务要求
风险平价要用波动率,但波动率是怎么传给优化器的?oxq 的做法是**在信号上挂指标依赖**——这是 oxq 框架的关键模式,把指标声明和信号绑在一起,再把公共配置抽出做成对照实验:
风险平价要用波动率,这就要定义波动率指标,并且要使用 oxq 提供的接口告诉风险平价这个权重分配模块,使用这个指标。
> **要求**
> **任务要求**
>
> 1. 阅读 `oxq.portfolio.optimizers.RiskParityOptimizer`(若 spec-01 未读 `Threshold`,补一下)。
> 2. 在 `Threshold` 信号上注册 vol 指标:`signal_rp.required_indicators = {"vol": (RollingVolatility(), {"column": "close", "period": 20})}`。这样回测引擎跑到信号时会自动算 vol 列,优化器再读这一列分配权重。
> 3. 用 `RiskParityOptimizer(volatility_col="vol")` 装配优化器,打印最新一天与等权权重的对比 + 10 万元换算。
> 4. 抽出公共配置 `COMMON = dict(universe=universe, ...)`,让等权和风险平价两个策略只在 `portfolio` 一行不同——`Strategy(name="equal-weight", **COMMON, portfolio=EqualWeightOptimizer())` vs `Strategy(name="risk-parity", **COMMON, portfolio=RiskParityOptimizer(...))`。
> 4. 抽出公共配置 `COMMON = dict(universe=universe, ...)`,让等权和风险平价两个策略只在 `portfolio` 一行不同`Strategy(name="equal-weight", **COMMON, portfolio=EqualWeightOptimizer())` vs `Strategy(name="risk-parity", **COMMON, portfolio=RiskParityOptimizer(...))`。
> 📌 **要点**oxq 用 `signal.required_indicators` 把“信号需要哪些指标”声明在信号本身上——q3 三个 spec 都依赖这条语法。**spec 要给到具体写法**(“在信号上注册”+ 字典字面量),AI 才能照抄;只写描述,它要么抄到 strategy 上,要么挂到 universe 上,接口对不上就报错。同样的精确度还体现在 COMMON 字典的设计上:把公共部分抽出来,让等权与风险平价的唯一差异落在 portfolio 一行——这是仓位分配章三联 spec 的主线设计,**写了 COMMON 就要兑现**,否则这条教学就失效
> 📌 **要点**这里的 `signal.required_indicators` 是 open-xquant 的代码入口,用来告诉回测引擎:先算出 vol 指标,再让风险平价方法读取 vol 来分配权重。读者不需要记接口细节,但 spec 要把“风险平价依赖波动率”这件事写清楚
#### 起草结果呈现
#### 第四段:验收标准
这一段把“权重检查 + 视觉契约”两件事一起锁——分析话术按实际数据走向描述,避免提前替结果下定义
这一段把“权重检查 + 图表要求”两件事一起
> **结果呈现**
> **验收标准**
>
> 1. **权重检查**:复用 spec-01 的 assert 模板,加一条`assert rp_w.nunique() > 1, "风险平价权重不应全相等(否则退化为等权)"`。
> 2. 权重对比表 + 10 万元换算 + 指标对比表。
> 3. 净值曲线对比图(figsize 12×6,等权灰虚 + 风险平价蓝实) + 权重历史堆叠面积图(figsize 12×4,展示三只 ETF 权重随时间变化)。
> 4. 末尾分析话术按数据动态描述方向(若波动率反升说明 vol 估计期偏短,不要硬写“降到”)。
> 4. 末尾分析文字按数据动态描述方向(若波动率反升说明 vol 估计期偏短,不要硬写“降到”)。
> 📌 **要点**仓位分配章的标志可视化是**权重历史堆叠面积图**——它让学员一眼看到“权重不是一成不变的”。等权下三条带等宽,风险平价下面积带宽随时间变化。spec 必须明确画这张图,figsize 12×4,堆叠面积,y 轴 0-1。这是 q3 起会反复出现的视觉模板。
> 📌 **要点**权重分配可视化采用了**权重历史堆叠面积图**。它让人一眼看到“权重不是一成不变的”。等权下三条带等宽,风险平价下面积带宽随时间变化。
完整 spec 在 [`specs/spec-02-risk-parity.md`](https://github.com/xingwudao/xquant-learning/blob/main/q3-how-much/specs/spec-02-risk-parity.md)——复制给 AI,弹窗选「允许」
完整示例 spec 在配套仓库的 [`q3-how-much/specs/spec-02-risk-parity.md`](https://github.com/xingwudao/xquant-learning/blob/main/q3-how-much/specs/spec-02-risk-parity.md)。参考它确认自己的 spec 后,再复制给 AI,按提示允许执行
AI 助手执行完毕后,你的 notebook 里应该出现了新的权重数字、一张对比表、两张图。
AI 执行完毕后,你的 notebook 里应该出现了新的权重数字、一张对比表、两张图。
风险平价信号的用法和 3.1 的等权信号完全一样——只是换了一个信号类,其余不变。但钱真的分得不一样了:等权是三等分,风险平价则是波动小的多分、波动大的少分。同样 10 万元,沪深300ETF 从 33,333 元涨到了 45,510 元,黄金ETF 从 33,333 元降到了 11,239 元。
风险平价法和 3.1 的等权方法用法相似,只是换了 `portfolio` 里的权重分配方法,其余不变。但钱真的分得不一样了:等权是三等分,风险平价则是波动小的多分、波动大的少分。同样 10 万元,沪深 300ETF 从 33,333 元涨到了 45,510 元,黄金 ETF 从 33,333 元降到了 11,239 元。
策略组装和上一节几乎一模一样——整个策略定义只有组合优化器那一行不同,从 `EqualWeightOptimizer()` 换成 `RiskParityOptimizer()`。其余的投资宇宙、信号、指标、再平衡频率、回测引擎全部复用。当框架搭好之后,换一种“分钱方式”就只是换一个优化器
策略组装和上一节几乎一模一样整个策略定义只有 `portfolio` 那一行不同,从 `EqualWeightOptimizer()` 换成 `RiskParityOptimizer()`。其余的全部复用。当框架搭好之后,换一种“分钱方式”就只是换一个权重分配方法
### 实验结果
两种方案的权重对比如表 3-3 所示。
一个细节先点透:表 3-3 的权重按**最近 20 天的滚动波动率**算,与表 3-2 那行**全期年化波动率**不是同一组数字——短期与长期波动可以差很多。
两种方案的权重对比如表 3-3 所示。这里有一个细节:表 3-3 的权重按**最近 20 天的滚动波动率**算,与表 3-2 那行**全期年化波动率**不是同一组数字。这两个指标反映的是短期波动与长期波动。
**表 3-3 等权 vs 风险平价权重对比**
| 标的 | 等权权重 | 风险平价权重 |
| ETF | 等权权重 | 风险平价权重 |
|------|---------|------------|
| 沪深300ETF | 33.3% | 45.5% |
| 纳指100ETF | 33.3% | 43.3% |
| 黄金ETF | 33.3% | 11.2% |
指标层面,两种方案的差异如表 3-4 所示。
指标,两种方案的差异如表 3-4 所示。
**表 3-4 等权 vs 风险平价指标对比**
@@ -201,100 +197,98 @@ AI 助手执行完毕后,你的 notebook 里应该出现了新的权重数字
### 读懂这些结果
先看图 3-2 的净值曲线。蓝色实线(风险平价)走得比灰色虚线(等权)更平滑,而且终点更高——风险平价不仅更稳,还赚得更多。
先看图 3-2 的净值曲线。蓝色实线(风险平价)走得比灰色虚线(等权)更平滑,而且终点更高。这说明,风险平价不仅更稳,还赚得更多。
再看指标表里的数字。波动率从 11.73% 降到 10.81%,最大回撤从 -16.79% 收窄到 -12.26%——最惨的时候少跌了 4.5 个百分点。收益率反而更高了(80.75% → 99.67%——波动小了,收益没丢,甚至更好。这不是巧合:波动小意味着你更容易在低点不慌,不会在大跌时恐慌卖出,反而能享受到后面的反弹
再看指标表里的数字。波动率从 11.73% 降到 10.81%,最大回撤从 -16.79% 收窄到 -12.26%最惨的时候少跌了 4.5 个百分点。收益率反而更高了(80.75% → 99.67%。在这段数据里,按波动调整不仅降低了波动,也保住了收益。对真实投资者来说,波动小还有一个好处:更容易坐得住,因此也许更能享受到后面的涨势
最后看图 3-3 的权重历史。你会发现一个有意思的现象:三只 ETF 的权重不是固定的,而是随时间在动态调整。某段时间黄金ETF 波动率飙升,它分到的钱就自动减少;某段时间沪深300ETF 变得平稳了,它分到的钱就自动增加。这就是节首讲的“按波动率倒数分配”在跑——市场自己在告诉你谁该多买谁该少买,你不用做任何主观判断。
最后看图 3-3 的权重历史。你会发现一个有意思的现象:三只 ETF 的权重不是固定的,而是随时间在动态调整。某段时间黄金 ETF 波动率飙升,它分到的钱就自动减少;某段时间沪深 300ETF 变得平稳了,它分到的钱就自动增加。这就是节首讲的“按波动率倒数分配”在跑。市场数据在告诉你谁该多买谁该少买,你不用做主观判断。
风险平价比等权更稳了。但你有没有注意到,它只看波动率,完全不看涨跌。如果一只 ETF 正在涨,风险平价不会多买它;如果一只正在跌,也不会少买它。能不能更贪心一点——让涨得好的多买点?
风险平价比等权更稳了。但你有没有注意到,它只看波动率,完全不看涨跌。如果一只 ETF 正在快速上涨,风险平价不会多买它;如果一只正在跌,也不会少买它。能不能更强调上涨趋势,让涨得好的多买点?
---
## 3.3 涨得好的多买点,跌的别买
风险平价让组合更稳了,但回头想想,它的判断标准只有一个——波动率。波动大的少买,波动小的多买,仅此而已。至于一只 ETF 是在涨还是在跌?它完全不关心。黄金如果正在涨,风险平价不会因此多买一分钱;沪深300如果正在跌,它也不会少买一分钱。它只看颠簸程度,不看方向。
风险平价让组合更稳了,但回头想想,它的判断标准只有一个波动率。波动大的少买,波动小的多买,仅此而已。至于一只 ETF 是在涨还是在跌?它完全不关心。黄金如果正在快速上涨,风险平价不会因此多买一分钱;沪深 300 如果正在明显下跌,它也不会少买一分钱。它只看颠簸程度,不看方向。
那能不能更进一点:涨得好的多买,跌的干脆不买?
还记得第二章我们提过几种交易方式吗?其中一种叫趋势交易——跟着涨势走。动量排名就是趋势交易思想的量化表达:过去一段时间涨得猛的,我认为它还会继续涨,多买;跌的,我认为它还会继续跌,不买。简单粗暴,但逻辑清晰。
那能不能更进一点:涨得好的多买,跌的干脆不买?我们可以借鉴一种交易思想:趋势交易。顾名思义,也就是跟着涨势走。动量排名就是趋势交易思想的量化表达:过去一段时间涨得好的,我认为它可能继续上涨,多买;过去一段时间下跌的,我认为它可能继续走弱,不买。
### 动手实验 3:动量排名组合回测
我们一起把这份 spec 写出来。这次重点看两件新东西:**多指标依赖链mom + vol → ram**、**排序型 spec 的过滤后归一化**。
我们一起把这份 spec 写出来。这次重点看两件新东西:**多指标依赖链**、**过滤后重新把权重分到 100%**。
#### 起草上下文 + 任务
#### 第一、二段:上下文和任务描述
这是 q3 三联 spec 的最后一份,从“加权(永远全持有)”跃迁到“排序(动量为负的不买”。上下文 + 任务一气呵成:
这是 q3 三联 spec 的最后一份,从“始终持有全部 ETF”走到“动量为负的不买”。上下文 + 任务一气呵成:
> **上下文**:在 `q3-how-much.ipynb` 中已有等权组合和风险平价组合的回测结果。学员已理解:等权不看任何指标,风险平价只看波动率。当前问题:风险平价更稳了,但它完全不看涨跌。能不能让涨得好的多买点?
> **上下文**:在 `q3-how-much.ipynb` 中已有等权组合和风险平价组合的回测结果。读者已理解:等权不看任何指标,风险平价只看波动率。当前问题:风险平价更稳了,但它完全不看涨跌。能不能让涨得好的多买点?
>
> **任务**:在 notebook 中新建代码单元格,用动量排名信号运行回测,与前两种方案三方对比。
> **任务描述**:在 notebook 中新建代码单元格,用动量排名权重分配方法运行回测,与前两种方案对比。
#### 起草要求
#### 第三段:任务要求
动量排名比风险平价多一层——它要算 ram = mom / vol**ram 依赖 mom 和 vol 两个指标**。oxq 的 required_indicators 支持这种链式依赖,再加一个排序型优化器收尾:
动量排名比风险平价多一步:它要先计算“动量 / 波动率”,作为权重分配要依赖的指标。而这个指标又依赖动量和波动率两个指标。oxq 支持这种链式指标依赖,也支持这种排序型权重分配。
> **要求**
> **任务要求**
>
> 1. 阅读 `TopNRankingOptimizer` / `Momentum` / `Ratio` 三个 oxq 模块。
> 2. 在信号上注册三个指标(指标依赖链):
> ```python
> signal_tn.required_indicators = {
> mom: (Momentum(), {column: close, period: 20}),
> vol: (RollingVolatility(), {column: close, period: 20}),
> ram: (Ratio(), {col_a: mom, col_b: vol}),
> "mom": (Momentum(), {"column": "close", "period": 20}),
> "vol": (RollingVolatility(), {"column": "close", "period": 20}),
> "ram": (Ratio(), {"col_a": "mom", "col_b": "vol"}),
> }
> ```
> 顺序很重要——ram 依赖 mom 和 vol,所以 mom 和 vol 要先注册。
> 3. 用 `TopNRankingOptimizer(score_col="ram", n=3, filter_negative=True)` 装配优化器。`n=3` 是因为候选池就 3 只 ETF,这里等于“全选 + 过滤负值”;`filter_negative=True` 是动量排名的硬规则——**正在跌的不买**。
> 顺序很重要ram(风险调整后的动量指标)依赖 mom(动量)和 vol(波动率,代表风险),所以 mom 和 vol 要先注册。
> 3. 用 `TopNRankingOptimizer(score_col="ram", n=3, filter_negative=True)` 装配优化器。`n=3` 是因为标的池里就 3 只 ETF,这里等于“全选 + 过滤负值”;`filter_negative=True` 是动量排名的硬规则**正在跌的不买**。
> 4. 复用 spec-02 的 COMMON 字典,只把 portfolio 换成 `TopNRankingOptimizer(...)`。
> 📌 **要点**:多指标依赖链是 oxq 信号系统的关键能力。spec-02 注册 1 个指标(vol),spec-03 注册 3 个且 ram 依赖前两个——**spec 必须把注册顺序写清楚**,而不是只列指标名。AI 写代码时如果先注册 ram 再注册 mom/vol,运行时 ram 算不出来就直接 NaN——你看不到报错,但权重全错。
> 📌 **要点**:多指标依赖链是 oxq 的关键能力。spec-02 注册 1 个指标(vol),spec-03 注册 3 个指标,而且 ram 依赖前两个spec 必须把注册顺序写清楚,而不是只列指标名。
> 📌 **要点**:排序型 spec(动量排名)与加权型 spec(等权、风险平价)的本质区别是:可以**放弃部分标的**。所以“权重和=1.0”的断言要改成“未被过滤的活跃权重和=1.0”——`active_w = tn_w[tn_w > 1e-6]; assert abs(active_w.sum() - 1.0) < 1e-6`。这是排序型 spec 的活跃权重检查,加权型 spec 不需要这一步。
> 📌 **要点**:排序型 spec(动量排名)与加权型 spec(等权、风险平价)的本质区别是:可以**放弃部分 ETF**。所以“权重和 = 1.0”的断言要改成“未被过滤的活跃权重和 = 1.0”`active_w = tn_w[tn_w > 1e-6]; assert abs(active_w.sum() - 1.0) < 1e-6`。这一步是在检查:被留下来的 ETF,权重是否重新分满 100%。加权型 spec 不需要这一步。
#### 起草结果呈现
#### 第四段:验收标准
这一段的两个着力点:用排序型断言代替原来的“权重和=1.0”硬话术,并把“动量为负时面积图出空白”这个特殊视觉点写进 spec。
这一段有个交付细节:把“动量为负时面积图出空白”,这是一个特殊图表要求,写进 spec。
> **结果呈现**
> **验收标准**
>
> 1. **权重检查**:用排序型断言(过滤后归一化)。
> 2. 三权重对比表 + 三指标对比表(三种信号同台 PK
> 1. **权重检查**:用排序型断言(过滤后重新分到 100%)。
> 2. 三种方法权重对比表 + 三种方法指标对比表。
> 3. 三条净值曲线图(等权灰虚 + 风险平价蓝实 + 动量排名橙实)。
> 4. 动量排名权重历史堆叠面积图,**注意:动量为负时权重为 0,面积会出现空白**——把这个视觉特征预先写进 spec。
> 5. 分析话术按实际数据动态描述方向,**不要硬写“更大”或“更深”**——市场不可预测,spec 不应假设结果方向。
> 4. 动量排名权重历史堆叠面积图,**注意:动量为负时权重为 0,面积会出现空白**把这个视觉特征预先写进 spec。
> 5. 分析文字按实际数据动态描述方向,不要硬写“更大”或“更深”市场不可预测,spec 不应假设结果方向。
> 📌 **要点**:可视化 spec 不仅要描述要画什么,还要把**特殊视觉点**预先点出——动量为负时面积图会出空白,这种“反常”的视觉如果 spec 不预告,AI 可能用 `fillna(0.33)` 把空白补上,反而抹掉了排序型策略最重要的视觉信号。这是仓位分配章的高级 spec 写法:**视觉契约不只规定颜色形状,还要规定“该出现的空白也要出现”**
> 📌 **要点**:可视化 spec 不仅要描述要画什么,还要把特殊图表现象预先点出,比如这里的:动量为负时面积图会出空白
完整 spec 在 [`specs/spec-03-momentum-ranking.md`](https://github.com/xingwudao/xquant-learning/blob/main/q3-how-much/specs/spec-03-momentum-ranking.md)——复制给 AI,弹窗选「允许」
完整示例 spec 在配套仓库的 [`q3-how-much/specs/spec-03-momentum-ranking.md`](https://github.com/xingwudao/xquant-learning/blob/main/q3-how-much/specs/spec-03-momentum-ranking.md)。参考它确认自己的 spec 后,再复制给 AI,按提示允许执行
AI 助手执行完毕后,你的 notebook 里应该出现了新的权重对比、一张三指标表、两张图。这个实验引入了两个新概念。
AI 执行完毕后,你的 notebook 里应该出现了新的权重对比、一张三种方法的指标表、两张图。这个实验引入了两个新概念。
**动量(Momentum** ——过去 N 天的涨跌趋势。正数 = 涨势,负数 = 跌势。就像看一辆车的速度表——动量为正说明车在加速前进,为负说明在倒退。这里用的是 20 日动量,也就是看过去一个月的涨跌方向。
**动量(Momentum**过去 N 天的涨跌趋势。正数代表上涨,负数代表下跌。就像看一辆车的速度表动量为正说明车在向前,动量为负说明车在后退。这里用的是 20 日动量,也就是看过去一个月的涨跌方向。
但单看动量有个问题:波动大的标的天然动量值更大——它涨 5%波动小的标的涨 2%,可能只是因为它“脾气大”,不一定代表趋势更强。所以我们把动量除以波动率,得到**风险调整动量(ram = mom / vol** ——每份波动带来的动量。这样不同脾气的标的之间才能公平比较。
但单看动量有个问题:波动大的标的天然动量值更大它涨 5%波动小的标的涨 2%,可能只是因为它“脾气大”,不一定代表趋势更强。所以我们把动量除以波动率,得到**风险调整动量(ram = mom / vol**。你可以把它理解成每份波动带来的动量。这样不同波动程度的标的之间才能公平比较。
**TopNRankingOptimizer** ——按 ram 值排名分配权重。ram 为负的标的直接权重设为 0——不买。剩下的按 ram 大小归一化分配权重。涨得越且波动越小,分到的钱越多。如果三只 ETF 里有两只 ram 为负,那所有的钱就全部集中到唯一 ram 为正的那只上。
**动量归一化权重分配方法**按 ram 值排名分配权重。ram 为负的 ETF 直接权重设为 0,也就是不买。剩下的 ETF 再按 ram 大小重新分配到 100%。涨得越且波动越小,分到的钱越多。如果三只 ETF 里有两只 ram 为负,那所有的钱就全部到唯一 ram 为正的那只上。
### 实验结果
三种信号的权重对比(最新一天)如表 3-5 所示。
三种权重分配方法的权重对比(最新一天)如表 3-5 所示。
**表 3-5 三种信号最新一天权重对比**
**表 3-5 三种权重分配方法最新一天权重对比**
| 标的 | 等权 | 风险平价 | 动量排名 |
| ETF | 等权 | 风险平价 | 动量排名 |
|------|------|---------|---------|
| 沪深300ETF | 33.3% | 45.5% | 0.0% |
| 纳指100ETF | 33.3% | 43.3% | 0.0% |
| 黄金ETF | 33.3% | 11.2% | 100.0% |
差异一目了然。等权三等分,风险平价按波动调整,而动量排名直接把沪深300和纳指100的权重归零——近期它们在跌,不买。所有的钱全部涌向唯一在涨的黄金ETF。
差异一目了然。等权三等分,风险平价按波动调整,而动量排名直接把沪深 300 和纳指 100 的权重归零。策略运行时,它们在跌,所以不买。所有的钱全部分到唯一在涨的黄金 ETF。
三种信号的指标对比如表 3-6 所示。
三种权重分配方法的指标对比如表 3-6 所示。
**表 3-6 三种信号指标对比**
**表 3-6 三种权重分配方法指标对比**
| 指标 | 等权 | 风险平价 | 动量排名 |
|------|------|---------|---------|
@@ -304,129 +298,119 @@ AI 助手执行完毕后,你的 notebook 里应该出现了新的权重对比
三条净值曲线同框对比如图 3-4 所示,动量排名的权重历史如图 3-5 所示。
![三种信号对比:等权 vs 风险平价 vs 动量排名](images/q3-how-much/04-three-signals-nav.png)
![三种权重分配方法对比:等权 vs 风险平价 vs 动量排名](images/q3-how-much/04-three-signals-nav.png)
![动量排名:涨的多买,跌的不买](images/q3-how-much/05-momentum-weight-history.png)
### 读懂这些结果
先看图 3-4 的净值曲线,三条线同框对比。橙色实线(动量排名)终点最高——累计收益 179.96%,远超风险平价的 99.67% 和等权的 80.75%。但这笔超额收益不是白来的:动量排名的波动率是 15.54%,高于风险平价的 10.81%;最大回撤 -15.77%,也深于风险平价的 -12.26%。**收益更高,但颠簸更大、回撤更深——这就是激进策略的代价。**
先看图 3-4 的净值曲线,三条线同框对比。橙色实线(动量排名)终点最高累计收益 179.96%,远超风险平价的 99.67% 和等权的 80.75%。但这笔更高收益不是白来的:动量排名的波动率是 15.54%,高于风险平价的 10.81%;最大回撤 -15.77%,也深于风险平价的 -12.26%。**收益更高,但颠簸更大、回撤更深这就是更进取方法的代价。**
别急着下结论。记得第章的教训吗?同一个策略换个时间段,结果可能完全不同。动量排名在这几年的数据上表现优异,是因为市场恰好有几段清晰的趋势——比如黄金从 2023 年开始的持续上涨。**换段时间,动量排名的判断可能跟不上市场反转的节奏——这种"数据上看着好、换个时段就出问题"的现象,第章会专门讨论,本章先不下结论。** 没有完美的分法——风险平价更稳,动量排名更激进——哪个更适合你,要看你打算拿多久、能承受多大波动、能不能扛得住一段时间的回撤。第章我们会用更系统的方法判断“一个策略到底好不好”。
别急着下结论。记得第 1 章的教训吗?同一个策略换个时间段,结果可能完全不同。动量排名在这几年的数据上表现优异,是因为市场恰好有几段清晰的趋势比如黄金从 2023 年开始的持续上涨。换段时间,动量排名的判断可能跟不上市场反转的节奏。这种“在这段数据上好看,换一段时间就出问题的现象,第 5 章会专门讨论,本章先不下结论。没有完美的分法风险平价更稳,动量排名更进取。哪个更适合你,要看你打算拿多久、能承受多大波动、能不能扛得住一段时间的回撤。第 5 章我们会用更系统的方法判断“一个策略到底好不好”。
再看图 3-5 的权重历史。对比 3.2 的风险平价权重图,动量排名的变化剧烈得多——某只 ETF 动量转负,权重直接从几十个百分点归零,所有钱涌向其他标的;等它动量转正,权重又突然拉满。你还会发现图中有些时段出现了空白——三只 ETF 的权重加起来不到 100%,甚至全部归零。这说明那段时间三只 ETF 的动量全是负的,没有一只在涨,信号的判断是“都别买”。风险平价的权重变化则平缓得多,三只 ETF 始终都持有,只是比例在缓慢调整。两种信号的性格完全不同:一个求稳,一个求进。风险平价像一个谨慎的管家,均匀分配、避免偏科;动量排名像一个激进的赌徒,押注赢家、果断弃子
再看图 3-5 的权重历史。对比 3.2 的风险平价权重图,动量排名的变化剧烈得多。某只标的动量转负,权重直接从几十个百分点归零,钱转向其他标的;等它动量转正,权重又突然变高。你还会发现图中有些时段出现了空白:三只标的的权重加起来不到 100%,甚至全部归零。这说明那段时间三只 ETF 的动量全是负的,没有一只在涨,这种权重分配方法给出的决策是“都别买”。风险平价的权重变化则平缓得多,三只 ETF 始终都持有,只是比例在缓慢调整。两种权重分配方法的特点完全不同:一个求稳,一个求进。风险平价像一个谨慎的管家,均匀分配、避免偏科;动量排名像一个进取的交易者,押注走势更好的资产,果断放弃走弱的资产
三种信号,三种结果。到这里,你可能已经有了一个模糊的感觉:刚才做的事情好像不简单——选信号、跑回测、比指标,这不就是在做量化研究吗?
回头看:前言里说量化第一步是“**做**”——把动作写成可重复执行的规则。三次实验里,你已经做了三次:等权、风险平价、动量排名,每一次都是把“怎么分钱”从凭感觉变成了一段 spec 能让 AI 重跑的规则。这就是“做”。具体每一种规则做了什么?我们回头看看。
三种方法,三种结果。到这里,你可能已经有了一个模糊的感觉:刚才做的事情好像不简单。选权重分配方法、跑回测、比指标,这不就是在做量化研究吗?
---
## 3.4 回头看:你刚才做了什么?
三次实验,你只换了一样东西——信号。数据、标的、回测引擎、再平衡频率全部一样,只有信号不同,结果就完全不同。
三次实验,你只换了一样东西:权重分配方法。数据、标的、回测引擎、再平衡频率全部一样,只有“钱怎么分”不同,结果就完全不同。
你刚才做的事情,在量化交易的框架里有一个正式的名字:**信号对比(Signal Comparison** ——保持其他条件不变,只切换信号,观察结果差异。这是量化研究中最基础、也最重要的操作之一。信号只管判断,不管执行——但判断之后,谁来动手?这是规则(Rules)的事,第四章会讲
你刚才做的事情,可以叫**权重分配方法对比**保持其他条件不变,只切换“钱怎么分”,观察结果差异。这是量化研究中最基础、也最重要的操作之一。权重分配方法只给出目标权重,不负责决定什么时候调整持仓。什么时候调整、什么条件下卖出,这是规则(Rules)的事,第 4 章会讲。怎么把目标权重变成真实订单、按什么价格成交,是第 7 章会讲的执行交易问题
我们来看看你现在走到了哪里:第章解决了“买什么”(Universe),第三章解决了“买多少”(Signal),第四章将解决“什么时候”(Rules)。
我们来看看你现在走到了哪里:第 2 章解决了“买什么”(标的池),第 3 章解决了“买多少”(权重分配),第 4 章将解决“什么时候动手”(Rules)。
章里的 Crossover 信号说的是“买还是不买”——价格上穿均线就买、下穿就卖,要么买要么不买。第章的三种信号说的是“每只买百分之多少”——从 0% 到 100%,可以精确到小数点。判断的粒度更细了,但本质没变:都是信号,都是观察市场之后做出的判断。三种信号、三种性格,对照如表 3-7 所示。
1 章里的 Crossover 信号说的是“买还是不买”价格上穿均线就买、下穿就卖,要么买要么不买。第 3 章的三种权重分配方法说的是“每只买百分之多少”从 0% 到 100%,可以精确到小数点。这两类东西不要混在一起。信号负责判断市场状态,权重分配方法负责把钱分到资产上。三种权重分配方法的特点如表 3-7 所示。
**表 3-7 三种信号的性格对照**
**表 3-7 三种权重分配方法的特点对照**
| 信号 | 看什么 | 性格 |
| 方法 | 看什么 | 特点 |
|------|--------|------|
| EqualWeightOptimizer | 什么都不看 | 佛系 |
| RiskParityOptimizer | 波动率 | 稳 |
| TopNRankingOptimizer | 涨跌趋势 | 激进 |
| 等权 | 什么都不看 | 简单平均 |
| 风险平价 | 波动率 | 更重视平稳 |
| 动量排名 | 涨跌趋势 | 更重视上涨 |
“性价比”这个问题——三种信号收益和波动都不同,能不能用一个数字综合衡量?这是下一节要回答的事。
“性价比”这个问题来了。三种方法的收益和波动都不同,能不能用一个数字综合衡量?这是下一节要回答的事。
---
## 3.5 夏普比率:一个数字概括“性价比”
三种信号的收益率和波动率都不同。等权收益最低,风险平价更稳且收益更高,动量排名收益最高但波动也最大。有没有一个数字能把收益和波动综合起来,告诉你“每承受一份颠簸,能赚多少收益”?
三种权重分配方法的收益率和波动率都不同。等权收益最低,风险平价更稳且收益更高,动量排名收益最高但波动也最大。有没有一个数字能把收益和波动综合起来,告诉你“每承受一份颠簸,能赚多少收益”?
有。**夏普比率(Sharpe Ratio** ——年化收益率除以年化波动率衡量的是“性价比”。就像买东西看“每块钱能买到多少克”,夏普比率看的是“每承一份颠簸能赚到多少收益”。数字越大,说明你承受的颠簸越值得。三种信号的夏普比率对比如表 3-8 所示
有。**夏普比率(Sharpe Ratio** 的标准定义是:(年化收益率 - 无风险收益率)/ 年化波动率。它衡量的是每承一份波动,能比无风险资产多获得多少额外收益
**表 3-8 三种信号的夏普比率**
本章先让你理解“收益 / 波动”的关系,所以暂时采用简化口径:年化收益率 / 年化波动率。严格说,这不是标准夏普值,而是一个近似的收益波动比。等你真正比较策略时,应该使用扣除无风险收益后的标准夏普比率。三种方法的简化夏普对比如表 3-8 所示。
**表 3-8 三种权重分配方法的简化夏普对比**
| 指标 | 等权 | 风险平价 | 动量排名 |
|------|------|---------|---------|
| 年化收益率 | 11.96% | 13.97% | 20.80% |
| 年化波动率 | 11.73% | 10.81% | 15.54% |
| 夏普比率 | 1.08 | 1.35 | 1.42 |
| 简化夏普 | 1.08 | 1.35 | 1.42 |
看最后一行。三种方案的夏普比率依次递增:等权 1.08,风险平价 1.35,动量排名 1.42。风险平价比等权高出不少,说明按波动调整确实提升了性价比。动量排名最高,但优势不如收益率差距那么大——因为它的波动率也更高,部分抵消了收益优势。不过,夏普比率有三个你需要知道的局限:
看最后一行。三种方案的简化夏普依次递增:等权 1.08,风险平价 1.35,动量排名 1.42。风险平价比等权高出不少,说明按波动调整确实提升了性价比。动量排名最高,但优势不如收益率差距那么大因为它的波动率也更高,部分抵消了收益优势。不过,夏普比率有三个你需要知道的局限:
1. 它把上涨和下跌的波动一视同仁——但你可能根本不介意往上的颠簸。
2. 同一个策略换个时间段,夏普比率可能完全不同——它只反映特定历史区间的表现。
3. 当波动率为 0 时它会失效——持有现金的夏普比率无穷大,但显然不是好策略。
1. 它把上涨和下跌的波动一视同仁但你可能根本不介意往上的颠簸。
2. 同一个策略换个时间段,夏普比率可能完全不同它只反映特定历史区间的表现。
3. 当波动率为 0 时它会失效持有现金的夏普比率可能被算成无穷大,但显然不是好策略。
结论:夏普比率适合在相同条件下横向对比几种方案的性价比,但别把决策押在这一个数字上。怎么综合多个指标来判断一个策略到底好不好?这是第章的主题。
结论:夏普比率适合在相同条件下横向对比几种方案的性价比,但别把决策押在这一个数字上。本章表格里的数字只是简化夏普,更不能当作最终结论。怎么综合多个指标来判断一个策略到底好不好?这是第 5 章的主题。
---
## 3.6 本章总结
三次实验,三种信号,从“各买三分之一”到“按波动调整”再到“按涨势分配”——你一步步把“买多少”这个问题从拍脑袋变成了有据可依。信号就是策略的大脑,换一个大脑,整个策略的表现就完全不同
还有一个细节值得带走:本章三种信号下回测都设定了每 10 个交易日重算权重——这叫**再平衡(Rebalancing)**,又名**调仓**。“多久重算一次 / 多久调一次”是另一个独立的设计选择,第四章会专门讲。
三次实验,三种权重分配方法,从“各买三分之一”到“按波动调整”再到“按涨势分配”你一步步把“买多少”这个问题从拍脑袋变成了有据可依。买多少不是小问题。权重一变,每个资产对账户的影响就会变,整个策略的表现也会跟着变
### 概念速查表
本章涉及的核心概念汇总如表 3-9 所示,方便随时回查。
**表 3-9 第章核心概念速查**
**表 3-9 第 3 章核心概念速查**
| 概念 | 含义 | 类比 |
|------|------|------|
| 权重(Weight | 每只标的分到总资金的百分之多少 | 一笔钱怎么分 |
| 信号(Signal) | 根据指标对市场做出的判断——要不要动、怎么动。只表达意愿,不负责执行 | 你看完天气预报后的判断:“带伞”。带不带是另一回事 |
| 风险平价(Risk Parity) | 按波动率倒数分配权重,让每只影响力均匀 | 成熟稳重的多管钱,情绪化的少管钱 |
| 权重(Weight | 每只资产分到总资金的百分之多少 | 一笔钱怎么分 |
| 权重分配方法 | 回答每只资产各买多少,在 open-xquant 里通常由 `PortfolioOptimizer` 实现 | 分蛋糕的方法 |
| 组合(Portfolio) | 实际拿来配置资金的一组资产,不是候选名单 | 购物车里真正放进去的商品 |
| 目标权重(Target Weight) | 按规则算出来的理想资金比例 | 预算表 |
| 持仓(Position) | 账户里实际持有的资产和数量 | 手里真正拿着的份额 |
| 实际权重 | 按持仓市值算出来的真实比例 | 结账后每样东西实际占了多少预算 |
| 风险平价(Risk Parity) | 按波动率倒数分配权重,让每只资产的影响更均匀 | 成熟稳重的多管钱,起伏大的少管钱 |
| 动量(Momentum) | 过去 N 天的涨跌趋势,正=涨势,负=跌势 | 最近的势头 |
| 动量排名(TopNRankingOptimizer | 按涨势强弱分配权重,跌的不买 | 趋势交易的量化表达 |
| 夏普比率(Sharpe Ratio | 年化收益 / 年化波动,衡量性价比 | 每份颠簸赚多少 |
| 再平衡/调仓Rebalancing) | 定期重新计算权重并调整仓位 | 定期重新分工 |
| 夏普比率(Sharpe Ratio | 标准定义是(年化收益率 - 无风险收益率)/ 年化波动率;本章暂用简化夏普理解收益和波动的关系 | 每份颠簸赚多少 |
| 再平衡(Rebalancing) | 定期重新计算权重并调整持仓比例 | 定期重新分工 |
### 策略进化路径
章产出了投资宇宙(沪深300、纳指100、黄金)。本章在此基础上探索了三种分钱方式Step 1 各买 1/3EqualWeightOptimizer)——简单,但波动大的把组合拉着走Step 2 按波动调整RiskParityOptimizer)——更稳,每只影响均匀Step 3 按涨势调整TopNRankingOptimizer)——趋势交易的量化表达。第三章产出:三种信号对比 + 信号就是策略的大脑
2 章产出了标的池(沪深 300、纳指 100、黄金)。本章在此基础上探索了三种分钱方式。第 1 步,各买 1/3,也就是等权,简单,但波动大的资产会把组合拉着走。第 2 步,按波动调整,也就是风险平价,更稳,每只资产的影响均匀。第 3 步,按涨势调整,也就是动量排名,这是趋势交易思想在“买多少”问题上的表达。第 3 章的产出:三种权重分配方法对比
### 本章最重要的收获
走完三个实验后,本章最值得带走的五条认知如表 3-10 所示。
**表 3-10 第章主线认知**
**表 3-10 第 3 章主线认知**
| 认知 | 来源 |
|------|------|
| 平均分钱 ≠ 平均分影响力 | 3.1 |
| 波动率小 = 你能在低点不慌 | 3.1 |
| 按波动调整让组合更稳 | 3.2 |
| 同样的策略,只换信号,结果完全不同 | 3.3 |
| 夏普比率适合对比,但别只看一个指标 | 3.4 |
| 同样的策略,只换权重分配方法,结果完全不同 | 3.3 |
| 简化夏普适合对比,但别只看一个指标 | 3.5 |
### 带走的问题
做完本章,你应该带着两个问题进入后续章节——它们将在下两章被一一回答:
做完本章,你应该带着两个问题进入后续章节它们将在下两章被一一回答:
- 信号只负责判断“想怎么分”,但**判断完了谁来执行?按什么规则下单** → 第
- 动量排名在这段数据上全面胜出,但**换一段时间还行吗?怎么区分真本事和运气?** → 第
- 权重分配方法只负责给出目标权重,但**什么时候调整持仓、什么条件下卖出** → 第 4
- 动量排名在这段数据上全面胜出,但**换一段时间还行吗?怎么区分真本事和运气?** → 第 5
第二个问题正是本书口号“**做要规则、看要指标、还要怀疑你的指标**”中“疑”的入口——动量排名的累计 179.96% 看起来很漂亮,但你已经隐隐感到不对劲:换段时间还能不能这样?这种警觉,是后面章要反复训练的主线动作
第二个问题正是本书口号“**做要规则、看要指标、还要怀疑你的指标**”中“疑”的入口动量排名的累计 179.96% 看起来很漂亮,但你已经隐隐感到不对劲:换段时间还能不能这样?这种警觉,是后面章要反复训练的主线。
> 本章所有代码的可运行版本见 `notebooks/q3-how-much.ipynb`
---
## 反馈与读者群
如果你发现本章有错别字、概念不清或实验结果不一致,欢迎
[提交 GitHub Issue](https://github.com/xingwudao/xquant-beginner/issues/new/choose)。
课程、正式书和后续更新见
[xquant.shop/courses](https://xquant.shop/courses)。
添加作者微信 `xquanter` 进入读者群。请备注:`XQuant 读者`
> 本章所有代码的可运行版本见配套仓库的 [`q3-how-much/notebooks/q3-how-much.ipynb`](https://github.com/xingwudao/xquant-learning/blob/main/q3-how-much/notebooks/q3-how-much.ipynb)
+149 -189
View File
@@ -1,97 +1,91 @@
# 第四章:给策略加规则:调仓、止损、止盈
# 第 4 章:决定何时买卖:再平衡、止损、止盈
章我们用三种信号解决了“买多少”——等权、风险平价、动量排名,各有各的性格。但有个细节你可能没注意:不管用哪种信号,策略都是每 10 个交易日自动调仓一次——重新把持仓比例调回目标权重。为什么是 10 天?5 天行不行?一个月呢?
3 章我们用三种权重分配方法解决了“买多少”等权、风险平价、动量排名,各有各的特点。但有个细节你可能没注意:不管用哪种权重分配方法,策略都是每 10 个交易日自动做一次再平衡,把实际权重调回目标权重。为什么是 10 天?5 天行不行?一个月呢?
更关键的问题是:**如果在两次调仓之间,某只 ETF 突然跌 20%,你只能等到下一个调仓日才能反应。** 这合理吗?
更关键的问题是:**如果在两次再平衡之间,某只 ETF 突然跌 20%,你只能等到下一个再平衡日才能反应。** 这合理吗?
> **🧭 本章边界**:章名虽然叫“什么时候买卖”,但本章**不做入场择时**——什么时候第一次进场、用什么信号判断“该买了”,那些是第三章信号已经回答的事。本章聚焦的是**已经持仓之后**的三个执行节奏:多久做一次再平衡(4.1)、跌到什么程度先撤(4.2)、涨到什么程度走人(4.3)。
> **本章边界**:章名虽然叫“什么时候买卖”,但本章不讨论第一次什么时候买入。第 3 章已经回答了“每只 ETF 应该分多少钱”。本章聚焦的是已经持仓之后的三个问题:多久做一次再平衡(4.1)、跌到什么程度先撤(4.2)、涨到什么程度走人(4.3)。
>
> **🧱 一个新元概念:规则(Rule** 第三章信号说的是“想怎么分钱”——是策略的大脑。本章引入的是**规则**——策略的手脚:感知持仓状态涨了多少、跌了多少、上次调仓多久了),按预设条件触发动作。信号管“想怎么动”,规则管“什么时候动手”。两者分工明确,缺一不可。从本章起,你会反复看到 `RebalanceFrequencyRule` / `StopLossRule` / `TakeProfitRule` 这些 Rule 类——它们就是规则的具体实现。
> **规则(Rule**:第 3 章的权重分配方法回答“钱应该怎么分”。本章引入规则,回答“什么时候动手”。规则会观察持仓状态,例如涨了多少、跌了多少、距离上次再平衡过了多久,然后按预设条件触发执行。从本章起,你会反复看到 `RebalanceFrequencyRule` / `StopLossRule` / `TakeProfitRule` 这些 Rule 类它们就是规则的具体实现。
### 路线图
我们正在走一条完整的策略构建之路:
本章进入四个阶段里的**制定规则**阶段。第 2 章解决买什么,第 3 章解决每个买多少,本章解决什么时候调整持仓、什么时候卖出。本章一共要动手做 3 次实验。
**选什么标的(第二章 已完成)→ 每个买多少(第三章 已完成)→ 什么时候买卖(第四章 本章)→ 怎么验证有效(第五章)**
**表 4-1 第 4 章路线图**
**这一章你仍在飞轮的“组合层”,主练“做”——把“什么时候动手”也写成可重复执行的规则。** 本章解决“什么时候买卖”,分三步揭示三个认知,最后你会发现:不是所有“听起来合理”的规则都有效。三节的问题与方法对照如表 4-1 所示。
**表 4-1 第四章三节问题与方法对照**
| 节 | 问题 | 方法 |
| 节 | 内容 | 实验 |
|----|------|------|
| 4.1 | 调仓频率怎么选? | 周/月/季度对比 + 引入交易成本 |
| 4.2 | 亏了要不要先跑? | 止损规则 |
| 4.3 | 赚了要不要走? | 止盈规则 |
| 4.1 | 多久做一次再平衡? | 1 |
| 4.2 | 亏了要不要先跑? | 1 |
| 4.3 | 赚了要不要走? | 1 |
本章使用第章的风险平价(RiskParity)作为基础信号——它始终持有全部 3 只 ETF,方便我们观察不同规则的效果。
(操作流程见前言“怎么使用这本书”。每一节按相同流程:阅读 → 复制 spec 给 AI → 看结果与解读。)
本章使用第 3 章的风险平价权重分配方法作为基础配置。它始终在 3 只 ETF 之间分配资金,方便我们观察不同规则的效果。
---
## 4.1 调仓频率有多重要?
## 4.1 再平衡频率有多重要?
章每 10 个交易日一次仓——这个“10”是拍脑袋定的。调得更勤(每周)会更好吗?调得更懒(每季度)会更差吗?
3 章每 10 个交易日一次再平衡。这个“10”是拍脑袋定的。更勤一点,比如每周一次,会更好吗?更慢一点,比如每季度一次,会更差吗?
直觉告诉我们:调得越勤,反应越快,效果应该越好。真的吗?
直觉告诉我们:再平衡越频繁,反应越快,效果应该越好。真的吗?
### 动手实验 1:不同调仓频率对比
### 动手实验 1:不同再平衡频率对比
本节用到两个新模块:`RebalanceFrequencyRule`(设定多少天调仓一次)和 `PercentageFee`(模拟交易手续费)。
本节用到两个新模块:`RebalanceFrequencyRule`(设定多少天做一次再平衡)和 `PercentageFee`(模拟交易手续费)。
我们一起把这份 spec 写出来。这次重点看两件新东西:**参数化扫描怎么列**,以及**辅助函数封装作为后续 spec 的接口**。
#### 起草上下文 + 任务
#### 第一、二段:上下文和任务描述
> **上下文**:本章探讨“什么时候买卖”。第三章用了三种信号(等权、风险平价、动量排名。本章选择风险平价(RiskParity)作为基础信号——它始终持有全部 3 只 ETF,方便我们观察止损和止盈规则的效果。第章每 10 个交易日一次,但为什么是 10 天?调得更勤或更,结果会怎样?
> **上下文**:本章探讨“什么时候调整持仓、什么时候卖出”。第 3 章用了三种权重分配方法:等权、风险平价、动量排名。本章选择风险平价作为基础配置,因为它始终持有全部 3 只 ETF,方便我们观察止损和止盈规则的效果。第 3 章每 10 个交易日一次再平衡,但为什么是 10 天?更勤或更,结果会怎样?
>
> **任务**:在 notebook `q4-when-to-trade.ipynb` 中创建代码,对比不同调仓频率的效果,并首次引入交易成本。
> **任务描述**:在 notebook `q4-when-to-trade.ipynb` 中创建代码,对比不同再平衡频率的效果,并首次引入交易成本。
#### 起草要求:参数化扫描 + 辅助函数封装
#### 第三段:任务要求
要求段是这份 spec 的灵魂——把“对比 4 种频率”和“先后跑两轮成本”两件事拧成一份 spec
任务要求段是这份 spec 的关键:把“对比 4 种频率”和“先后跑两轮成本”两件事写清楚
> **要求**
> **任务要求**
>
> 1. 先读 oxq 源码:`PercentageFee` / `SimBroker` / `RebalanceFrequencyRule` / `RiskParityOptimizer` / `Engine` / `Strategy`
> 2. 复用第章的 SYMBOLSSTART、RiskParity 信号等基础配置
> 2. 复用第 3 章的 `SYMBOLS`、`START`、风险平价权重分配方法等基础配置
> 3. 定义辅助函数 `run_backtest(frequency, fee_model=None, order_rules=None)` 封装策略构建和回测执行,**供后续 spec 复用**
> 4. 定义 4 种调仓频率:`FREQUENCIES = [5, 10, 21, 63]`(约每周 / 两周 / 每月 / 每季度)
> 5. 第一轮回测**不含成本**`fee_model=None`第二轮回测**含成本**`PercentageFee(rate=Decimal("0.001"), min_fee=Decimal("5"))`
> 6. 含成本版本画净值曲线对比图(figsize 12x6,4 条线,归一化到起点 = 100
> 4. 定义 4 种再平衡频率:`FREQUENCIES = [5, 10, 21, 63]`(约每周 / 两周 / 每月 / 每季度)
> 5. 第一轮回测不含成本(`fee_model=None`第二轮回测含成本(`PercentageFee(rate=Decimal("0.001"), min_fee=Decimal("5"))`
> 6. 含成本版本画净值曲线对比图(figsize 12x6,4 条线,统一从 100 开始
> 7. 分析按实际数据动态描述(不要硬写“更高”或“更低”)
> 📌 **要点**:参数化扫描的关键是把“被对比的维度”列成一个数组(`FREQUENCIES = [5, 10, 21, 63]`+ 一个 for 循环跑完。spec 里把数组写出来,AI 就不用猜要对比哪些值。后面 spec-02 / spec-03 也会沿用同样的写法——`STOP_THRESHOLDS = [...]`、`TP_THRESHOLDS = [...]`。这是研究型工作中最常见的 spec 套路
> 📌 **要点**:参数化扫描的关键是把“被对比的维度”列成一个数组(`FREQUENCIES = [5, 10, 21, 63]`,再用一个 for 循环跑完。spec 里把数组写出来,AI 就不用猜要对比哪些值。后面 spec-02 / spec-03 也会沿用同样的写法`STOP_THRESHOLDS = [...]`、`TP_THRESHOLDS = [...]`。这是研究型工作中最常见的 spec 写法
> 📌 **要点**:辅助函数 `run_backtest` 是为 spec-02 / spec-03 留的接口。整章三份 spec 都通过它跑回测spec-02 把止损规则进 `order_rules` 参数,spec-03 再把止盈进去。**spec 链有共享逻辑时,把它封装成函数放在源头 spec,后续 spec 直接调用——这是接续型 spec 链的核心架构。**
> 📌 **要点**:辅助函数 `run_backtest` 是为 spec-02 / spec-03 留的接口。整章三份 spec 都通过它跑回测spec-02 把止损规则进 `order_rules` 参数,spec-03 再把止盈规则放进去。spec 链有共享逻辑时,要在第一份 spec 里封装成函数,后续 spec 直接调用。
#### 起草结果呈现
#### 第四段:验收标准
> **结果呈现**
> **验收标准**
>
> 1. 无成本对比表(4 行 6 列)
> 2. 含成本对比表(4 行 7 列,多一列“总手续费”)
> 3. 含成本净值曲线对比图
> 4. 分析文字(按实际数据描述方向)
完整 spec 在 [`specs/spec-01-rebalance-frequency.md`](https://github.com/xingwudao/xquant-learning/blob/main/q4-when-to-trade/specs/spec-01-rebalance-frequency.md)——复制给 AI,弹窗选「允许」
完整示例 spec 在配套仓库的 [`q4-when-to-trade/specs/spec-01-rebalance-frequency.md`](https://github.com/xingwudao/xquant-learning/blob/main/q4-when-to-trade/specs/spec-01-rebalance-frequency.md)。参考它确认自己的 spec 后,再复制给 AI,按提示允许执行
AI 助手执行完毕后,你的 notebook 里应该出现两张对比表和一张净值图。我们来解读结果。
AI 执行完毕后,你的 notebook 里应该出现两张对比表和一张净值图。我们来解读结果。
四种频率分别对应每周(5 个交易日)、每两周(10)、每月(21)、每季度(63)。代码跑了两轮回测:第一轮不含成本,第二轮含成本(万分之一佣金,最低 5 元)。同样的策略,唯一的差别是有没有扣手续费。
**交易成本(Transaction Cost** ——每次买卖都要付的费用,主要是付给券商的**佣金**和(部分市场)付给政府的**印花税**。本章回测里只模拟了佣金部分(万分之一,最低 5 元),没有加印花税——实际交易的成本会比回测里更高。记住这一点:**回测里的成本是乐观估计,现实只会更贵。** A 股 / 美股 / 港股具体费率有差别,详情见章末「拓展阅读」
**交易成本(Transaction Cost**每次买卖都要付的费用,主要是付给券商的佣金,以及部分市场要交的税费。本章回测里只模拟了佣金部分(万分之一,最低 5 元),没有加印花税实际交易的成本会比回测里更高。记住这一点:**回测里的成本是乐观估计,现实只会更贵。** A 股 / 美股 / 港股具体费率有差别,大家通过互联网可以查到公开资料
在之前的第二章和第三章里,我们的回测都没有考虑交易成本,就像在一个“免手续费”的理想世界里做实验。从现在开始,我们把手续费加回来,看看现实世界会怎样。
在之前的第 2 章和第 3 章里,我们的回测都没有考虑交易成本,就像在一个“免手续费”的理想世界里做实验。从现在开始,我们把手续费加回来,看看现实世界会怎样。
### 实验结果
不含成本时四种频率的对比如表 4-2 所示。
**表 4-2 不含交易成本时四种调仓频率的对比**
**表 4-2 不含交易成本时四种再平衡频率的对比**
| 频率 | 累计收益率 | 年化波动率 | 最大回撤 | 夏普比率 | 交易次数 |
| 频率 | 累计收益率 | 年化波动率 | 最大回撤 | 简化夏普 | 交易次数 |
|------|-----------|-----------|---------|---------|---------|
| 5 | 101.22% | 10.90% | -12.32% | 1.34 | 744 |
| 10 | 106.80% | 11.19% | -12.44% | 1.36 | 372 |
@@ -100,9 +94,9 @@ AI 助手执行完毕后,你的 notebook 里应该出现了两张对比表和
加上手续费后的对比如表 4-3 所示。
**表 4-3 含交易成本(万分之一佣金)时四种调仓频率的对比**
**表 4-3 含交易成本(万分之一佣金)时四种再平衡频率的对比**
| 频率 | 累计收益率 | 年化波动率 | 最大回撤 | 夏普比率 | 交易次数 | 总手续费 |
| 频率 | 累计收益率 | 年化波动率 | 最大回撤 | 简化夏普 | 交易次数 | 总手续费 |
|------|-----------|-----------|---------|---------|---------|---------|
| 5 | 93.36% | 10.91% | -12.97% | 1.27 | 744 | 4,665 元 |
| 10 | 101.68% | 11.19% | -12.81% | 1.31 | 372 | 3,022 元 |
@@ -111,88 +105,88 @@ AI 助手执行完毕后,你的 notebook 里应该出现了两张对比表和
含成本下的净值曲线对比如图 4-1 所示。
![调仓频率对比(含交易成本)](images/q4-when-to-trade/01-frequency-comparison.png)
![再平衡频率对比(含交易成本)](images/q4-when-to-trade/01-frequency-comparison.png)
### 读懂这些结果
先看表 4-2。四种频率的夏普比率差异不大,从 1.24 到 1.36——不含成本时,每 10 天调仓的夏普比率最高(1.36)。
先看表 4-2。四种频率的简化夏普差异不大,从 1.24 到 1.36不含成本时,每 10 天再平衡的简化夏普最高(1.36)。
再看表 4-3。加上手续费后,每 10 天调仓的夏普比率仍然最高(1.31),排名没变。但有一组数字值得关注——最右边两列。交易次数从频率 5 的 744 笔降到频率 63 的 57 笔,相差 13 倍;总手续费从 4,665 元降到 926 元,相差 5 倍多。**频率越高,交易越多,成本越大——这是铁律。**
再看表 4-3。加上手续费后,每 10 天再平衡的简化夏普仍然最高(1.31),排名没变。但有一组数字值得关注最右边两列。交易次数从频率 5 的 744 笔降到频率 63 的 57 笔,相差 13 倍;总手续费从 4,665 元降到 926 元,相差 5 倍多。**频率越高,交易越多,成本越大。**
对比表 4-2 与表 4-3 中频率 5 的行:不含成本时累计收益 101.22%,含成本时跌到了 93.36%,手续费吃掉了将近 8 个百分点的收益。而频率 63 呢?从 105.56% 只降到 104.09%,手续费只吃掉了 1.5 个百分点——因为它只交易了 57 笔。
对比表 4-2 与表 4-3 中频率 5 的行:不含成本时累计收益 101.22%,含成本时跌到了 93.36%,手续费吃掉了将近 8 个百分点的收益。而频率 63 从 105.56% 只降到 104.09%,手续费只吃掉了 1.5 个百分点因为它只交易了 57 笔。
最后看净值图。四条线走势相似,终点差异不算大——频率的选择有影响,但不是决定性的。更重要的发现是:**交易不是免费的**。每次调仓——不管是买还是卖——都要付佣金、交税(根据政策)。在不含成本的“理想世界”里看起来差不多的策略,加上成本后差距就拉开了。
最后看净值图。四条线走势相似,终点差异不算大频率的选择有影响,但不是决定性的。更重要的发现是:**交易不是免费的**。每次调整持仓,不管是买还是卖都要付佣金、交税。在不含成本的“理想世界”里看起来差不多的策略,加上成本后差距就拉开了。
**数据说了算:频率的选择是个权衡——反应速度 vs 交易成本**
**数据说了算:频率的选择是个权衡,要在反应速度和交易成本之间取舍**
但频率不是最大的问题。想象一下:你刚调完仓,第二天某只 ETF 跌 15%。下一次调仓要等好几天——这段时间你只能眼睁睁看着亏损扩大。能不能加一个“紧急出口”——不管调仓日到没到,亏到一定程度就先跑?
但频率不是最大的问题。想象一下:你刚做完再平衡,第二天某只 ETF 跌 15%。下一次再平衡还要等好几天这段时间你只能眼睁睁看着亏损扩大。能不能加一个“紧急出口”:不管再平衡日到没到,亏到一定程度就先跑?
---
## 4.2 亏了能不能先跑?
定期调仓的硬伤很明显:两次调仓之间如果市场跌,你只能等。
定期再平衡的硬伤很明显:两次再平衡之间如果市场跌,你只能等。
直觉告诉我们:亏了先跑卖出止损,总比硬扛着不动好。但“跑”也不是免费的——每次卖出再买回来,都要付手续费。到底值不值?先猜后验。
直觉告诉我们:亏了先跑,也就是卖出止损,总比硬扛着不动好。但“跑”也不是免费的每次卖出再买回来,都要付手续费。到底值不值?先猜后验。
### 动手实验 2:止损规则对比
我们一起把这份 spec 写出来。这次重点看两件新东西:**累积接续怎么把上一份 spec 的最优结果流到这一份**,以及**为什么这件事必须 Rule 才能做**。
我们一起把这份 spec 写出来。这次重点看两件新东西:**如何接续上一份 spec 的最优结果**,以及**为什么止损必须用规则来做**。
#### 起草上下文 + 任务
#### 第一、二段:上下文和任务描述
> **上下文**:在 `q4-when-to-trade.ipynb` 中已有 RiskParity 策略在不同调仓频率下的回测结果,学员已看到交易成本的影响,理解了“交易不是免费的”。当前问题:定期调仓有个硬伤——中间跌只能等。能不能加个保护?
> **上下文**:在 `q4-when-to-trade.ipynb` 中已有风险平价策略在不同再平衡频率下的回测结果。读者已看到交易成本的影响,理解了“交易不是免费的”。当前问题:定期再平衡有个硬伤中间跌只能等。能不能加个保护?
>
> **任务**:在 notebook 中新建代码单元格,给策略加上止损规则,对比不同止损阈值的效果。
> **任务描述**:在 notebook 中新建代码单元格,给策略加上止损规则,对比不同止损阈值的效果。
#### 起草要求:累积接续 + Signal vs Rule
#### 第三段:任务要求
止损是 Rule 而不是 Signal——这是 oxq 框架的关键设计,spec 要把这件事掰开讲。
止损是规则,不是信号。这是 open-xquant 的关键设计,spec 要把这件事讲清楚
> **要求**
> **任务要求**
>
> 1. 先读 oxq 源码:`StopLossRule`(注意它作为 rule 传入 `Engine.run(rules=[...])`,不再是 Strategy 字段)
> 2. 解释 StopLossRule 工作原理:以 `avg_cost x (1 - threshold)` 为止损价提交 stop SELL 挂单SimBroker 后续每个 bar 检查是否触发。**为什么 Signal 做不到**Signal 只看市场数据,不知道你的买入价是多少;只有 Rule 能感知持仓状态
> 3. **用代码动态选**含成本后夏普最高的频率作为基准(不要硬编码):`BEST_FREQ = max(FREQUENCIES, key=lambda f: results_with_fee[f].sharpe_ratio())`
> 2. 解释 `StopLossRule` 工作原理:以 `avg_cost x (1 - threshold)` 为止损价提交 stop SELL 挂单`SimBroker` 后续每个 bar 检查是否触发。为什么信号做不到:信号只看市场数据,不知道你的买入价是多少;只有规则能感知持仓状态
> 3. 用代码动态选含成本后简化夏普最高的频率作为基准(不要硬编码):`BEST_FREQ = max(FREQUENCIES, key=lambda f: results_with_fee[f].sharpe_ratio())`
> 4. 定义 4 组对比:无止损 / threshold=0.05 / 0.10 / 0.15
> 5. 调用 spec-01 的 `run_backtest`,把止损规则作为 `order_rules=[StopLossRule(threshold=T)]` 传入
> 6. 净值曲线对比图(无止损灰色虚线 vs 3 种阈值彩色实线),并在无止损曲线上用浅红色阴影标注最大回撤时段
> 7. 分析按实际数据动态描述,要点出阈值太紧(5%)和太松(15%)的各自问题
> 📌 **要点**累积接续的关键是**用代码现场算出最优值**而不是硬编码上一份 spec 的结果。`BEST_FREQ = max(FREQUENCIES, key=...)` 这一行让 spec-02 永远跟着 spec-01 的真实数据走——上一份跑出来什么频率最优,这一份就用什么。如果硬编码 `BEST_FREQ = 10`,数据更新后排名变了,基准就错了。**接续型 spec 之间应该用变量传结果,不应该用数字传结果。** **对你的练习**:以后你写第二份 spec 复用第一份的最优值时,宁可写一行 `BEST = max(...)`,也别硬编码具体数字——这样你的 spec 链就能跟着数据自动跑。
> 📌 **要点**接续上一份 spec 的关键,是用代码现场算出最优值而不是硬编码上一份 spec 的结果。`BEST_FREQ = max(FREQUENCIES, key=...)` 这一行让 spec-02 永远跟着 spec-01 的真实数据走上一份跑出来什么频率最优,这一份就用什么。如果硬编码 `BEST_FREQ = 10`,数据更新后排名变了,基准就错了。接续型 spec 之间应该用变量传结果,不应该用数字传结果。
> 📌 **要点**:信号(Signal)和规则(Rule)分工不同——这是 oxq 框架的认知基石,后面 q5 / q7 调度多种 Rule 都靠它。spec 要求第 2 条把这区分写进去,目的是让 AI 在 notebook 里也输出这段解释,给学员第二次曝光。**核心概念应该跨 spec / notebook / book 三处复述,不能只指望 book markdown 单方面承担。** **对你的练习**:以后你 spec 里出现一个不熟的核心概念,要求 AI 在生成的 notebook 里也用一两行打印解释一下——这样你跑代码的时候,notebook 自己就成了你的第二份“小词典”
> 📌 **要点**:信号和规则分工不同。信号看市场状态,规则看持仓状态。spec 要求第 2 条把这区分写进去,是为了让 notebook 里也输出这段解释核心概念可以在 specnotebook、正文里重复出现,但每次都要服务当前任务
#### 起草结果呈现
#### 第四段:验收标准
> **结果呈现**
> **验收标准**
>
> 1. StopLossRule 工作原理说明(挂单机制 + Signal vs Rule 的区别)
> 1. `StopLossRule` 工作原理说明(挂单机制 + 信号和规则的区别)
> 2. 指标对比表(4 行 7 列)
> 3. 净值曲线对比图(含回撤阴影标注)
> 4. 分析文字
完整 spec 在 [`specs/spec-02-stop-loss.md`](https://github.com/xingwudao/xquant-learning/blob/main/q4-when-to-trade/specs/spec-02-stop-loss.md)——复制给 AI,弹窗选「允许」
完整示例 spec 在配套仓库的 [`q4-when-to-trade/specs/spec-02-stop-loss.md`](https://github.com/xingwudao/xquant-learning/blob/main/q4-when-to-trade/specs/spec-02-stop-loss.md)。参考它确认自己的 spec 后,再复制给 AI,按提示允许执行
AI 助手执行完毕后,你的 notebook 里应该出现止损规则的工作原理说明、一张对比表和一张带回撤标注的净值图。我们来解读结果。
AI 执行完毕后,你的 notebook 里应该出现止损规则的工作原理说明、一张对比表和一张带回撤标注的净值图。我们来解读结果。
本节引入了两个新概念。
**止损(Stop Loss** ——预设一个底线如果从买入价跌了 X%,自动卖出。就像你去赌场之前跟自己说“今最多 500 块,到了就走人”——止损就是这个“输到了就走”的自动化版本。“活着才能赚钱”——保住本金,才有机会等到下一次上涨。
**止损(Stop Loss**预设一个底线如果从买入价跌了 X%自动卖出。就像你出门前跟自己说“今最多 500 块,到了就停手”,止损就是这个底线的自动化版本。保住本金,才有机会等到下一次上涨。
**挂单(Pending Order** ——不是立即交易,而是“预设条件,满足了再执行”。止损规则在你持有某只 ETF 时,自动向券商挂一个条件单:“如果跌到某个价格以下,就帮我卖掉。”你不用每天盯盘,系统帮你盯着。
**挂单(Pending Order**不是立即交易,而是“预设条件,满足了再执行”。止损规则在你持有某只 ETF 时,自动向券商挂一个条件单:“如果跌到某个价格以下,就帮我卖掉。”你不用每天盯盘,系统帮你盯着。
代码从 Step 1 的结果中自动选出含成本后夏普比率最高的频率(10 天),然后在这个基准上叠加止损规则。四组对比:无止损、5% 止损、10% 止损、15% 止损。
代码从第 1 个实验的结果中自动选出含成本后简化夏普最高的频率(10 天),然后在这个基准上叠加止损规则。四组对比:无止损、5% 止损、10% 止损、15% 止损。
这里有一个关键的新概念**规则(Rule** ——策略的“执行层”。第三章的信号(Signal)负责判断“怎么分”,但信号不知道你的持仓情况——它只看市场数据波动率、动量,不知道你什么时候买的、买了多少钱、现在是赚还是亏。规则不同——规则能感知持仓状态知道“我什么时候买的、现在亏了多少”。止损就是这种“看持仓”的判断,只有 Rule 能做。
这里有一个关键概念**规则(Rule**。第 3 章的权重分配方法负责判断“怎么分”,但不知道你的持仓情况它只看市场数据,例如波动率、动量,不知道你什么时候买的、买了多少钱、现在是赚还是亏。规则不同规则能感知持仓状态知道“我什么时候买的、现在亏了多少”。止损就是这种“看持仓”的判断,只有规则能做。
### 实验结果
止损规则的对比结果如表 4-4 所示(基准频率:每 10 天,含交易成本)。
止损规则的对比结果如表 4-4 所示(基准频率每 10 天含交易成本)。
**表 4-4 止损阈值对比(含交易成本)**
| 止损阈值 | 累计收益率 | 年化波动率 | 最大回撤 | 夏普比率 | 交易次数 | 总手续费 |
| 止损阈值 | 累计收益率 | 年化波动率 | 最大回撤 | 简化夏普 | 交易次数 | 总手续费 |
|---------|-----------|-----------|---------|---------|---------|---------|
| 无止损 | 101.68% | 11.19% | -12.81% | 1.31 | 372 | 3,022 元 |
| 5% | 100.78% | 10.71% | -13.25% | 1.36 | 389 | 3,903 元 |
@@ -205,63 +199,63 @@ AI 助手执行完毕后,你的 notebook 里应该出现了止损规则的工
### 读懂这些结果
先看图 4-2 的大趋势。三条彩色实线(有止损)和灰色虚线(无止损)走势接近——有止损的线在浅红色阴影区域(最大回撤时段)并没有比无止损的线跌得更浅。这是个值得停下来想一想的反直觉信号。
先看图 4-2 的大趋势。三条彩色实线(有止损)和灰色虚线(无止损)走势接近有止损的线在浅红色阴影区域(最大回撤时段)并没有比无止损的线跌得更浅。这是个值得停下来想一想的反直觉信号。
再看表 4-4。三种止损阈值的最大回撤都比无止损更深一点:5% 止损 -13.25%vs 无止损 -12.81%)、10% 止损 -13.08%、15% 止损 -14.99%——阈值设得越紧,回撤反而没收窄。但夏普比率方向不一样:5% 止损夏普 1.36 反而比无止损的 1.31 高一点,因为它把年化波动率从 11.19% 压到了 10.71%——分母(波动率)下降抵消了分子收益略降的影响。
再看表 4-4。三种止损阈值的最大回撤都比无止损更深一点:5% 止损 -13.25%vs 无止损 -12.81%)、10% 止损 -13.08%、15% 止损 -14.99%。也就是说,止损并没有收窄回撤。但简化夏普的方向不一样:5% 止损的简化夏普 1.36反而比无止损的 1.31 高一点,因为它把年化波动率从 11.19% 压到了 10.71%。分母下降抵消了分子收益略降的影响。
为什么三种止损都没把回撤压住?这不是 bug,而是止损在**持续下跌的市场**中的副作用——**锯齿效应**。
为什么三种止损都没把回撤压住?这不是 bug,而是止损在**持续下跌的市场**中的副作用**锯齿效应**。
2022 年市场一路缓慢下跌,不是一天跌 10%,而是今天跌一点、明天跌一点,连着跌好几个月。5% 止损在这种环境下反复触发,形成一个循环:持仓 —— 跌 5% 触发止损卖出(亏损 + 手续费)—— 调仓日买回来(手续费)—— 继续跌 5% 又触发止损(亏损 + 手续费)—— 调仓日又买回来......
2022 年市场一路缓慢下跌,不是一天跌 10%,而是今天跌一点、明天跌一点,连着跌好几个月。5% 止损在这种环境下反复触发,形成一个循环:持仓跌 5% 触发止损卖出,产生亏损和手续费;再平衡日买回来,再付手续费;继续跌 5% 又触发止损,再产生亏损和手续费;再平衡日又买回来。
每一轮“卖-买”付两笔手续费,但并没有真正躲开下跌——因为买回来之后市场继续跌。止损只是让你“分批亏”而非“一次亏到底”额外的交易成本累积起来,反而让总回撤略微加深。从交易次数也能看出端倪:5% 止损多了 17 笔交易,多花了 881 元手续费——这些都是锯齿效应的代价。
每一轮“卖出再买回”都要付手续费,但并没有真正躲开下跌因为买回来之后市场继续跌。止损只是让你“分批亏”,而不是“一次亏到底”额外的交易成本累积起来,反而让总回撤略微加深。从交易次数也能看出端倪:5% 止损多了 17 笔交易,多花了 881 元手续费这些都是锯齿效应的代价。
**止损对一次性大跌有效(跌到位就跑),对持续缓慢下跌有害(反复卖出又买回来。在 20212025 这段持续震荡下跌的数据里,三种阈值都锯齿效应反复收割——这不代表止损永远无效,而是说在这个市场环境 + 这套基准上,紧的止损没占到便宜。**
**止损对一次性大跌有效,跌破底线就卖出;对持续缓慢下跌可能有害,因为会反复卖出又买回来。** 在 2021-2025 这段持续震荡下跌的数据里,三种阈值都受到了锯齿效应影响。这不代表止损永远无效,而是说在这个市场环境这套基准上,紧的止损没占到便宜。
**数据说了算:止损“保护本金”的逻辑没错——夏普比率确实因为波动率下降而抬了一点;但“保护”在这段数据上的含义是“压低波动”而不是“压低回撤”。阈值怎么选、保护的是什么,要看数据怎么说。**
**数据说了算:止损“保护本金”的逻辑没错,简化夏普确实因为波动率下降而抬了一点;但“保护”在这段数据上的含义是“压低波动”而不是“压低回撤”。阈值怎么选、保护的是什么,要看数据怎么说。**
止损解决的是亏损方向。涨的方向呢——下一节继续看。
止损解决的是亏损方向。涨的方向呢下一节继续看。
---
## 4.3 赚了要不要走?
亏的方向有止损兜底了。涨的方向呢——市场给你 10%,你拿着不动,要不要主动伸手去摘?万一涨上去又跌回来,岂不是白赚一场?
亏的方向有止损兜底了。涨的方向呢市场给你 10%,你拿着不动,要不要主动卖出?万一涨上去又跌回来,岂不是白赚一场?
直觉告诉我们:赚了就走卖出止盈,落袋为安,总比看着利润蒸发好。真的吗?先猜后验。
直觉告诉我们:赚了就走,也就是卖出止盈,落袋为安,总比看着利润蒸发好。真的吗?先猜后验。
### 动手实验 3:止盈规则对比
我们一起把这份 spec 写出来。这次 spec 的结构和前两份**完全一样**——一样的对比表、一样的净值图、一样的辅助函数。我们要演示的是:**同样的 spec 模板,不同的规则,结果可能截然不同**——结果是什么方向,跑完再揭晓。
我们一起把这份 spec 写出来。这次 spec 的结构和前两份一样:一样的对比表、一样的净值图、一样的辅助函数。我们要演示的是:同样的 spec 模板,不同的规则,结果可能截然不同结果是什么方向,跑完再揭晓。
#### 起草上下文 + 任务
#### 第一、二段:上下文和任务描述
> **上下文**:在 `q4-when-to-trade.ipynb` 中已有不同调仓频率的对比结果、止损规则的效果(保护了本金,但有成本)。当前问题:止损帮你控制亏损。反过来——赚了一定程度,要不要主动卖掉、落袋为安?
> **上下文**:在 `q4-when-to-trade.ipynb` 中已有不同再平衡频率的对比结果、止损规则的效果。当前问题:止损试图控制亏损。反过来,赚到一定程度以后,要不要主动卖掉、落袋为安?
>
> **任务**:在 notebook 中新建代码单元格,给策略加上止盈规则,对比不同止盈阈值的效果。
> **任务描述**:在 notebook 中新建代码单元格,给策略加上止盈规则,对比不同止盈阈值的效果。
#### 起草要求:累积接续到第三层 + 微观证据
#### 第三段:任务要求
这份 spec 在 spec-01(最优频率)+ spec-02(最优止损)的基础上,叠加第三层规则。
这份 spec 在 spec-01(最优频率)+ spec-02(最优止损)的基础上,继续叠加止盈规则。
> **要求**
> **任务要求**
>
> 1. 先读 oxq 源码:`TakeProfitRule`(挂单机制和 `StopLossRule` 类似,只是方向相反——以 `avg_cost x (1 + threshold)` 为止盈价提交 limit SELL 挂单)
> 2. 累积接续:用代码动态选 spec-02 中夏普最高的止损阈值,与 spec-01 的最优频率叠加作为基准`BEST_SL = max([sl for sl in STOP_THRESHOLDS if sl is not None], key=lambda sl: results_sl[sl].sharpe_ratio())`
> 1. 先读 oxq 源码:`TakeProfitRule`(挂单机制和 `StopLossRule` 类似,只是方向相反以 `avg_cost x (1 + threshold)` 为止盈价提交 limit SELL 挂单)
> 2. 累积接续:用代码动态选 spec-02 中简化夏普最高的止损阈值,与 spec-01 的最优频率叠加作为基准,代码写法如下:`BEST_SL = max([sl for sl in STOP_THRESHOLDS if sl is not None], key=lambda sl: results_sl[sl].sharpe_ratio())`
> 3. 定义 4 组对比:不止盈 / threshold=0.10 / 0.20 / 0.30
> 4. 调用 spec-01 的 `run_backtest`,把止损和止盈一起塞进 `order_rules=[StopLossRule(threshold=BEST_SL), TakeProfitRule(threshold=TP)]`
> 5. 净值曲线对比图(不止盈灰色虚线 vs 3 种阈值彩色实线)
> 6. **打印交易记录**(取止盈阈值最小的那组,展示前 10 笔),让学员看到 stop / limit / market 三种 order\_type 在同一回测里共存
> 6. 打印交易记录(取止盈阈值最小的那组,展示前 10 笔),让读者看到 stop / limit / market 三种 `order_type` 在同一回测里共存
> 7. 分析按实际数据动态描述,要点出止损 vs 止盈的对照(止损截断亏损,逻辑清晰;止盈截断利润,越紧伤害越大),并收尾“让利润奔跑,截断亏损”这句行业老话
> 📌 **要点**累积接续到第三层——`BEST_FREQ`spec-01+ `BEST_SL`(本份现场算)+ 4 组止盈阈值。每加一规则,都是在前一最优配置之上加。**接续型 spec 链有两种模式:简单复用前一份的产出,以及把前 N 份的最优结果层层叠加。后者就是“累积接续”,适合“逐步给策略加规则”这类研场景。**
> 📌 **要点**这次接续到第三个实验:`BEST_FREQ`spec-01+ `BEST_SL`(本份现场算)+ 4 组止盈阈值。每加一规则,都是在前一最优配置之上加。接续型 spec 链有两种模式:简单复用前一份的产出,或者把前几份的最优结果逐步叠加。后者适合“逐步完善交易规则”这类研场景。
> 📌 **要点**:这是一份**对照实验 spec**——前两份 spec 给学员造的预期是“加规则就会变好”,这一份要做的是同样的对比表、同样的净值图、同样的辅助函数复用一次,只换一种规则,看数据怎么说。**写对照实验 spec,关键是不要在 spec 里预先暗示结果方向,让数据自己说话。** **对你的练习**:写一份你不知道答案的对照实验 spec 时,反复检查任务段——里面有没有“应该哪个赢”的暗示词(比如“展示 X 的优势”“证明 Y 有效”)。把这类词都改成中性的“对比 / 评估”,数据才能给你一个干净的答案。这是量化研发里最重要的一类 spec:用数据告诉自己有些规则该不该用
> 📌 **要点**:这是一份对照实验 spec前两份 spec 可能让你产生“加规则就会变好”的预期,这一份要做的是复用同样的对比表、同样的净值图、同样的辅助函数,只换一种规则,看数据怎么说。写对照实验 spec,关键是不要在 spec 里预先暗示结果方向。不要写“展示 X 的优势”“证明 Y 有效”,要写中性的“对比”“评估”
> 📌 **要点**要求第 6 条让学员**亲眼看到** stop / limit / market 三种 order\_type 的交易记录前 10 笔。前面所有对比都是宏观指标,这 10 行把规则机制下沉到单笔交易级别。**宏观指标 spec 应该配套微观证据——光看一个总数字不够,要把机制的痕迹露出来给学员看。**
> 📌 **要点**:第 6 条让读者看到 stop / limit / market 三种 `order_type` 的交易记录前 10 笔。前面所有对比都是宏观指标,这 10 行把规则机制下沉到单笔交易级别。宏观指标最好配套微观证据,不能只看一个总数字
#### 起草结果呈现
#### 第四段:验收标准
> **结果呈现**
> **验收标准**
>
> 1. TakeProfitRule 工作原理说明
> 2. 指标对比表(4 行)
@@ -269,21 +263,21 @@ AI 助手执行完毕后,你的 notebook 里应该出现了止损规则的工
> 4. 交易记录(前 10 笔,含 order\_type 列)
> 5. 分析文字
完整 spec 在 [`specs/spec-03-take-profit.md`](https://github.com/xingwudao/xquant-learning/blob/main/q4-when-to-trade/specs/spec-03-take-profit.md)——复制给 AI,弹窗选「允许」
完整示例 spec 在配套仓库的 [`q4-when-to-trade/specs/spec-03-take-profit.md`](https://github.com/xingwudao/xquant-learning/blob/main/q4-when-to-trade/specs/spec-03-take-profit.md)。参考它确认自己的 spec 后,再复制给 AI,按提示允许执行
AI 助手执行完毕后,你的 notebook 里应该出现止盈规则的工作原理说明、一张对比表、一张净值图和一段交易记录。我们来解读结果。
AI 执行完毕后,你的 notebook 里应该出现止盈规则的工作原理说明、一张对比表、一张净值图和一段交易记录。我们来解读结果。
**止盈(Take Profit** ——预设一个目标如果从买入价涨了 X%,自动卖出锁定利润。就像你在闲鱼上卖东西挂一个“到了这个价就成交”的价格——止盈就是给你的持仓挂一个“到了就卖”的目标价。和止损一样,也是通过挂单机制实现的你不用盯盘,系统帮你盯着。
**止盈(Take Profit**预设一个目标如果从买入价涨了 X%自动卖出锁定利润。就像你卖东西挂一个“到了这个价就成交”的价格止盈就是给你的持仓挂一个“到了就卖”的目标价。和止损一样,也是通过挂单机制实现的你不用盯盘,系统帮你盯着。
基准配置是 Step 1 和 Step 2 选出的最优组合:每 10 天调仓 + 5% 止损。在这个基础上,分四组测试:不止盈、10% 止盈、20% 止盈、30% 止盈。止损和止盈可以同时生效。
基准配置是第 1 个实验和第 2 个实验选出的组合:每 10 天再平衡 + 5% 止损。在这个基础上,分四组测试:不止盈、10% 止盈、20% 止盈、30% 止盈。止损和止盈可以同时生效。
### 实验结果
止盈规则的对比结果如表 4-5 所示(基准:每 10 天调仓 + 5% 止损,含交易成本)。
止盈规则的对比结果如表 4-5 所示(基准:每 10 天再平衡 + 5% 止损,含交易成本)。
**表 4-5 止盈阈值对比(含交易成本)**
| 止盈阈值 | 累计收益率 | 年化波动率 | 最大回撤 | 夏普比率 | 交易次数 | 总手续费 |
| 止盈阈值 | 累计收益率 | 年化波动率 | 最大回撤 | 简化夏普 | 交易次数 | 总手续费 |
|---------|-----------|-----------|---------|---------|---------|---------|
| 不止盈 | 100.78% | 10.71% | -13.25% | 1.36 | 389 | 3,903 元 |
| 10% | 72.72% | 9.70% | -9.47% | 1.18 | 431 | 6,703 元 |
@@ -313,146 +307,112 @@ AI 助手执行完毕后,你的 notebook 里应该出现了止盈规则的工
### 读懂这些结果
这个结果可能出乎你的意料——**多数止盈阈值降低了收益率,但 30% 止盈反而把收益和夏普都抬了上去。**
这个结果可能出乎你的意料**多数止盈阈值降低了收益率,但 30% 止盈反而提高了收益和简化夏普。**
先看表 4-5。不止盈时累计收益 100.78%,加了 10% 止盈后降到 72.72%,少赚了 28 个百分点;20% 止盈降到 83.47%,也是少赚。但 30% 止盈给了一个反弹——累计收益 104.32%,比不止盈还高 3.5 个百分点;夏普比率从 1.36 升到 1.45,也是四组里最高的。“落袋为安”在不同阈值上给出了完全不同的结论。
先看表 4-5。不止盈时累计收益 100.78%,加了 10% 止盈后降到 72.72%,少赚了 28 个百分点;20% 止盈降到 83.47%,也是少赚。但 30% 止盈给了一个反弹累计收益 104.32%,比不止盈还高 3.5 个百分点;简化夏普从 1.36 升到 1.45,也是四组里最高的。“落袋为安”在不同阈值上给出了完全不同的结论。
为什么 10% / 20% 止盈都在拖后腿?看看我们用的是什么信号。风险平价组合里有三只 ETF,各自承担相的风险。当某只 ETF 表现好、价格上涨时,它正在为你赚钱。10% / 20% 这种紧的止盈恰好在这个时候把它卖掉——**你亲手切断了正在帮你赚钱的持仓。** 阈值越紧,“切断”得越早,伤害越大。
为什么 10% / 20% 止盈都在拖后腿?看看我们用的是什么基础配置。风险平价组合里有三只 ETF,各自承担相的风险。当某只 ETF 表现好、价格上涨时,它正在为你赚钱。10% / 20% 这种紧的止盈恰好在这个时候把它卖掉你亲手切断了正在帮你赚钱的持仓。阈值越紧,“切断”得越早,伤害越大。
那 30% 止盈为什么反而抬了一手?阈值放到 30% 之后,止盈极少被触发——表 4-5 里 30% 止盈的交易次数 396 笔,只比不止盈多 7 笔;多花的手续费也只有 403 元。这 7 笔含止盈触发的卖出 + 触发后下次调仓日的买回具体止盈触发了几次,可在 notebook 的交易记录里数 `limit` 行。)但少量触发恰好抓住了几次“涨过头又掉头”的极端波动,把波动率从 10.71% 压到 10.23%——分母(波动)小了一点,分子(收益)反而稳了一点,夏普就抬上去了。**这是一个边界值的故事**:阈值太紧,把“正在赚钱的仓”切断;阈值放得够远,止盈基本不动手,少数被触发的恰好是涨过头又掉头的几次,把波动尾部削掉了一点——这就是 30% 止盈在四组里夏普反而最高的原因。
那 30% 止盈为什么反而提高了结果?阈值放到 30% 之后,止盈极少被触发表 4-5 里 30% 止盈的交易次数 396 笔,只比不止盈多 7 笔;多花的手续费也只有 403 元。这 7 笔含止盈触发的卖出,以及触发后下一次再平衡日的买回具体止盈触发了几次,可在 notebook 的交易记录里数 `limit` 行。
再看手续费那列。不止盈时 3,903 元,10% 止盈跳到 6,703 元——多花了 2,800 元。止盈每触发一次就是一笔额外交易——这一项就把手续费推高了 72%
少量触发恰好抓住了几次“涨过头又掉头”的极端波动,把波动率从 10.71% 压到 10.23%。分母波动小了一点,分子收益反而稳了一点,简化夏普就抬上去了。这是一个边界值的故事:阈值太紧,会把“正在赚钱的持仓”切断;阈值放得够远,止盈基本不动手,少数被触发的恰好是涨过头又掉头的几次,把波动尾部削掉了一点。这就是 30% 止盈在四组里简化夏普反而最高的原因
表 4-6 的交易记录也很有说明力。注意“订单类型”那列:前 10 笔全是 `market`——也就是再平衡买卖。这意味着这个基准虽然同时挂了 5% 止损 + 10% 止盈两种条件单(一份 `stop` 挂单 + 一份 `limit` 挂单),但前 10 笔里两种都还没被触发。**学员在等的“止盈触发”——`limit` 类型——在 ETF 这种宽基上很少被命中**,但每命中一次都会切断正在涨的持仓
再看手续费那列。不止盈时 3,903 元,10% 止盈跳到 6,703 元,多花了 2,800 元。止盈触发一次就是一笔额外交易,这一项就把手续费推高了 72%
对比一下 Step 2 的止损:
表 4-6 的交易记录也很有说明力。注意“订单类型”那列:前 10 笔全是 `market`,也就是再平衡产生的买卖。这意味着这个基准虽然同时挂了 5% 止损和 10% 止盈两种条件单(一份 `stop` 挂单,一份 `limit` 挂单),但前 10 笔里两种都还没被触发。读者可能在等的“止盈触发”,也就是 `limit` 类型,在 ETF 这种宽基上很少被命中,但每命中一次都会切断正在涨的持仓。
- **止损截断亏损**——下跌中越拿越亏,跑了是对的
- **止盈截断利润**——上涨中越拿越赚,跑了通常是错的(除非阈值放得够远)
对比一下第 2 个实验里的止损:
**注意:这个结论是在 2021-2025 年这段数据 + 我们用的风险平价组合上得出的。** 行业里的老话**“让利润奔跑,截断亏损”(Let profits run, cut losses short** 也是同一个方向——你不知道还会跌多少,先跑为敬;但涨到头没人提前知道,一只 ETF 涨了 10% 后可能还会涨 50%,你在 10% 就卖了,等于亲手把机会让出去。但这句话**不是定理**——换一段时间、换一类标的(比如做短线动量、抓波段反转的策略),止盈在某些场景下可能反而帮了忙。本章给你的不是“止盈一定不要加”这种死结论,而是**“加规则之前,先用数据验证它在你的策略 + 你的时段上到底是帮忙还是添乱”**这个工作方法——这才是带得走的东西
- **止损截断亏损**:下跌中越拿越亏,跑了可能是对的
- **止盈截断利润**:上涨中越拿越赚,跑了通常会错过后面的收益,除非阈值放得够远。
**数据说了算:不是所有“听起来合理”的规则都有效。每加一条规则,都要用数据验证它到底是帮忙还是添乱——而且要在不同阈值上都看一遍,避免用单一参数的结果下死结论。**
**注意:这个结论是在 2021-2025 年这段数据和风险平价组合上得出的。** 行业里的老话“让利润奔跑,截断亏损”(Let profits run, cut losses short)也是同一个方向。你不知道还会跌多少,先卖出可能更稳妥;但涨到头没人提前知道,一只 ETF 涨了 10% 后可能还会涨 50%,你在 10% 就卖了,等于亲手把机会让出去。但这句话不是定理。换一段时间、换一类投资对象,比如频繁短线买卖、专门做涨跌反转的策略,止盈在某些场景下可能反而帮了忙。本章给你的不是“止盈一定不要加”这种绝对结论,而是一个工作方法:**加规则之前,先用数据验证它在你的策略和你的时段上到底是帮忙还是造成额外损耗。**
**数据说了算:不是所有“听起来合理”的规则都有效。每加一条规则,都要用数据验证它到底是帮忙还是造成额外损耗。还要在不同阈值上都看一遍,避免用单一参数的结果下绝对结论。**
---
## 4.4 回头看:你刚才做了什么?
这一章你做了三件事——给策略加调仓节奏、加止损护栏、加止盈出口。三走下来,每一步都让你撞了一次“加规则不一定变好”的小墙。下面把三次撞墙打包成三个认知。
这一章你做了三件事给策略加再平衡节奏、加止损护栏、加止盈出口。三个实验走下来,你会反复看到同一个提醒:加规则不一定变好。下面把三次发现归纳成三个认知。
实验,三个认知升级:
实验,三个认知升级:
1. **调仓频率**:交易不是免费的。频率越高,交易越多,成本越大。选频率就是在“反应速度”和“交易成本”之间做权衡。
1. **再平衡频率**:交易不是免费的。频率越高,交易越多,成本越大。选频率就是在“反应速度”和“交易成本”之间做权衡。
2. **止损规则**:保护本金的逻辑听起来稳——跌破底线就。但在 20212025 这段持续震荡下跌的数据里,三种阈值都没把回撤压住,只把波动压低了。代价是额外交易和手续费——而且要分清楚“保护”在你的数据上到底是压回撤还是压波动。
2. **止损规则**:保护本金的逻辑听起来稳跌破底线就卖出。但在 2021-2025 这段持续震荡下跌的数据里,三种阈值都没把回撤压住,只把波动压低了。代价是额外交易和手续费。你要分清楚“保护”在你的数据上到底是压回撤还是压波动。
3. **止盈规则**:逻辑不止损那么稳。10% / 20% 阈值都降低了收益率,但 30% 阈值反而把收益和夏普都抬了上去——同一类规则,不同阈值给出反向结论。“落袋为安”听起来合理,但数据告诉我们要谨慎。
3. **止盈规则**:逻辑不止损直观。10% / 20% 阈值都降低了收益率,但 30% 阈值反而提高了收益和简化夏普。同一类规则,不同阈值给出反向结论。“落袋为安”听起来合理,但数据告诉我们要谨慎。
你刚才做的事情,在量化交易的框架里叫做**规则设计(Rule Design** ——在信号的基础上,添加执行层面的保护和约束。我们来看看现在走到了哪里:
你刚才做的事情,可以叫**规则设计(Rule Design**:在权重分配方法的基础上,添加交易执行上的保护和约束。我们来看看现在走到了哪里:
- **第二章: 买什么** —— Universe(投资宇宙
- **第三章: 买多少** —— Signal(信号)——告诉策略每只买百分之多少
- **第四章: 什么时候** —— Rules(规则)——决定何时执行、如何保护 **← 你在这里**
- **第 2 章:买什么**:标的池(Universe
- **第 3 章:买多少**:权重分配方法,告诉策略每只买百分之多少
- **第 4 章:什么时候动手**:规则(Rules),决定何时执行、如何保护
本章把“规则”补进了第三章的“信号”——信号想分钱,规则才动手。两者分工明确,缺一不可。
本章把“规则”补进了第 3 章的权重分配方法。权重分配方法给出目标权重,规则决定什么时候调整持仓、什么时候触发保护。两者分工明确,缺一不可。
**一个更深的认知:不是所有“合理”的规则都有效。** 止损的逻辑听起来站得住脚——截断亏损,保护本金但在我们这段数据上“保护”的含义是压波动而不是压回撤。止盈的逻辑就更模糊——你怎么知道涨到头了?而且换个阈值结论可能反过来。加规则之前,先问两个问题:
**一个更深的认知:不是所有“合理”的规则都有效。** 止损的逻辑听起来站得住脚截断亏损,保护本金但在我们这段数据上“保护”的含义是压波动而不是压回撤。止盈的逻辑就更模糊你怎么知道涨到头了?而且换个阈值结论可能反过来。加规则之前,先问两个问题:
- 这条规则的逻辑站得住脚吗?
- 换个参数、换个时段,结论还一样吗?
第二个问题,正是下一章(第五章)要回答的。
第二个问题,正是第 5 章要回答的。
---
## 4.5 本章总结
实验,从“多久一次”到“亏了跑不跑”再到“赚了走不走”——你一步步给策略加上了执行层面的规则,也第一次体验到了交易成本的威力和“规则越多越好”这个直觉的破灭。**本章主练的还是“做”——三步都把“动还是不动”从凭感觉变成可重复执行的规则;而止盈在不同阈值上方向反过来的那一刻,正是“看要指标、还要怀疑你的指标”在飞轮组合层的第一次小预演——下一章你会正式把“看”练成主线。**
实验,从“多久再平衡一次”到“亏了跑不跑”再到“赚了走不走”你一步步给策略加上了交易执行上的规则,也第一次体验到了交易成本的威力和“规则越多越好”这个直觉的破灭。本章仍然训练“做”这个环节:把“动还是不动”从凭感觉变成可重复执行的规则止盈在不同阈值上方向反过来的那一刻,也是在提醒你:下一章要开始系统训练“看”和“疑”。
### 策略进化路径
本章接着第章的产出,在 RiskParity 信号上逐步叠加规则——每加一条都用数据验证,而不是凭直觉。
本章接着第 3 章的产出,在风险平价权重分配方法上逐步叠加规则每加一条规则,都用数据验证而不是凭直觉。
- **第章产出**RiskParity 信号 + 每 10 天调仓
- Step 1: 调频率 —— 频率有影响 + 交易成本不可忽视
- Step 2: 加止损 —— 压低了波动,但回撤锯齿效应反复收割
- Step 3: 加止盈 —— 多数阈值降低收益,30% 阈值反而抬高夏普
- **第章产出**:完整的规则配置 + “每条规则都要在不同阈值上验证”的认知
- **第 3 章产出**风险平价权重分配方法 + 每 10 天再平衡
- 第 1 步:比较再平衡频率,频率有影响交易成本不可忽视
- 第 2 步:加止损,压低了波动,但回撤锯齿效应变深
- 第 3 步:加止盈,多数阈值降低收益,30% 阈值反而抬高简化夏普
- **第 4 章产出**:完整的规则配置 + “每条规则都要在不同阈值上验证”的认知
### 概念速查表
本章涉及的核心概念汇总如表 4-7 所示,方便随时回查。
本章涉及的核心概念汇总如表 4-7 所示方便随时回查。
**表 4-7 第章核心概念速查**
**表 4-7 第 4 章核心概念速查**
| 概念 | 含义 | 类比 |
|------|------|------|
| 交易成本(Transaction Cost | 每次买卖都要付的费用——佣金(付给券商)+ 税(付给政府) | 去银行换汇要收手续费 |
| 交易成本(Transaction Cost | 每次买卖都要付的费用,包括佣金、税费、滑点等 | 去银行换汇要收手续费 |
| 止损(Stop Loss) | 预设底线:从买入价跌了 X% 就自动卖出 | “今晚最多输 500,输到了就走” |
| 止盈(Take Profit) | 预设目标:从买入价涨了 X% 就自动卖出 | “到了这个价就成交” |
| 挂单(Pending Order) | 预设条件,满足了再执行的订单 | “到价自动触发” |
| 锯齿效应(Whipsaw) | 在持续下跌的市场里反复卖出—回涨—又跌—又卖”,每来回一次付一笔成本 | 像锯子来回切,越切越薄 |
| 规则(Rule | 策略的执行层——感知持仓状态,决定何时动手 | 信号是大脑,规则是手脚 |
| 锯齿效应(Whipsaw) | 在持续下跌的市场里反复卖出又买回,每来回一次都增加成本 | 来回买卖,成本越积越多 |
| 规则(Rule) | 感知持仓状态,决定何时动手 | 到点提醒、到价触发 |
| 让利润奔跑,截断亏损 | 止损截断亏损(通常有帮助),别用止盈截断利润 | 交易经典格言 |
### 本章最重要的收获
走完三个实验后,本章最值得带走的核心认知归纳如表 4-8 所示。
走完三个实验后本章最值得带走的核心认知归纳如表 4-8 所示。
**表 4-8 第章核心认知**
**表 4-8 第 4 章核心认知**
| 认知 | 来源 |
|------|------|
| 交易不是免费的——每次买卖都有成本 | 4.1 |
| 调仓频率是反应速度与交易成本的权衡 | 4.1 |
| 止损压低了波动,但回撤可能锯齿效应反复收割 | 4.2 |
| 信号看市场,规则看持仓——两者角色不同 | 4.2 |
| 止盈结论随阈值反向——单一参数不能下死结论 | 4.3 |
| 交易不是免费的每次买卖都有成本 | 4.1 |
| 再平衡频率是反应速度与交易成本的权衡 | 4.1 |
| 止损压低了波动,但回撤可能锯齿效应变深 | 4.2 |
| 信号看市场,规则看持仓两者角色不同 | 4.2 |
| 止盈结论随阈值反向,不能只看单一参数 | 4.3 |
| 不是所有“合理”的规则都有效,要在不同阈值上用数据验证 | 4.3 |
| 让利润奔跑,截断亏损(经验,不是定理) | 4.2 + 4.3 对比 |
| 先猜后验,数据说了算 | 贯穿全章 |
### 带走的问题
读完本章,你应该带着两个挥之不去的疑问进入下一章——它们正是第章要回答的:
读完本章你应该带着两个问进入下一章它们正是第 5 章要回答的
- 策略看起来不错,但**怎么知道不是恰好在这段时间表现好?** 换个时间段结果还一样吗?→ 第五章
- 回测结果漂亮就能赚钱吗?**怎么避免自欺欺人?** → 第五章
- 策略看起来不错,但**怎么知道不是恰好在这段时间表现好?** 换个时间段结果还一样吗?这是第 5 章要回答的问题。
- 回测结果漂亮就能赚钱吗?**怎么避免自欺欺人?** 这也是第 5 章要回答的问题。
> 本章所有代码的可运行版本见 `notebooks/q4-when-to-trade.ipynb`
---
## 拓展阅读
本章正文聚焦“调仓频率 / 止损 / 止盈”三个执行规则,下面这段是动手之前可以省略的视野铺垫——读完正文后回头看,反而吸收得更快。
### A 股 / 美股 / 港股交易成本对照
不同市场的费率结构有所不同。读者按自己实际交易的市场对照即可,回测里只要把它们都算进 `PercentageFee` 模型就好。三个市场的常见费率对照如表 4-9 所示。
**表 4-9 三大市场常见交易成本对照**
| 市场 | 佣金(付给券商) | 税(付给政府) | 备注 |
|------|----------------|--------------|------|
| A 股 | 万分之一 ~ 万分之三,最低 5 元 | 卖出收千分之一印花税(买入不收) | 还有过户费(沪市)等小项目,多数券商给打包简化 |
| 美股 | 按笔(如 0.5 ~ 1 美元)或按股(每股 0.005 美元起),各家不同 | 没有印花税;卖出有 SEC 费 + FINRA TAF(按金额比例,极小) | 多数零佣金券商以 PFOF(订单流付费)或点差变相补偿 |
| 港股 | 万分之五左右,部分券商更低 | 买卖双向各 0.13% 印花税 | 还有 0.005% 证监会征费、0.0027% AFRC 征费等小项目 |
**怎么估你的实际成本?** 把“佣金 + 税 + 滑点(先按 0.1% 估)”加起来,就是你单次往返的预算。回测里把这个数填进 `PercentageFee.rate`,结果会更接近实盘。
### 为什么“让利润奔跑,截断亏损”是格言而不是定理
本章 4.3 你已经看到,10% / 20% 这种紧的止盈阈值都让累计收益下降了——多数情况下“落袋为安”输给了“让利润奔跑”;只有放到 30% 这种很少触发的阈值,才偶尔把波动压低、夏普反弹。但这并不意味着任何场景下都不该止盈。**这条格言适用的是“中长周期、跟趋势”的策略,比如本章用的风险平价 + 中频再平衡。** 如果你跑的是短线波段、做反转、或者高杠杆策略,止盈往往是必要的——不止盈就可能在一次回调里把所有浮盈吐光。换句话说:**格言是经验的提炼,不是定理;它把概率告诉你,但需要你结合策略类型 + 阈值去用。**
---
## 反馈与读者群
如果你发现本章有错别字、概念不清或实验结果不一致,欢迎
[提交 GitHub Issue](https://github.com/xingwudao/xquant-beginner/issues/new/choose)。
课程、正式书和后续更新见
[xquant.shop/courses](https://xquant.shop/courses)。
添加作者微信 `xquanter` 进入读者群。请备注:`XQuant 读者`
> 本章所有代码的可运行版本见配套仓库的 [`q4-when-to-trade/notebooks/q4-when-to-trade.ipynb`](https://github.com/xingwudao/xquant-learning/blob/main/q4-when-to-trade/notebooks/q4-when-to-trade.ipynb)
+215 -232
View File
@@ -1,129 +1,127 @@
# 第五章:策略好不好4 个评估角度
# 第 5 章:判断策略好不好4 个关键视角
章我们给策略加上了规则——调仓频率、止损、止盈,学会了“不是所有合理的规则都有效”。现在手上有一个“看起来不错”的策略组合:RiskParity + 5% 止损年化收益、夏普比都还行
4 章我们给策略加上了规则:再平衡频率、止损、止盈,学会了“不是所有合理的规则都有效”。现在手上有一个“看起来不错”的配置:风险平价 + 5% 止损。它的年化收益接近 15%,简化夏普也不低
但“看起来不错”到底是什么意思?年化 15% 好不好?夏普 0.8 够不够?**跟什么比?**
但“看起来不错”到底是什么意思?年化 15% 好不好?简化夏普 1.4 左右够不够?**跟什么比?**
更关键的问题:**这些数字是好几年平均出来的。如果某一年亏了 20%,你扛得住吗?**
### 路线图
我们正在走一条完整的策略构建之路:
本章进入四个阶段里的**评估归因**阶段。前面几章主要在训练“做”:把想法写成规则,让程序跑起来。本章开始训练“看”:拿到一组回测结果后,知道先看什么、再看什么、每个数字说明什么。
**选什么标的(第二章 已完成)→ 每个买多少(第三章 已完成)→ 什么时候买卖(第四章 已完成)→ 怎么验证有效(第五章 本章)→ 如何避免自欺欺人(第六章)**
本章给策略做一次“全面体检”,分四个角度检查结果。全章一共要动手做 4 次实验,路线如表 5-1 所示。
**这一章你跨进了飞轮的”归因层”——主练动作从前几章的”做”切到”看”。指标是”看”的眼睛,本书的口号”做要规则、看要指标”中”看要指标”指的就是这一层的训练。** 本章给策略做一次”全面体检”,分四步检查四个评估维度,对照如表 5-1 所示。
**表 5-1 第 5 章路线图**
**表 5-1 第五章四节问题与方法对照**
| 节 | 问题 | 方法 |
| 节 | 内容 | 实验 |
|----|------|------|
| 5.1 | 跟什么比? | 基准对比 + 风险调整指标 |
| 5.2 | 每年都有收益吗? | 分年拆解 |
| 5.3 | 收益结构什么样? | 收益分布 + 水下曲线 |
| 5.4 | 参数动一动崩不崩? | 参数敏感性扫描 |
| 5.1 | 跟什么比? | 1 |
| 5.2 | 每年都有收益吗? | 1 |
| 5.3 | 收益结构什么样? | 1 |
| 5.4 | 参数动一动,结果还稳吗? | 1 |
铁律不变:**先猜后验,数据说了算。**
本章用第三、四章的四个策略作为“被检查对象”,4 个实验都按统一流程跑——见前言“怎么使用这本书”。四个候选策略对照如表 5-2 所示。
本章用第 3 章和第 4 章的四个策略配置作为“被检查对象”。四个候选配置对照如表 5-2 所示。
**表 5-2 第章评估的四个候选策略**
**表 5-2 第 5 章评估的四个候选配置**
| 策略 | 来源 | 特点 |
|------|------|------|
| EqualWeight | 第章等权 | 最简单,无参数 |
| RiskParity | 第章风险平价 | 稳健,低敏感 |
| TopNRanking | 第章动量排名 | 收益但高波动 |
| RP+止损5% | 第四章最终产出 | RiskParity + 5% 止损 |
| EqualWeight | 第 3 章等权 | 最简单,无参数 |
| RiskParity | 第 3 章风险平价 | 波动相对低 |
| TopNRanking | 第 3 章动量排名 | 收益高,但波动也高 |
| RP+止损5% | 第 4 章规则配置 | 风险平价 + 5% 止损 |
> 提示:本章 📌 要点比前几章长——因为 q5 spec 涉及金融口径、视觉契约、同方法不同目的等高密度知识,每条都把“为什么、不这样会怎样”写透。读到要点段不必赶——慢一点消化更划算
> 提示:本章的要点段会比前几章更长,因为本章开始处理指标口径、图表约定和同一种方法不同用途。读到要点段时不用赶,重点是看清楚:为什么要这样写,不这样会怎样。
---
## 5.1 跟什么比?
章的策略年化收益 15%、夏普 0.8——好不好?
4 章的策略年化收益接近 15%,简化夏普也不低,好不好?
直觉告诉我们:收益高就是好,夏普越大越好。但“好”是跟什么比?如果同期市场涨了 20%,你忙活半天不如躺平
直觉告诉我们:收益高就是好,简化夏普越大越好。但“好”是跟什么比?如果同期简单买入并持有也涨了很多,你忙活半天可能并没有占到便宜
第一章已经讲过 **Beta**(跟着市场赚的钱)和 **Alpha**(你靠技能多赚的钱)。本章要做的事,就是把 第三、四章 产出的策略放到“跟谁比”的语境里——搞清楚它赚的是 Beta 还是 Alpha。一个策略只赚到 Beta,买指数 ETF 就够了;只有创造出 Alpha,策略才有真正的价值。
本章要做的第一件事,就是把第 3 章、第 4 章产出的策略配置放到“跟谁比”的语境里。一个策略自己涨了,不代表它好。只有它比一个简单可行的参照做法更好,才说明这套规则可能有价值。
在开始之前,先复习四个你已经见过的“基础体检指标”——它们在第一、二、三章都已经登场过,第一次看到时回来表 5-3 就好。
在开始之前,先复习四个你已经见过的“基础体检指标”它们在第 1 章、第 2 章、第 3 章都已经登场过。如果一时忘了,回来表 5-3 就好。
**表 5-3 四个基础体检指标速查(第一、二、三章已登场)**
**表 5-3 四个基础体检指标速查(第 1 章、第 2 章、第 3 章已登场)**
| 指标 | 白话含义 |
|------|---------|
| 累计收益率 | 从头到尾总共赚了多少 |
| 年化收益率 | 平均每年赚多少 |
| 年化波动率 | 价格颠簸程度——数字越大越像坐过山车 |
| 最大回撤 | 最惨时从最高点跌了多少——衡量“最坏情况有多坏” |
| 年化波动率 | 价格上下波动的程度,数字越大越颠簸 |
| 最大回撤 | 最惨时从最高点跌了多少,用来衡量“最坏情况有多坏” |
这四个指标已经看出策略“赚没赚 / 颠不颠 / 摔得惨不惨”。但它们各自看一面——你还需要把“综合尺”把收益和风险揉到一起。这把尺第三章已经提过一次(夏普比率);本章实验跑完后我们**把它和另外两把“换种风险定义”的尺子(卡玛 / 索提诺)一起请上场**。先做实验,再上尺子
这四个指标已经看出策略“赚没赚、颠不颠、跌得深不深”。但它们各自看一面你还需要把“综合尺”把收益和风险放在一起看。第 3 章已经介绍过夏普比率,也说明本书表格里暂时使用的是简化口径:年化收益率 / 年化波动率。本章实验跑完后我们会把简化夏普和另外两把尺子放在一起看:卡玛比、索提诺比。先做实验,再解释
### 动手实验 1:策略 vs 买入持有基准
### 动手实验 1:策略对比买入持有基准
我们一起把这份 spec 写出来。这次重点看三件新东西:**金融指标的年化口径必须显式**、**oxq 评估与构造模块都要白话点名**、以及**防 LLM 幻觉的标准句**。
我们一起把这份 spec 写出来。这次重点看三件新东西:**金融指标的年化口径必须显式**、**open-xquant 评估与构造模块都要白话点名**、以及**防止 AI 写出错误分析的标准句**。
> 先看完三段演示再回到运行结果——这一节信息会比前面密一些,因为 5.1 是 q5 信息量最大的一节。
> 先看完下面的 spec 拆解,再回到运行结果这一节信息会比前面密一些,因为 5.1 是本章信息量最大的一节。
#### 起草上下文 + 任务
#### 第一、二段:上下文和任务描述
章是“看”的一章——把前几章产出的策略放进体检台。上下文不引入新的策略,只把第三、四章的产物集合起来:
5 章是“看”的一章把前几章产出的策略配置放进体检台。上下文不引入新的策略,只把第 3 章、第 4 章的产物集合起来:
> **上下文**:本章评估策略的有效性。第三、四章产出了 EqualWeight、RiskParity、TopNRanking、RiskParity+5% 止损四个策略,回测指标看起来不错。但“不错”是跟什么比?年化 15% 好不好,取决于同期市场涨了多少。
> **上下文**:本章评估策略是否真的好。第 3 章和第 4 章产出了 EqualWeight、RiskParity、TopNRanking、RiskParity+5% 止损四个策略配置,回测指标看起来不错。但“不错”是跟什么比?年化 15% 好不好,取决于同期一个简单参照做法赚了多少。
>
> **任务**:在 notebook `q5-how-to-validate.ipynb` 中,把四个策略和“等权买入持有”基准放在一起比较,引入多种风险调整指标。
> **任务描述**:在 notebook `q5-how-to-validate.ipynb` 中,把四个策略配置和“等权买入持有”基准放在一起比较,引入多种风险调整指标。
> 📌 **要点**:第章不引入新策略,只把前几章的产物集中送进体检台。spec 的上下文段把“被检查对象”明确点名——这种“集合体检”型 spec 的好处是:学员的认知负担只在“评估方法”这一条线上,不必再消化新的策略逻辑。
> 📌 **要点**:第 5 章不引入新策略,只把前几章的产物集中送进体检台。spec 的上下文段把“被检查对象”明确点名这种“集合体检”型 spec 的好处是:读者只需要学习评估方法,不必再消化新的策略逻辑。
#### 起草要求:模块点名 + 年化口径
#### 第三段:任务要求
要求段做两件硬约束。第一件**白话点名 oxq 评估端和构造端要用哪几个模块**——AI 自己去查接口细节。第二件**指标的年化方法必须显式锁定**——否则跨次/跨工具的数字对不上
任务要求段做两件硬约束。第一件,点名 open-xquant 评估端和构造端要用哪几个模块,接口细节让 AI 自己去查。第二件指标的年化方法必须写清楚,否则不同工具、不同时间跑出来的数字可能对不上。
> **要求**
> **任务要求**
>
> 1. 评估端使用 oxq 的 `RunResult`,指标的**计算口径**显式锁定:
> 1. 评估端使用 oxq 的 `RunResult`,指标的计算口径显式锁定:
> - 年化方法:**252 个交易日**(非 365 自然日)
> - 夏普`annualized_return / annualized_volatility`,无风险利率 `rf=0`
> - 简化夏普:`annualized_return / annualized_volatility`不扣无风险收益
> - 卡玛比:`annualized_return / abs(max_drawdown)`
> - 索提诺比:`annualized_return / downside_volatility`(下行 = 收益 < 0 的标准差)
> - 基准的夏普要用同一口径252 + rf=0
> 2. 构造端使用 oxq 的三个组合优化器——等权(`EqualWeightOptimizer`)、风险平价(`RiskParityOptimizer`)、动量排名(`TopNRankingOptimizer`),按各自的构造参数和所需指标实例化
> 3. 数据快照固定:`START = "2021-01-01"``END = "2025-12-31"`(本章数字以此为基准,不要用 `today`)
> - 基准的简化夏普要用同一口径
> 2. 构造端使用 oxq 的三个权重分配方法:等权(`EqualWeightOptimizer`)、风险平价(`RiskParityOptimizer`)、动量排名(`TopNRankingOptimizer`),按各自的构造参数和所需指标实例化
> 3. 数据快照固定:`START = "2021-01-01"``END = "2026-03-18"`(本章数字以此为基准,不要用 `today`)
> 4. 定义辅助函数 `run_strategy(portfolio, indicators=None, freq=10, stop_loss=None)` 供后续 spec 复用
> 5. 运行四个策略 + 构造“等权买入持有”基准(每只 ETF 归一化到 1,等权平均,之后不交易)
> 📌 **要点**:金融指标的年化口径(252 vs 365、`rf=0`、下行波动定义)是**专业默契,但 spec 必须写明**。零基础学员不知道这约定,同一个数字在不同文献里口径不同。如果 spec 不锁,你跑出 1.43,同学跑出 1.27,谁也不知道差在哪——指标 spec 的“精度”恰恰落在这些看不见的小字上。
> 📌 **要点**:金融指标的年化口径(252 个交易日还是 365 个自然日、是否扣无风险收益、下行波动怎么定义)必须写明。零基础读者不知道这约定,同一个指标在不同资料里口径可能不同。如果 spec 不锁定口径,你跑出 1.43别人跑出 1.27,谁也不知道差在哪指标 spec 的“精度”恰恰落在这些看不见的小字上。
> 📌 **要点****评估端 + 构造端两层接口分别点名**——要求 1 锁定评估端`RunResult` 的指标计算),要求 2 列出构造端(三个优化器)。零基础学员只需要知道"调谁",AI 会自己去查接口。spec 把"用哪些模块"白话点清楚就够了,剩下的对照工作交给 AI。
> 📌 **要点**:评估端和构造端要分别点名。第 1 锁定评估端,也就是 `RunResult` 的指标计算;第 2 列出构造端,也就是三个权重分配方法。零基础读者只需要知道“用哪些模块”,AI 会自己去查接口。spec 把模块点清楚,剩下的对照工作交给 AI。
#### 起草结果呈现:动态描述方向
#### 第四段:验收标准
最后一段最关键的不是“画几张图”,而是**怎么让 AI 在不知道结果方向的情况下写出准确的分析文字**
最后一段最关键的不是“画几张图”,而是怎么让 AI 在不知道结果方向的情况下写出准确的分析文字:
> **结果呈现**
> **验收标准**
>
> 1. 策略 vs 基准对比表(5 行 × 8 列):策略名 / 累计收益 / 年化收益 / 波动率 / 最大回撤 / 夏普 / 卡玛比 / 索提诺比;最后一行是等权买入持有基准
> 2. 超额收益(Alpha
> 1. 策略基准对比表(5 行 × 8 列):策略名 / 累计收益 / 年化收益 / 波动率 / 最大回撤 / 简化夏普 / 卡玛比 / 索提诺比;最后一行是等权买入持有基准
> 2. 相对基准收益差
> 3. 净值曲线对比图(figsize 12×6,4 实线 + 1 灰虚线,归一化到 100)
> 4. 分析文字(**根据实际数据动态描述方向,不要硬写“更高”或“更低”**):哪些策略跑赢了基准?三把尺子的冠军是不是同一个?
> 📌 **要点****「根据实际数据动态描述方向,不要硬写“更高”或“更低”」是 q5 章四份 spec 的标准句**。LLM 喜欢“看似中立其实瞎写”——不锁这一句,它会编一段听上去合理但和数据对不上的结论。把这句话当成 q5 的标配,任何要 AI 写“分析文字”的 spec 都该带上。
> 📌 **要点**:「根据实际数据动态描述方向,不要硬写“更高”或“更低”」是 5 章四份 spec 的标准句。AI 容易写出听上去合理但和数据对不上的结论。把这句话当成 5 的标配,任何要 AI 写“分析文字”的 spec 都该带上。
完整 spec 在 [`specs/spec-01-benchmark-and-metrics.md`](https://github.com/xingwudao/xquant-learning/blob/main/q5-how-to-validate/specs/spec-01-benchmark-and-metrics.md)——复制给 AI,弹窗选「允许」
完整示例 spec 在配套仓库的 [`q5-how-to-validate/specs/spec-01-benchmark-and-metrics.md`](https://github.com/xingwudao/xquant-learning/blob/main/q5-how-to-validate/specs/spec-01-benchmark-and-metrics.md)。参考它确认自己的 spec 后,再复制给 AI,按提示允许执行
AI 助手执行完毕后,你的 notebook 里应该出现一张对比表、一张超额收益表和一张净值曲线图。
AI 执行完毕后,你的 notebook 里应该出现一张对比表、一张相对基准收益表和一张净值曲线图。
这个实验做了什么?把 第三、四章 产出的四个策略(等权、风险平价、动量排名、风险平价+止损)全部重新跑一遍含成本的回测,同时构造一个“买入持有”基准——等权买入三只 ETF 后不再做任何交易,代表“什么都不做”的结果。然后把五条净值曲线画在一起,用七个指标(累计收益、年化收益、波动率、最大回撤、夏普、卡玛比、索提诺比)逐一比较,算出每个策略相对基准的超额收益(Alpha
这个实验做了什么?把第 3 章、第 4 章产出的四个策略配置(等权、风险平价、动量排名、风险平价 + 止损)全部重新跑一遍含成本的回测,同时构造一个“买入持有”基准等权买入三只 ETF 后不再做任何交易,代表“什么都不做”的结果。然后把五条净值曲线画在一起,用七个指标(累计收益、年化收益、波动率、最大回撤、简化夏普、卡玛比、索提诺比)逐一比较,算出每个策略相对基准多赚或少赚了多少
**运行结果**
### 实验结果
四个策略与基准的指标对比如表 5-4 所示。
**表 5-4 四个策略 vs 等权买入持有基准的指标对比**
**表 5-4 四个策略等权买入持有基准的指标对比**
| 策略 | 累计收益 | 年化收益 | 波动率 | 最大回撤 | 夏普 | 卡玛比 | 索提诺比 |
| 策略 | 累计收益 | 年化收益 | 波动率 | 最大回撤 | 简化夏普 | 卡玛比 | 索提诺比 |
|------|---------|---------|--------|---------|--------|--------|---------|
| EqualWeight | 84.37% | 12.33% | 12.49% | -16.86% | 1.05 | 0.73 | 0.92 |
| RiskParity | 98.01% | 13.77% | 11.16% | -12.81% | 1.29 | 1.08 | 1.16 |
@@ -131,11 +129,11 @@ AI 助手执行完毕后,你的 notebook 里应该出现了一张对比表、
| RP+止损5% | 106.23% | 14.59% | 10.61% | -13.07% | 1.43 | 1.12 | 1.33 |
| 等权买入持有 | 94.82% | 14.39% | 13.00% | -17.24% | 1.10 | — | — |
超额收益 / Alpha(策略累计 基准累计 94.82%)如表 5-5 所示。
相对基准收益差(策略累计收益 - 基准累计收益 94.82%)如表 5-5 所示。
**表 5-5 四个策略相对等权买入持有基准的 Alpha**
**表 5-5 四个策略相对等权买入持有基准的收益差**
| 策略 | Alpha |
| 策略 | 相对基准收益差 |
|------|-------|
| EqualWeight | -10.45% |
| RiskParity | +3.18% |
@@ -144,37 +142,37 @@ AI 助手执行完毕后,你的 notebook 里应该出现了一张对比表、
净值曲线对比如图 5-1 所示。
![策略 vs 买入持有基准](images/q5-how-to-validate/01-strategy-vs-benchmark-nav.png)
![策略对比买入持有基准](images/q5-how-to-validate/01-strategy-vs-benchmark-nav.png)
### 结果解读
第一个认知升级:**“好不好”必须有参照物。**
**基准(Benchmark** —— 用来做比较的参照物。这里的基准是“什么都不做”——等权买入三只 ETF 后不再交易。就像考试的“及格线”不跟基准比,你根本不知道自己的策略是好还是坏。
**基准(Benchmark**用来做比较的参照物。这里的基准是“什么都不做”等权买入三只 ETF 后不再交易。就像考试的“及格线”不跟基准比,你根本不知道自己的策略是好还是坏。
年化 14% 听起来很棒,但等权买入持有同期也赚了 14.39%。EqualWeight 忙活了一堆操作,累计收益 84.37%,反而没跑赢“什么都不做”的 94.82%——你忙活半天不如躺平
年化 14% 听起来不错,但等权买入持有同期也赚了 14.39%。EqualWeight 做了很多次交易,累计收益 84.37%,反而没跑赢“什么都不做”的 94.82%。这说明:多做事不一定带来更好结果
跑赢基准的部分叫 **超额收益(Alpha**——回忆一下 Alpha:第一章用电梯/楼梯类比讲过,Beta 是跟着市场赚的钱(被电梯抬着上楼),Alpha 是比市场多赚的钱(靠你自己爬楼梯爬出来的部分),是你的技能回报。这一章把它放回”有了基准之后才能算”的语境再用一次——上面对比表里的”基准累计 94.82%”就是那部电梯,每个策略减去基准的差额才是 Alpha。EqualWeight 的 Alpha 是 -10.45%,说明它不但没创造技能回报,反而因为频繁调仓的交易成本”亏”了。TopNRanking 的 Alpha 最高(+58.20%),但先别急着下结论——看看其他指标怎么说。
表 5-5 看的就是“比基准多赚或少赚了多少”。EqualWeight 的相对基准收益差是 -10.45%,说明它不但没有跑赢简单基准,反而可能被频繁调整持仓带来的交易成本拖累了。TopNRanking 的相对基准收益差最高(+58.20%),但先别急着下结论。还要看其他指标怎么说。
第二个发现:**不同的尺子量出不同的结论。**
实验跑完了,**正式介绍三把“综合尺”**——它们各自把收益和风险揉到一起,但对“风险”的定义不同:
实验跑完了,正式介绍三把“综合尺”它们各自把收益和风险放在一起,但对“风险”的定义不同:
- **夏普比率(Sharpe Ratio)** —— 第三章见过:年化收益除以年化波动率。把“收益高”和“颠簸大”折成一个数字。
- **卡玛比(Calmar Ratio** —— 年化收益除以最大回撤。每承受 1% 的最大回撤,能换来多少年化收益。如果你最怕“一次跌太深”,卡玛比是最直接的尺子。
- **索提诺比(Sortino Ratio** —— 只用**下行波动率**做分母。上涨波动不是风险——涨得猛不应该被惩罚,所以比夏普比更“公平”
- **简化夏普**:年化收益除以年化波动率。把“收益高”和“波动大”折成一个数字。注意,这里仍然沿用本书前面章节的简化口径,不扣无风险收益。
- **卡玛比(Calmar Ratio**年化收益除以最大回撤。每承受 1% 的最大回撤,能换来多少年化收益。如果你最怕“一次跌太深”,卡玛比是最直接的尺子。
- **索提诺比(Sortino Ratio**:只用下行波动率做分母。上涨波动不是风险涨得猛不应该被惩罚,所以索提诺比只关注亏损方向的波动
简单记忆:**夏普看“颠不颠”,卡玛看“摔多惨”,索提诺只关心“亏的部分”。**
把这三把尺子放进表 5-4 之后回头看——三把尺子的冠军都是 RP+止损5%:夏普 1.43、卡玛比 1.12、索提诺比 1.33。TopNRanking 虽然收益最高,但三把尺子都不是冠军。这次三把尺子碰巧给出了同一个冠军,但不是每次都这么巧——不同的尺子关注不同的风险维度,完全可能给出不同答案。你最怕什么风险,就用什么尺子。
把这三把尺子放进表 5-4 之后回头看三把尺子的冠军都是 RP+止损5%简化夏普 1.43、卡玛比 1.12、索提诺比 1.33。TopNRanking 虽然收益最高,但三把尺子都不是冠军。这次三把尺子碰巧给出了同一个冠军,但不是每次都这么巧不同的尺子关注不同的风险维度,完全可能给出不同答案。你最怕什么风险,就优先看对应的尺子。
但这些都是好几年平均出来的数字。**平均年化 14% 听着不错——但如果其中一年亏了 20%,你扛得住吗?**
但这些都是好几年平均出来的数字。**平均年化 14% 听着不错但如果其中一年亏了 20%,你扛得住吗?**
---
## 5.2 每年都有收益吗?
刚才的指标覆盖了 2021 年初到 2026 年 3 月,整整 5 年多、1250 个交易日。这么长时间的平均值,可能掩盖某些年份的亏损。就像考试平均 80 分,但其中一科考了 30 分。
刚才的指标覆盖了 2021 年初到 2026 年 3 月,整整 5 年多、1250 个交易日。这么长时间的平均值,可能掩盖某些年份的亏损。就像考试平均 80 分,但其中一科考了 30 分。
直觉告诉我们:平均好就是好吧,哪年赚哪年亏不重要?
@@ -184,48 +182,48 @@ AI 助手执行完毕后,你的 notebook 里应该出现了一张对比表、
我们一起把这份 spec 写出来。这次重点看两件新东西:**接续型 spec 共享 notebook 状态**、以及**时间维度切片要避免重复回测**。
#### 起草上下文 + 任务
#### 第一、二段:上下文和任务描述
spec-02 不需要重新介绍策略——它直接接着 spec-01 在同一个 notebook 里跑。上下文段把“已经有什么”列清楚就够了:
spec-02 不需要重新介绍策略它直接接着 spec-01 在同一个 notebook 里跑。上下文段把“已经有什么”列清楚就够了:
> **上下文**:在 `q5-how-to-validate.ipynb` 中已有四个策略 vs 基准的整体指标对比、`results` 字典、`run_strategy` 辅助函数、`TNR_PORTFOLIO` 等变量。学员已了解基准、超额收益(Alpha、卡玛比/索提诺比等概念。
> **上下文**:在 `q5-how-to-validate.ipynb` 中已有四个策略基准的整体指标对比、`results` 字典、`run_strategy` 辅助函数、`TNR_PORTFOLIO` 等变量。读者已了解基准、相对基准收益差、卡玛比索提诺比等概念。
>
> **任务**:按年度拆解策略表现,并深挖 TopNRanking 在负收益年份的异常。
> **任务描述**:按年度拆解策略表现,并深挖 TopNRanking 在负收益年份的异常。
> 📌 **要点**q5 章四份 spec 共享同一个 notebook 状态——spec-02 直接读 spec-01 留下的 `results`/`run_strategy`/`TNR_PORTFOLIO`。**接续型 spec 的上下文段不必再声明,只列“已经有什么”**。这样 AI 不会重新下载数据、不会重新构建策略、不会假设一个不存在的变量。
> 📌 **要点**5 章四份 spec 共享同一个 notebook 状态spec-02 直接读 spec-01 留下的 `results` / `run_strategy` / `TNR_PORTFOLIO`。接续型 spec 的上下文段不必从头说明,只列“已经有什么”。这样 AI 不会重新下载数据、不会重新构建策略、不会假设一个不存在的变量。
#### 起草要求:时间切片 + 缓存复用
#### 第三段:任务要求
要求段做两件事:按年度切片计算指标;深挖 TopNRanking 在负收益年份——**这里是最容易让数字打脸的地方**:
任务要求段做两件事:按年度切片计算指标;深挖 TopNRanking 在负收益年份。这是最容易出现重复计算和口径不一致的地方。
> **要求**
> **任务要求**
>
> 1. 用 oxq `RunResult` 的日收益序列(`daily_returns()`)和净值曲线(`equity_curve`)做时间切片
> 2. 按年度拆解四策略 + 基准:用 `daily_returns()` 按年分组,计算年收益率(累乘)、年波动率、年夏普、年内最大回撤。**跳过样本不足的年份**`if len(year_data) < 20: continue`,避开新年初只有几个交易日的边界)
> 2. 按年度拆解四策略 + 基准:用 `daily_returns()` 按年分组,计算年收益率(累乘)、年波动率、年简化夏普、年内最大回撤。跳过样本不足的年份(`if len(year_data) < 20: continue`,避开新年初只有几个交易日的边界)
> 3. 打印逐年对比表(pandas DataFrame,存入变量 `annual_df` 供后续 spec 复用)
> 4. 画逐年收益柱状图(figsize 14×6,每年一组 5 根柱子,与 spec-01 颜色一致)
> 5. 深挖 TopNRanking 负收益年份——扫描 `mom_periods = [5, 10, 15, 20, 25, 30]`**每个窗口只跑一次回测,结果存入 `deep_dive[period]`**,从字典里直接读,不要重新调用 `run_strategy` 做“是否所有窗口都亏”的二次判断
> 5. 深挖 TopNRanking 负收益年份扫描 `mom_periods = [5, 10, 15, 20, 25, 30]`,每个窗口只跑一次回测,结果存入 `deep_dive[period]`,从字典里直接读,不要重新调用 `run_strategy` 做“是否所有窗口都亏”的二次判断
> 📌 **要点****时间维度切片不要重复回测**。“按年度拆”听起来每年都跑一遍——错。整段时间只跑一次,得到日收益序列后按 `index.year` 分组算指标。深挖也一样:6 个动量窗口跑 6 次,**不要因为后面要二次判断又跑 6 次**。spec 不点名“结果存 dict 复用”,AI 写出的代码大概率会跑 12 次
> 📌 **要点**:时间维度切片不要重复回测。“按年度拆”听起来每年都跑一遍,其实不需要。整段时间只跑一次,得到日收益序列后按 `index.year` 分组算指标。深挖也一样:6 个动量窗口跑 6 次不要因为后面要二次判断又跑 6 次。spec 不点名“结果存 dict 复用”,AI 写出的代码大概率会重复回测
> 📌 **要点****最小样本过滤是隐式约定**。年内 < 20 个交易日不进表,是金融统计的默契。spec 不写明,AI 可能用 1 月份的 5 天数据计算“年化”得出离谱数字——表上突然多出一行“2026 年化 +800%”,学员看不出哪里错了。
> 📌 **要点**:最小样本过滤必须写清楚。年内少于 20 个交易日不进表,是为了避免样本太少导致年化数字失真。spec 不写明,AI 可能用 1 月份的 5 天数据计算“年化”得出离谱数字表上突然多出一行“2026 年化 +800%”,读者很难看出哪里错了。
#### 起草结果呈现
#### 第四段:验收标准
> **结果呈现**
> **验收标准**
>
> 1. 逐年收益对比表(DataFrame,行=年份,列=4 策略+基准,负收益用 `*` 标注)
> 2. 逐年收益柱状图
> 3. TopNRanking 异常年份深挖表(行=动量窗口,列=负收益年份)
> 4. 分析文字(**根据实际数据动态描述方向**):哪些策略“每年都赚”?深挖结论是“参数没选对”还是“策略结构性问题”?
完整 spec 在 [`specs/spec-02-annual-breakdown.md`](https://github.com/xingwudao/xquant-learning/blob/main/q5-how-to-validate/specs/spec-02-annual-breakdown.md)——复制给 AI,弹窗选「允许」
完整示例 spec 在配套仓库的 [`q5-how-to-validate/specs/spec-02-annual-breakdown.md`](https://github.com/xingwudao/xquant-learning/blob/main/q5-how-to-validate/specs/spec-02-annual-breakdown.md)。参考它确认自己的 spec 后,再复制给 AI,按提示允许执行
AI 助手执行完毕后,你的 notebook 里应该出现逐年对比表、柱状图和一份 TopNRanking 深挖分析。
AI 执行完毕后,你的 notebook 里应该出现逐年对比表、柱状图和一份 TopNRanking 深挖分析。
这个实验做了什么?把 Step 1 的整体指标拆成逐年成绩单——按年份分组计算每个策略和基准的年收益率、年波动率、年内最大回撤,看看哪些策略“每年都赚”哪些有负收益年份。然后针对 TopNRanking 在负收益年份做深挖:换遍 6 种动量窗口(5/10/15/20/25/30 天)重新跑回测,看这一年的亏损是参数选错了,还是策略本身的结构性问题。
这个实验做了什么?把第 1 个实验的整体指标拆成逐年成绩单按年份分组计算每个策略和基准的年收益率、年波动率、年内最大回撤,看看哪些策略“每年都赚”哪些有负收益年份。然后针对 TopNRanking 在负收益年份做深挖:换遍 6 种动量窗口(5 / 10 / 15 / 20 / 25 / 30 天)重新跑回测,看这一年的亏损是参数选错了,还是策略本身的结构性问题。
**运行结果**
### 实验结果
四个策略 + 基准的逐年收益拆解如表 5-6 所示(`*` 标注负收益年份)。
@@ -244,7 +242,7 @@ AI 助手执行完毕后,你的 notebook 里应该出现了逐年对比表、
![逐年收益对比](images/q5-how-to-validate/02-annual-return-comparison.png)
深挖 TopNRanking 在 2021 年的负收益——把动量窗口换遍,结果如表 5-7 所示。
深挖 TopNRanking 在 2021 年的负收益把动量窗口换遍,结果如表 5-7 所示。
**表 5-7 TopNRanking 2021 年不同动量窗口的年收益**
@@ -261,69 +259,69 @@ AI 助手执行完毕后,你的 notebook 里应该出现了逐年对比表、
### 结果解读
**分段检验(Segment Analysis** —— 把回测结果按时间段拆开检查。整体指标是“平均分”,逐年拆开才是“每科成绩单”——整体及格不等于每科都及格。
**分段检验(Segment Analysis**把回测结果按时间段拆开检查。整体指标是“平均分”,逐年拆开才是“每科成绩单”整体及格不等于每科都及格。
拆开后两个重要发现:
**第一,没有哪个策略“年年赚钱”。** EqualWeight、RiskParity 和 RP+止损5% 在 2022 年都出现了负收益,同期基准也亏了 -13.44%——策略和基准一起亏,说明策略并没有真正提供“下跌保护”。这是资产配置策略的特点:它能分散风险,但不能消除市场整体下跌带来的损失。
**第一,没有哪个策略“年年赚钱”。** EqualWeight、RiskParity 和 RP+止损5% 在 2022 年都出现了负收益,同期基准也亏了 -13.44%策略和基准一起亏,说明策略并没有真正提供“下跌保护”。这是资产配置策略的特点:它能分散风险,但不能消除市场整体下跌带来的损失。
**第二,TopNRanking 的负收益不是参数问题。** 2021 年,TopNRanking 是唯一亏钱的策略(-10.09%),而其他三个策略和基准都是正收益。换遍所有动量窗口(5 天到 30 天),在 2021 年全部亏钱。这是**动量策略的结构性弱点**。
**动量滞后性** —— 动量指标基于“过去涨了多少”来判断买什么。但当市场反复上下波动、没有明确方向时,过去涨得好的恰恰可能是“已经涨到头了”的那个。动量指标看的是后视镜——它有天然的滞后性。2021 年的市场就是典型的来回波动环境,动量策略在这种环境下注定吃亏,换什么参数都救不了
**动量滞后性**动量指标基于“过去涨了多少”来判断买什么。但当市场反复上下波动、没有明确方向时,过去涨得好的恰恰可能是“已经涨到头了”的那个。动量指标看的是后视镜它有天然的滞后性。2021 年的市场就是典型的来回波动环境,动量策略在这种环境下容易吃亏,换什么参数都不一定能解决
**数据说了算:逐年拆开,才能看到整体指标掩盖的问题。不同策略类型有不同的市场环境偏好。**
但年度还是太粗了——知道了哪年亏钱,但赚钱的年份里,是每月稳稳地赚,还是靠某几个月的暴利覆盖其他月份的亏损?
但年度还是太粗了知道了哪年亏钱还不够。赚钱的年份里,是每月稳稳地赚,还是靠某几个月的大涨覆盖其他月份的亏损?
---
## 5.3 收益结构什么样?
年度表现解释了“什么时候赚、什么时候亏”,但没解释“怎么赚的”——是每个月稳稳地赚一点,还是靠几次大赚覆盖很多次小亏?
年度表现解释了“什么时候赚、什么时候亏”,但没解释“怎么赚的”是每个月稳稳地赚一点,还是靠几次大赚覆盖很多次小亏?
同样赚钱的两个策略,赚法可能完全不同。你能接受“一年中 8 个月亏钱、靠 4 个月大赚拉回来”的策略吗?
另外,回撤不只是一个数字——最大回撤 -15% 但 3 个月恢复,和最大回撤 -10% 但 1 年没恢复,哪个更痛苦?
另外,回撤不只是一个数字最大回撤 -15% 但 3 个月恢复,和最大回撤 -10% 但 1 年没恢复,哪个更痛苦?
### 动手实验 3:月度分布 + 回撤深度
我们一起把这份 spec 写出来。这次重点看两件新东西:**视觉契约要写到颜色与子图布局**以及**关键阈值(包括看似微小的 -0.001)必须显式**。
我们一起把这份 spec 写出来。这次重点看两件新东西:**图表约定要写到颜色与子图布局**以及**关键阈值(包括看似微小的 -0.001)必须显式**。
#### 起草上下文 + 任务
#### 第一、二段:上下文和任务描述
接续 spec-02 的发现:胜率全时段都差不多,但 2021 年有人亏 10% 有人赚 3%。这一份 spec 把“为什么差距这么大”拆开看:
> **上下文**notebook 里已有四策略 vs 基准的整体对比、逐年拆解、TopNRanking 在反复波动市场中的结构性弱点。
> **上下文**notebook 里已有四策略基准的整体对比、逐年拆解、TopNRanking 在反复波动市场中的结构性弱点。
>
> **任务**:分析月度收益分布和历史回撤事件——同样赚钱的策略,赚法可能完全不同。
> **任务描述**:分析月度收益分布和历史回撤事件同样赚钱的策略,赚法可能完全不同。
#### 起草要求:阈值显式 + 视觉契约
#### 第三段:任务要求
第一组要求做月度分布。第二组做回撤事件——**回撤事件的“什么算事件”阈值是这份 spec 最容易被遗漏的精度点**
第一组要求做月度分布。第二组做回撤事件回撤事件的“什么算事件”阈值是这份 spec 最容易被遗漏的精度点
> **要求**
> **任务要求**
>
> 1. 用 oxq `RunResult` 的月度收益(`monthly_returns()`)和回撤序列(`drawdown_series()`)做收益结构分析
> 2. 月度分布分析(四策略):胜率、平均盈利月、平均亏损月、盈亏比、盈亏因子、最长连赢/连亏(**月收益 = 0 算亏损**)
> 3. 打印月度统计对比表 + 画月度收益分布直方图(figsize 14×82×2 子图,20 binsx=0 处红色虚线)
> 4. 回撤深度分析——识别每次回撤事件,**阈值锁定 `THRESHOLD = -0.001`**(即跌幅 ≥ 0.1% 才算事件,避开数值噪声生成假事件):起点 = dd 跌穿阈值的那天,恢复 = dd 反弹回阈值之上的那天,未恢复则标“—”
> 4. 回撤深度分析识别每次回撤事件,阈值锁定 `THRESHOLD = -0.001`(即跌幅 ≥ 0.1% 才算事件,避开数值噪声生成假事件):起点 = dd 跌穿阈值的那天,恢复 = dd 反弹回阈值之上的那天,未恢复则标“—”
> 5. 打印每个策略前 5 大回撤事件 + 回撤汇总
> 6. 画水下曲线(figsize 14×104 子图垂直,**与 spec-01 同色继承**EW=#4472C4 / RP=#ED7D31 / TNR=#70AD47 / RP+止损=#FFC000
> 7. 拆解 TopNRanking 2021 年月度——证明胜率 50% 但盈亏比 0.47 的“胜率陷阱”
> 7. 拆解 TopNRanking 2021 年月度,说明为什么胜率 50% 但盈亏比 0.47 仍然会亏钱
> 📌 **要点****视觉契约的“阈值”包括看不见的数字**。回撤事件的 `THRESHOLD = -0.001` 看似微不足道,但它直接决定有多少行进表——`-0.001` 可能给你 50 个事件,`-0.01` 可能只剩 10 个。spec 不锁阈值,AI 选不同值得出完全不同的“前 5 大回撤”,学员两次跑结果对不上,根本不知道差在哪。**任何“看起来微小”的阈值都是 spec 必写项**
> 📌 **要点**图表和事件识别里的“阈值”包括看不见的数字。回撤事件的 `THRESHOLD = -0.001` 看似微不足道,但它直接决定有多少行进表`-0.001` 可能给你 50 个事件,`-0.01` 可能只剩 10 个。spec 不锁阈值,AI 选不同值就会得出完全不同的“前 5 大回撤”,读者两次跑结果对不上,根本不知道差在哪。任何“看起来微小”的阈值,都应该写进 spec
> 📌 **要点****跨 spec 颜色继承**。spec-01 的净值曲线用了 4 个颜色,spec-03 的水下曲线必须沿用——否则学员从“绿色 = TNR 净值线”到“水下曲线全是蓝色”,无法联结哪条是 TNR。q5 章一开始就锁的“视觉编码”(颜色、figsize、布局)要在每份后续 spec 显式继承,不能假设 AI 会自动记住。
> 📌 **要点**:跨 spec 颜色继承。spec-01 的净值曲线用了 4 个颜色,spec-03 的水下曲线也要沿用。否则读者刚记住“绿色 = TNR 净值线”,下一张图又变成全蓝色,就很难对应。第 5 章一开始锁定的图表约定(颜色、figsize、布局)要在每份后续 spec 显式继承,不能假设 AI 会自动记住。
#### 起草结果呈现
#### 第四段:验收标准
> **结果呈现**:月度统计表(4 行)+ 月度直方图(2×2+ TopNRanking 2021 年逐月明细 + 四策略 2021 月度对比 + 前 5 大回撤事件表 + 回撤汇总表 + 水下曲线图(4 子图)+ 分析文字(**根据实际数据动态描述方向**
> **验收标准**:月度统计表(4 行)+ 月度直方图(2×2+ TopNRanking 2021 年逐月明细 + 四策略 2021 月度对比 + 前 5 大回撤事件表 + 回撤汇总表 + 水下曲线图(4 子图)+ 分析文字(根据实际数据动态描述方向)
完整 spec 在 [`specs/spec-03-return-distribution-and-drawdown.md`](https://github.com/xingwudao/xquant-learning/blob/main/q5-how-to-validate/specs/spec-03-return-distribution-and-drawdown.md)——复制给 AI,弹窗选「允许」
完整示例 spec 在配套仓库的 [`q5-how-to-validate/specs/spec-03-return-distribution-and-drawdown.md`](https://github.com/xingwudao/xquant-learning/blob/main/q5-how-to-validate/specs/spec-03-return-distribution-and-drawdown.md)。参考它确认自己的 spec 后,再复制给 AI,按提示允许执行
AI 助手执行完毕后,你的 notebook 里应该出现月度统计表、分布直方图、2021 年胜率陷阱分析、回撤事件表和水下曲线图。
AI 执行完毕后,你的 notebook 里应该出现月度统计表、分布直方图、2021 年胜率陷阱分析、回撤事件表和水下曲线图。
这个实验做了什么?从两个维度拆解“怎么赚的钱”。第一个维度是月度收益分布——把每个策略的日收益按月汇总,统计胜率(赚钱月占比)、盈亏比(平均赚多少 vs 平均亏多少)、最长连赢/连亏月数等。还专门拆解了 TopNRanking 2021 年的逐月明细,和其他三个策略对比同年的胜率和盈亏比,解释为什么胜率差不多、结局却完全不同。第二个维度是回撤深度——识别每次从高点下跌到恢复的完整事件,记录深度、下跌天数、恢复天数,画出水下曲线。
这个实验做了什么?从两个维度拆解“怎么赚的钱”。第一个维度是月度收益分布把每个策略的日收益按月汇总,统计胜率(赚钱月占比)、盈亏比(平均赚多少平均亏多少)、最长连赢/连亏月数等。还专门拆解了 TopNRanking 2021 年的逐月明细,和其他三个策略对比同年的胜率和盈亏比,解释为什么胜率差不多、结局却完全不同。第二个维度是回撤深度识别每次从高点下跌到恢复的完整事件,记录深度、下跌天数、恢复天数,画出水下曲线。
内容比较多,我们分两个维度来看。
@@ -344,9 +342,9 @@ AI 助手执行完毕后,你的 notebook 里应该出现了月度统计表、
![月度收益分布](images/q5-how-to-validate/03-monthly-return-distribution.png)
乍一看,四个策略的胜率都差不多——62% 左右。但 Step 2 告诉我们 TopNRanking 在 2021 年亏了 10%,而其他策略同年都是正收益。胜率差不多,为什么结局完全不同?拆解 2021 年的月度数据就能看清原因。
乍一看,四个策略的胜率都差不多,都是 62% 左右。但第 2 个实验告诉我们TopNRanking 在 2021 年亏了 10%,而其他策略同年都是正收益。胜率差不多,为什么结局完全不同?拆解 2021 年的月度数据就能看清原因。
**拆解 TopNRanking 2021 年——胜率的陷阱**
**拆解 TopNRanking 2021 年胜率的陷阱**
TopNRanking 在 2021 年的逐月收益如表 5-9 所示。
@@ -378,15 +376,15 @@ TopNRanking 在 2021 年的逐月收益如表 5-9 所示。
| TopNRanking | 50% | 1.46% | -3.14% | 0.47 | -10.09% |
| RP+止损5% | 58% | 1.77% | -1.82% | 0.97 | 3.04% |
看上面的 2021 年对比——TopNRanking 的胜率 50%,跟 EqualWeight 一样。但它赚的月份平均只赚 1.46%,亏的月份平均亏 3.14%,盈亏比只有 0.47。翻译一下:赚的时候赚得少,亏的时候亏得。结果虽然一半的月份赚钱,但亏损月把所有利润全吃掉还倒贴了,全年 -10.09%。
看上面的 2021 年对比TopNRanking 的胜率 50%,跟 EqualWeight 一样。但它赚的月份平均只赚 1.46%,亏的月份平均亏 3.14%,盈亏比只有 0.47。翻译一下:赚的时候赚得少,亏的时候亏得。结果是,虽然一半的月份赚钱,但亏损月把所有利润全吃掉还倒贴了,全年 -10.09%。
同年的 EqualWeight 胜率也是 50%,但盈亏比 1.48——赚的月份平均赚 2.03%,亏的月份平均只亏 1.37%。同样的胜率,完全不同的结局。
同年的 EqualWeight 胜率也是 50%,但盈亏比 1.48赚的月份平均赚 2.03%,亏的月份平均只亏 1.37%。同样的胜率,完全不同的结局。
这就是 **胜率的陷阱** —— 大多数人天然觉得“赢的次数越多越好”,但胜率本身意义不大。胜率 90%,每次赚 1 元;亏的那 10% 每次亏 20 元。10 次交易赚 9 元、亏 20 元,净亏 11 元。高胜率,亏钱。
这就是**胜率的陷阱**大多数人天然觉得“赢的次数越多越好”,但胜率本身意义不大。胜率 90%,每次赚 1 元;亏的那 10% 每次亏 20 元。10 次交易赚 9 元、亏 20 元,净亏 11 元。高胜率,也可能亏钱。
真正决定收益的是 **盈亏比(Win/Loss Ratio** —— 平均每次赚多少除以平均每次亏多少。优秀策略的秘诀是“小亏大赚”:用止损果断控制亏损幅度(小亏),赶上趋势时让利润奔跑(大赚)。追求高胜率,往往是捡了芝麻丢了西瓜
真正决定收益的是**盈亏比(Win/Loss Ratio**平均每次赚多少除以平均每次亏多少。优秀策略的关键是“小亏大赚”:亏的时候尽量控制亏损幅度,赚的时候让利润继续增长。只追求高胜率,可能会忽略亏损幅度
**盈亏因子(Profit Factor** —— 总盈利除以总亏损的绝对值。大于 1 说明赚的比亏的多,大于 2 算优秀。四个策略的盈亏因子都在 2.7-3.1 之间,都算优秀。但注意这是全时段的数字——拆到具体年份比如 2021TopNRanking 的盈亏因子会远低于 1。
**盈亏因子(Profit Factor**总盈利除以总亏损的绝对值。大于 1 说明赚的比亏的多,大于 2 通常已经不错。四个策略的盈亏因子都在 2.73.1 之间,整体看都不错。但注意这是全时段的数字拆到具体年份比如 2021TopNRanking 的盈亏因子会远低于 1。
**维度二:回撤深度**
@@ -404,7 +402,7 @@ EqualWeight 的前 5 大回撤事件如表 5-11 所示。
| 4 | 2026-01-30 | 2026-02-02 | 未恢复 | -7.95% | 3 | — |
| 5 | 2024-07-18 | 2024-08-05 | 2024-09-26 | -7.82% | 18 | 52 |
最大回撤: -16.86% / 平均恢复: 17天 / 最长恢复: 251天
最大回撤-16.86% / 平均恢复17 天 / 最长恢复251
RiskParity 的前 5 大回撤事件如表 5-12 所示。
@@ -418,7 +416,7 @@ RiskParity 的前 5 大回撤事件如表 5-12 所示。
| 4 | 2026-01-30 | 2026-02-02 | 未恢复 | -6.02% | 3 | — |
| 5 | 2024-07-19 | 2024-08-05 | 2024-09-26 | -5.94% | 17 | 52 |
最大回撤: -12.81% / 平均恢复: 15天 / 最长恢复: 203天
最大回撤-12.81% / 平均恢复15 天 / 最长恢复203
TopNRanking 的前 5 大回撤事件如表 5-13 所示。
@@ -432,7 +430,7 @@ TopNRanking 的前 5 大回撤事件如表 5-13 所示。
| 4 | 2023-07-20 | 2023-12-05 | 2024-02-23 | -9.34% | 138 | 80 |
| 5 | 2025-04-23 | 2025-04-28 | 2025-06-30 | -6.48% | 5 | 63 |
最大回撤: -17.77% / 平均恢复: 14天 / 最长恢复: 292天
最大回撤-17.77% / 平均恢复14 天 / 最长恢复292
RP+止损5% 的前 5 大回撤事件如表 5-14 所示。
@@ -446,7 +444,7 @@ RP+止损5% 的前 5 大回撤事件如表 5-14 所示。
| 4 | 2024-07-19 | 2024-08-05 | 2024-09-26 | -5.94% | 17 | 52 |
| 5 | 2025-02-20 | 2025-04-07 | 2025-04-11 | -5.57% | 46 | 4 |
最大回撤: -13.07% / 平均恢复: 13天 / 最长恢复: 210天
最大回撤-13.07% / 平均恢复13 天 / 最长恢复210
四个策略的水下曲线如图 5-4 所示,可以直观看到“在水下度过的时间”。
@@ -454,84 +452,80 @@ RP+止损5% 的前 5 大回撤事件如表 5-14 所示。
### 结果解读
收益结构揭示了“怎么赚的钱”——两个关键维度。
收益结构揭示了“怎么赚的钱”,这里有两个关键维度。
**胜率是陷阱,盈亏比才是关键。** 四个策略全时段的胜率都在 62% 左右,看不出差别。但拆到 2021 年,TopNRanking 的盈亏比只有 0.47——每赚 1 块钱,对应要亏 2 块多。同年 EqualWeight 盈亏比 1.48,赚的月份能覆盖亏的月份。同样的胜率,截然不同的结局。真正决定收益的不是赢的次数,而是赢的时候赚多少。
**胜率是陷阱,盈亏比才是关键。** 四个策略全时段的胜率都在 62% 左右,看不出差别。但拆到 2021 年,TopNRanking 的盈亏比只有 0.47每赚 1 块钱,对应要亏 2 块多。同年 EqualWeight 盈亏比 1.48,赚的月份能覆盖亏的月份。同样的胜率,截然不同的结局。真正决定收益的不是赢的次数,而是赢的时候赚多少、亏的时候亏多少
**回撤不只是深度,还有恢复时间。** TopNRanking 的最大回撤 -17.77%,从 2021 年 2 月开始下跌,直到 2023 年 3 月才恢复——整整两年在“水下”。RiskParity 最大回撤 -12.81%,恢复用了 203 天。RP+止损5% 最大回撤 -13.07%,恢复用了 210 天。
**回撤不只是深度,还有恢复时间。** TopNRanking 的最大回撤 -17.77%,从 2021 年 2 月开始下跌,直到 2023 年 3 月才恢复整整两年都没有回到前高。RiskParity 最大回撤 -12.81%,恢复用了 203 天。RP+止损5% 最大回撤 -13.07%,恢复用了 210 天。
**水下曲线(Underwater Curve** —— 每天距离历史最高点跌了多少。它直观展示一个残酷的事实:你有多少时间是在“还没回本”的状态中度过的。回撤不只是一个数字——它是一段煎熬。深度和恢复时间同样重要。
**水下曲线(Underwater Curve**每天距离历史最高点跌了多少。它直观展示一个残酷的事实:你有多少时间是在“还没回本”的状态中度过的。回撤不只是一个数字它是一段真实的持有体验。深度和恢复时间同样重要。
**回撤恢复时间** —— 从最低点爬回前高需要多久。回撤 -15% 但 3 个月恢复,vs 回撤 -10% 但 1 年没恢复——后者对实际持有体验的伤害更大。比起回撤深度,恢复时间更影响你能不能拿得住。
**回撤恢复时间**从最低点爬回前高需要多久。回撤 -15% 但 3 个月恢复,回撤 -10% 但 1 年没恢复相比,后者对实际持有体验的伤害可能更大。比起回撤深度,恢复时间更影响你能不能拿得住。
**数据说了算:赚钱的方式和赚钱一样重要。** 别被高胜率迷惑,关注盈亏比;别只看回撤深度,还要看恢复时间。
评估够全面了吗?其实还有一个隐藏问题——你用的参数(动量窗口 20 天、波动率窗口 20 天、止损阈值 5%)是回测出来的“最优值”。但如果把 20 天前后扰动一下,比如改成 15 天或 25 天,结果还差不多吗?如果稍微一动就天差地别,那这个“最优”在未来的数据上恐怕也靠不住。
评估够全面了吗?其实还有一个隐藏问题你用的参数(动量窗口 20 天、波动率窗口 20 天、止损阈值 5%)是在历史数据里选出来的。但如果把 20 天前后扰动一下,比如改成 15 天或 25 天,结果还差不多吗?如果稍微一动就差很多,那这个“最优”在未来的数据上恐怕也靠不住。
---
## 5.4 参数动一动,崩不崩
## 5.4 参数动一动,结果还稳吗
前面三的评估都基于特定参数——动量窗口 20 天、波动率窗口 20 天、止损阈值 5%。这些参数是回测跑出来的,在历史数据上表现最好
前面三个实验的评估都基于特定参数动量窗口 20 天、波动率窗口 20 天、止损阈值 5%。这些参数是在历史数据里选出来的,至少在这段数据上表现不错
但问题是:在最优参数附近前后扰动一下——比如动量窗口从 20 天改成 15 天或 25 天——结果是差不多,还是会差很多?如果附近的参数表现都还行,说明这个最优”是真的稳;如果稍微一动夏普就从 1.27 跌到 0.85,那历史上的最优”很可能在未来数据上不再最优。
但问题是:在最优参数附近前后扰动一下比如动量窗口从 20 天改成 15 天或 25 天结果是差不多,还是会差很多?如果附近的参数表现都还行,说明这个最优”相对稳定;如果稍微一动,简化夏普就从 1.27 跌到 0.85,那历史上的最优”很可能在未来数据上不再最优。
直觉告诉我们:找到最优参数就行了。真的吗?
### 动手实验 4:参数敏感性扫描
我们一起把这份 spec 写出来。这次重点看两件新东西:**与 q1/spec-06 同样的扫描技术、不同的教学目的**——必须显式区分;以及**“高原 vs 山峰”的视觉对比要靠 y 轴统一来呈现**
我们一起把这份 spec 写出来。这次重点看两件新东西:同样是参数扫描,这次目的不同;要让“高原型”和“山峰”的视觉对比成立,y 轴范围必须统一
#### 起草上下文 + 任务
#### 第一、二段:上下文和任务描述
学员第二次见到参数扫描——上次是第一章用来“找最优参数”这次是用来“判稳健”。这种“老技术新目的”必须在上下文段就讲清楚,否则学员会困惑为什么又来一次:
这是读者第二次见到参数扫描。上次在第 1 章,是用来“找最优参数”这次在第 5 章,是用来“判断结果是否稳定”。同样的技术,不同的目的。这个区别必须在上下文段就讲清楚,否则读者会困惑为什么又来一次:
> **上下文**:notebook 里已有四策略的整体指标、逐年收益、月度分布、回撤分析。所有分析都基于特定参数(period=20, freq=10, threshold=0.05)。
>
> 当前问题:如果参数稍微变一下,结论还成立吗?
>
> **注意**:这里的参数扫描和第章 spec-06 不同——
> - 第一章是“用扫描找最优参数”(目的:挑值)
> - 本章是“用扫描判稳健性”(目的:判形状)
> **注意**:这里的参数扫描和第 1 章 spec-06 不同。第 1 章是“用扫描找最优参数”,目的是挑值;本章是“用扫描判稳健性”,目的是看形状。同样的技术,完全不同的目的。本章不是要找出比 20 更好的窗口,而是要判断“在 20 附近的所有值,结果是不是都差不多”。
>
> 同样的技术,完全不同的目的。本章不是要找出比 20 更好的窗口,而是要判断“在 20 附近的所有值,结果是不是都差不多”。
>
> **任务**:扫描 RiskParity 波动率窗口、TopNRanking 动量窗口、止损阈值三个维度,对比“高原型”和“山峰型”。
> **任务描述**:扫描 RiskParity 波动率窗口、TopNRanking 动量窗口、止损阈值三个维度,对比“高原型”和“山峰型”。
> 📌 **要点****同方法不同目的,必须在 spec 里显式区分**。参数扫描在第章是过拟合的诱因(找漂亮数字),在第五章是过拟合的解药(看是不是高原)。如果 spec 不点名“这次不是为了找最优”,学员会以为“哦又是一次找最优,下一章 q6 讲过拟合时就接不上。**工具的义会随章节变化——spec 必须写出当前章节用它干什么**
> 📌 **要点**:同方法不同目的,必须在 spec 里显式区分。参数扫描在第 1 章是过拟合的诱因,因为它容易让你挑出漂亮数字;在第 5 章则是检查稳健性的方法,因为它用来观察附近参数是不是都差不多。如果 spec 不点名“这次不是为了找最优”,读者会以为又是一次找最优,下一章讲过拟合时就接不上。工具的义会随章节变化spec 必须写出当前章节用它干什么。
#### 起草要求:扫描点设计 + 极差对比
#### 第三段:任务要求
> **要求**
> **任务要求**
>
> 1. RiskParity 波动率窗口扫描 `vol_period = [10, 15, 20, 25, 30, 40]`:中心 20 是 spec-01 选定值,左右对称展开 ±10/±15,加 40 看远端外推;不试 5(5 天太短无法稳定估计波动率)
> 2. TopNRanking 动量窗口扫描 `mom_period = [10, 15, 20, 25, 30, 40]`(波动率窗口固定 20
> 3. 止损阈值扫描 `thresholds = [0.02, 0.03, 0.05, 0.07, 0.10, 0.15, 0.20]` + 无止损(共 8 行表,无止损作为最后一行,“止损阈值”列填“无”)
> 4. 计算每组的夏普极差(max - min),打印对比 + 标注“高原型”(极差小)vs“山峰型”(极差大)
> 5. 画三子图折线(figsize 15×5,1×3 布局),**y 轴范围统一**——这样 RP 的“平”和 TNR 的“尖”才能立刻可见
> 4. 计算每组的简化夏普极差(max - min),打印对比 + 标注“高原型”(极差小)“山峰型”(极差大)
> 5. 画三子图折线(figsize 15×5,1×3 布局),y 轴范围统一这样 RP 的“平”和 TNR 的“尖”才能立刻可见
> 6. 输出“山顶营地”隐喻:高原型像一片平坦的高原(站在哪都差不多高),山峰型像一座尖峰(偏一步就掉下去)
> 📌 **要点****参数列表的设计必须有可解释的对称性**。`[10, 15, 20, 25, 30, 40]` 不是随便挑——中心 20 是当前选定值,左右展开看附近,加远端 40 看外推。spec 不解释扫描点的来历,学员仿写时只会傻乎乎地照抄数字,遇到自己的策略不知道该挑哪些值扫。
> 📌 **要点**:参数列表的设计必须能解释。`[10, 15, 20, 25, 30, 40]` 不是随便挑中心 20 是当前选定值,左右展开看附近,加远端 40 看外推。spec 不解释扫描点的来历,读者仿写时只会照抄数字,遇到自己的策略不知道该挑哪些值扫。
> 📌 **要点****y 轴统一是“高原 vs 山峰”对比的视觉钥匙**。三个子图各自缩放,看起来都“波动很大”每个图都填满了画布);统一 ylim 后,RP 是平的TNR 是尖的,视觉冲击力立现。**对比图必须共享坐标轴**——这是参数敏感性可视化的固定动作
> 📌 **要点**y 轴统一是“高原型”和“山峰”对比能不能看出来的关键。三个子图如果各自缩放,看起来都“波动很大”,因为每个图都填满了画布统一 ylim 后,RP 是平的TNR 是尖的,差异会立刻出现。参数敏感性对比图,坐标轴要尽量共享
#### 起草结果呈现
#### 第四段:验收标准
> **结果呈现**RiskParity 波动率窗口表(6×5+ TopNRanking 动量窗口表(6×5)+ 止损阈值表(8×5)+ 夏普极差对比 + 折线敏感性图(1×3)+ 分析文字(**根据实际数据动态描述方向**),用“山顶营地”隐喻收束
> **验收标准**RiskParity 波动率窗口表(6×5+ TopNRanking 动量窗口表(6×5)+ 止损阈值表(8×5)+ 简化夏普极差对比 + 折线敏感性图(1×3)+ 分析文字(根据实际数据动态描述方向),用“山顶营地”隐喻收束
完整 spec 在 [`specs/spec-04-parameter-sensitivity.md`](https://github.com/xingwudao/xquant-learning/blob/main/q5-how-to-validate/specs/spec-04-parameter-sensitivity.md)——复制给 AI,弹窗选「允许」
完整示例 spec 在配套仓库的 [`q5-how-to-validate/specs/spec-04-parameter-sensitivity.md`](https://github.com/xingwudao/xquant-learning/blob/main/q5-how-to-validate/specs/spec-04-parameter-sensitivity.md)。参考它确认自己的 spec 后,再复制给 AI,按提示允许执行
AI 助手执行完毕后,你的 notebook 里应该出现三组参数敏感性表和一张折线对比图。
AI 执行完毕后,你的 notebook 里应该出现三组参数敏感性表和一张折线对比图。
这个实验做了什么?分三组做参数扰动测试。第一组:保持其他参数不变,把 RiskParity 的波动率窗口从 10 天到 40 天逐个试一遍,看夏普变化多大。第二组:对 TopNRanking 做同样的事,把动量窗口从 10 天到 40 天逐个试。第三组:固定 RiskParity 策略,把止损阈值从 2% 到 20% 逐个试(外加无止损对照)。然后算出每组的夏普极差(最高减最低),判断策略是“高原型”(参数怎么调都差不多)还是“山峰型”(偏一点就掉到水里)。
这个实验做了什么?分三组做参数扰动测试。第一组:保持其他参数不变,把 RiskParity 的波动率窗口从 10 天到 40 天逐个试一遍,看简化夏普变化多大。第二组:对 TopNRanking 做同样的事,把动量窗口从 10 天到 40 天逐个试。第三组:固定 RiskParity 策略,把止损阈值从 2% 到 20% 逐个试(外加无止损对照)。然后算出每组的简化夏普极差(最高减最低),判断策略是“高原型”(参数怎么调都差不多)还是“山峰型”(偏一点结果就明显变差)。
**运行结果**
### 实验结果
RiskParity 波动率窗口扫描结果如表 5-15 所示。
**表 5-15 RiskParity 波动率窗口敏感性**
| 窗口(天) | 累计收益 | 夏普 | 卡玛比 | 回撤 |
| 窗口(天) | 累计收益 | 简化夏普 | 卡玛比 | 回撤 |
|----------|---------|--------|--------|------|
| 10 | 104.32% | 1.33 | 1.13 | -12.80% |
| 15 | 102.65% | 1.33 | 1.13 | -12.64% |
@@ -540,13 +534,13 @@ RiskParity 波动率窗口扫描结果如表 5-15 所示。
| 30 | 109.29% | 1.39 | 1.10 | -13.49% |
| 40 | 111.45% | 1.40 | 1.05 | -14.37% |
夏普极差: 0.13
简化夏普极差0.13
TopNRanking 动量窗口扫描结果如表 5-16 所示。
**表 5-16 TopNRanking 动量窗口敏感性**
| 窗口(天) | 累计收益 | 夏普 | 卡玛比 | 回撤 |
| 窗口(天) | 累计收益 | 简化夏普 | 卡玛比 | 回撤 |
|----------|---------|--------|--------|------|
| 10 | 104.21% | 1.00 | 0.72 | -19.91% |
| 15 | 83.31% | 0.85 | 0.73 | -16.78% |
@@ -555,13 +549,13 @@ TopNRanking 动量窗口扫描结果如表 5-16 所示。
| 30 | 105.42% | 1.02 | 0.87 | -16.64% |
| 40 | 101.54% | 1.00 | 0.74 | -19.03% |
夏普极差: 0.42
简化夏普极差0.42
止损阈值的扫描结果如表 5-17 所示(基于 RiskParity 策略,调仓频率 10 天)。
止损阈值的扫描结果如表 5-17 所示(基于 RiskParity 策略,再平衡频率 10 天)。
**表 5-17 止损阈值敏感性**
| 止损阈值 | 累计收益 | 夏普 | 最大回撤 | 交易次数 |
| 止损阈值 | 累计收益 | 简化夏普 | 最大回撤 | 交易次数 |
|---------|---------|--------|---------|---------|
| 2% | 125.87% | 1.63 | -9.65% | 402 |
| 3% | 118.80% | 1.56 | -11.62% | 394 |
@@ -572,19 +566,19 @@ TopNRanking 动量窗口扫描结果如表 5-16 所示。
| 20% | 97.04% | 1.29 | -13.23% | 370 |
| 无止损 | 98.01% | 1.29 | -12.81% | 369 |
夏普极差: 0.35
简化夏普极差0.35
三组参数的夏普极差汇总如表 5-18 所示。
三组参数的简化夏普极差汇总如表 5-18 所示。
**表 5-18 参数敏感性极差对比**
| 参数组 | 夏普极差 | 类型 |
| 参数组 | 简化夏普极差 | 类型 |
|--------|----------|------|
| RiskParity | 0.13 | 高原型(参数不敏感) |
| TopNRanking | 0.42 | 山峰型(参数敏感) |
| 止损阈值 | 0.35 | 山峰型(参数敏感) |
三组参数的夏普折线对比如图 5-5 所示。
三组参数的简化夏普折线对比如图 5-5 所示。
![参数敏感性对比](images/q5-how-to-validate/05-parameter-sensitivity.png)
@@ -592,36 +586,36 @@ TopNRanking 动量窗口扫描结果如表 5-16 所示。
最后一个认知升级:**好策略不怕参数变。**
**参数敏感性(Parameter Sensitivity** —— 改一个参数值,策略表现变化有多大。这是策略体检中不可或缺的一项。
**参数敏感性(Parameter Sensitivity**改一个参数值,策略表现变化有多大。这是策略体检中不可或缺的一项。
看 RiskParity:波动率窗口从 10 天调到 40 天,夏普在 1.28-1.40 之间,极差只有 0.13。不管你选哪个窗口,结果都差不多。这叫 **高原型(Plateau** —— 像一片平坦的高原,站在哪里都差不多高。
看 RiskParity:波动率窗口从 10 天调到 40 天,简化夏普在 1.281.40 之间,极差只有 0.13。不管你选哪个窗口,结果都差不多。这叫**高原型(Plateau)**像一片平坦的高原,站在哪里都差不多高。
再看 TopNRanking:动量窗口从 10 天调到 40 天,夏普在 0.85-1.27 之间,极差 0.42——是 RiskParity 的 3 倍多。窗口选 20 天时夏普 1.27,选 15 天时只有 0.85,掉了 1/3。这叫 **山峰型(Peak** —— 像一座尖峰,只有山尖上最高,稍微偏一步就掉下去。
再看 TopNRanking:动量窗口从 10 天调到 40 天,简化夏普在 0.851.27 之间,极差 0.42是 RiskParity 的 3 倍多。窗口选 20 天时简化夏普 1.27,选 15 天时只有 0.85,掉了 1/3。这叫**山峰型(Peak)**像一座尖峰,只有山尖上最高,稍微偏一步就掉下去。
想象你在山上找营地。高原型策略像一片平坦的高原——站在哪里都差不多高。山峰型策略像一座尖峰——只有山尖上最高,稍微偏一步就掉下去。你选哪个?
想象你在山上找营地。高原型策略像一片平坦的高原站在哪里都差不多高。山峰型策略像一座尖峰只有山尖上最高,稍微偏一步就掉下去。你选哪个?
**好策略不依赖某个”魔法参数** 如果换一个参数值夏普就**从 1.27 跌到 0.85**(掉了 1/3),说明你可能只是运气好选对了那个值。RiskParity 的有效性来自底层逻辑——“波动大的少买”不管你用 10 天还是 40 天窗口来衡量波动,排序关系不会剧。TopNRanking 的高收益则部分依赖于恰好选对了参数 20”。
**好策略不应该依赖某一个特殊参数。** 如果换一个参数值,简化夏普就从 1.27 跌到 0.85(掉了 1/3),说明你可能只是运气好选对了那个值。RiskParity 的有效性来自基本逻辑:“波动大的少买”不管你用 10 天还是 40 天窗口来衡量波动,排序关系不会剧烈变化。TopNRanking 的高收益则部分依赖于恰好选对了参数 20”。
这也呼应了 Step 2 的发现:TopNRanking 在某些年份负收益、对参数敏感,它的高收益可能有运气成分。RiskParity 虽然收益不如 TopNRanking 高,但参数稳、年度一致性好——这才是更可靠的策略。
这也呼应了第 2 个实验的发现:TopNRanking 在某些年份负收益、对参数敏感,它的高收益可能有运气成分。RiskParity 虽然收益不如 TopNRanking 高,但参数稳、年度一致性更好,这才是更可靠的策略。
**选策略就像选营地——要选高原,不选尖峰。**
**选策略就像选营地要选高原,不选尖峰。**
回头看:还记得第章末尾你亲眼看到的那次“参数扫描里冠军在样本外掉到了水下”吗?那就是山峰型在参数 + 时间两个维度上同时露出的脸——你当时亲手撞了一次墙,从此对回测多一份警觉。
回头看:还记得第 1 章末尾你亲眼看到的那次“参数扫描里冠军在样本外掉到了水下”吗?那就是山峰型在参数时间两个维度上同时暴露的问题。你当时亲手看到一次漂亮回测失效,从此对回测多一份警觉。
**这一章的高原型 vs 山峰型,正是“做要规则、看要指标、还要怀疑你的指标”这句口号里“看 → 疑”的桥梁——下一章你会把“疑”练成主线**
**这一章的高原型和山峰型,是从“看”走向“疑”的桥梁下一章你会专门训练“疑”:检查回测本身是否可信**
---
## 5.5 回头看:你刚才做了什么?
**这一节是“看”的操作流程总图——把四实验铺成你以后每次接到一份回测都能照着走的检查路径。** 四步实验,一张完整的“策略体检地图”
这一节是“看”的操作流程总图把四实验铺成一条以后可以复用的检查路径
- **Step 1: 跟什么比?** -- 基准 + Alpha(超额收益)+ 多维指标(夏普比/卡玛比/索提诺比)
- **Step 2: 每年都有收益吗?** -- 分段检验 + 策略类型弱点(动量滞后性)
- **Step 3: 收益结构什么样?** -- 胜率陷阱/盈亏比/盈亏因子 + 回撤深度/恢复时间
- **Step 4: 参数动一动?** -- 参数敏感性 + 高原型 vs 山峰型
- **第 1 个实验:跟什么比?** 基准 + 相对基准收益差 + 多维指标(简化夏普 / 卡玛比 / 索提诺比)
- **第 2 个实验:每年都有收益吗?** 分段检验 + 策略类型弱点(动量滞后性)
- **第 3 个实验:收益结构什么样?** 胜率陷阱 / 盈亏比 / 盈亏因子 + 回撤深度 / 恢复时间
- **第 4 个实验:参数动一动,结果还稳吗?** 参数敏感性 + 高原型山峰型
从今以后,拿到任何策略的回测结果,你都可以用这四步做一次“全面体检”,五个维度的体检问题如表 5-19 所示。
从今以后,拿到任何策略的回测结果,你都可以用这四步做一次“全面体检”。这四步会拆出五个具体检问题如表 5-19 所示。
**表 5-19 策略体检维度速查**
@@ -633,18 +627,19 @@ TopNRanking 动量窗口扫描结果如表 5-16 所示。
| 回撤 | 最坏到什么程度? |
| 参数 | 参数改了还行吗? |
**从“看收益率”到“全面体检”——这是本章最重要的认知升级。**
**从“看收益率”到“全面体检”这是本章最重要的认知升级。**
### 个最容易让你“被自己骗”的陷阱
### 个最容易让你“被自己骗”的陷阱
每个维度对应一种最容易踩的陷阱——它们各有不同名字,但本质都是“指标好看,结果不靠谱”。下次别人(或者你自己)拿一份“漂亮”的回测来,先用这四个 lens 扫一遍:
每个维度对应一种最容易踩的陷阱它们各有不同名字,但本质都是“指标好看,结果不靠谱”。下次别人(或者你自己)拿一份“漂亮”的回测来,先用这五个视角扫一遍:
1. **基准陷阱**——只看年化收益,没和“什么都不做”对比。EqualWeight 累计 84% 看着还行,等权买入持有同期 95%——你忙了半天反而落后。
2. **整体陷阱**——只看多年累计,掩盖了某些年份的大亏。TopNRanking 累计第一,但 2021 年是唯一亏钱的策略(-10.09%),其他三个策略同期都是正收益——普通人在那年就放弃了。
3. **胜率陷阱**——胜率 90% 不等于赚钱。一次大亏可以抹平十次小赚,真正决定结果的是盈亏比和盈亏因子。
4. **山峰陷阱**——某个参数最优,稍微一偏夏普就掉一大截。这往往不是规律,是巧合——和第一章末尾你撞上的那堵墙是同一种东西
1. **基准陷阱**只看年化收益,没和“什么都不做”对比。EqualWeight 累计 84% 看着还行,等权买入持有同期 95%你忙了半天反而落后。
2. **整体陷阱**只看多年累计,掩盖了某些年份的大亏。TopNRanking 累计第一,但 2021 年是唯一亏钱的策略(-10.09%),其他三个策略同期都是正收益普通人在那年可能就放弃了。
3. **胜率陷阱**胜率 90% 不等于赚钱。一次大亏可以抹平十次小赚,真正决定结果的是盈亏比和盈亏因子。
4. **回撤陷阱**:只看最大回撤深度,不看恢复时间。跌 15% 但 3 个月恢复,和跌 10% 但 1 年不恢复,持有体验完全不同
5. **山峰陷阱**:某个参数最优,稍微一偏简化夏普就掉一大截。这往往不是规律,是巧合,和第 1 章末尾那次样本外失效是同一种问题。
**四个陷阱合起来,就是“看要指标”这一层在跟你强调的事——指标越多越好;但每个指标只看一面你得把面合起来看,才能少被自己骗。**
个陷阱合起来,就是本章反复强调的事:不是指标越多越好,而是每个指标只看一面你得把面合起来看,才能少被自己骗。
---
@@ -652,96 +647,84 @@ TopNRanking 动量窗口扫描结果如表 5-16 所示。
### 策略进化路径
**第章产出:RiskParity + 止损 + 三个对比策略**
**第 4 章产出:风险平价 + 止损 + 三个对比策略**
- Step 1: 跟基准比 -- 不是所有策略都跑赢了“什么都不做”
- Step 2: 逐年拆 -- 某些年份亏损,动量策略有结构性弱点
- Step 3: 看分布 -- 胜率是陷阱,盈亏比才是关键
- Step 4: 动参数 -- 高原型稳,山峰型脆
- 第 1 个实验:跟基准比,不是所有策略都跑赢了“什么都不做”
- 第 2 个实验:逐年拆,某些年份亏损,动量策略有结构性弱点
- 第 3 个实验:看分布,胜率是陷阱,盈亏比才是关键
- 第 4 个实验:动参数,高原型稳,山峰型脆
**第章产出:完整的策略评估方法 + “体检清单”**
**第 5 章产出:完整的策略评估方法 + “体检清单”**
### 概念速查表
本章涉及的核心概念汇总如表 5-20 所示,方便随时回查。
**表 5-20 第章核心概念速查**
**表 5-20 第 5 章核心概念速查**
| 概念 | 含义 | 类比 |
|------|------|------|
| 基准(Benchmark) | 用来做比较的参照物——“什么都不做”时你能赚多少 | 考试的“及格线” |
| 超额收益 / Alpha | 策略收益减去基准收益——超出及格线的部分才是你的本事 | 第一章电梯/楼梯类比,本章放回“基准之后才能算”的语境 |
| 基准(Benchmark) | 用来做比较的参照物“什么都不做”时你能赚多少 | 考试的“及格线” |
| 相对基准收益差 | 策略收益减去基准收益 | 比及格线高多少或低多少 |
| 卡玛比(Calmar) | 年化收益 / 最大回撤 | 每承受 1% 最大回撤换多少收益 |
| 索提诺比(Sortino) | 只用下行波动率做分母 | 上涨波动不算风险 |
| 分段检验 | 把回测按年份拆开 | 整体及格不等于每科都及格 |
| 动量滞后性 | 动量指标基于“过去涨了多少” | 后视镜——过去涨不代表未来继续涨 |
| 动量滞后性 | 动量指标基于“过去涨了多少” | 后视镜过去涨不代表未来继续涨 |
| 胜率的陷阱 | 胜率本身意义不大 | 胜率 90% 也可能亏钱 |
| 盈亏比(Win/Loss Ratio | 平均盈利 / 平均亏损 | 真正决定收益的指标 |
| 盈亏因子(Profit Factor | 总盈利 / 总亏损 | 大于 1 赚钱,大于 2 优秀 |
| 水下曲线(Underwater) | 每天距离历史最高点跌了多少 | “你在水下待了多久” |
| 回撤恢复时间 | 从最低点爬回前高要多久 | 比深度更影响持有体验 |
| 参数敏感性 | 改参数后结果变化多大 | 好策略不怕参数变 |
| 高原型 vs 山峰型 | 高原:参数怎么调都差不多。山峰:偏一点夏普极差大 | 找营地选高原不选尖峰 |
| 高原型山峰型 | 高原:参数怎么调都差不多。山峰:偏一点简化夏普极差大 | 找营地选高原不选尖峰 |
### 本章核心认知
走完四步实验,沉淀下来的认知如表 5-21 所示。
**表 5-21 第章核心认知**
**表 5-21 第 5 章核心认知**
| 认知 | 来源 |
|------|------|
| “好不好”必须有参照物——基准 | Step 1 实验 |
| 跑赢基准的部分才是 Alpha——你的技能回报 | Step 1 实验 |
| 夏普、卡玛比、索提诺比是三把不同的尺子 | Step 1 实验 |
| 整体指标可能掩盖年度亏损——分段检验 | Step 2 实验 |
| 动量策略在反复震荡的市场中有结构性弱点——滞后性 | Step 2 深挖 |
| 胜率是陷阱——真正决定收益的是盈亏比,优秀策略的秘诀是“小亏大赚” | Step 3 实验 |
| 回撤不只是深度,还有恢复时间——水下曲线 | Step 3 实验 |
| 好策略不怕参数变——高原型 >> 山峰型 | Step 4 实验 |
| “好不好”必须有参照物基准 | 1 实验 |
| 相对基准收益差告诉你有没有跑赢简单做法 | 第 1 实验 |
| 简化夏普、卡玛比、索提诺比是三把不同的尺子 | 1 实验 |
| 整体指标可能掩盖年度亏损分段检验 | 2 实验 |
| 动量策略在反复震荡的市场中有结构性弱点滞后性 | 第 2 个实验 |
| 胜率是陷阱真正决定收益的是盈亏比 | 第 3 实验 |
| 回撤不只是深度,还有恢复时间水下曲线 | 3 实验 |
| 好策略不怕参数变高原型优于山峰型 | 4 实验 |
| 先猜后验,数据说了算 | 贯穿全章 |
### 带走的问题
- 体检工具给了你客观数据。但**回测本身可信吗?** 回测能调参数让收益翻倍——这是真的好,还是自欺欺人? → 第六章
- 如果你在 2023 年跑回测发现某个策略 2021-2023 表现好,你怎么知道它 2024 年还会好? → 第六章
- 体检工具给了你客观数据。但**回测本身可信吗?** 回测能调参数让收益翻倍这是真的好,还是自欺欺人?这是第 6 章要回答的问题。
- 如果你在 2023 年跑回测发现某个策略 2021-2023 表现好,你怎么知道它 2024 年还会好?这也是第 6 章要回答的问题。
带着这两个问题进入下一章——它们正是本书口号“**做要规则、看要指标、还要怀疑你的指标**”中“**疑**”那一步要训练的核心。这一章你已经把“看”练了一遍,下一章我们专门把“疑”练成主线。
带着这两个问题进入下一章它们正是“疑”这个环节要训练的核心。这一章你已经把“看”练了一遍,下一章我们专门把“疑”练成主线。
> 本章所有代码的可运行版本见 `notebooks/q5-how-to-validate.ipynb`
> 本章所有代码的可运行版本见配套仓库的 [`q5-how-to-validate/notebooks/q5-how-to-validate.ipynb`](https://github.com/xingwudao/xquant-learning/blob/main/q5-how-to-validate/notebooks/q5-how-to-validate.ipynb)
---
## 拓展阅读
本章正文聚焦“四步策略体检”。下面三段是动手之前可以省略的视野铺垫——读完正文回头看,你会更清楚指标背后的量化惯例
本章正文聚焦“四步策略体检”。下面三段是主线之外的补充,读完正文后再看更容易吸收
### 为什么“年化”用 252 这个数字
正文反复说“年化收益率”、“年化波动率”——背后都隐含一个换算系数:**252**。
正文反复说“年化收益率”、“年化波动率”背后都隐含一个换算系数:**252**。
A 股一年大约 250-252 个交易日(节假日不开盘),美股 252 个,全球主流市场都接近这个数。所以“日 → 年”的换算是:**年化收益率 ≈ 平均日收益率 × 252**;**年化波动率 ≈ 日收益率标准差 × √252**注意是开根号,不是直接乘。比如一个策略日波动率 1%,年化波动率 ≈ 1% × √252 ≈ 15.87%。回测里所有“年化”指标都用了这个换算——不是定理,但是行业惯例。
A 股一年大约 250252 个交易日(节假日不开盘),美股 252 个,全球主流市场都接近这个数。所以,从日收益换算成年化时,常见做法是:**年化收益率 ≈ 平均日收益率 × 252**;**年化波动率 ≈ 日收益率标准差 × √252**注意,波动率是乘以根号 252,不是直接乘以 252。比如一个策略日波动率 1%,年化波动率 ≈ 1% × √252 ≈ 15.87%。回测里所有“年化”指标都用了这个换算。它不是定理,但是常见行业惯例。
### 夏普比率 vs 信息比率(IR
### 夏普比率信息比率(IR
夏普比率 = 年化收益 / 年化波动。你可能在论文或基金报告里见过另一个相似的指标——**信息比率(Information Ratio, IR**。
本书表格里用的是简化夏普:年化收益 / 年化波动。你可能在论文或基金报告里见过另一个相似的指标**信息比率(Information Ratio, IR**。
二者长得很像,区别在分子:**夏普比率分子是策略的“全部收益”,IR 分子是“策略相对基准的超额收益(Alpha)”**。也就是说,IR 衡量的是“主动管理的性价比”——剔除了 Beta,纯看 Alpha 部分。基金经理之间互相比 IR 比夏普比率更公平,因为大家都被市场抬着走,IR 才能看出谁真的有本事。本书课程默认用夏普比率(更直观),但你以后看到 IR 不要感到陌生——它只是把“分子换成 Alpha”的夏普比率
二者长得很像,区别在分子:简化夏普的分子是策略本身的年化收益,信息比率的分子是“策略收益减去基准收益”。也就是说,信息比率衡量的是:在扣掉简单参照做法之后,策略多赚的那一部分是否值得它承担的波动。本书默认用简化夏普,因为更直观但你以后看到信息比率不要陌生,它只是把比较对象换成了“相对基准的收益差”
### 索提诺比的“MAR”参数
正文说索提诺比 = 年化收益 / 下行波动率。但什么算“下行”?业界常用的定义是:**收益低于某个阈值(叫 MARMinimum Acceptable Return,最低可接受收益率)的那部分波动**
正文说索提诺比 = 年化收益 / 下行波动率。但什么算“下行”?常见定义是:收益低于某个阈值(叫 MARMinimum Acceptable Return,最低可接受收益率)的那部分波动。
常见的两种 MAR 选法:**MAR = 0**——把“亏损”定义为“日收益率 < 0”,最简单;**MAR = 无风险利率**(如美债 5%)——把“没跑赢国债”也算“下行”,更严格。本书计算时用的是 MAR = 0更直观),但你看到不同来源的索提诺比数值有差异时,先确认它们用的是哪种 MAR——否则比的不是同一个东西。
---
## 反馈与读者群
如果你发现本章有错别字、概念不清或实验结果不一致,欢迎
[提交 GitHub Issue](https://github.com/xingwudao/xquant-beginner/issues/new/choose)。
课程、正式书和后续更新见
[xquant.shop/courses](https://xquant.shop/courses)。
添加作者微信 `xquanter` 进入读者群。请备注:`XQuant 读者`
常见的两种 MAR 选法:第一,**MAR = 0**把“亏损”定义为“日收益率 < 0”,最简单;第二,**MAR = 无风险利率**,把“没跑赢无风险收益”也算“下行”,更严格。本书计算时用的是 MAR = 0更直观你看到不同来源的索提诺比数值有差异时,先确认它们用的是哪种 MAR否则比的不是同一个东西。
+207 -212
View File
@@ -1,109 +1,111 @@
# 第六章:看清好看的回测:别被自己骗了
# 第 6 章:别高兴太早:防止过拟合
章我们给策略做了一次全面体检——基准对比、逐年拆解、收益分布、参数敏感性。现在手上有一套完整的评估方法
5 章我们给策略做了一次全面体检基准对比、逐年拆解、收益分布、参数敏感性。现在手上有一套完整的评估方法。
但所有体检结果都基于一个前提:**回测结果是真实的。**
真的吗?
回顾第章的发现:TopNRanking 策略在 2021 年亏了 10%,换遍所有动量窗口都救不回来——这是动量策略的结构性弱点。但同样的策略在 2023-2025 的好市场上表现不错。这引出一个危险的念头:**如果我们在市场精心挑选参数,找到“收益最好”的组合,信心满满地投入资金——未来再遇到差市场会怎么样?**
回顾第 5 章的发现:TopNRanking 策略在 2021 年亏了 10%,换遍所有动量窗口都救不回来这是动量策略的结构性弱点。但同样的策略在 2023-2025 表现不错。这引出一个危险的念头:**如果我们在一段有利的市场精心挑选参数,找到“收益最好”的组合,信心满满地投入资金,未来市场环境变了会怎么样?**
回测可以调参数让收益翻倍——这是真的好,还是自欺欺人?
回测可以调参数让收益翻倍这是真的好,还是自欺欺人?
### 路线图
**选什么标的(第二章)→ 每个买多少(第三章)→ 什么时候买卖(第四章)→ 怎么验证有效(第五章)→ 如何避免自欺欺人(第六章)**
本章仍然属于四个阶段里的**评估归因**阶段。第 5 章训练的是“看”:看指标、看年份、看分布、看参数敏感性。第 6 章训练的是“疑”:怀疑漂亮回测是否真实,检查它有没有把历史数据里的偶然性当成规律。
**这一章你仍在飞轮的“归因层”,但主练动作切到了“疑”——本书口号“做要规则、看要指标、还要怀疑你的指标”中“还要怀疑你的指标”,就是这一章要让你练成肌肉记忆的事。** 第一章你已经亲手撞过一次墙——参数扫描里“冠军”样本外掉到水下;这一章我们给你**四把铲子**,让那种墙以后看见就能挖开:样本内/外、Walk-forward、交叉验证、规则负担。本章解决第五步——识别过拟合陷阱,学会检验策略的真实有效性。四步对应的问题与方法如表 6-1 所示。
第 1 章你已经亲眼看过一次过拟合:参数扫描里“冠军”,到了样本外反而掉到水下。第 5 章又留下两个问题:回测能调参数让收益翻倍,这是真的好,还是自欺欺人?如果你在 2023 年跑回测发现某个策略 2021-2023 年表现好,怎么知道它 2024 年还会好?
**表 6-1 第六章四节问题与方法对照**
本章就回答这两个问题。全章一共做 4 个实验,路线如表 6-1 所示。
| 步骤 | 问题 | 方法 |
**表 6-1 第 6 章路线图**
| 节 | 内容 | 实验 |
|------|------|------|
| Step 1 | 能不能找到收益最好的参数? | 参数优化 + 样本内/样本外 |
| Step 2 | 市场在变,参数也该跟着变吧? | Walk-forward 分析 |
| Step 3 | 一条路径够可靠吗? | 交叉验证 |
| Step 4 | 规则越多回测越好——是好事吗? | 规则负担 |
| 6.1 | 能不能找到收益最好的参数? | 1 |
| 6.2 | 市场在变,参数也该跟着变吧? | 1 |
| 6.3 | 一条路径够可靠吗? | 1 |
| 6.4 | 规则越多回测越好是好事吗? | 1 |
铁律不变:**先猜后验,数据说了算。**
(操作流程见前言“怎么使用这本书”。)
---
## 6.1 能不能找到收益最好的参数?
**和第一章 1.5 节的关系:** 第一章末尾你扫过 24 个一维参数均线窗口,用 60-40 切分做样本外测试,已经撞过一次过拟合的墙。这一章接着上一次的脚印继续走——**同一种切法,但量级跃迁**:参数从 1 维涨到 4 维(动量 / 波动 / 调仓 / 止损),组合数从 24 涨到 490。维度多了之后,过拟合的样子会变什么样?这一节给你答案
第 1 章末尾你扫过 24 个一维参数,也就是均线窗口,用 60/40 切分做样本外测试。那一次你已经看见过拟合的样子:历史上最好看的参数,到了没见过的数据里并不好
第五章用了默认参数——动量窗口 20 天、波动率窗口 20 天、调仓频率 10 天。但这些是随便选的,不是天经地义的
这一章接着上一次往前走。同样是切分数据,但难度升了一档:参数从 1 维增加到 4 维,包含动量窗口、波动率窗口、再平衡频率、止损阈值;组合数从 24 个增加到约 490 个。维度多了以后,过拟合会变成什么样?这一节给你答案
第 5 章用了默认参数:动量窗口 20 天、波动率窗口 20 天、再平衡频率 10 天。但这些不是天经地义的。
直觉告诉我们:**多试几组参数,选收益最高的就行了。** 几百种组合里总有比默认值更好的吧?
这个想法太自然了。就像买手机——把市面上所有型号的参数列出来,逐项比较,选评分最高的那款。参数也一样,把所有组合都试一遍,选出表现最好的——这叫**参数优化**,也叫**网格搜索(Grid Search**——一个参数组合就像一个格子,挨个格子试一遍,总能找到最优解
这个想法太自然了。就像买手机把市面上所有型号的参数列出来,逐项比较,选评分最高的那款。参数也一样,把所有组合都试一遍,选出表现最好的这叫**参数优化**,也叫**网格搜索(Grid Search**一个参数组合就像一个格子,挨个格子试一遍,总能找到表现最好的组合
真的这么简单吗?试试看。
### 动手实验 1:参数优化与样本内/样本外
### 动手实验 1:参数优化与样本内 / 样本外
这个实验用到 open-xquant 的参数优化工具:`GridSearch`(网格搜索——把所有参数组合都试一遍)和 `WalkForward`(前推分析——模拟“边走边调”的真实场景)。
这个实验用到 open-xquant 的参数优化工具:`GridSearch`(网格搜索把所有参数组合都试一遍)和 `WalkForward`(前推分析模拟“边走边调”的真实场景)。
我们一起把这份 spec 写出来。这次重点看三件新东西:**维度跃迁**(从一维到四维参数)、**时段语义切分**(市场挑参数 / 差市场验证)、**业务约束的可读字符串写法**。
我们一起把这份 spec 写出来。这次重点看三件新东西:**维度跃迁**(从一维到四维参数)、**时段语义切分**(有利市场挑参数 / 反复波动环境验证)、**业务约束的可读字符串写法**。
#### 起草上下文 + 任务
#### 第一、二段:上下文和任务描述
章 1.5 节用一行“前 60% / 后 40%”切了一刀——这是结构性切分。这一节不一样:每段时间要带金融含义。
1 章 1.5 节用一行“前 60% / 后 40%”切了一刀。这是按比例切分。这一节不一样:每段时间要带市场含义。
> **上下文**:本章讨参数优化和过拟合。第章看默认参数(动量 20 / 波动率 20 / 调仓 10)的 plateau vs peak,但学员从没试过“从一堆参数里找最好的”。承接第章的叙事——TopNRanking 在 2017-2020 好市场上参数选得好,2021 年差市场会怎么样?
> **上下文**:本章讨参数优化和过拟合。第 5 章看默认参数(动量 20 / 波动率 20 / 再平衡 10)的高原型和山峰型,但读者还没有试过“从一堆参数里找最好的”。承接第 5 章的叙事:如果 TopNRanking 在 2017-2020 年走势较顺的市场里选出好参数,到了 2021 年之后的反复波动环境会怎么样?
>
> **任务**:在 `q6-avoid-overfitting.ipynb` 用 `oxq.optimize.GridSearch` 跑参数网格搜索,用样本内 / 样本外方法检验过拟合。
> **任务描述**:在 `q6-avoid-overfitting.ipynb` 用 `oxq.optimize.GridSearch` 跑参数网格搜索,用样本内 / 样本外方法检验过拟合。
> 📌 **要点**:数据切分要带“市场含义”。第章的 60/40 是按比例切的,这里换成“2017-2020 好市场 / 2021-2023 差市场”——后者把每段对应的市场行情说清楚,学员才能看懂“为什么是这两段”。**任何切分型 spec,起止日期都要四个全锁死(IS_START / IS_END / OOS_START / OOS_END),否则今年跑、明年跑切出来不一样,过拟合证据就站不住。**
> 📌 **要点**:数据切分要带“市场含义”。第 1 章的 60/40 是按比例切的,这里换成“2017-2020 年走势较顺 / 2021-2023 年反复波动”。后者把每段对应的市场环境说清楚,读者才能看懂“为什么是这两段”。任何切分型 spec,起止日期都要四个全锁死(IS_START / IS_END / OOS_START / OOS_END),否则今年跑、明年跑切出来不一样,过拟合证据就站不住。
#### 起草要求:维度跃迁与业务约束
#### 第三段:任务要求
章扫过 24 个一维参数均线窗口。这一节直接跳到 4 维——动量 / 波动率 / 调仓频率 / 止损阈值。
1 章扫过 24 个一维参数,也就是均线窗口。这一节直接跳到 4 维:动量窗口、波动率窗口、再平衡频率、止损阈值。
> **要求**
> **任务要求**
>
> 1. 数据切割:样本内 `2017-01-01 ~ 2020-12-31`、样本外 `2021-01-01 ~ 2023-01-31`,四个端点全锁死
> 2. 用 `ParameterSet` 定义参数空间:动量 / 波动率窗口 `[10, 15, 20, 25, 30]`、调仓频率 `[5, 10, 15, 21]`、止损阈值 `[0.03, 0.05, 0.07, 0.10, 0.15, 0.20]`
> 2. 用 `ParameterSet` 定义参数空间:动量 / 波动率窗口 `[10, 15, 20, 25, 30]`、再平衡频率 `[5, 10, 15, 21]`、止损阈值 `[0.03, 0.05, 0.07, 0.10, 0.15, 0.20]`
> 3. 分两组跑:无止损组(3 维)+ 含止损组(4 维),约束后总组合数约 490
> 4. 业务约束用可读字符串写:`add_constraint("RebalanceFrequencyRule.interval_days <= mom.period")`、`<= vol.period`
> 5. 调仓频率不应超过信号窗口——这是领域知识,不是技术约束
> 5. 再平衡频率不应超过信号窗口这是领域知识,不是技术约束
> 6. 用 `GridSearch` 跑两组参数空间,在样本内、样本外各跑一次
> 📌 **要点**:维度跃迁后要分组管理。一维扫描随便扫,四维直接扫会爆——5×5×4×6 = 600 还要约束过滤。**分两组跑(无止损 70 组合 + 含止损 420 组合)既能控制总数,又能让止损这一维在结果里被单独看清——也为后面“规则负担”那一节埋伏笔。**
> 📌 **要点**:维度增加后要分组管理。一维扫描可以直接扫,四维直接扫会膨胀到 5×5×4×6 = 600 个组合,还要再做约束过滤。分两组跑(无止损 70 组合 + 含止损 420 组合)既能控制总数,又能让止损这一维在结果里被单独看清也为后面“规则负担”那一节埋伏笔。
> 📌 **要点**:业务约束用可读字符串,不要用 lambda。`"RebalanceFrequencyRule.interval_days <= mom.period"` 这种 DSL 字符串比 `lambda p: p["RebalanceFrequencyRule.interval_days"] <= p["mom.period"]` 更适合 0 基础学员看。**约束的“为什么”要写到 spec 注释里——“信号 20 天更新一次,调仓 60 天才看一次 = 在用过期信号做决策”——这是把领域知识嵌入 spec 的标准写法。**
> 📌 **要点**:业务约束用可读字符串,不要用 lambda。`"RebalanceFrequencyRule.interval_days <= mom.period"` 这种 DSL 字符串比 `lambda p: p["RebalanceFrequencyRule.interval_days"] <= p["mom.period"]` 更适合零基础读者看。约束的“为什么”要写到 spec 注释里信号 20 天更新一次,再平衡 60 天才看一次,等于在用过期信号做决策这是把领域知识嵌入 spec 的标准写法。
#### 起草结果呈现
#### 第四段:验收标准
> **结果呈现**
> **验收标准**
>
> 1. 打印 Top 20 表(按样本内夏普排名)+ 样本外对比 + 全局相关系数 + 衰减分布
> 2. 散点图(样本内 vs 样本外夏普,y=x 对角线虚线,Top 20 红色高亮)
> 1. 打印 Top 20 表(按样本内简化夏普排名)+ 样本外对比 + 全局相关系数 + 衰减分布
> 2. 散点图(样本内样本外简化夏普,y=x 对角线虚线,Top 20 红色高亮)
> 3. Top 20 柱状图(每组双柱:样本内 / 样本外)
> 4. 总结打印按实际数据动态描述:Top 20 平均衰减、对角线下方点的占比、过拟合定义
完整 spec 在 [`specs/spec-01-parameter-optimization.md`](https://github.com/xingwudao/xquant-learning/blob/main/q6-avoid-overfitting/specs/spec-01-parameter-optimization.md)——复制给 AI,弹窗选「允许」
完整示例 spec 在配套仓库的 [`q6-avoid-overfitting/specs/spec-01-parameter-optimization.md`](https://github.com/xingwudao/xquant-learning/blob/main/q6-avoid-overfitting/specs/spec-01-parameter-optimization.md)。参考它确认自己的 spec 后,再复制给 AI,按提示允许执行
AI 助手执行完毕后,你的 notebook 里应该出现网格搜索的完整结果。我们来看看代码做了什么。
AI 执行完毕后,你的 notebook 里应该出现网格搜索的完整结果。我们来看看代码做了什么。
数据被切成了两段:**样本内(In-Sample** 用 2017-2020 年做优化,**样本外(Out-of-Sample** 用 2021-2023 做验证。为什么选这么早的数据?因为样本内需要足够长至少 3-4 年,才能覆盖牛熊周期,避免只在单一行情下调参。时间顺序也必须正确——先优化,后验证就像真实投资中你只能用历史数据做决策,然后面对未来。
数据被切成了两段:**样本内(In-Sample** 用 2017-2020 年做优化,**样本外(Out-of-Sample** 用 2021-2023 做验证。为什么选这么早的数据?因为样本内需要足够长至少 3-4 年,才能覆盖不同市场环境,避免只在单一市场走势下调参。时间顺序也必须正确先优化,后验证就像真实投资中你只能用历史数据做决策,然后面对未来。
**样本内**就是你看着答案复习的模拟考试卷;**样本外**就是真正的考试——你从没见过的题目。
**样本内**就是你看着答案复习的模拟考试卷;**样本外**就是真正的考试,是你从没见过的题目。
四个维度的参数组合——动量窗口 5 种 × 波动率窗口 5 种 × 调仓频率 4 种 × 止损阈值(有/无),加上约束过滤(调仓频率不能超过信号窗口——用过期信息做决策没有意义),最终产生 490 种有效组合。
四个维度的参数组合动量窗口 5 种 × 波动率窗口 5 种 × 再平衡频率 4 种 × 止损阈值(有/无),加上约束过滤(再平衡频率不能超过信号窗口,因为用过期信息做决策没有意义),最终产生 490 种有效组合。
`GridSearch` 把 490 种参数组合逐个跑回测,按夏普排名。然后用同样的参数在样本外再跑一遍,看“真正的考试”成绩如何。
`GridSearch` 把 490 种参数组合逐个跑回测,按简化夏普排名。然后用同样的参数在样本外再跑一遍,看“真正的考试”成绩如何。
### 运行结果
490 种组合全部跑完,先看样本内 Top 20 的“样本外真相”——样本内夏普 Top 20 及其样本外表现(部分)如表 6-2 所示。
490 种组合全部跑完,先看样本内 Top 20 的“样本外真相”样本内简化夏普 Top 20 及其样本外表现(部分)如表 6-2 所示。
**表 6-2 样本内夏普 Top 20 及其样本外表现(部分)**
**表 6-2 样本内简化夏普 Top 20 及其样本外表现(部分)**
| # | mom | vol | freq | sl | 样本内夏普 | 样本外夏普 | 衰减 |
| # | mom | vol | freq | sl | 样本内简化夏普 | 样本外简化夏普 | 衰减 |
|---|-----|-----|------|----|-----------|-----------|------|
| 1 | 15 | 10 | 10 | 0.03 | 1.39 | 0.50 | -0.88 |
| 2 | 15 | 15 | 10 | 0.03 | 1.34 | 0.49 | -0.85 |
@@ -113,31 +115,31 @@ AI 助手执行完毕后,你的 notebook 里应该出现了网格搜索的完
| 7 | 30 | 10 | 10 | 0.03 | 1.24 | -0.07 | -1.31 |
| 14 | 15 | 10 | 10 | 无 | 1.18 | 0.01 | -1.17 |
**样本内 Top 20 统计:** 样本内夏普均值 1.22(范围 1.15 ~ 1.39),样本外夏普均值 0.20(范围 -0.09 ~ 0.50),平均衰减 -1.02。
**样本内 Top 20 统计:** 样本内简化夏普均值 1.22(范围 1.15 ~ 1.39),样本外简化夏普均值 0.20(范围 -0.09 ~ 0.50),平均衰减 -1.02。
**全部 490 个组合统计:** 样本内/样本外夏普相关系数 0.04,样本外比样本内差的比例 100%,夏普衰减均值 -0.82、中位数 -0.88。
**全部 490 个组合统计:** 样本内 / 样本外简化夏普相关系数 0.04,样本外比样本内差的比例 100%,简化夏普衰减均值 -0.82、中位数 -0.88。
散点图与 Top 20 柱状图如图 6-1 所示。
![490 个参数组合:样本内 vs 样本外夏普](images/q6-avoid-overfitting/01-is-vs-oos-scatter-and-top20.png)
![490 个参数组合:样本内样本外简化夏普](images/q6-avoid-overfitting/01-is-vs-oos-scatter-and-top20.png)
### 结果解读
第一个认知升级:**参数优化是一把双刃剑。**
先看数字。样本内 Top 20 的平均夏普 1.22——这在量化里属于很拿得出手的成绩。但拿到样本外(2021-2023,没见过的数据),平均夏普跌到了 0.20。衰减了 83%。
先看数字。样本内 Top 20 的平均简化夏普 1.22。这在本书的简化口径下已经很好看。但拿到样本外(2021-2023,没见过的数据),平均简化夏普跌到了 0.20。衰减了 83%。
再看散点图。每个点代表一种参数组合,对角虚线是“样本外 = 样本内”的理想状态。如果所有点都在对角线上,说明样本内的好成绩在样本外也能兑现。但现实是:**100% 的点都在对角线以下**——没有一种参数组合的样本外表现达到样本内水平。
再看散点图。每个点代表一种参数组合,对角虚线是“样本外 = 样本内”的理想状态。如果所有点都在对角线上,说明样本内的好成绩在样本外也能兑现。但现实是:**100% 的点都在对角线以下**没有一种参数组合的样本外表现达到样本内水平。
扎心的是:样本内夏普和样本外夏普的相关系数只有 0.04——几乎没有关系。样本内排名第一的组合,样本外并不一定最好。
刺眼的是:样本内简化夏普和样本外简化夏普的相关系数只有 0.04几乎没有关系。样本内排名第一的组合,样本外并不一定最好。
这就是**过拟合(Overfitting**——你的策略只会死读书,课后习题全都会做,结果高考考砸了。它“记住了”过去数据的特征(噪音),而不是发现了真正的规律。
这就是**过拟合(Overfitting**你的策略只会死读书,课后习题全都会做,结果高考考砸了。它“记住了”过去数据的特征(噪音),而不是发现了真正的规律。
柱状图更直观:蓝色柱子(样本内)高高在上,红色柱子(样本外)矮了一大截。有些组合样本外甚至为负——在“真正的考试”中亏了钱。
柱状图更直观:蓝色柱子(样本内)高高在上,红色柱子(样本外)矮了一大截。有些组合样本外甚至为负在“真正的考试”中亏了钱。
**数据说了算:参数优化后,必须在没见过的数据上验证。样本内的好成绩不算数。**
**这就是第一把铲子:样本内 / 样本外切分。** 它不能直接帮你赚钱——但它能让你在第一时间看到“漂亮回测背后到底有没有真东西”。第章那次“被自己骗”的肉身记忆,到这里第一次有了一可以挂在墙上的工具
**这就是第一个检查工具:样本内 / 样本外切分。** 它不能直接帮你赚钱但它能让你在第一时间看到“漂亮回测背后到底有没有真东西”。第 1 章那次“被自己骗”的经历,到这里变成了一可以重复执行的检查
怎么办?我们把数据切成了“优化”和“验证”两段,只验证了一次。但只验证一次够吗?市场在变,参数也该跟着变吧?
@@ -145,83 +147,83 @@ AI 助手执行完毕后,你的 notebook 里应该出现了网格搜索的完
## 6.2 市场在变,参数也该跟着变吧?
Step 1 把数据切成了“优化”和“验证”两段,发现了过拟合问题。但我们只切了一次、只验证了一次。
第 1 个实验把数据切成了“优化”和“验证”两段,发现了过拟合问题。但我们只切了一次、只验证了一次。
市场每年都在变。2020 年选的参数,2023 年还适用吗?能不能模拟“每隔一段时间重新优化参数”的过程?
直觉告诉我们:**市场每年不一样,参数当然应该定期更新。** 用最近的数据重新优化,应该比死守一组参数更好。
这个想法叫 **Walk-forward(前推验证)分析**——“边走边调”。在历史数据上模拟“每隔一段时间重新优化参数”的过程:用过去两年数据选参数,在接下来半年验证;然后窗口往前滑半年,再选参数、再验证……如此反复。
这个想法叫 **Walk-forward(前推验证)分析**,也就是“边走边调”。在历史数据上模拟“每隔一段时间重新优化参数”的过程:用过去两年数据选参数,在接下来半年验证;然后窗口往前滑半年,再选参数、再验证如此反复。
有两种滑动方式:
- **滚动式(Rolling**——固定数据窗口宽度,窗口左边位置不断往右移动。好处是只看最近的市场,坏处是丢弃了更早的数据。就像只翻最近两年的课本复习
- **锚定式(Anchored**——固定数据窗口左边位置,窗口宽度不断拉长。好处是利用了所有历史数据,坏处是旧数据可能不再适用。就像从高一课本开始全部复习
- **滚动式(Rolling**固定数据窗口宽度,窗口左边位置不断往右移动。好处是只看最近的市场,坏处是丢弃了更早的数据。就像只翻最近两年的课本复习
- **锚定式(Anchored**固定数据窗口左边位置,窗口宽度不断拉长。好处是利用了所有历史数据,坏处是旧数据可能不再适用。就像从高一课本开始全部复习
试试看——用两种不同的方式“边走边调”。
试试看用两种不同的方式“边走边调”。
### 动手实验 2Walk-forward 分析
我们一起把这份 spec 写出来。这次重点看两件新东西:**循环型 spec 的端点全锁**(窗口起止 + 长度 + 步长四个参数都不留浮动)、**对照实验式 spec 的并列结构**(让 Rolling / Anchored 自己跑出对比)。
我们一起把这份 spec 写出来。这次重点看两件新东西:**循环型 spec 的端点全锁**(窗口起止 + 长度 + 步长四个参数都不留浮动)、**对照实验式 spec 的并列结构**(让 Rolling Anchored 自己跑出对比)。
#### 起草上下文 + 任务
#### 第一、二段:上下文和任务描述
> **上下文**:在 `q6-avoid-overfitting.ipynb` 中已有 490 个组合的网格搜索结果 + 样本内/样本外对比,过拟合现象已暴露。当前问题:数据只切了一次。市场在变,2023 年选的参数 2025 年还适用吗?能不能模拟“定期重新优化”的过程?
> **上下文**:在 `q6-avoid-overfitting.ipynb` 中已有 490 个组合的网格搜索结果 + 样本内 / 样本外对比,过拟合现象已暴露。当前问题:数据只切了一次。市场在变,2023 年选的参数 2025 年还适用吗?能不能模拟“定期重新优化”的过程?
>
> **任务**:在 notebook 新建单元格,用 `oxq.optimize.WalkForward` 实现 Walk-forward 分析,对比 Rolling 和 Anchored 两种方式。
> **任务描述**:在 notebook 新建单元格,用 `oxq.optimize.WalkForward` 实现 Walk-forward 分析,对比 Rolling 和 Anchored 两种方式。
#### 起草要求:循环窗口的四个端点
#### 第三段:任务要求
循环型 spec 比单次 spec 脆弱得多——单次 spec 漏掉一个日期可能没事,循环型 spec 漏掉就会让“折数”在不同时间跑出不同结果。
循环型 spec 比单次 spec 脆弱得多单次 spec 漏掉一个日期可能没事,循环型 spec 漏掉就会让“折数”在不同时间跑出不同结果。
> **要求**
> **任务要求**
>
> 1. 复用 Step 1 的参数空间(无止损组 70 组合)和约束(`RebalanceFrequencyRule.interval_days <= mom.period` / `<= vol.period`
> 2. 数据范围:`DATA_START="2017-01-01"` 到 `ANALYSIS_END="2026-05-06"`——四个端点全锁死,不要用 `pd.Timestamp.now()`
> 1. 复用第 1 个实验的参数空间(无止损组 70 组合)和约束(`RebalanceFrequencyRule.interval_days <= mom.period` / `<= vol.period`
> 2. 数据范围:`DATA_START="2017-01-01"` 到 `ANALYSIS_END="2026-05-06"`四个端点全锁死,不要用 `pd.Timestamp.now()`
> 3. Rolling Walk-forward:训练窗口 `"2Y"`、测试窗口 `"6M"`、`anchored=False`
> 4. Anchored Walk-forward:同上,`anchored=True`(训练窗口从起点不断扩大)
> 5. `train_period="2Y"` 的依据:覆盖一个完整中期周期;`test_period="6M"` 的依据:足够长以观察策略表现,又足够短以让多个折落进数据范围
> 📌 **要点**:循环型 spec 的窗口端点要四个全锁死——**起始日 + 结束日 + 窗口长度 + 步长**。漏掉任何一个,折数都会随时间漂移。如果用 `pd.Timestamp.now()` 当结束日,半年后跑出来折数从 15 变成 16,参数稳定性的结论可能就反过来了。**任何带 `for` 循环的 spec 都要回头检查这四个端点。**
> 📌 **要点**:循环型 spec 的窗口端点要四个全锁死:起始日、结束日、窗口长度、步长。漏掉任何一个,折数都会随时间漂移。如果用 `pd.Timestamp.now()` 当结束日,半年后跑出来折数从 15 变成 16,参数稳定性的结论可能就反过来了。任何带 `for` 循环的 spec 都要回头检查这四个端点。
> 📌 **要点**:循环窗口的“为什么”也要写。`"2Y" / "6M"` 不是随手挑的——背后有“覆盖一个完整中期周期”和“折数 ≥ 5 才有统计意义”两个判断。**领域知识里的窗口长度选择,跟参数选择本身一样重要——把判断写进 spec 注释,别让 AI 揣摩。**
> 📌 **要点**:循环窗口的“为什么”也要写。`"2Y" / "6M"` 不是随手挑的背后有“覆盖一个完整中期周期”和“折数 ≥ 5 才有统计意义”两个判断。领域知识里的窗口长度选择,跟参数选择本身一样重要把判断写进 spec 注释,别让 AI 揣摩。
#### 起草结果呈现
#### 第四段:验收标准
> **结果呈现**
> **验收标准**
>
> 1. 用 `to_dataframe()` 打印每折详情:训练时段、验证时段、最优参数、样本内/样本外夏普
> 1. 用 `to_dataframe()` 打印每折详情:训练时段、验证时段、最优参数、样本内 / 样本外简化夏普
> 2. 汇总:`oos_sharpe_ratio()`、`deterioration()`、各折参数集合
> 3. 对比图(figsize 14×6,上下两栏):上栏每折样本外夏普(Rolling 红 / Anchored 蓝两条线),下栏参数稳定性线图(mom.period / interval_days 各折取值)
> 4. 标题「Walk-forward 分析:Rolling vs Anchored」
> 3. 对比图(figsize 14×6,上下两栏):上栏每折样本外简化夏普(Rolling 红 / Anchored 蓝两条线),下栏参数稳定性线图(mom.period / interval_days 各折取值)
> 4. 标题「Walk-forward 分析:Rolling Anchored」
> 5. 打印分析按实际数据动态描述:Anchored 是否锁定参数、Rolling 是否乱跳、衰减对比
完整 spec 在 [`specs/spec-02-walk-forward.md`](https://github.com/xingwudao/xquant-learning/blob/main/q6-avoid-overfitting/specs/spec-02-walk-forward.md)——复制给 AI,弹窗选「允许」
完整示例 spec 在配套仓库的 [`q6-avoid-overfitting/specs/spec-02-walk-forward.md`](https://github.com/xingwudao/xquant-learning/blob/main/q6-avoid-overfitting/specs/spec-02-walk-forward.md)。参考它确认自己的 spec 后,再复制给 AI,按提示允许执行
AI 助手执行完毕后,你的 notebook 里应该出现 Rolling 和 Anchored 两种 Walk-forward 的完整结果。
AI 执行完毕后,你的 notebook 里应该出现 Rolling 和 Anchored 两种 Walk-forward 的完整结果。
训练窗口 2 年,验证窗口 6 个月。每次用 2 年数据选出最优参数,在接下来 6 个月验证。然后窗口滑动,重复这个过程。最终得到 15 折结果——15 次“选参数验证”的记录。
训练窗口 2 年,验证窗口 6 个月。每次用 2 年数据选出最优参数,在接下来 6 个月验证。然后窗口滑动,重复这个过程。最终得到 15 折结果,也就是 15 次“选参数、再验证”的记录。
### 运行结果
两种方式的对比如表 6-3 所示。
**表 6-3 Rolling vs Anchored 对比**
**表 6-3 Rolling Anchored 对比**
| 方式 | 样本外夏普 | 衰减 | 唯一参数组合 |
| 方式 | 样本外简化夏普 | 衰减 | 唯一参数组合 |
|------|-----------|------|-------------|
| Rolling | 0.14 | -36.2% | 7/15 |
| Anchored | 0.13 | -23.5% | 5/15 |
Anchored 参数更稳定。
关键发现:Rolling 在 15 折(每次滑动算一折,一共滑了 15 次)中选了 7 种不同的参数组合——平均两折就换一种。而 Anchored 只有 5 种,且其中 7 折选了相同的参数(动量窗口 20、波动率窗口 15、调仓频率 5——单一组合占了近一半折数。
关键发现:Rolling 在 15 折(每次滑动算一折,一共滑了 15 次)中选了 7 种不同的参数组合平均两折就换一种。而 Anchored 只有 5 种,且其中 7 折选了相同的参数(动量窗口 20、波动率窗口 15、再平衡频率 5单一组合占了近一半折数。
Anchored 的逐折参数一览如表 6-4 所示。
**表 6-4 Anchored Walk-forward 逐折参数与样本外夏普**
**表 6-4 Anchored Walk-forward 逐折参数与样本外简化夏普**
| Fold | mom | vol | freq | 样本外夏普 |
| Fold | mom | vol | freq | 样本外简化夏普 |
|------|-----|-----|------|-----------|
| 1 | 15 | 15 | 5 | 2.50 |
| 2 | 15 | 15 | 5 | 0.71 |
@@ -239,21 +241,21 @@ Anchored 的逐折参数一览如表 6-4 所示。
| 14 | 20 | 15 | 5 | 1.31 |
| 15 | 30 | 15 | 5 | 0.98 |
mom=20, vol=15, freq=5)这组参数前后出现了 7 次,是 Anchored 的最频繁选择。其余四种组合——15, 15, 5)、(30, 15, 5)、(15, 10, 5)、(20, 20, 5——各出现 1 到 4 次。换句话说:动量窗口在 15-30 之间换、波动率窗口大多停在 15、调仓频率 15 折全选 5。Rolling 的波动更宽——动量窗口在 15-30 之间跳波动率窗口从 10 一直到 30,没有明确偏好。
mom=20, vol=15, freq=5)这组参数前后出现了 7 次,是 Anchored 的最频繁选择。其余四种组合(15, 15, 5)、(30, 15, 5)、(15, 10, 5)、(20, 20, 5)各出现 1 到 4 次。换句话说:动量窗口在 15-30 之间换、波动率窗口大多停在 15、再平衡频率 15 折全选 5。Rolling 的波动更宽动量窗口在 15-30 之间跳波动率窗口从 10 一直到 30,没有明确偏好。
每折样本外夏普走势与参数稳定性对比如图 6-2 所示。
每折样本外简化夏普走势与参数稳定性对比如图 6-2 所示。
![Walk-forward 分析:Rolling vs Anchored](images/q6-avoid-overfitting/02-walk-forward-rolling-vs-anchored.png)
![Walk-forward 分析:Rolling Anchored](images/q6-avoid-overfitting/02-walk-forward-rolling-vs-anchored.png)
### 结果解读
第二个认知升级:**参数稳定性比参数最优更重要。**
先看上图。两条实线是样本外夏普——Rolling(红色)和 Anchored(蓝色)都有起伏,但起伏的方向大体一致。下图更关键:Rolling 的参数线在 mom=15-30、vol=10-30 间跳来跳去,Anchored 的两条线被压在更窄的区间内(mom 多在 20、vol 多在 15)。
先看上图。两条实线是样本外简化夏普。Rolling(红色)和 Anchored(蓝色)都有起伏,但起伏的方向大体一致。下图更关键:Rolling 的参数线在 mom=15-30、vol=10-30 间跳来跳去,Anchored 的两条线被压在更窄的区间内(mom 多在 20、vol 多在 15)。
**参数稳定性(Parameter Stability**——好的参数组合应该在不同时间窗口中保持一致。如果每换一个窗口参数就变,说明“最优参数”取决于你看哪段市场,这个参数可能只是在拟合噪音。
**参数稳定性(Parameter Stability**好的参数组合应该在不同时间窗口中保持一致。如果每换一个窗口参数就变,说明“最优参数”取决于你看哪段市场,这个参数可能只是在拟合噪音。
Anchored 在 15 折中 7 折都选了(mom=20, vol=15, freq=5)这一组——15 次里 7 次是同一个答案;剩下的折在四组里跳,但波动率窗口大多稳在 15、调仓频率 15 折全是 5。不管训练集从 2 年扩大到 9 年,参数选择都被限制在一个窄区间——这是一个好信号。而 Rolling 在 15 折里选出 7 种不同组合,每两折就换一次,说明它在追逐短期市场特征,而不是捕捉长期规律。
Anchored 在 15 折中 7 折都选了(mom=20, vol=15, freq=5)这一组15 次里 7 次是同一个答案;剩下的折在四组里跳,但波动率窗口大多稳在 15、再平衡频率 15 折全是 5。不管训练集从 2 年扩大到 9 年,参数选择都被限制在一个窄区间这是一个好信号。而 Rolling 在 15 折里选出 7 种不同组合,每两折就换一次,说明它在追逐短期市场特征,而不是捕捉长期规律。
样本内到样本外的衰减比例:Rolling 衰减 36.2%Anchored 衰减 23.5%。Anchored 衰减更小,因为它用了更多数据训练,不容易被噪音骗。
@@ -261,9 +263,9 @@ Walk-forward 的核心价值:不是找到一个“永远最优”的参数,
**数据说了算:参数稳定 = 更可信。如果最优参数总在变,你怎么知道现在选的参数下个月还是最优?**
**这就是第二把铲子Walk-forward。** 它把“换段时间还稳定吗”从一种焦虑变成了一项可以滚动跑的检查——参数稳定的策略你可以拿得动,参数到处变的策略你可以早早放弃
**这就是第二个检查工具Walk-forward。** 它把“换段时间还稳定吗”从一种焦虑变成了一项可以滚动跑的检查参数稳定的策略你可以继续研究,参数到处变的策略就要谨慎
但 Walk-forward 只用了一种切法——如果数据切的方式不同,结论还一致吗?
但 Walk-forward 只用了一种切法如果数据切的方式不同,结论还一致吗?
---
@@ -271,20 +273,20 @@ Walk-forward 的核心价值:不是找到一个“永远最优”的参数,
Walk-forward 模拟了“边走边调”,但我们只用了一种切法。如果换一种切数据的方式,结论还一样吗?
做科学实验有个基本原则:**一次实验不够,要重复多次。** 只做一次实验就下结论,可能只是碰巧。同样的道理——只验证一次(Step 1)或只用一种切法(Step 2)都不够可靠。
做科学实验有个基本原则:**一次实验不够,要重复多次。** 只做一次实验就下结论,可能只是碰巧。同样的道理只验证一次(第 1 个实验)或只用一种切法(第 2 个实验)都不够可靠。
直觉告诉我们:**好策略不管怎么切都好。** 如果 5 次验证里 4 次都选了同样的参数,比只验证 1 次可信得多。
这就是**交叉验证(Cross Validation**——用多种数据切分方式验证同一个策略,看结论是否一致。
这就是**交叉验证(Cross Validation**用多种数据切分方式验证同一个策略,看结论是否一致。
但金融数据有一个特殊的限制,先用一个生活类比说清楚:**做菜可以“先看明天的菜单再决定今天买什么菜”——但炒股不行**,因为你今天根本不知道明天会发生什么。
但金融数据有一个特殊的限制,先用一个生活类比说清楚:**做菜可以“先看明天的菜单再决定今天买什么菜”,但交易不行**,因为你今天根本不知道明天会发生什么。
把这个限制翻译成两个量化术语:
- **时间序列属性(Time-Series Nature** —— 金融数据有先后顺序今天比昨天晚发生,比明天早发生。顺序不能打乱。
- **时间穿越 / 数据泄露(Look-Ahead Bias / Data Leakage** —— 在训练或验证时,不小心让模型“看到了未来”。比如本应该用 2020 年的数据训练,结果你混了一点 2024 年的数据进来——这等于让你的策略在考试前“偷看了答案”,回测会异常漂亮,但放到实盘上一笔笔真金白银地亏
- **时间序列属性(Time-Series Nature**金融数据有先后顺序今天比昨天晚发生,比明天早发生。顺序不能打乱。
- **时间穿越 / 数据泄露(Look-Ahead Bias / Data Leakage**在训练或验证时,不小心让模型“看到了未来”。比如本应该用 2020 年的数据训练,结果你混了一点 2024 年的数据进来这等于让你的策略在考试前“偷看了答案”,回测会异常漂亮,但真实交易时可能亏钱
所以做交叉验证时**每折必须保持时间顺序**——这就是 **时间序列分割(Time-Series Split**:训练集永远在测试集之前,绝不让“未来”出现在“过去”的训练里。
所以做交叉验证时**每折必须保持时间顺序**这就是 **时间序列分割(Time-Series Split**:训练集永远在测试集之前,绝不让“未来”出现在“过去”的训练里。
试试看。
@@ -292,42 +294,42 @@ Walk-forward 模拟了“边走边调”,但我们只用了一种切法。如
我们一起把这份 spec 写出来。这次重点看两件新东西:**时序切分而非随机切分**(金融数据的硬约束)、**接续型 spec 的减法**(不复述前面已经讲过的概念,专心做一件新事)。
#### 起草上下文 + 任务
#### 第一、二段:上下文和任务描述
> **上下文**:在 `q6-avoid-overfitting.ipynb` 中已有参数优化 + Walk-forward 分析。Walk-forward 只用了一种切法——如果换一种切法,结论还一样吗?一次验证和多次验证,可信度差多少?
> **上下文**:在 `q6-avoid-overfitting.ipynb` 中已有参数优化 + Walk-forward 分析。Walk-forward 只用了一种切法如果换一种切法,结论还一样吗?一次验证和多次验证,可信度差多少?
>
> **任务**:在 notebook 新建单元格,用 `oxq.optimize.TimeSeriesCV` 实现时间序列交叉验证。
> **任务描述**:在 notebook 新建单元格,用 `oxq.optimize.TimeSeriesCV` 实现时间序列交叉验证。
#### 起草要求:时序切分的硬约束
#### 第三段:任务要求
> **要求**
> **任务要求**
>
> 1. 复用 Step 2 的参数空间和约束(无止损组 70 组合)
> 2. 数据范围:`DATA_START="2017-01-01"` 到 `ANALYSIS_END="2026-05-06"`——与 Step 2 同步锁死,保证 5 折切分点在任何时间跑都一致
> 1. 复用第 2 个实验的参数空间和约束(无止损组 70 组合)
> 2. 数据范围:`DATA_START="2017-01-01"` 到 `ANALYSIS_END="2026-05-06"`,与第 2 个实验同步锁死,保证 5 折切分点在任何时间跑都一致
> 3. 用 `oxq.optimize.TimeSeriesCV`(不是 sklearn 的 `KFold`):`n_splits=5`、`expanding=True`
> 4. 传入 `paramset`,让 `cross_validate()` 自动在每折训练集上做 GridSearch
> 5. `n_splits=5` 的依据:ML 教科书默认值,7 年数据上每折约 1.4 年验证集,足够长
> 6. `expanding=True` 的依据:训练集逐折扩大——Step 2 的 Anchored 比 Rolling 更稳定,本步跟随该结论
> 6. `expanding=True` 的依据:训练集逐折扩大。第 2 个实验的 Anchored 比 Rolling 更稳定,本步跟随该结论
> 📌 **要点**:金融数据要用时序切分,不能用随机切分。普通 ML 用 sklearn 的 `KFold` 随机切的(可设 `random_state`);金融场景必须用 `TimeSeriesSplit`——**训练集永远在测试集之前,绝不让“未来”出现在“过去”的训练里**。否则就是把答案偷给了考试中的你——回测看起来漂亮,实盘上一笔笔真金白银地亏,是这条硬约束最常见的代价。这是 q6 比通用 ML 课程多出的一条硬约束
> 📌 **要点**:金融数据要用时序切分,不能用随机切分。普通机器学习里常见的 `KFold` 可以随机切,但金融场景必须用时间序列切分:训练集永远在测试集之前,绝不让“未来”出现在“过去”的训练里。否则就是把答案偷给了考试中的你回测看起来漂亮,真实交易时可能亏钱,这是这条硬约束最常见的代价
> 📌 **要点**:接续型 spec 应该做减法。spec-03 比 spec-01 / spec-02 都短得多——它不重新定义参数空间直接复用 Step 2 的 `wf_paramset`)、不重讲一次什么是过拟合,专心做“5 折结果对比”一件事。**承接型 spec 应当克制重复——前面 spec 讲过的就别再讲只引入本 spec 真正新的东西。**
> 📌 **要点**:接续型 spec 应该做减法。spec-03 比 spec-01 / spec-02 都短得多它不重新定义参数空间直接复用第 2 个实验的 `wf_paramset`;也不重讲一次什么是过拟合,专心做“5 折结果对比”一件事。承接型 spec 应当克制重复前面 spec 讲过的就别再讲只引入本 spec 真正新的东西。
#### 起草结果呈现
#### 第四段:验收标准
> **结果呈现**
> **验收标准**
>
> 1. 用 `cv_result.to_dataframe()` 打印汇总表,再 for 循环逐折打印(训练时段、验证时段、最优参数、样本内/样本外夏普、样本外收益)
> 1. 用 `cv_result.to_dataframe()` 打印汇总表,再 for 循环逐折打印(训练时段、验证时段、最优参数、样本内 / 样本外简化夏普、样本外收益)
> 2. 汇总:`mean_oos_metric()` ± `std_oos_metric()` + 范围 [min, max] + 唯一参数组合数
> 3. 柱状图(figsize 12×6):每折样本内 / 样本外夏普双柱 + 样本外均值水平线
> 4. 标题「5 折交叉验证:每折的样本内 vs 样本外夏普
> 5. 打印分析按实际数据动态描述:5 折中几折选了相同参数、变异系数大小、与 Step 2 Anchored 结论是否一致
> 3. 柱状图(figsize 12×6):每折样本内 / 样本外简化夏普双柱 + 样本外均值水平线
> 4. 标题「5 折交叉验证:每折的样本内样本外简化夏普」
> 5. 打印分析按实际数据动态描述:5 折中几折选了相同参数、变异系数大小、与第 2 个实验 Anchored 结论是否一致
完整 spec 在 [`specs/spec-03-cross-validation.md`](https://github.com/xingwudao/xquant-learning/blob/main/q6-avoid-overfitting/specs/spec-03-cross-validation.md)——复制给 AI,弹窗选「允许」
完整示例 spec 在配套仓库的 [`q6-avoid-overfitting/specs/spec-03-cross-validation.md`](https://github.com/xingwudao/xquant-learning/blob/main/q6-avoid-overfitting/specs/spec-03-cross-validation.md)。参考它确认自己的 spec 后,再复制给 AI,按提示允许执行
AI 助手执行完毕后,你的 notebook 里应该出现 5 折交叉验证的完整结果。
AI 执行完毕后,你的 notebook 里应该出现 5 折交叉验证的完整结果。
`TimeSeriesCV` 把数据切成 5 折。每折用前面的数据训练(训练集逐渐扩大),紧跟着的一段做验证。每折都独立做网格搜索——选出该折的最优参数,再到验证集上检验。
`TimeSeriesCV` 把数据切成 5 折。每折用前面的数据训练(训练集逐渐扩大),紧跟着的一段做验证。每折都独立做网格搜索选出该折的最优参数,再到验证集上检验。
### 运行结果
@@ -335,7 +337,7 @@ AI 助手执行完毕后,你的 notebook 里应该出现了 5 折交叉验证
**表 6-5 5 折交叉验证逐折详情**
| Fold | 训练时段 | 验证时段 | 最优参数 | 样本内夏普 | 样本外夏普 | 样本外收益 |
| Fold | 训练时段 | 验证时段 | 最优参数 | 样本内简化夏普 | 样本外简化夏普 | 样本外收益 |
|------|----------|----------|----------|-----------|-----------|-----------|
| 1 | 2017-01-01 ~ 2018-07-22 | 2018-07-23 ~ 2020-02-11 | mom=15, vol=15, freq=5 | 0.87 | 0.69 | 14.37% |
| 2 | 2017-01-01 ~ 2020-02-11 | 2020-02-12 ~ 2021-09-02 | mom=15, vol=10, freq=5 | 0.72 | 0.44 | 10.82% |
@@ -343,62 +345,62 @@ AI 助手执行完毕后,你的 notebook 里应该出现了 5 折交叉验证
| 4 | 2017-01-01 ~ 2023-03-25 | 2023-03-26 ~ 2024-10-14 | mom=20, vol=15, freq=5 | 0.53 | 1.56 | 37.83% |
| 5 | 2017-01-01 ~ 2024-10-14 | 2024-10-15 ~ 2026-05-06 | mom=20, vol=20, freq=5 | 0.72 | 1.79 | 67.22% |
**TopNRanking CV 汇总:** 样本外夏普 0.95 ± 0.68,样本外范围 [0.28, 1.79],唯一参数组合 4/5。
**TopNRanking CV 汇总:** 样本外简化夏普 0.95 ± 0.68,样本外范围 [0.28, 1.79],唯一参数组合 4/5。
5 折中 Fold 3 与 Fold 4 选择了相同的参数(mom=20, vol=15, freq=5),其余三折各不相同——这组结果该怎么读?
5 折中 Fold 3 与 Fold 4 选择了相同的参数(mom=20, vol=15, freq=5),其余三折各不相同这组结果该怎么读?
每折样本内 vs 样本外夏普对比如图 6-3 所示。
每折样本内样本外简化夏普对比如图 6-3 所示。
![5 折交叉验证:每折的样本内 vs 样本外夏普](images/q6-avoid-overfitting/03-cross-validation-5fold.png)
![5 折交叉验证:每折的样本内样本外简化夏普](images/q6-avoid-overfitting/03-cross-validation-5fold.png)
### 结果解读
第三个认知升级:**多次验证比单次验证更可靠。**
先看参数一致性。5 折里只有 Fold 3 和 Fold 4 选了同一组参数(mom=20, vol=15, freq=5),其余三折各不相同——单看 5 折,共识不算强。但有意思的是:这组(mom=20, vol=15, freq=5)也正是 Step 2 里 Anchored Walk-forward 在 15 折里出现 7 次的最频繁参数。两种独立的切法(一种 15 折滑动一种 5 折时序切分)都把概率最高的位置指向了同一组参数——切法换了,结论没变。
先看参数一致性。5 折里只有 Fold 3 和 Fold 4 选了同一组参数(mom=20, vol=15, freq=5),其余三折各不相同单看 5 折,共识不算强。但有意思的是:这组(mom=20, vol=15, freq=5)也正是第 2 个实验里 Anchored Walk-forward 在 15 折里出现 7 次的最频繁参数。两种独立的切法(一种 15 折滑动一种 5 折时序切分)都把概率最高的位置指向了同一组参数切法换了,结论没变。
再看表现波动。样本外夏普的范围从 0.28 到 1.79,标准差 0.68。变异系数(标准差/均值)约为 0.72,仍小于 1——说明样本外表现整体相对一致,但波动不容忽视:最好的折(1.79)比最差的折(0.28)高了 6 倍以上。参数对了不代表什么时候都有正收益——策略对市场环境仍然敏感。
再看表现波动。样本外简化夏普的范围从 0.28 到 1.79,标准差 0.68。变异系数(标准差 / 均值)约为 0.72,仍小于 1说明样本外表现整体相对一致,但波动不容忽视:最好的折(1.79)比最差的折(0.28)高了 6 倍以上。参数对了不代表什么时候都有正收益策略对市场环境仍然敏感。
交叉验证的价值不是给你一个“最优参数”,而是告诉你**你的参数选择有多可靠**。5 折下来如果共识强,可以更有信心;共识弱,就该接受“参数选择本身有不确定性”,而不是盯着某一折的结果。
**数据说了算:多次验证给出更立体的画像——共识强可以更进一步,共识弱就该承认不确定性。即使参数对了,也别期望每年都有正收益。**
**数据说了算:多次验证给出更立体的画像共识强可以更进一步,共识弱就该承认不确定性。即使参数对了,也别期望每年都有正收益。**
**这就是第三把铲子:交叉验证。** 它让你在多种切法下独立验证同一个结论——一次可能是巧合,多次都指向同一个答案就更可信。前两把铲子告诉你“是不是过拟合”,这一告诉你“你有多大把握”。
**这就是第三个检查工具:交叉验证。** 它让你在多种切法下独立验证同一个结论一次可能是巧合,多次都指向同一个答案就更可信。前两个检查工具告诉你“是不是过拟合”,这一告诉你“你有多大把握”。
到这里,我们从三个角度检验了参数选择——一次验证(Step 1)、动态验证(Step 2)、多次验证(Step 3)。但还有一种过拟合更隐蔽:不是参数选错了,而是**规则太多了**。
到这里,我们从三个角度检验了参数选择一次验证(第 1 个实验)、动态验证(第 2 个实验)、多次验证(第 3 个实验)。但还有一种过拟合更隐蔽:不是参数选错了,而是**规则太多了**。
---
## 6.4 规则越多回测越好——是好事吗?
## 6.4 规则越多回测越好是好事吗?
前三检验的都是参数选择——找参数、更新参数、换切法验证参数。但还有一种过拟合更隐蔽。
前三个实验检验的都是参数选择找参数、更新参数、换切法验证参数。但还有一种过拟合更隐蔽。
回顾第章:我们给策略加了止损、止盈、移动止损等交易规则。当时的结论是“不是所有规则都有用”。但那时我们没有做样本内/样本外验证。如果在样本内不断加规则、调参数,回测收益一路上升——这是真的在改进策略,还是只是在“凑”出好看的数字?
回顾第 4 章:我们给策略加了止损、止盈、移动止损等交易规则。当时的结论是“不是所有规则都有用”。但那时我们没有做样本内 / 样本外验证。如果在样本内不断加规则、调参数,回测收益一路上升这是真的在改进策略,还是只是在“凑”出好看的数字?
直觉告诉我们:**规则越多保护越全面,效果应该越好。** 止损防亏损、止盈锁利润、移动止损跟踪趋势——每一条都有道理。
直觉告诉我们:**规则越多保护越全面,效果应该越好。** 止损防亏损、止盈锁利润、移动止损跟踪趋势每一条都有道理。
真的吗?试试看——从最简单的策略开始,一层一层加规则,看样本内和样本外分别怎么变。
真的吗?试试看从最简单的策略开始,一步一步加规则,看样本内和样本外分别怎么变。
### 动手实验 4:规则负担
我们一起把这份 spec 写出来。这次重点看两件新东西:**规则数的定义**(“规则数”和“自由度”是两个不同的轴)、**业务约束链的金融语义**(止损 < 止盈 / 移动止损 ≥ 固定止损 / 回撤 ≥ 止损都是领域知识)。
#### 起草上下文 + 任务
#### 第一、二段:上下文和任务描述
> **上下文**:在 `q6-avoid-overfitting.ipynb` 中已有参数优化 + Walk-forward + 交叉验证。前三步检验“参数选得对不对”。本步检验另一种过拟合——规则太多。每加一条规则,就多了一个可调旋钮;旋钮越多,越容易在样本内“调出”好看的结果。
> **上下文**:在 `q6-avoid-overfitting.ipynb` 中已有参数优化 + Walk-forward + 交叉验证。前三步检验“参数选得对不对”。本步检验另一种过拟合规则太多。每加一条规则,就多了一个可调旋钮;旋钮越多,越容易在样本内“调出”好看的结果。
>
> **任务**:在 notebook 新建单元格,做两个堆叠实验展示规则负担。
> **任务描述**:在 notebook 新建单元格,做两个堆叠实验展示规则负担。
#### 起草要求:4A 规则堆叠 + 4B 自由度堆叠
#### 第三段:任务要求
把“加规则”和“加自由度”分两个实验跑,**学员才能看出“是规则本身的问题还是自由度的问题”**
把“加规则”和“加自由度”分两个实验跑,读者才能看出“是规则本身的问题还是自由度的问题”。
> **要求**
> **任务要求**
>
> 1. 编写 `run_layer(layer_name, rules, paramset, description)` 辅助函数,复用 Step 1 的样本内/外切分
> 2. **4A 规则堆叠**6 ):基础策略 mom=20/vol=20/freq=10 固定,逐添加交易规则
> 1. 编写 `run_layer(layer_name, rules, paramset, description)` 辅助函数,复用第 1 个实验的样本内 / 样本外切分
> 2. **4A 规则堆叠**6 ):基础策略 mom=20 / vol=20 / freq=10 固定,逐添加交易规则
> - Layer 0:基础策略(无额外规则)
> - Layer 1+ 优化调仓频率
> - Layer 1+ 优化再平衡频率
> - Layer 2+ 优化止损
> - Layer 3+ 优化止盈(约束 sl < tp
> - Layer 4+ 优化移动止损(约束 trail >= sl
@@ -406,43 +408,43 @@ AI 助手执行完毕后,你的 notebook 里应该出现了 5 折交叉验证
> 3. **4B 自由度堆叠**(5 配置):从 1 自由度逐步加到 6 自由度
> - A:仅动量窗口(1 自由度)
> - B+ 波动率(2
> - C+ 调仓频率(3
> - C+ 再平衡频率(3
> - D+ 止损阈值(4
> - E:+ 止盈 + 移动止损(6 自由度,粗网格 [10,20,30] 防止组合数爆炸)
> 4. 每在样本内选最优 → 在样本外验证
> 4. 每一步在样本内选最优,再到样本外验证
> 5. 约束链按金融语义编码:止损 < 止盈、移动止损 ≥ 固定止损、回撤阈值 ≥ 止损
> 📌 **要点**q6 第一次显式区分“规则数”和“自由度”两个轴。**4A 数的是“加了几条交易规则”**止损是一条规则,即便它只有一个 threshold 参数);**4B 数的是“有几个可优化的旋钮”**一条规则可能贡献 1 个或多个自由度。两个轴不一样——通用 ML 课程很少这么分,但金融领域必须分:止损是一条独立业务规则,它在 4A 里占一;但它的 threshold 在 4B 里只贡献 1 自由度。**学员看到两个实验的曲线分别走出 plateau 拐点,才能确认“既不是规则数的问题,也不是单纯自由度的问题,是两者都到了 plateau”。**
> 📌 **要点**6 第一次显式区分“规则数”和“自由度”两个轴。4A 数的是“加了几条交易规则”止损是一条规则,即便它只有一个 threshold 参数4B 数的是“有几个可优化的旋钮”一条规则可能贡献 1 个或多个自由度。两个轴不一样,金融策略里必须分清楚:止损是一条独立业务规则,它在 4A 里占一;但它的 threshold 在 4B 里只贡献 1 自由度。读者看到两个实验的曲线分别出现拐点,才能理解问题不只来自规则数,也不只来自自由度,而是两者都会增加过拟合风险。
> 📌 **要点**:业务约束链要用金融语义,不是技术约束。`sl < tp`止损不能高于止盈,否则止损永远先触发)、`trail >= sl`移动止损不能比固定止损更严,否则后者无意义)、`max_dd >= sl`(组合层风控阈值不应小于个仓止损,否则单仓止损还没触发组合就停了)——三组约束本身就是领域知识。**spec 里的约束链 = 嵌入领域知识的标准位置;学员看 spec 学的不只是参数,还有“为什么这些参数之间该有先后关系”。**
> 📌 **要点**:业务约束链要用金融语义,不是技术约束。`sl < tp` 表示止损不能高于止盈,否则止损永远先触发`trail >= sl` 表示移动止损不能比固定止损更严,否则后者无意义`max_dd >= sl` 表示组合整体风控阈值不应小于单个持仓的止损,否则单个持仓止损还没触发组合整体就先停了。这三组约束本身就是领域知识。spec 里的约束链,是嵌入领域知识的标准位置。读者看 spec 学的不只是参数,还有“为什么这些参数之间该有先后关系”。
#### 起草结果呈现
#### 第四段:验收标准
> **结果呈现**
> **验收标准**
>
> 1. 两张汇总表:4A 六 + 4B 五配置(含自由度、组合数、样本内/样本外夏普、衰减)
> 2. 两张图(各 figsize 12×6,双折线):4A「规则堆叠:样本内 vs 样本外」+ 4B「自由度越多越好」
> 3. 打印分析按实际数据动态描述:4A 找出“超过这一的规则都是规则负担”的最佳、4B 找“自欺欺人开始”的拐点
> 4. 引用 Brian Peterson 两段原话:「Rule burden is a form of overfitting」+「Too many rules will make a backtest look excellent in-sample, and may even work in walk forward analysis, but are very dangerous in production.」
> 1. 两张汇总表:4A 六 + 4B 五配置(含自由度、组合数、样本内 / 样本外简化夏普、衰减)
> 2. 两张图(各 figsize 12×6,双折线):4A「规则堆叠:样本内样本外」+ 4B「自由度越多不等于越好」
> 3. 打印分析按实际数据动态描述:4A 找出“超过这一的规则都是规则负担”的最佳、4B 找“自欺欺人开始”的拐点
> 4. 用一句话总结规则负担:规则本身也可能成为过拟合来源
完整 spec 在 [`specs/spec-04-rule-burden.md`](https://github.com/xingwudao/xquant-learning/blob/main/q6-avoid-overfitting/specs/spec-04-rule-burden.md)——复制给 AI,弹窗选「允许」
完整示例 spec 在配套仓库的 [`q6-avoid-overfitting/specs/spec-04-rule-burden.md`](https://github.com/xingwudao/xquant-learning/blob/main/q6-avoid-overfitting/specs/spec-04-rule-burden.md)。参考它确认自己的 spec 后,再复制给 AI,按提示允许执行
AI 助手执行完毕后,你的 notebook 里应该出现两组实验的完整结果。
AI 执行完毕后,你的 notebook 里应该出现两组实验的完整结果。
**4A:逐添加规则。** 从最简单的基础策略出发,每多加一条规则,每都在样本内做优化。约束条件先把不合理的组合(比如止损大于止盈、移动止损小于固定止损)过滤掉——剩下的层差异才不会被荒谬组合污染。
**4A:逐添加规则。** 从最简单的基础策略出发,每一步多加一条规则,每一步都在样本内做优化。约束条件先把不合理的组合(比如止损大于止盈、移动止损小于固定止损)过滤掉剩下的结果才不会被荒谬组合污染。
**4B:逐步增加参数维度。** 从只调 1 个参数(动量窗口)开始,逐步增加可调参数的数量。**自由度(Degrees of Freedom**——可以调节的参数和规则数量。自由度越多,优化器的“调节空间”就越大。
**4B:逐步增加参数维度。** 从只调 1 个参数(动量窗口)开始,逐步增加可调参数的数量。**自由度(Degrees of Freedom**,就是可以调节的参数和规则数量。自由度越多,优化器的“调节空间”就越大。
### 运行结果
4A 规则堆叠的样本内/样本外表现如表 6-6 所示。
4A 规则堆叠的样本内 / 样本外表现如表 6-6 所示。
**表 6-6 4A 规则堆叠:每规则的样本内/样本外夏普与衰减**
**表 6-6 4A 规则堆叠:每一步规则的样本内 / 样本外简化夏普与衰减**
| Layer | 描述 | 组合数 | 样本内夏普 | 样本外夏普 | 衰减 |
| Layer | 描述 | 组合数 | 样本内简化夏普 | 样本外简化夏普 | 衰减 |
|-------|------|--------|-----------|-----------|------|
| 0 | 基础(固定参数) | 1 | 0.94 | 0.52 | -0.43 |
| 1 | + 优化调仓频率 | 3 | 1.06 | -0.07 | -1.13 |
| 1 | + 优化再平衡频率 | 3 | 1.06 | -0.07 | -1.13 |
| 2 | + 优化止损 | 18 | 1.11 | 0.36 | -0.75 |
| 3 | + 优化止盈 | 57 | 1.33 | 0.15 | -1.18 |
| 4 | + 优化移动止损 | 183 | 1.33 | 0.15 | -1.18 |
@@ -450,52 +452,52 @@ AI 助手执行完毕后,你的 notebook 里应该出现了两组实验的完
4B 指标堆叠(按可优化的自由度分档)的对比如表 6-7 所示。
**表 6-7 4B 指标堆叠:自由度递增下的样本内/样本外夏普**
**表 6-7 4B 自由度堆叠:自由度递增下的样本内 / 样本外简化夏普**
| 配置 | 自由度 | 组合数 | 描述 | 样本内夏普 | 样本外夏普 | 衰减 |
| 配置 | 自由度 | 组合数 | 描述 | 样本内简化夏普 | 样本外简化夏普 | 衰减 |
|------|--------|--------|------|-----------|-----------|------|
| A | 1 | 5 | 动量窗口 | 1.07 | -0.17 | -1.24 |
| B | 2 | 25 | + 波动率窗口 | 1.18 | 0.01 | -1.17 |
| C | 3 | 70 | + 调仓频率 | 1.18 | 0.01 | -1.17 |
| C | 3 | 70 | + 再平衡频率 | 1.18 | 0.01 | -1.17 |
| D | 4 | 420 | + 止损阈值 | 1.39 | 0.50 | -0.88 |
| E | 6 | 323 | + 止盈 + 移动止损 | 1.56 | -0.07 | -1.62 |
数 / 自由度增加,样本内一路向上样本外却平甚至下降,对比如图 6-4 所示。
规则数 / 自由度增加,样本内一路向上样本外却平甚至下降,对比如图 6-4 所示。
![4A 规则堆叠 与 4B 自由度越多不等于越好](images/q6-avoid-overfitting/04-rule-burden-4a-4b.png)
![4A 规则堆叠与 4B 自由度越多不等于越好](images/q6-avoid-overfitting/04-rule-burden-4a-4b.png)
### 结果解读
第四个认知升级:**规则越多 != 策略越好,简单就是美。**
第四个认知升级:**规则越多不等于策略越好,简单就是美。**
这是本章最重要的认知冲击。
先看 4A。样本内夏普从 Layer 0 的 0.94 爬升到 Layer 3 的 1.33——每加一条规则,优化器都能在样本内找到更好看的结果。但样本外呢?Layer 0 (基础策略,完全没有额外规则)的样本外夏普是 0.52——这是所有中最高的加了调仓频率优化后(Layer 1),样本外直接变成了 -0.07。止损(Layer 2)挽回了一些(0.36),但之后的止盈、移动止损、回撤风控全部无效——样本外停在 0.15 不再提升。
先看 4A。样本内简化夏普从 Layer 0 的 0.94 爬升到 Layer 3 的 1.33每加一条规则,优化器都能在样本内找到更好看的结果。但样本外呢?Layer 0(基础策略,完全没有额外规则)的样本外简化夏普是 0.52这是所有步骤中最高的加了再平衡频率优化后(Layer 1),样本外直接变成了 -0.07。止损(Layer 2)挽回了一些(0.36),但之后的止盈、移动止损、回撤风控全部无效样本外停在 0.15 不再提升。
注意 Layer 3-5 三行的样本内、样本外、衰减数字一字不差(IS 1.33 / OS 0.15 / 衰减 -1.18——这不是抄错。Layer 3 引入了 `sl < tp`Layer 4 又叠 `trail >= sl`Layer 5 再叠 `max_dd >= sl`三条约束把搜索空间一层层锁死。再增加的旋钮被前面的约束吃掉,最后什么都调不动。**这是规则负担的另一面:旋钮多了,相互之间的约束也多,优化器其实根本没机会真正“利用”那几个新自由度。**
注意 Layer 3-5 三行的样本内、样本外、衰减数字一字不差(IS 1.33 / OS 0.15 / 衰减 -1.18这不是抄错。Layer 3 引入了 `sl < tp`Layer 4 又叠 `trail >= sl`Layer 5 再叠 `max_dd >= sl`三条约束把搜索空间一步步锁住。再增加的旋钮被前面的约束吃掉,最后什么都调不动。**这是规则负担的另一面:旋钮多了,相互之间的约束也多,优化器其实根本没机会真正“利用”那几个新自由度。**
也就是说:最简单的基础策略,反而是样本外表现最好的。
再看 4B。从配置 D 到配置 E,自由度从 4 增加到 6,样本内夏普从 1.39 升到 1.56(好了 12%),但样本外从 0.50 跌到 -0.07——**自欺欺人的拐点**出现了。多出来的两个参数让优化器在样本内找到了更好看的结果,但在样本外完全不兑现。
再看 4B。从配置 D 到配置 E,自由度从 4 增加到 6,样本内简化夏普从 1.39 升到 1.56(好了 12%),但样本外从 0.50 跌到 -0.07**自欺欺人的拐点**出现了。多出来的两个参数让优化器在样本内找到了更好看的结果,但在样本外完全不兑现。
为什么会这样?每条新规则都增加一个**自由度**——可以调节的旋钮。旋钮越多,优化器就有越多的空间去“凑”出好看的回测结果。这不是在发现真规律,而是在记忆噪音。这就是**规则负担(Rule Burden**——规则本身就是一种过拟合的来源。规则太多会让回测看起来很漂亮,甚至在前推验证中也可能过关,但放到实盘就很危险。
为什么会这样?每条新规则都增加一个**自由度**,也就是可以调节的旋钮。旋钮越多,优化器就有越多的空间去“凑”出好看的回测结果。这不是在发现真规律,而是在记忆噪音。这就是**规则负担(Rule Burden**规则本身也可能成为过拟合的来源。规则太多会让回测看起来很漂亮,甚至在前推验证中也可能过关,但真实交易时很危险。
这个判断不是我们一拍脑袋——R 语言量化框架 quantstrat 的作者 Brian Peterson 早就说过:“规则负担本身就是一种过拟合(Rule burden is a form of overfitting)。规则太多会让回测在样本内异常漂亮,甚至在前推验证里也能过关,但放到实盘就非常危险。”每条规则都是给优化器多一个旋钮——旋钮越多,凑出好看回测的余地就越大,对真实未来的把握反而越小
规则越多,凑出漂亮回测的余地就越大,对真实未来的把握反而可能越小。你不是在让策略更聪明,而是在给历史数据更多机会“配合你”
**铁律:能用 2 个参数解决的问题,不要用 6 个。每增加一条规则,都要问——样本外也改善了吗?如果没有,删掉它。少即是多。**
**铁律:能用 2 个参数解决的问题,不要用 6 个。每增加一条规则,都要问样本外也改善了吗?如果没有,删掉它。少即是多。**
**这就是第四把铲子:规则负担。** 它告诉你少即是多——不是“加规则越多策略越好”,而是“每加一条规则都要在样本外验证它真的帮上忙”。这把铲子和前三不同:前三帮你检查已有的策略,这一帮你**在设计阶段就少挖坑**
**这就是第四个检查工具:规则负担。** 它告诉你少即是多不是“加规则越多策略越好”,而是“每加一条规则都要在样本外验证它真的帮上忙”。这个工具和前三不同:前三帮你检查已有的策略,这一帮你在设计阶段就少走弯路
---
## 6.5 回头看:你刚才做了什么?
**6.5 是“做”——四把铲子的操作流程总图,把四步实验铺成你以后每次接到一份回测都能照着走的检查路径。** 四步下来,我们从四个角度回答了同一个核心问题——“这个参数组合是真的好,还是运气好?
这一节把本章四个实验铺成一条以后可以复用的检查路径。四个实验回答的是同一个核心问题这个参数组合是真的好,还是运气好?
- **Step 1: 参数优化** 490 种组合,样本内很好样本外全面衰减过拟合
- **Step 2: Walk-forward** 模拟“边走边调” → 参数稳定才可信
- **Step 3: 交叉验证** 5 折独立验证共识强可以更进一步,共识弱就该承认不确定性
- **Step 4: 规则负担** 规则越多样本内越好样本外不买账简单就是美
- **第 1 个实验:参数优化** 490 种组合,样本内很好样本外全面衰减,这就是过拟合
- **第 2 个实验:Walk-forward** 模拟“边走边调”,看参数是否稳定。
- **第 3 个实验:交叉验证** 5 折独立验证共识强可以更进一步,共识弱就该承认不确定性
- **第 4 个实验:规则负担** 规则越多样本内越好,但样本外不买账简单就是美
从今以后,拿到任何策略的回测结果,可以用一套防过拟合检查清单核对一遍真实性,如表 6-8 所示。
@@ -504,30 +506,35 @@ AI 助手执行完毕后,你的 notebook 里应该出现了两组实验的完
| 检查项 | 怎么做 | 好的信号 | 坏的信号 |
|--------|--------|----------|----------|
| 样本外验证 | 在没见过的数据上跑 | 衰减小 | 衰减大 |
| Walk-forward | 分窗口优化+验证 | 参数跨窗口稳定 | 参数每次都变 |
| Walk-forward | 分窗口优化 + 验证 | 参数跨窗口稳定 | 参数每次都变 |
| 交叉验证 | 多折验证 | 各折结果一致 | 各折差异巨大 |
| 规则负担 | 从简到繁,逐加 | 样本外同步改善 | 样本内改善但样本外不变 |
| 参数敏感性(第章) | 扫描参数 | 高原型 | 山峰型 |
| 规则负担 | 从简到繁,逐加 | 样本外同步改善 | 样本内改善但样本外不变 |
| 参数敏感性(第 5 章) | 扫描参数 | 高原型 | 山峰型 |
加上第章的参数敏感性,你手上已经有五把检验策略的武器。第一章那次“被自己的回测骗”的肉身记忆,到这里沉淀成了一份可以挂在墙上的检查清单——这就是“疑”从感觉变成方法的过程,也是飞轮归因层在你手里第一次完整闭环的一小步。下次再接到一份“漂亮”的回测,你不会再赤手空拳,而是知道该问哪几个问题、哪几把铲子去挖
加上第 5 章的参数敏感性,你手上已经有五个检查工具。第 1 章那次“被自己的回测骗”的经历,到这里沉淀成了一份可以复用的检查清单这就是“疑”从感觉变成方法的过程。下次再接到一份“漂亮”的回测,你不会只看收益率,而是知道该问哪几个问题、哪几项检查
更重要的是:这四把铲子不只是让你“能识破自己的过拟合”,它们也让你**敢于做更激进的策略实验**——因为你知道有工具可以兜住底。怀疑回测不是为了畏手畏脚,是为了能放心动手。这就是“疑”和“做”在飞轮里的真正关系:**疑得越深,做得越敢。**
更重要的是:这些检查工具不只是让你识别过拟合,也让你敢于做更有探索性的策略实验因为你知道,有一套方法可以把漂亮结果拆开验证。怀疑回测不是为了畏手畏脚,是为了更负责任地动手。
---
## 6.6 本章总结
**6.6 是“概念速查 + 带走的问题”——和 6.5 严格分工:6.5 写“怎么做”,6.6 写“用到了什么 / 还有什么没回答”。**
### 策略进化路径
第五章产出:完整的策略评估方法 + 体检清单 → Step 1: 参数优化(样本内很好但样本外衰减,过拟合) → Step 2: Walk-forward(模拟定期重新优化,参数稳定性) → Step 3: 交叉验证(多次验证更可靠,但表现差异大) → Step 4: 规则负担(规则/参数越多越危险,简单就是美) → 第六章产出:四把防身工具 + 防过拟合检查清单 + “怀疑回测”的态度
**第 5 章产出:完整的策略评估方法 + 体检清单**
- 第 1 个实验:参数优化。样本内很好,但样本外衰减,这就是过拟合。
- 第 2 个实验:Walk-forward。模拟定期重新优化,检查参数稳定性。
- 第 3 个实验:交叉验证。多次验证更可靠,也能暴露结果差异。
- 第 4 个实验:规则负担。规则和参数越多,越要警惕过拟合。
**第 6 章产出:防过拟合检查清单 + “怀疑回测”的方法**
### 概念速查表
本章涉及的核心概念汇总如表 6-9 所示,方便随时回查。
**表 6-9 第章核心概念速查**
**表 6-9 第 6 章核心概念速查**
| 概念 | 含义 | 类比 |
|------|------|------|
@@ -541,29 +548,29 @@ AI 助手执行完毕后,你的 notebook 里应该出现了两组实验的完
| 参数稳定性 | 好参数在不同窗口中保持一致 | 不管怎么考,你的强项科目都一样 |
| 交叉验证 | 用多种切法验证同一个策略 | 做实验要重复多次才可信 |
| 时间序列分割 | 保持时间顺序的交叉验证 | 不能用未来考题“复习”过去 |
| 规则负担 | 每条新规则增加过拟合风险 | 考试作弊手段越多,被抓的风险越大 |
| 自由度 | 可以调节的参数和规则数量 | 作弊的旋钮越多,越容易凑出高分 |
| 规则负担 | 每条新规则都可能增加过拟合风险 | 规则越多,越容易把历史数据配合好 |
| 自由度 | 可以调节的参数和规则数量 | 可调旋钮越多,越容易凑出高分 |
### 本章核心认知
走完四步实验后,本章最值得带走的八条认知如表 6-10 所示。
**表 6-10 第章核心认知**
**表 6-10 第 6 章核心认知**
| 认知 | 来源 |
|------|------|
| 参数优化是双刃剑——优化后必须在新数据上验证 | Step 1 |
| 样本内的好成绩不算数 | Step 1 |
| 参数稳定性比参数最优更重要 | Step 2 |
| 好参数跨窗口稳定,坏参数到处变 | Step 2 |
| 多次验证比单次验证更可靠 | Step 3 |
| 不同验证方法指向同一个参数可信度高 | Step 3 |
| 规则越多 != 策略越好 | Step 4 |
| 能用 2 个参数解决的问题,不要用 6 个 | Step 4 |
| 参数优化是双刃剑优化后必须在新数据上验证 | 第 1 个实验 |
| 样本内的好成绩不算数 | 第 1 个实验 |
| 参数稳定性比参数最优更重要 | 第 2 个实验 |
| 好参数跨窗口稳定,坏参数到处变 | 第 2 个实验 |
| 多次验证比单次验证更可靠 | 第 3 个实验 |
| 不同验证方法指向同一个参数可信度高 | 第 3 个实验 |
| 规则越多不等于策略越好 | 第 4 个实验 |
| 能用 2 个参数解决的问题,不要用 6 个 | 第 4 个实验 |
### 带走的问题
我们已经知道怎么评估策略(第章)、怎么防过拟合(第章)。回测世界的问题基本解决了。
我们已经知道怎么评估策略(第 5 章)、怎么防过拟合(第 6 章)。回测世界的问题基本解决了。
但回测终究是模拟。在回测里,你可以按任何价格、在任何时间成交。真实世界里呢?
@@ -571,20 +578,8 @@ AI 助手执行完毕后,你的 notebook 里应该出现了两组实验的完
- 你下单到成交之间,价格会不会已经变了?
- 回测假设的手续费,和真实交易一样吗?
**从回测到实盘,还有多少在等着你?** → 第七章
**从回测到真实交易,还有多少问题在等着你?** 这就是第 7 章要回答的问题。
**你在第一章撞过那堵墙,第五章手里有了体检表,这一章把怀疑变成了肌肉记忆。下一章,回测世界要让位给真实成交的世界——你将带着这四把铲子,走进实盘。**
你在第 1 章看见过过拟合,第 5 章手里有了体检表,这一章把怀疑变成了具体检查。下一章,回测世界要让位给真实成交的世界
> 本章所有代码的可运行版本见 `notebooks/q6-avoid-overfitting.ipynb`
---
## 反馈与读者群
如果你发现本章有错别字、概念不清或实验结果不一致,欢迎
[提交 GitHub Issue](https://github.com/xingwudao/xquant-beginner/issues/new/choose)。
课程、正式书和后续更新见
[xquant.shop/courses](https://xquant.shop/courses)。
添加作者微信 `xquanter` 进入读者群。请备注:`XQuant 读者`
> 本章所有代码的可运行版本见配套仓库的 [`q6-avoid-overfitting/notebooks/q6-avoid-overfitting.ipynb`](https://github.com/xingwudao/xquant-learning/blob/main/q6-avoid-overfitting/notebooks/q6-avoid-overfitting.ipynb)
+151 -171
View File
@@ -1,39 +1,31 @@
# 第七章:真的下单:跨到实盘
# 第 7 章:从理想到现实:执行交易
章你建立了一套完整的防线——参数优化、前推验证、交叉验证、规则负担检查。策略通过了所有考验,你信心满满。
6 章你建立了一套完整的防线参数优化、前推验证、交叉验证、规则负担检查。策略通过了所有考验,你信心满满。
**明天就去买!**
但打开券商 APP,你愣住了。策略说“沪深300ETF 权重 35%,纳指100ETF 权重 25%,黄金ETF 权重 40%”——可 APP 上只能输入股数和价格。35% 是多少股?按什么价格下单?
但打开券商 APP,你愣住了。券商就是帮你买卖股票和基金的机构。策略说“沪深300ETF 权重 35%,纳指100ETF 权重 25%,黄金ETF 权重 40%”可 APP 上只能输入股数和价格。35% 是多少股?按什么价格下单?
> **📌 小词典:券商 / 下单 / 成交价**
>
> - **券商** —— 你委托买卖股票的中介机构(类似机票代理人)。
> - **下单** —— 告诉券商“买/卖什么、多少股、什么价格”(把订单递给售票窗口)。
> - **成交价** —— 券商真正帮你买到 / 卖出时的价格(想 ¥4.27 买、实际成交 ¥4.30、差额就是滑点)。
下单,就是告诉券商“买什么、卖什么、多少股、什么价格”。成交价,就是券商实际帮你买到或卖出的价格。你想用 ¥4.27 买,最后 ¥4.30 才买到,中间差出来的部分就是滑点。
回测引擎在内部用精确的浮点数分配权重——你以为回测结果就是实结果。但中间隔一整条执行链:权重要翻译成股数,价格在不停变化,下单要花佣金和印花税,成交价和预期价格总有差距。这些差距不是抽象的——本章后面你会亲眼看到,仅仅因为成交价模式不同,同一策略最好和最差之间累计差出 6.64%;再叠加佣金 + 印花税 + 滑点,四年累计又被侵蚀 5.72%。这一章就是要看清这些“看不见的钱”都去哪了。
回测引擎在内部用精确的数分配权重。你很容易误以为回测结果就是实际交易结果。但中间隔一整条执行链:权重要翻译成股数,价格在不停变化,下单要花佣金和印花税,成交价和预期价格总有差距。这些差距不是抽象的本章后面你会亲眼看到,仅仅因为成交价模式不同,同一策略最好和最差之间累计差出 6.64%;再叠加佣金、印花税和滑点,四年累计又被侵蚀 5.72%。这一章就是要看清这些“看不见的钱”都去哪了。
### 路线图
**选什么标的(第章)→ 每个买多少(第章)→ 什么时候买卖(第章)→ 怎么验证有效(第章)→ 如何避免自欺欺人(第章)→ 如何真正执行(第章本章)**
**选什么标的(第 2 章)→ 每个买多少(第 3 章)→ 什么时候买卖(第 4 章)→ 怎么验证有效(第 5 章)→ 如何避免自欺欺人(第 6 章)→ 如何真正执行(第 7 章本章)**
**这一章你跨进了飞轮的“执行层”——主练动作回到“做+看”:把“权重 → 订单 → 成交”这条执行链上的每一步都做成可重复的规则,再用执行报告“看”出回测和实盘之间的差距。** 本章从“迫不及待想操作”的状态出发,在操作中暴露四个问题,四步对应的方法如表 7-1 所示。
**这一章进入四个阶段里的执行交易阶段。重点落在“做”和“看”两个环节:把“权重 → 订单 → 成交”这条执行链上的每一步都做成可重复的规则,再用执行报告出回测和真实交易之间的差距。** 本章从“迫不及待想操作”的状态出发,在操作中暴露四个问题,四步对应的方法如表 7-1 所示。
**表 7-1 第章四步问题与方法对照**
**表 7-1 第 7 章四步问题与方法对照**
| 步骤 | 问题 | 方法 |
|------|------|------|
| Step 1 | 35% 是多少股? | 目标权重 → 具体订单 |
| Step 2 | 价格变了怎么办? | 成交价压力测试 + 成本层叠 |
| Step 3 | 回测 vs 实盘,差距有多大? | Alpaca 模拟交易 |
| Step 4 | 执行得对不对? | 执行报告 + 逐笔对账 |
| 第 1 步 | 35% 是多少股? | 目标权重 → 具体订单 |
| 第 2 步 | 价格变了怎么办? | 成交价压力测试 + 成本层叠 |
| 3 | 回测和模拟交易差距有多大? | Alpaca 模拟交易 |
| 第 4 步 | 执行得对不对? | 执行报告 + 逐笔对账 |
> **📌 小词典:实盘 vs 模拟盘**
>
> - **实盘** —— 行业惯例统称把策略接入交易接口、订单走真实撮合的运行模式。
> - **本章特指** —— Alpaca 模拟盘(paper trading),订单走 Alpaca 的撮合环境,不涉及真金白银。本章后文提到的“实盘”都是这个意思。
> - **真正用钱下单** —— 需要按所在市场的开户、入金、合规流程独立办理(A 股的对照见拓展阅读)。
本章会反复提到“模拟交易”。这里指 Alpaca 模拟盘(paper trading):订单进入 Alpaca 的撮合环境,不动用真实资金。真正用钱交易,需要按所在市场的开户、入金和合规流程独立办理。本章先用模拟交易练执行流程,避免还没学会检查执行风险,就先把真钱放进去。
(操作流程见前言“怎么使用这本书”。)
@@ -51,41 +43,41 @@
### 动手实验 1:目标权重 → 具体订单
我们一起把这份 spec 写出来。这次重点看三件新东西:**实盘金额必须用 Decimal**、**lot_size 是市场制度的硬约束**,以及**单点 → 横切 → 网格的三段递进**。
我们一起把这份 spec 写出来。这次重点看三件新东西:**交易金额必须用 Decimal**、**lot_size 是市场制度的硬约束**,以及**单点 → 横切 → 网格的三段递进**。
#### 起草上下文 + 任务
#### 第一、二段:上下文和任务描述
q7 的 spec 必须从第一行就交代为什么是实盘场景」——上下文用调仓日开场,把学员从「浮点数权重的舒适世界拉到券商 APP 只接受整数股的现实。
7 的 spec 必须从第一行就交代为什么是执行交易场景”。上下文用调整持仓的日子开场,把读者从“小数权重的舒适世界拉到券商 APP 只接受整数股的现实。
> **上下文**学员跑完第章,策略通过了所有检验。今天是调仓日,风险平价信号给出目标权重——沪深300ETF 35% / 纳指100ETF 25% / 黄金ETF 40%。打开券商 APP,只能输入股数和价格。
> **上下文**读者跑完第 6 章,策略通过了所有检验。今天需要调整持仓,风险平价方法给出目标权重沪深300ETF 35% / 纳指100ETF 25% / 黄金ETF 40%。打开券商 APP,只能输入股数和价格。
>
> **任务**:在 `q7-execution.ipynb` 中用 `generate_orders()` 把目标权重翻译成具体订单,并对比 A 股(lot_size=100)和美股(lot_size=1)的取整偏差。
> **任务描述**:在 `q7-execution.ipynb` 中用 `generate_orders()` 把目标权重翻译成具体订单,并对比 A 股(lot_size=100)和美股(lot_size=1)的取整偏差。
> 📌 **要点**实盘金额计算必须用 `Decimal` 而非 float。spec 在 import 段写 `from decimal import Decimal`,常量定义里所有权重、价格、本金都用 `Decimal("0.35")` 字符串构造——浮点累积误差在大额账户上会到分到角的差异,这是 q7 起的第一条领域规矩。
> 📌 **要点**交易金额计算必须用 `Decimal` 而非 float。spec 在 import 段写 `from decimal import Decimal`,常量定义里所有权重、价格、本金都用 `Decimal("0.35")` 字符串构造浮点累积误差在大额账户上会到分到角的差异,这是 7 起的第一条领域规矩。
#### 起草要求:lot_size 与三段递进
#### 第三段:任务要求
要求段把市场规则当不变量写死——A 股 100 股一手美股 1 股一手——这不是 oxq 的设计选择,是真实世界的制度差异。然后用三段递进暴露问题。
任务要求段把市场规则当不变量写死A 股 100 股一手美股 1 股一手这不是 oxq 的设计选择,是真实世界的制度差异。然后用三段递进暴露问题。
> **要求**
> **任务要求**
>
> 1. 常量:A 股标的 `("510300.SS", "513100.SS", "518880.SS")`、目标权重 35%/25%/40%、模拟价格 ¥4.27/¥1.72/¥8.04(全用 Decimal 字符串构造)
> 2. 第一步——空仓 + 10 万人民币 + `lot_size=100`,生成 A 股订单清单
> 3. 第二步——A 股(lot_size=100vs 美股(SPY/QQQ/GLDlot_size=1)的对比表
> 4. 第三步——五档资金量(1 万 / 5 万 / 10 万 / 50 万 / 100 万)× 两市场的网格扫描
> 2. 第 1 步:空仓 + 10 万人民币 + `lot_size=100`,生成 A 股订单清单
> 3. 第 2 步:A 股(lot_size=100vs 美股(SPY/QQQ/GLDlot_size=1)的对比表
> 4. 第 3 步:五档资金量(1 万 / 5 万 / 10 万 / 50 万 / 100 万)× 两市场的网格扫描
> 5. 对比柱状图(figsize 14×5):左图最大权重偏差、右图现金残余占比;A 股红、美股蓝
> 📌 **要点**`lot_size=100` 是 A 股的硬约束(100 股一手是市场规则),`lot_size=1` 是美股的硬约束。把这种制度差异参数化进 spec学员才能看清真相——不是 oxq 在挑选实现,是市场在挑选规则。
> 📌 **要点**`lot_size=100` 是 A 股的硬约束(100 股一手是市场规则),`lot_size=1` 是美股的硬约束。把这种制度差异参数化进 spec读者才能看清真相不是 oxq 在挑选实现,是市场在挑选规则。
> 📌 **要点**:三段递进(单点 → 横切 → 网格)是 q7 整章的节奏——第一步跑通接口、第二步两市场对比、第三步五档资金 × 两市场。先看到现象再看到对比再看到规律,避免一上来用网格扫描压垮 0 基础学员
> 📌 **要点**:三段递进(单点 → 横切 → 网格)是 7 整章的节奏。第 1 步跑通接口,第 2 步对比两个市场,第 3 步扫描五档资金 ×市场。先看到现象再看到对比再看到规律,避免一上来用网格扫描压垮 0 基础读者
#### 起草结果呈现
#### 第四段:验收标准
> **结果呈现**:订单表格 + A 股 vs 美股对比表 + 五档资金量两张表 + 对比柱状图;打印投入金额和剩余现金,过渡句订单有了,但按什么价格下单?自然带到下一节。
> **验收标准**:订单表格 + A 股 vs 美股对比表 + 五档资金量两张表 + 对比柱状图;打印投入金额和剩余现金,过渡句订单有了,但按什么价格下单?自然带到下一节。
完整 spec 在 [`specs/spec-01-order-generator.md`](https://github.com/xingwudao/xquant-learning/blob/main/q7-execution/specs/spec-01-order-generator.md)——复制给 AI,弹窗选「允许」
完整示例 spec 见配套仓库的 [`q7-execution/specs/spec-01-order-generator.md`](https://github.com/xingwudao/xquant-learning/blob/main/q7-execution/specs/spec-01-order-generator.md)。参考示例后,确认自己的 spec,再复制给 AI
AI 助手执行完毕后,你的 notebook 里应该出现了订单生成的完整结果。`generate_orders()` 就是那个翻译器——输入目标权重、当前持仓、最新价格和总资金,输出具体的订单清单。`lot_size=100` 表示 A 股必须买 100 股的整数倍。A 股 ETF 单价虽然低(几元钱),但 100 股一手的约束让偏差远大于美股。
AI 编程工具执行完毕后,你的 notebook 里应该出现了订单生成的完整结果。`generate_orders()` 就是那个翻译器输入目标权重、当前持仓、最新价格和总资金,输出具体的订单清单。`lot_size=100` 表示 A 股必须买 100 股的整数倍。A 股 ETF 单价虽然低(几元钱),但 100 股一手的约束让偏差远大于美股。
### 运行结果
@@ -131,7 +123,7 @@ A 股的偏差明显更大,现金残余也翻了一倍多。这就是 `lot_siz
回测引擎在内部用浮点数精确分配权重——沪深300ETF 就是精确的 35.000%。但你去下单时,只能买 100 股的整数倍。8100 股沪深300ETF 的实际权重是 34.6%,不是 35.0%。
**取整偏差是系统性的**——每次调仓都会产生,日积月累就是真实的收益偏差。A 股的 100 股整手约束让偏差尤其明显。小资金账户(比如 10 万人民币做三只 ETF 的风险平价)可能连基本的权重分布都无法实现。
**取整偏差是系统性的**每次调整持仓都会产生,日积月累就是真实的收益偏差。A 股的 100 股整手约束让偏差尤其明显。小资金账户(比如 10 万人民币做三只 ETF 的风险平价)可能连基本的权重分布都无法实现。
还有一笔“隐形损失”:**现金残余**。那些因为凑不成整股而趴在账户里的现金,一分钱收益都没产生。
@@ -141,15 +133,15 @@ A 股的偏差明显更大,现金残余也翻了一倍多。这就是 `lot_siz
## 7.2 价格变了怎么办
回测里你只看到“权重”,但实盘里每一笔下单都要付钱给三个人——这是先建立预算感再做实验的前提
回测里你只看到“权重”,但真实交易里每一笔下单都要付钱给三方。先建立预算感再做实验。
**三笔执行成本你必须先认清:**
- **佣金(Commission)** —— 付给券商的服务费,A 股通常是万分之一到万分之三,最低 5 元;美股按笔或按股;港股约万分之五。本章 SimBroker 默认填的是万分之一。
- **印花税(Stamp Duty)** —— 付给政府的,A 股**卖出**时收千分之一(买入不收);港股买卖双向各 0.13%;美股没有印花税。第章拓展阅读已经预告过,本章你将在执行报告里第一次亲眼看到它把钱怎么慢慢吃掉。
- **印花税(Stamp Duty)** —— 付给政府的,A 股**卖出**时收千分之一(买入不收);港股买卖双向各 0.13%;美股没有印花税。第 2 章拓展阅读已经预告过,本章你将在执行报告里第一次亲眼看到它把钱怎么慢慢吃掉。
- **滑点(Slippage)** —— 你下单时看到的价格和最终成交的价格之间的差。看似小,每笔 0.1%-0.3%,但乘以一年几十笔交易,累积下来不可忽视。这一节就是要让你看到它有多大。
带着这三个成本的画面感,我们再回到具体场景:Step 1 生成了订单清单——“买沪深300ETF 8100 股,按 ¥4.27”。但 ¥4.27 是昨天的收盘价。你现在下单,价格可能已经涨到 ¥4.30 了,也可能跌到 ¥4.24。
带着这三个成本的画面感,我们再回到具体场景: 1 生成了订单清单“买沪深300ETF 8100 股,按 ¥4.27”。但 ¥4.27 是昨天的收盘价。你现在下单,价格可能已经涨到 ¥4.30 了,也可能跌到 ¥4.24。
按哪个价格买?这个差异有多大影响?
@@ -163,44 +155,44 @@ A 股的偏差明显更大,现金残余也翻了一倍多。这就是 `lot_siz
我们一起把这份 spec 写出来。这次重点看两件新东西:**带宽即认知**(用 fill_between 把执行不确定性变成可见的橙色带)和**积分式归因**(成本拆四层逐层加,把“扣了 5%”分解成佣金/印花税/滑点各扣多少)。
#### 起草上下文 + 任务
#### 第一、二段:上下文和任务描述
> **上下文**:在 `q7-execution.ipynb` 中已有 Step 1 的订单生成实验。Step 1 生成了订单买沪深300ETF 8100 股,按 ¥4.27」——但这是昨天收盘价,现在下单价格已经变了。按哪个价格买?差异有多大影响?
> **上下文**:在 `q7-execution.ipynb` 中已有 1 的订单生成实验。 1 生成了订单买沪深300ETF 8100 股,按 ¥4.27”。但这是昨天收盘价,现在下单价格已经变了。按哪个价格买?差异有多大影响?
>
> **任务**:用 `FillPriceMode` 的五种模式做压力测试,再用四层成本层叠展示佣金、印花税和滑点的持续侵蚀。
> **任务描述**:用 `FillPriceMode` 的五种模式做压力测试,再用四层成本层叠展示佣金、印花税和滑点的持续侵蚀。
> 📌 **要点**:金融术语在 spec 内最低限度解释。q7 的金融黑话密度高(成交价、滑点、印花税折算、归一化净值、夏普比率),spec 写作要假设学员单读 spec 也能看懂——在要求段开头加一段术语简释,不让 spec 完全依赖 book 才能读懂。
> 📌 **要点**:金融术语在 spec 内最低限度解释。7 的金融术语密度高(成交价、滑点、印花税折算、归一化净值、简化夏普),spec 写作要假设读者单读 spec 也能看懂。在任务要求段开头加一段术语简释,不让 spec 完全依赖正文才能读懂。
#### 起草要求:五种模式与四层成本
#### 第三段:任务要求
要求段同时压缩了五种 FillPriceMode 全跑」和「四层成本层叠两条主线。第一条让学员看到执行不确定性的带宽第二条让学员看到成本归因。
任务要求段同时压缩了五种 FillPriceMode 全跑”和“四层成本层叠两条主线。第一条让读者看到执行不确定性的带宽第二条让读者看到成本归因。
> **要求**
> **任务要求**
>
> 1. 数据:标的 `510300.SS / 513100.SS / 518880.SS`,起始 `2021-01-01`,用 `YFinanceDownloader` + 本地缓存
> 2. 策略沿用 q4 产出参数:`RebalanceFrequencyRule(interval_days=21)` + `StopLossRule(threshold=0.10)`,封装成 `make_strategy()` / `make_rules()` 辅助函数
> 2. 策略沿用 4 产出参数:`RebalanceFrequencyRule(interval_days=21)` + `StopLossRule(threshold=0.10)`,封装成 `make_strategy()` / `make_rules()` 辅助函数
> 3. 五种 `FillPriceMode`CLOSE / MID / NEXT_OPEN / NEXT_HIGH / NEXT_LOW)各跑一次完整回测,佣金 `PercentageFee(rate=Decimal("0.0006"), min_fee=Decimal("5"))`A 股万一佣金 + 印花税折算双边约万五,合计约万六)
> 4. 净值曲线图(figsize 12×8,上下两图高度比 3:1`layout='constrained'`):上图五条归一化净值(起点=100),下图各模式与 CLOSE 的差距折线 + NEXT_HIGH / NEXT_LOW 之间用橙色 `fill_between` 填充
> 5. 四层成本层叠(NEXT_OPEN 模式):无成本 → 加万一佣金 → 加佣金+印花税(万六) → 加佣金+印花税+滑点(千一)
> 6. 成本层叠图(同样 12×8 / 3:1):上图四条归一化净值,下图成本侵蚀折线(三条差距线 ≤ 0,正值说明计算有误)
> 📌 **要点****带宽即认知**——HIGH 和 LOW 之间的橙色填充把“执行不确定性”从抽象概念变成可视化的带宽。学员第一眼看到的不是数字,是带宽。把“画带宽”作为视觉契约写进 spec,胜过千言文字。
> 📌 **要点****带宽即认知**HIGH 和 LOW 之间的橙色填充把“执行不确定性”从抽象概念变成可视化的带宽。读者第一眼看到的不是数字,是带宽。把“画带宽”作为视觉契约写进 spec,胜过千言文字。
> 📌 **要点****积分式归因**——成本拆四层逐层加,每层一次完整回测、四条净值曲线对比。学员看到的不是成本扣 X%一个数字,而是佣金扣多少、印花税扣多少、滑点扣多少的归因分解。这是 q5/q6 已建立的“分布优于均值”思想在执行场景的延续。
> 📌 **要点****积分式归因**成本拆四层逐层加,每层一次完整回测、四条净值曲线对比。读者看到的不是成本扣 X%一个数字,而是佣金扣多少、印花税扣多少、滑点扣多少的归因分解。这是第 5 章、第 6 已建立的“分布优于均值”思想在执行场景的延续。
> 📌 **要点**:印花税近似要主动声明“用 PercentageFee 折算为双边费率,实际略有差异”。这是教学诚实——不把模型当真理,承认精度边界。q7 涉及真实金额的 spec 都要遵循这条规矩。
> 📌 **要点**:印花税近似要主动声明“用 PercentageFee 折算为双边费率,实际略有差异”。这是教学诚实不把模型当真理,承认精度边界。7 涉及真实金额的 spec 都要遵循这条规矩。
#### 起草结果呈现
#### 第四段:验收标准
> **结果呈现**:五种模式对比表(5 行含收益/夏普)+ 净值曲线图(含执行不确定性带)+ 成本层叠表(4 行成本逐层递增、收益逐层递减)+ 成本层叠图(含成本侵蚀曲线);过渡句这些都是估算,真实执行到底差多少?带到下一节。
> **验收标准**:五种模式对比表(5 行含收益和简化夏普)+ 净值曲线图(含执行不确定性带)+ 成本层叠表(4 行成本逐层递增、收益逐层递减)+ 成本层叠图(含成本侵蚀曲线);过渡句这些都是估算,真实执行到底差多少?带到下一节。
完整 spec 在 [`specs/spec-02-fill-price.md`](https://github.com/xingwudao/xquant-learning/blob/main/q7-execution/specs/spec-02-fill-price.md)——复制给 AI,弹窗选「允许」
完整示例 spec 见配套仓库的 [`q7-execution/specs/spec-02-fill-price.md`](https://github.com/xingwudao/xquant-learning/blob/main/q7-execution/specs/spec-02-fill-price.md)。参考示例后,确认自己的 spec,再复制给 AI
AI 助手执行完毕后,你的 notebook 里应该出现了五种成交价模式的对比结果和成本层叠分析。
AI 编程工具执行完毕后,你的 notebook 里应该出现了五种成交价模式的对比结果和成本层叠分析。
`FillPriceMode` 有五种模式:`CLOSE`(收盘价,回测默认假设)、`NEXT_OPEN`(次日开盘价,接近实)、`NEXT_HIGH`(次日最高价,最差买入场景)、`NEXT_LOW`(次日最低价,最好买入场景)、`MID`(最高与最低均价)。同一个策略跑五遍,唯一的区别是“假设在什么价格成交”。
`FillPriceMode` 有五种模式:`CLOSE`(收盘价,回测默认假设)、`NEXT_OPEN`(次日开盘价,接近实际交易)、`NEXT_HIGH`(次日最高价,最差买入场景)、`NEXT_LOW`(次日最低价,最好买入场景)、`MID`(最高与最低均价)。同一个策略跑五遍,唯一的区别是“假设在什么价格成交”。
成本层叠则用四层配置逐层加码:无成本 → 加佣金(万一) → 加佣金 + 印花税 → 加佣金 + 印花税 + 滑点。用“次日开盘价”作为基础,因为这最接近实。**滑点(Slippage)** 是你下单时价格因市场波动而偏离预期的部分——就像你在拥挤的菜市场买菜,问价时 5 块,等你掏钱时变 5.1 了。印花税是 A 股特有的成本——每次卖出时收取 0.05%,这里用 PercentageFee 近似折算为双边费率。
成本层叠则用四层配置逐层加码:无成本 → 加佣金(万一) → 加佣金 + 印花税 → 加佣金 + 印花税 + 滑点。用“次日开盘价”作为基础,因为它更接近实际交易。**滑点(Slippage)** 是你下单时价格因市场波动而偏离预期的部分就像你在拥挤的菜市场买菜,问价时 5 块,等你掏钱时变 5.1 了。印花税是 A 股特有的成本每次卖出时收取 0.05%,这里用 PercentageFee 近似折算为双边费率。
### 运行结果
@@ -208,11 +200,11 @@ AI 助手执行完毕后,你的 notebook 里应该出现了五种成交价模
**表 7-4 五种成交价模式下的回测指标对比**
| 成交价模式 | 累计收益率 | 年化收益率 | 最大回撤 | 夏普比率 | 交易次数 |
| 成交价模式 | 累计收益率 | 年化收益率 | 最大回撤 | 简化夏普 | 交易次数 |
|------------|------------|------------|----------|----------|----------|
| 收盘价(回测理想) | 83.22% | 12.90% | -15.45% | 1.15 | 184 |
| 中间价 | 85.62% | 13.19% | -14.34% | 1.18 | 184 |
| 次日开盘价(接近实 | 80.00% | 12.50% | -15.98% | 1.12 | 184 |
| 次日开盘价(接近实际交易 | 80.00% | 12.50% | -15.98% | 1.12 | 184 |
| 次日最高价(最差买入) | 81.02% | 12.62% | -16.13% | 1.14 | 185 |
| 次日最低价(最好买入) | 76.58% | 12.06% | -17.19% | 1.08 | 184 |
@@ -224,13 +216,13 @@ AI 助手执行完毕后,你的 notebook 里应该出现了五种成交价模
再看下图——橙色填充区域就是 NEXT_HIGH 和 NEXT_LOW 之间的差距,叫**执行不确定性区间**。这个区间越宽,说明你对执行结果的控制力越弱。
收盘价(回测理想)83.22%;次日开盘价(接近实80.00%,比理想少了 3.22%;次日最高价(最差情况)81.02%,比理想少了 2.20%;次日最低价(最好情况)76.58%,比理想少了 6.64%。最好和最差之间的差距:4.45%。
收盘价(回测理想)83.22%;次日开盘价(接近实际交易80.00%,比理想少了 3.22%;次日最高价(最差情况)81.02%,比理想少了 2.20%;次日最低价(最好情况)76.58%,比理想少了 6.64%。最好和最差之间的差距:4.45%。
再看成本层叠,如表 7-5 所示。
**表 7-5 成本层叠对回测指标的影响**
| 成本层级 | 累计收益率 | 年化收益率 | 夏普比率 |
| 成本层级 | 累计收益率 | 年化收益率 | 简化夏普 |
|----------|------------|------------|----------|
| 无成本 | 82.44% | 12.80% | 1.15 |
| + 佣金(万一) | 80.92% | 12.61% | 1.13 |
@@ -255,44 +247,44 @@ AI 助手执行完毕后,你的 notebook 里应该出现了五种成交价模
NEXT_HIGH 和 NEXT_LOW 之间的带状区域就是“执行不确定性”——**同一个策略决策,仅仅因为执行时机不同,结果就有这么大的差距。** 这个差距不是策略的问题,而是执行的问题。
**第二,成本是持续的侵蚀。** 佣金、印花税和滑点不是一次性的损失,而是每次交易都在发生。一年几十次调仓下来,成本层层叠加,侵蚀掉的收益比你想象的多得多。
**第二,成本是持续的侵蚀。** 佣金、印花税和滑点不是一次性的损失,而是每次交易都在发生。一年几十次调整持仓下来,成本层层叠加,侵蚀掉的收益比你想象的多得多。
你刚才看到的就是 A 股的真实成本结构。佣金万一是基础,印花税在每次卖出时额外扣 0.05%,滑点更是无法避免。三者叠加,四年累计侵蚀了 5.72% 的收益——这就是为什么低频策略往往比高频策略更适合个人投资者。
但以上都是“估算”——用不同的成交价模式模拟出来的。真实执行到底差多少
但以上都是“估算”,是用不同的成交价模式模拟出来的。真正进入交易接口以后,差距到底有多大
---
## 7.3 回测 vs 实盘,差距有多大
## 7.3 回测和模拟交易,差距有多大
把前两步的具体数字摊在桌面上:佣金 + 印花税 + 滑点,**四年累计侵蚀 5.72% 的收益**;同一笔订单按“最好”和“最差”成交价跑,**累计收益差距高达 6.64%**。这些不是抽象的“差距”——是真金白银从你的账户里走掉。
把前两步的具体数字摊在桌面上:佣金 + 印花税 + 滑点,**四年累计侵蚀 5.72% 的收益**;同一个策略按“最好”和“最差”成交价跑,**累计收益差距高达 6.64%**。这些不是抽象的“差距”是真金白银从你的账户里走掉。
**这一节标的物切换为美股 ETFSPY、QQQ、GLD)。** 前两个实验沿用 A 股 ETF(沪深300、纳指100、黄金),和 第二、六章 保持一致从这一节开始换成美股 ETF——因为 open-xquant 内置了 Alpaca 美股模拟交易接口,可以让你亲手体验真实下单。放心——美股和 A 股面对的执行问题完全一样(取整偏差、滑点、成本),只是 A 股有 100 股整手约束和印花税,某些问题更严重。解读中会详细桥接
**这一节投资对象切换为美股 ETFSPY、QQQ、GLD)。** 前两个实验沿用 A 股 ETF(沪深300、纳指100、黄金),和第 2 章、第 6 章保持一致从这一节开始换成美股 ETF因为 open-xquant 内置了 Alpaca 美股模拟交易接口,可以让你亲手体验提交订单、等待成交、查看回报。美股和 A 股面对的执行问题本质相同:取整偏差、滑点、成本。区别是 A 股有 100 股整手约束和印花税,某些问题会更明显
### A 股桥接:为什么用美股练手,结论却双向适用
你接下来要上的 Alpaca 美股模拟盘——选它是因为接入简单注册即用,无需开户、无需入金。但**美股看到的所有问题,A 股一模一样、且大多更严重**
你接下来要用的是 Alpaca 美股模拟盘选它是因为接入简单注册即可使用,不需要开户入金。但**美股看到的所有执行问题,A 股一样存在,而且大多更明显**
1. **取整偏差** —— A 股 100 股整手,偏差比美股大得多(美股可买 1 股)
2. **价格滑点** —— A 股同样存在,开盘竞价和收盘竞价时段尤其明显
3. **交易成本** —— A 股还要额外付印花税(卖出 0.05%)
4. **执行落差** —— 同样的策略,A 股的执行落差通常比美股更大
**“用美股练手 + 搬回 A 股操作”是这一节的实操路径**——美股 Alpaca 让你在不用开户的情况下亲手感受成交,看到的所有结论搬回 A 股完全成立。当你回到 A 股券商 APP 手动操作时,建议你:
**“用美股模拟盘练手,再把检查方法搬回 A 股操作”是这一节的实操路径**美股 Alpaca 让你在不用动用真实资金的情况下亲手感受成交。等你回到 A 股券商 APP 手动操作时,建议你:
- 先用 `generate_orders()` 生成订单清单(Step 1 的 SOP),不要凭感觉手敲数量
- 先用 `generate_orders()` 生成订单清单(第 1 步的流程),不要凭感觉手敲数量
- 定期对比回测净值和实际账户净值,监控执行落差是否在预算内
- 如果执行落差持续放大,回到本节诊断三问——是成本问题,还是执行时机问题?
- 如果执行落差持续放大,回到本节诊断三问是成本问题,还是执行时机问题?
现在,我们让策略接入 Alpaca 模拟交易平台,真正跑一遍。然后把回测净值和模拟实盘净值画在同一张图上——差距一目了然。
现在,我们让策略接入 Alpaca 模拟交易平台跑一遍。然后把回测净值和模拟交易净值画在同一张图上差距一目了然。
**关键概念:** Engine 和 Strategy 完全不变,只是把数据源和交易接口换了。回测用 SimBroker + LocalMarketDataProvider实盘用 LiveBroker + AlpacaMarketDataProvider。策略逻辑一行都不用改。
**关键概念:** Engine 和 Strategy 完全不变,只是把数据源和交易接口换了。回测用 SimBroker + LocalMarketDataProvider模拟交易用 LiveBroker + AlpacaMarketDataProvider。策略逻辑一行都不用改。
这就像你做菜——同一份菜谱,在家里用燃气灶做和在户外用柴火做,食材和步骤一样,但火候控制不同,最终味道总有差异。策略是“菜谱”,交易接口是“灶”。
### 动手实验 3Alpaca 模拟交易
为了体验真实交易摩擦,本节使用美股 ETF(SPY、QQQ、GLD)。这是因为 open-xquant 内置了 Alpaca 美股模拟交易接口,可以让你亲手感受真实下单——提交订单、等待成交、查看回报,而不是在历史数据上模拟成交。
为了体验交易摩擦,本节使用美股 ETF(SPY、QQQ、GLD)。这是因为 open-xquant 内置了 Alpaca 美股模拟交易接口,可以让你亲手感受提交订单、等待成交、查看回报,而不是在历史数据上模拟成交。
要使用 Alpaca 模拟盘,你需要:
1. 在 alpaca.markets 免费注册一个账号
@@ -302,46 +294,46 @@ NEXT_HIGH 和 NEXT_LOW 之间的带状区域就是“执行不确定性”——
ALPACA_API_KEY=你的Key
ALPACA_SECRET_KEY=你的Secret
```
4. 把上面的步骤告诉 AI 助手,它会帮你完成配置
4. 把上面的步骤告诉 AI 编程工具,它会帮你完成配置
我们一起把这份 spec 写出来。这是 q7 最有挑战的一份——同时要让有 Alpaca 账号的学员体验真实下单、让没账号的学员能完成完整流程还不能把 API key 写进任何文件。重点看三件新东西:**API key 三重防线**、**Part A / B-1 / B-2 三段切分**,以及**外部依赖的兜底设计**。
我们一起把这份 spec 写出来。这是 7 最有挑战的一份同时要让有 Alpaca 账号的读者体验模拟交易,让没账号的读者能完成完整流程还不能把 API key 写进任何文件。重点看三件新东西:**API key 三重防线**、**A / B-1 / B-2 三段切分**,以及**外部依赖的兜底设计**。
#### 起草上下文 + 任务
#### 第一、二段:上下文和任务描述
> **上下文**:在 `q7-execution.ipynb` 中已有 Step 1 的订单生成(A 股 ETF+ Step 2 的成交价压力测试(A 股 ETF)。前两步暴露了取整偏差、滑点和成本,但都是估算。现在切换为美股 ETFSPY/QQQ/GLD),接入 Alpaca 模拟交易平台让策略真正跑一遍。
> **上下文**:在 `q7-execution.ipynb` 中已有 1 的订单生成(A 股 ETF+ 2 的成交价压力测试(A 股 ETF)。前两步暴露了取整偏差、滑点和成本,但都是估算。现在切换为美股 ETFSPY/QQQ/GLD),接入 Alpaca 模拟交易平台让策略跑一遍。
>
> **任务**:完成三段不可省略的演示——Part ASimBroker 回测基准;Part B-1:Alpaca 数据源做历史回测对比数据源差异;Part B-2LiveBroker 提交真实订单到 Alpaca 模拟盘。无 Alpaca 账号时提供替代方案。
> **任务描述**:完成三段不可省略的演示。A 部分SimBroker 回测基准;B-1 部分Alpaca 数据源做历史回测对比数据源差异;B-2 部分LiveBroker 提交订单到 Alpaca 模拟盘。无 Alpaca 账号时提供替代方案。
> 📌 **要点**:A 股 → 美股的标的切换要诚实交代——open-xquant 当前只内置 Alpaca 美股 paper trading,国内券商接口尚未集成。spec 上下文段必须说清这个限制,避免学员误解只有美股才有 paper trading」
> 📌 **要点**:A 股 → 美股的投资对象切换要诚实交代open-xquant 当前只内置 Alpaca 美股 paper trading,国内券商接口尚未集成。spec 上下文段必须说清这个限制,避免读者误解只有美股才有模拟交易”
#### 起草要求:API key 三重防线 + 三段切分
#### 第三段:任务要求
要求段把 API key 处理设计成三重防线:环境变量、try/except 兜底、验证段安全断言。一道防线漏了还有两道兜着。
> **要求**
> **任务要求**
>
> 1. **Part A**YFinance 下载 SPY/QQQ/GLD 起始 `2021-01-01`,用 `make_us_strategy()` / `make_us_rules()` 跑收盘价 + 佣金回测,结果赋给 `result_sim`
> 1. **A 部分**YFinance 下载 SPY/QQQ/GLD 起始 `2021-01-01`,用 `make_us_strategy()` / `make_us_rules()` 跑收盘价 + 佣金回测,结果赋给 `result_sim`
> 2. 定义公共画图函数 `plot_sim_vs_live(norm_sim, norm_live, title, ...)`figsize 12×83:1 子图,`layout='constrained'`,下图差距填充)
> 3. 检测环境变量 `os.environ.get("ALPACA_API_KEY")`,全部 Alpaca 操作包 try/except,失败自动转兜底
> 4. **Part B-1**`AlpacaMarketDataProvider(feed="iex")` + `Engine.setup() / step()` 逐 bar 执行 + SimBroker,对比 YFinance vs Alpaca IEX 的数据源差异
> 5. **Part B-2**:核心作 a) 用 `AlpacaClient` 读持仓 + 账户权益;b) `generate_orders(..., lot_size=1)` 算调c) `LiveBroker(paper=True)` 逐笔提交;d) 等待 3 秒后查成交;e) 打印滑点对比表;f) `live_broker.close()` + 打印最终持仓。边界条件:无需调仓时跳过下单、市场关闭时查订单状态
> 6. **无 Alpaca 替代方案**`SimBroker(fill_price_mode=NEXT_OPEN)` + 滑点 + 佣金模拟实盘,同样赋给 `result_live` 供 Step 4 使用
> 4. **B-1 部分**`AlpacaMarketDataProvider(feed="iex")` + `Engine.setup() / step()` 逐 bar 执行 + SimBroker,对比 YFinance vs Alpaca IEX 的数据源差异
> 5. **B-2 部分**:核心作 a) 用 `AlpacaClient` 读持仓 + 账户权益;b) `generate_orders(..., lot_size=1)` 算调整持仓订单c) `LiveBroker(paper=True)` 逐笔提交;d) 等待 3 秒后查成交;e) 打印滑点对比表;f) `live_broker.close()` + 打印最终持仓。边界条件:无需调整持仓时跳过下单、市场关闭时查订单状态
> 6. **无 Alpaca 替代方案**`SimBroker(fill_price_mode=NEXT_OPEN)` + 滑点 + 佣金模拟交易,同样赋给 `result_live` 供 4 使用
> 📌 **要点****API key 三重防线**——① 环境变量 `os.environ.get("ALPACA_API_KEY")` 不传参;② 整段包 try/except,连接失败自动切兜底;③ 验证段显式断言不包含任何硬编码的 API Key」。q7 是学员第一次面对真实可花钱的接口,即使是 paper trading,养成绝不硬编码 key的习惯关键。这三层缺一不可,可作为涉外部 API 的 spec的安全模板。
> 📌 **要点****API key 三重防线**。第一,环境变量 `os.environ.get("ALPACA_API_KEY")` 不传参。第二,整段包 try/except,连接失败自动切兜底。第三,验收标准显式断言不包含任何硬编码的 API Key”。第 7 章是读者第一次面对真实可花钱的接口,即使是 paper trading,养成绝不硬编码 key的习惯也很关键。这三层缺一不可,可作为涉外部 API 的 spec的安全模板。
> 📌 **要点****Part A / B-1 / B-2 三段切分**承接 spec-02 的积分式归因」——Part A 建立基准、Part B-1 揭示数据源差异、Part B-2 展示真实下单。让学员看到两层执行落差(数据层 + 真实成交层),而不是一笔糊涂账。
> 📌 **要点****A / B-1 / B-2 三段切分**承接 spec-02 的积分式归因”。A 部分建立基准,B-1 部分揭示数据源差异B-2 部分展示模拟下单。让读者看到两层执行落差(数据层 + 成交层),而不是一笔糊涂账。
> 📌 **要点**:**外部依赖的兜底设计**是 q7 唯一的双轨 spec」——q1-q6 都假设学员能跑通核心流程,但 q7 必须接受少数学员没 Alpaca 账号的现实。无账号路径用 NEXT_OPEN + 滑点的 SimBroker 模拟,让没账号的学员也能完整跑完这一节。涉及外部账号 / 网络 / 付费服务的 spec 应推广这种设计。
> 📌 **要点**:**外部依赖的兜底设计**是 7 唯一的双轨 spec”。第 1 章到第 6 都假设读者能跑通核心流程,但 7 必须接受少数读者没 Alpaca 账号的现实。无账号路径用 NEXT_OPEN + 滑点的 SimBroker 模拟,让没账号的读者也能完整跑完这一节。涉及外部账号、网络或付费服务的 spec 应推广这种设计。
#### 起草结果呈现
#### 第四段:验收标准
> **结果呈现**:有 Alpaca 时输出必须含三部分(SimBroker 基准累计收益 / 数据源对比图 + Alpaca IEX 累计收益 / LiveBroker 实盘演示标题 + 当前持仓 + 调仓计划 + 最终持仓);无 Alpaca 时显示模拟实盘对比图;`result_live` 必须赋值供 Step 4 使用。
> **验收标准**:有 Alpaca 时输出必须含三部分(SimBroker 基准累计收益 / 数据源对比图 + Alpaca IEX 累计收益 / LiveBroker 模拟交易演示标题 + 当前持仓 + 调整持仓计划 + 最终持仓);无 Alpaca 时显示模拟交易对比图;`result_live` 必须赋值供 4 使用。
完整 spec 在 [`specs/spec-03-alpaca-paper.md`](https://github.com/xingwudao/xquant-learning/blob/main/q7-execution/specs/spec-03-alpaca-paper.md)——复制给 AI,弹窗选「允许」。如果你跳过 Alpaca 配置,下一段代码会自动切到 SimBroker 模拟实盘——不影响后续阅读。
完整示例 spec 见配套仓库的 [`q7-execution/specs/spec-03-alpaca-paper.md`](https://github.com/xingwudao/xquant-learning/blob/main/q7-execution/specs/spec-03-alpaca-paper.md)。参考示例后,确认自己的 spec,再复制给 AI。如果你跳过 Alpaca 配置,下一段代码会自动切到 SimBroker 模拟交易,不影响后续阅读。
AI 助手执行完毕后,你的 notebook 里应该出现了回测 vs 实盘的对比结果。
AI 编程工具执行完毕后,你的 notebook 里应该出现了回测和模拟交易的对比结果。
实验分三部分:Part A 用收盘价 + 佣金跑标准回测,作为“理想基准”;Part B-1 把行情数据从 YFinance 换成 Alpaca IEX,看“数据源不同”本身能带来多大差异;Part B-2 用 **LiveBroker** 把订单真正发送到 Alpaca 交易所——SimBroker 在本地模拟成交,LiveBroker 等待交易所返回真实成交回报。即使是 Paper Trading(模拟盘),订单也经过了交易所的撮合引擎,成交价是市场真实报价。
实验分三部分:A 部分用收盘价 + 佣金跑标准回测,作为“理想基准”;B-1 部分把价格数据从 YFinance 换成 Alpaca IEX,看“数据源不同”本身能带来多大差异;B-2 部分用 **LiveBroker** 把订单发送到 Alpaca 模拟盘。SimBroker 在本地模拟成交,LiveBroker 等待 Alpaca 返回成交回报。即使是 Paper Trading(模拟盘),订单也经过 Alpaca 的撮合环境,成交价来自市场报价。
### 运行结果
@@ -361,27 +353,27 @@ AI 助手执行完毕后,你的 notebook 里应该出现了回测 vs 实盘的
先看上图——蓝色线(YFinance 数据)和红色线(Alpaca IEX 数据)走势相似,但终点差了将近 6 个百分点。下图的执行落差从 2022 年开始持续扩大。
同一个策略、同一个 SimBroker,仅仅因为行情数据来自不同供应商,结果就差了 5.93%。你以为回测结果是“确定的”,其实连数据本身都有不确定性——不同数据源的收盘价可能有微小差异(四舍五入方式、调整方法、采样时间点),四年累积下来就是这个数字。
同一个策略、同一个 SimBroker,仅仅因为价格数据来自不同供应商,结果就差了 5.93%。你以为回测结果是“确定的”,其实连数据本身都有不确定性不同数据源的收盘价可能有微小差异(四舍五入方式、调整方法、采样时间点),四年累积下来就是这个数字。
再看 LiveBroker 的真实订单提交。这次运行时账户里已经有了之前建好的仓GLD 83 股、QQQ 41 股、SPY 51 股),策略只需要做小幅调整——卖出 1 股 QQQ。
再看 LiveBroker 的模拟订单提交。这次运行时账户里已经有了之前建好的仓(GLD 83 股、QQQ 41 股、SPY 51 股),策略只需要做小幅调整卖出 1 股 QQQ。
订单提交后,由于市场已关闭,订单状态为“accepted”(已接受,等待下次开盘执行)。这就是真实交易的常态——你不能随时成交,要等市场开门。如果你在开盘时间运行,订单通常会在几秒内成交,成交价和预期价格之间的差距就是真实的滑点。
订单提交后,由于市场已关闭,订单状态为“accepted”(已接受,等待下次开盘执行)。这就是交易执行的常态你不能随时成交,要等市场开门。如果你在开盘时间运行,订单通常会在几秒内成交,成交价和预期价格之间的差距就是滑点。
### 结果解读
这一步揭示了两层执行落差。
**第一层:数据源差异。** 同一个策略、同一个 SimBroker,仅仅因为行情数据来自不同的供应商(YFinance vs Alpaca IEX),回测结果就不一样。不同数据源的收盘价可能有微小差异(四舍五入、调整方式、采样时间点不同)。你以为回测结果是“确定的”,其实连数据本身都有不确定性。
**第一层:数据源差异。** 同一个策略、同一个 SimBroker,仅仅因为价格数据来自不同的供应商(YFinance vs Alpaca IEX),回测结果就不一样。不同数据源的收盘价可能有微小差异(四舍五入、调整方式、采样时间点不同)。你以为回测结果是“确定的”,其实连数据本身都有不确定性。
**第二层:真实执行差异。** 用 LiveBroker 向 Alpaca 模拟盘提交真实订单后,成交价和你预期的收盘价总是不一样。这就是**执行落差(Implementation Shortfall**——从“策略决定买”到“真正买到”之间的距离。差距来自三个方面:
**第二层:成交执行差异。** 用 LiveBroker 向 Alpaca 模拟盘提交订单后,成交价和你预期的收盘价总是不一样。这就是**执行落差(Implementation Shortfall**从“策略决定买”到“真正买到”之间的距离。差距来自三个方面:
1. **成交价偏差**——回测用收盘价,实盘在某个时刻成交,价格已经变了
1. **成交价偏差**——回测用收盘价,模拟交易或真实交易在某个时刻成交,价格已经变了
2. **交易成本**——佣金、印花税(A 股)
3. **市场冲击**——你的买单本身推高了价格(大资金才明显,小资金可忽略)
关键认知:**执行落差像每次下单都要交一笔过路费——不是 bug,是从回测到实盘的必经收费站。** 关键是知道这多大、有没有突然涨价;你需要做的不是消除它(不可能),而是监控它的浮动是否在合理范围在评估策略时预留这个空间。
关键认知:**执行落差像每次下单都要交一笔过路费。它不是 bug是从回测走向交易执行必然会出现的成本。** 关键是知道这笔费用有多大、有没有突然变贵。你需要做的不是消除它(不可能),而是监控它的浮动是否在合理范围内,并在评估策略时预留这个空间。
还有一个重要的设计细节值得注意:**Engine 和 Strategy 一行都没改。** 从回测切换到模拟实盘,只是换了数据源和交易接口。策略逻辑和执行环境完全解耦——这意味着你在回测中验证过的策略,可以直接接入实盘,不用重写任何代码。
还有一个重要的设计细节值得注意:**Engine 和 Strategy 一行都没改。** 从回测切换到模拟交易,只是换了数据源和交易接口。策略逻辑和执行环境完全解耦这意味着你在回测中验证过的策略,可以接入交易接口,不用重写策略代码。
差距看到了,但具体哪笔交易差得多、哪笔差得少?
@@ -389,7 +381,7 @@ AI 助手执行完毕后,你的 notebook 里应该出现了回测 vs 实盘的
## 7.4 怎么知道执行得对不对
Step 3 让你看到了整体的执行落差。但“整体差 6%”这个数字不够——你需要知道:
3 让你看到了整体的执行落差。但“整体差 6%”这个数字不够。你还需要知道:
- 具体哪笔交易差得最多?
- 是特定标的的问题,还是特定日期的问题?
@@ -401,49 +393,49 @@ Step 3 让你看到了整体的执行落差。但“整体差 6%”这个数字
### 动手实验 4:执行报告
我们一起把这份 spec 写出来。这是 q7 的收尾 spec——前三份让学员看到执行落差整体上有多大,这份让学员**逐笔对账**找到落差的具体来源。重点看两件新东西:**反平均设计**(最贵的一笔比平均更值钱)和**实盘 vs 回测的对照表**(双图各回答一个问题)。
我们一起把这份 spec 写出来。这是 7 的收尾 spec前三份让读者看到执行落差整体上有多大,这份让读者**逐笔对账**找到落差的具体来源。重点看两件新东西:**反平均设计**(最贵的一笔比平均更值钱)和**模拟交易 vs 回测的对照表**(双图各回答一个问题)。
#### 起草上下文 + 任务
#### 第一、二段:上下文和任务描述
> **上下文**:在 `q7-execution.ipynb` 中已有 Step 1 / 2 / 3 的产出,特别是 `result_sim` 和 `result_live` 两个回测结果。Step 3 让学员看到了整体的执行落差,但整体差 6%这个数字不够——需要知道具体哪笔差得最多、是标的问题还是日期问题。
> **上下文**:在 `q7-execution.ipynb` 中已有 1 / 2 / 3 的产出,特别是 `result_sim` 和 `result_live` 两个结果。第 3 步让读者看到了整体的执行落差,但整体差 6%这个数字不够需要知道具体哪笔差得最多、是标的问题还是日期问题。
>
> **任务**:用 `ExecutionReport` 逐笔对比回测和实盘的成交,分析执行质量分布。
> **任务描述**:用 `ExecutionReport` 逐笔对比回测和模拟交易的成交,分析执行质量分布。
> 📌 **要点**q7 的 spec 链同 q1 一样讲究接续型 spec」——`result_live` 不论走 spec-03 的 Part B-2(Alpaca)还是兜底路径都会赋值。spec 的上下文段必须把上游变量名、形状、来源全列出,AI 才知道直接用哪份数据,不会重新跑回测。
> 📌 **要点**7 的 spec 链同 1 一样讲究接续型 spec”。`result_live` 不论走 spec-03 的 B-2 部分(Alpaca)还是兜底路径都会赋值。spec 的上下文段必须把上游变量名、形状、来源全列出,AI 才知道直接用哪份数据,不会重新跑回测。
#### 起草要求:实盘 vs 回测的对照表
#### 第三段:任务要求
要求段是这份 spec 的灵魂——五步走完一条诊断闭环:逐笔对比 → 汇总 → 最贵的一笔 → 分布图 → 桥接 A 股。
任务要求段是这份 spec 的核心。五步走完一条诊断流程:逐笔对比 → 汇总 → 最贵的一笔 → 分布图 → 桥接 A 股。
> **要求**
> **任务要求**
>
> 1. 用 `result_sim.trades` 和 `result_live.trades` 构造 `ExecutionReport`,打印前 20 笔逐笔对比表(日期/标的/方向/回测价/实盘价/滑点/回测股数/实盘股数/股数差)
> 2. 汇总统计:总笔数、匹配交易数、仅回测有、仅实盘有、平均价格滑点、总手续费差异
> 1. 用 `result_sim.trades` 和 `result_live.trades` 构造 `ExecutionReport`,打印前 20 笔逐笔对比表(日期/标的/方向/回测价/模拟交易价/滑点/回测股数/模拟交易股数/股数差)
> 2. 汇总统计:总笔数、匹配交易数、仅回测有、仅模拟交易有、平均价格滑点、总手续费差异
> 3. **最贵的一笔**:从匹配交易(`sim_shares > 0 且 live_shares > 0`)中按 `|price_slippage|` 排序,打印 Top 1 详情;滑点 > 1% 时分析原因(波动大/流动性差/跳空)
> 4. 执行质量分布图(figsize 14×5,两子图并排):左图滑点直方图(30 bins,标零滑点红色虚线 + 平均值橙色虚线)+ 右图按标的分组的滑点箱线图(SPY/QQQ/GLD 三色填充)
> 5. 桥接 A 股:用文字说明美股的执行问题在 A 股一样存在(取整偏差更大 / 滑点 / 印花税 / 执行落差),并给出操作建议(`generate_orders()` 生成订单清单 / 定期对比净值 / 异常时检查原因)
> 📌 **要点****反平均设计**——「找出最贵的一笔是关键的反平均。执行落差的“平均”是 0.05% 看起来无害,但少数几笔可能差 1-2%。学员只看汇总统计会得到错误结论,必须看尾部。这是 q5/q6 已建立的分布优于均值原则在执行场景的延续。
> 📌 **要点****反平均设计**。“找出最贵的一笔是关键的反平均。执行落差的“平均”是 0.05% 看起来无害,但少数几笔可能差 1-2%。读者只看汇总统计会得到错误结论,必须看尾部。这是第 5 章、第 6 已建立的分布优于均值原则在执行场景的延续。
> 📌 **要点****实盘 vs 回测的对照表**用双图各回答一个问题——直方图回答整体怎样」(看分布形状),箱线图回答问题在哪只标的」(看哪个箱子异常多。两个视角不可互相替代,缺一不可。
> 📌 **要点****模拟交易 vs 回测的对照表**用双图各回答一个问题直方图回答整体怎样”,看分布形状箱线图回答问题在哪只标的”,看哪个箱子异常多。两个视角不可互相替代,缺一不可。
> 📌 **要点**:桥接 A 股要克制——q7 的主线是认知(了解执行落差),不是工程(A 股自动化交易)。spec 用文字桥接而非要求重跑,避免给学员塞超出本章范围的负担国内券商 API 是另一套基础设施
> 📌 **要点**:桥接 A 股要克制。第 7 的主线是认知(了解执行落差),不是工程(A 股自动化交易)。spec 用文字桥接而非要求重跑,避免给读者塞超出本章范围的负担国内券商 API 是另一套基础设施。
#### 起草结果呈现
#### 第四段:验收标准
> **结果呈现**:逐笔对比表 ≥ 20 行 + 汇总统计(含匹配交易数和平均滑点)+ 最贵一笔具体数值 + 直方图 + 箱线图 + 桥接 A 股的操作建议。
> **验收标准**:逐笔对比表 ≥ 20 行 + 汇总统计(含匹配交易数和平均滑点)+ 最贵一笔具体数值 + 直方图 + 箱线图 + 桥接 A 股的操作建议。
完整 spec 在 [`specs/spec-04-execution-report.md`](https://github.com/xingwudao/xquant-learning/blob/main/q7-execution/specs/spec-04-execution-report.md)——复制给 AI,弹窗选「允许」
完整示例 spec 见配套仓库的 [`q7-execution/specs/spec-04-execution-report.md`](https://github.com/xingwudao/xquant-learning/blob/main/q7-execution/specs/spec-04-execution-report.md)。参考示例后,确认自己的 spec,再复制给 AI
AI 助手执行完毕后,你的 notebook 里应该出现了逐笔对比的执行报告。`ExecutionReport` 接收两个成交记录列表——回测的和实盘的——按日期和标的匹配,逐笔对比价格差异。
AI 编程工具执行完毕后,你的 notebook 里应该出现了逐笔对比的执行报告。`ExecutionReport` 接收两个成交记录列表回测的和模拟交易的。它会按日期和标的匹配,逐笔对比价格差异。
### 运行结果
先看逐笔对比表(前 20 笔,节选),如表 7-7 所示。
**表 7-7 回测 vs 实盘逐笔成交对比(前 20 笔节选)**
**表 7-7 回测 vs 模拟交易逐笔成交对比(前 20 笔节选)**
| 日期 | 标的 | 方向 | 回测价 | 实盘价 | 滑点 | 回测股数 | 实盘股数 | 股数差 |
| 日期 | 标的 | 方向 | 回测价 | 模拟交易价 | 滑点 | 回测股数 | 模拟交易股数 | 股数差 |
|------|------|------|--------|--------|------|----------|----------|--------|
| 2021-02-02 | GLD | BUY | $172.11 | $172.14 | +0.02% | 213 | 211 | -2 |
| 2021-03-04 | GLD | BUY | $0.00 | $159.00 | +0.00% | 0 | 3 | +3 |
@@ -455,22 +447,22 @@ AI 助手执行完毕后,你的 notebook 里应该出现了逐笔对比的执
汇总统计如表 7-8 所示。
**表 7-8 回测 vs 实盘成交汇总统计**
**表 7-8 回测 vs 模拟交易成交汇总统计**
| 指标 | 数值 |
|------|------|
| 总交易笔数 | 194 |
| 匹配交易 | 177(回测和实盘都有) |
| 匹配交易 | 177(回测和模拟交易都有) |
| 仅回测有 | 7 |
| 仅实盘有 | 10 |
| 仅模拟交易有 | 10 |
| 平均价格滑点 | +1.6199% |
| 总手续费差异 | $-17.33 |
177 笔匹配交易,平均滑点 1.62%。还有 7 笔只在回测中出现、10 笔只在实盘中出现——这是因为两个数据源的价格差异导致策略在不同时间触发调仓。
177 笔匹配交易,平均滑点 1.62%。还有 7 笔只在回测中出现、10 笔只在模拟交易中出现这是因为两个数据源的价格差异导致策略在不同时间触发调整持仓。
最贵的一笔:日期 2021-02-02,标的 SPY(标普500ETF),方向 BUY,回测成交价 $356.17实盘成交价 $381.39,价格滑点 +7.08%。
最贵的一笔:日期 2021-02-02,标的 SPY(标普500ETF),方向 BUY,回测成交价 $356.17模拟交易成交价 $381.39,价格滑点 +7.08%。
一笔交易的滑点高达 7.08%——这是第一笔交易,回测和实盘的起始价格差距最大。后续交易的滑点会小得多。
一笔交易的滑点高达 7.08%这是第一笔交易,回测和模拟交易的起始价格差距最大。后续交易的滑点会小得多。
执行质量分布如图 7-5 所示。
@@ -478,7 +470,7 @@ AI 助手执行完毕后,你的 notebook 里应该出现了逐笔对比的执
先看左图的直方图。橙色虚线标注了平均滑点位置。大部分交易的滑点集中在 0-2% 之间,但右侧有一条长尾——少数几笔交易的滑点特别大。
再看右图的箱线图。三只 ETF 的滑点分布差异不大,但每只都有几个“异常值”(箱子外面的点)——这些就是执行层面的尾部风险。
再看右图的箱线图。三只 ETF 的滑点分布差异不大,但每只都有几个“异常值”(箱子外面的点)这些就是交易执行里的尾部风险。
执行质量统计如表 7-9 所示。
@@ -495,45 +487,45 @@ AI 助手执行完毕后,你的 notebook 里应该出现了逐笔对比的执
### 结果解读
执行报告是实盘运行后最重要的“对账”工具。
执行报告是策略接入交易接口后最重要的“对账”工具。
滑点直方图讲了一个重要的事实:**大部分交易的执行质量是正常的,滑点很小。但总有少数几笔偏差特别大。** 这就是执行层面的“尾部风险”——和策略收益的尾部风险一样,你无法消除它,但你需要知道它的存在。
滑点直方图讲了一个重要的事实:**大部分交易的执行质量是正常的,滑点很小。但总有少数几笔偏差特别大。** 这就是交易执行里的“尾部风险”和策略收益的尾部风险一样,你无法消除它,但你需要知道它的存在。
箱线图按标的分组,能帮你发现:是某只 ETF 的执行质量特别差(可能流动性不够),还是所有标的都差不多?如果某只标的的滑点持续偏大,你可能需要考虑换一只流动性更好的标的,或者调整下单时机。
**实盘运行的习惯:** 每次调仓后,生成执行报告对一遍账。不是看一眼净值就完了——要逐笔检查,发现异常及时处理。这和开车一样:你不会只看速度表,还要看油量、水温、胎压。
**交易执行后的习惯:** 每次调整持仓后,生成执行报告对一遍账。不是看一眼净值就完了要逐笔检查,发现异常及时处理。这和开车一样:你不会只看速度表,还要看油量、水温、胎压。
---
## 7.5 本章总结
四步下来,我们从四个角度回答了同一个核心问题——“回测结果能直接用吗?
四步下来,我们从四个角度回答了同一个核心问题回测结果能直接用吗?
Step 1:订单生成 → 权重只能买整数股 → 取整偏差 + 现金残余 → Step 2:成交价压力测试 → 五种价格假设差距巨大 → 执行不确定性 → Step 3:Alpaca 模拟盘 → 数据源不同结果就不同 → 执行落差是常态 → Step 4:执行报告 → 逐笔对账 → 大部分正常,少数偏差大。
第 1 步:订单生成 → 权重只能买整数股 → 取整偏差 + 现金残余 → 第 2 步:成交价压力测试 → 五种价格假设差距巨大 → 执行不确定性 → 第 3 步:Alpaca 模拟盘 → 数据源不同结果就不同 → 执行落差是常态 → 第 4 步:执行报告 → 逐笔对账 → 大部分正常,少数偏差大。
从今以后,从回测走向实盘,你可以用这套流程管理执行风险,整理成检查清单如表 7-10 所示。
从今以后,从回测走向真实交易之前,你可以用这套流程管理执行风险,整理成检查清单如表 7-10 所示。
**表 7-10 从回测走向实盘的执行风险检查清单**
**表 7-10 从回测走向真实交易的执行风险检查清单**
| 检查项 | 怎么做 | 好的信号 | 坏的信号 |
|--------|--------|----------|----------|
| 取整偏差 | 用 `generate_orders()` 提前计算 | 偏差 < 0.5% | 偏差 > 2%,资金太小或 lot_size 太大 |
| 成交价敏感性 | 用五种 FillPriceMode 跑压力测试 | 五种模式差距小 | 差距大,策略对执行时机敏感 |
| 交易成本 | 逐层叠加佣金、印花税、滑点 | 成本占年化收益 < 20% | 成本吃掉大部分收益 |
| 执行落差 | 对比回测净值和实盘净值 | 落差稳定且可预期 | 落差持续放大 |
| 执行落差 | 对比回测净值和模拟交易净值 | 落差稳定且可预期 | 落差持续放大 |
| 执行质量 | 生成执行报告逐笔对账 | 滑点集中在 0 附近 | 频繁出现大滑点 |
**一个更深的认知:回测收益不等于实盘收益。** 中间隔着取整偏差、滑点、佣金、印花税。在评估策略时,回测收益减去预估的执行落差,才是你的合理预期。
**一个更深的认知:回测收益不等于真实交易收益。** 中间隔着取整偏差、滑点、佣金、印花税。在评估策略时,回测收益减去预估的执行落差,才是你的合理预期。
### 策略进化路径
章产出:通过检验的策略,信心满满 → Step 1:权重转订单(取整偏差 + 现金残余)→ Step 2:五种成交价压力测试(滑点 + 成本全景)→ Step 3:回测 vs 实盘(亲眼看到执行落差)→ Step 4:执行报告(逐笔对账,找到差距来源)→ 第章产出:从回测到实盘的完整闭环 + “执行有成本”的认知
6 章产出:通过检验的策略,信心满满 → 第 1 步:权重转订单(取整偏差 + 现金残余)→ 第 2 步:五种成交价压力测试(滑点 + 成本全景)→ 第 3 步:回测和模拟交易对比(亲眼看到执行落差)→ 第 4 步:执行报告(逐笔对账,找到差距来源)→ 第 7 章产出:从回测到交易执行的完整流程 + “执行有成本”的认知
### 概念速查表
本章涉及的核心概念汇总如表 7-11 所示,方便随时回查。
**表 7-11 第章核心概念速查**
**表 7-11 第 7 章核心概念速查**
| 概念 | 含义 | 类比 |
|------|------|------|
@@ -542,31 +534,31 @@ Step 1:订单生成 → 权重只能买整数股 → 取整偏差 + 现金残
| 成交价模式 | 回测中假设订单在什么价格成交 | 网上标价和到店实际付款价的区别 |
| 执行不确定性 | 最好和最差成交价之间的差距区间 | 同一航班,不同时间买的票价差异 |
| 滑点 | 下单时价格因市场波动偏离预期的部分 | 菜市场问价 5 块,掏钱时变 5.1 了 |
| 执行落差 | 回测收益和实盘收益之间的差距 | 菜谱上说 30 分钟做好,实际花了 45 分钟 |
| LiveBroker | 把订单真正发送到交易所的交易接口 | 从纸上模拟下单”到“真的点了购买按钮” |
| 执行报告 | 逐笔对比回测成交和实盘成交的对账单 | 收到外卖后核对订单:点了三道菜,价格对不对,少没少 |
| 执行落差 | 回测收益和模拟交易收益之间的差距 | 菜谱上说 30 分钟做好,实际花了 45 分钟 |
| LiveBroker | 把订单发送到交易接口的工具 | 从纸上订单清单到把订单递给交易系统 |
| 执行报告 | 逐笔对比回测成交和模拟交易成交的对账单 | 收到外卖后核对订单:点了三道菜,价格对不对,少没少 |
### 本章核心认知
走完四步实验后,本章最值得带走的核心认知如表 7-12 所示。
**表 7-12 第章核心认知**
**表 7-12 第 7 章核心认知**
| 认知 | 来源 |
|------|------|
| 回测权重是理想值,实际只能买整数股 | Step 1 实验 |
| 资金量越小、整手约束越大,偏差越大 | Step 1 实验 |
| 同一个决策,执行时机不同,结果差距很大 | Step 2 实验 |
| 回测的收盘价假设是最乐观的 | Step 2 实验 |
| 交易成本(佣金+滑点+印花税)持续侵蚀收益 | Step 2 实验 |
| 回测和实盘永远有差距——执行落差 | Step 3 实验 |
| 策略逻辑和执行环境可以完全解耦 | Step 3 实验 |
| 实盘要养成逐笔对账的习惯 | Step 4 实验 |
| 大部分交易正常,少数几笔偏差大——执行的尾部风险 | Step 4 实验 |
| 回测权重是理想值,实际只能买整数股 | 1 实验 |
| 资金量越小、整手约束越大,偏差越大 | 1 实验 |
| 同一个决策,执行时机不同,结果差距很大 | 2 实验 |
| 回测的收盘价假设是最乐观的 | 2 实验 |
| 交易成本(佣金+滑点+印花税)持续侵蚀收益 | 2 实验 |
| 回测和模拟交易永远有差距执行落差 | 3 实验 |
| 策略逻辑和执行环境可以完全解耦 | 3 实验 |
| 接入交易接口后要养成逐笔对账的习惯 | 4 实验 |
| 大部分交易正常,少数几笔偏差大执行的尾部风险 | 4 实验 |
### 带走的问题
我们已经走完了从选标的(第二章)到真正执行(第七章)的完整闭环。回测世界和实盘世界之间的桥梁搭好了。
我们已经走完了从选标的(第 2 章)到执行交易(第 7 章)的完整流程。回测世界和真实交易世界之间的桥梁搭好了。
但策略跑起来之后,新的问题出现了:
@@ -574,20 +566,8 @@ Step 1:订单生成 → 权重只能买整数股 → 取整偏差 + 现金残
- 什么时候应该停下来,什么时候应该继续坚持?
- 发现问题后,怎么系统性地改进,而不是推翻重来?
**策略活着的时候,怎么照顾它** → 第
**策略跑起来以后,怎么监控和改进** → 第 8
**到这里,你完成了飞轮的“执行层”——这一章把“做 + 看”落到了真金白银的边界:“做”是把 35% 的权重翻成 8100 股的订单把 NEXT_OPEN 当成更诚实的成交假设再用 LiveBroker 把订单递给 Alpaca;“看”是用执行报告逐笔对账,看清那 1.62% 的平均滑点和 7.08% 的最贵一笔藏在哪里。本书口号“做要规则、看要指标、还要怀疑你的指标”——前两步在这一章贯穿到底,“疑”留到下一章在实盘环境里继续。**
到这里,你完成了执行交易阶段。第 7 章把“做”和“看”落到了真实交易的边界:“做”是把 35% 的权重翻成 8100 股的订单把 NEXT_OPEN 当成更诚实的成交假设再用 LiveBroker 把订单递给 Alpaca 模拟盘;“看”是用执行报告逐笔对账,看清那 1.62% 的平均滑点和 7.08% 的最贵一笔藏在哪里。下一章会继续练“看”和“疑”:策略跑起来以后,怎么判断它是正常波动,还是出了问题。
> 本章所有代码的可运行版本见 `notebooks/q7-execution.ipynb`
---
## 反馈与读者群
如果你发现本章有错别字、概念不清或实验结果不一致,欢迎
[提交 GitHub Issue](https://github.com/xingwudao/xquant-beginner/issues/new/choose)。
课程、正式书和后续更新见
[xquant.shop/courses](https://xquant.shop/courses)。
添加作者微信 `xquanter` 进入读者群。请备注:`XQuant 读者`。
> 本章所有代码的可运行版本见配套仓库的 [`q7-execution/notebooks/q7-execution.ipynb`](https://github.com/xingwudao/xquant-learning/blob/main/q7-execution/notebooks/q7-execution.ipynb)
+176 -186
View File
@@ -1,29 +1,29 @@
# 第章:策略活着:监控、诊断、迭代
# 第 8 章:策略跑起来以后:监控、诊断、迭代
章你搭好了从回测到实盘的桥梁——订单生成、成交价压力测试、模拟交易、执行报告。策略终于跑起来了。
7 章你搭好了从回测到交易执行的桥梁订单生成、成交价压力测试、模拟交易、执行报告。策略终于可以跑起来了。
**然后你开始每天看净值。**
上个月赚了 3%,开心。这个月亏了 5%,慌了。盯着那条往下走的曲线,你脑子里全是问号——策略是不是坏了?要不要停?要不要改?还是再等等?
上个月赚了 3%,开心。这个月亏了 5%,慌了。盯着那条往下走的曲线,你脑子里全是问号策略是不是坏了?要不要停?要不要改?还是再等等?
别慌。这一章教你三件事:怎么看(监控)、怎么查(诊断)、怎么改(迭代)。
**飞轮转回起点了。** 这一章你站在飞轮的“归因层”——但和第五章不同,第五章你“看”的是离线回测;这一章你“看”的是活着的实盘
这一章进入评估归因阶段,但场景和第 5 章不同。第 5 章看的是离线回测,第 8 章看的是一个已经开始日常跟踪的策略。你要用监控发现异常,用诊断找到原因,再用对照实验决定怎么改
本书口号“做要规则、看要指标、还要怀疑你的指标”——做(监控规则)、看(仪表盘指标)、疑(诊断三问),三个动作在这一章被你**第二次**走了一遍
前言里说过,量化实验离不开“做、看、疑”三个环节。第 8 章会把这三个环节放进日常运行场景:做,是定好监控规则和迭代规则;看,是读仪表盘指标;疑,是用诊断三问检查结果是否可信
### 路线图
**选什么标的(第章)→ 每个买多少(第章)→ 什么时候买卖(第章)→ 怎么验证有效(第章)→ 如何避免自欺欺人(第章)→ 如何真正执行(第章)→ 如何持续改进(第章本章)**
**选什么标的(第 2 章)→ 每个买多少(第 3 章)→ 什么时候买卖(第 4 章)→ 怎么验证有效(第 5 章)→ 如何避免自欺欺人(第 6 章)→ 如何真正执行(第 7 章)→ 如何持续改进(第 8 章本章)**
本章从“盯着净值焦虑”的状态出发,用三个步骤建立系统性的迭代能力,三个步骤的问题与方法对照如表 8-1 所示。
**表 8-1 第章三步迭代框架——问题与方法对照**
**表 8-1 第 8 章三步迭代框架问题与方法对照**
| 步骤 | 问题 | 方法 |
|------|------|------|
| 第 1 步 | 策略是不是坏了? | 监控仪表盘——用指标替代焦虑 |
| 第 2 步 | 问题出在哪? | 诊断三问——由外到内定位原因 |
| 第 1 步 | 策略是不是坏了? | 监控仪表盘用指标替代焦虑 |
| 第 2 步 | 问题出在哪? | 诊断三问由外到内定位原因 |
| 第 3 步 | 怎么改? | 假设驱动的对照实验 |
(操作流程见前言“怎么使用这本书”。)
@@ -34,7 +34,7 @@
上个月赚了 3%,这个月亏了 5%。你盯着净值曲线,心里发慌。
但“净值在跌”这个信息太粗糙了——就像你身体不舒服,光说“不舒服”没用,得量体温、测血压、做检查。策略也一样,需要一个“仪表盘”,用具体的指标告诉你:现在到底是正常波动,还是真的出了问题。
但“净值在跌”这个信息太粗糙了就像你身体不舒服,光说“不舒服”没用,得量体温、测血压、做检查。策略也一样,需要一个“仪表盘”,用具体的指标告诉你:现在到底是正常波动,还是真的出了问题。
我们用三个指标构成监控仪表盘,三者覆盖的角度如表 8-2 所示。
@@ -42,70 +42,70 @@
| 指标 | 含义 | 类比 |
|------|------|------|
| **滚动夏普**(最近 3 个月) | 最近 3 个月的风险调整收益 | 体温计——正常在 0 以上,跌破 0 说明在“发烧” |
| **滚动回撤** | 从近期高点到现在跌了多少 | 水位线——持续下降说明“水在退” |
| **对沪深 300 多赚了多少**(你 - 沪深 300 | 同期你的策略沪深 300 指数的差额 | 跑步比赛——持续落后说明跑法有问题 |
| **滚动夏普**(最近 3 个月) | 最近 3 个月的风险调整收益 | 体温计正常在 0 以上,跌破 0 说明在“发烧” |
| **滚动回撤** | 从近期高点到现在跌了多少 | 水位线持续下降说明“水在退” |
| **对沪深 300 表现** | 同期你的策略沪深 300 多赚或少赚多少 | 跑步比赛持续落后说明跑法有问题 |
### 动手实验 1:监控仪表盘
这是本章第一份“运营 spec”,和前七章那种“建一个新功能”不同——这里我们要写的是**怎么周期性地看一个已经在跑的策略**。我们一起把这份 spec 写出来。这次重点看两件新东西:**心智切换**——上下文段第一句话就要告诉 AI“这不是建造,是体检”;以及**周期性运营 spec 的四要素**——触发条件 / 频率 / 阈值 / 输出形态全部显式。
这是本章第一份“运营 spec”,和前七章那种“建一个新功能”不同这里我们要写的是**怎么周期性地看一个已经在跑的策略**。我们一起把这份 spec 写出来。这次重点看两件新东西:**心智切换**上下文段第一句话就要告诉 AI“这不是建造,是体检”;以及**周期性运营 spec 的四要素**触发条件、频率、阈值、输出形态全部显式。
#### 起草上下文 + 任务
#### 第一、二段:上下文和任务描述
上下文段开头那句“用具体指标替代焦虑”是整个第章的总动机。运营章 spec 的上下文不必交代环境怎么搭,要交代**学员现在的情绪状态和场景**
上下文段开头那句“用具体指标替代焦虑”是整个第 8 章的总动机。运营章 spec 的上下文不必交代环境怎么搭,要交代**读者现在的情绪状态和场景**
> **上下文**学员跑完第章,策略终于在实盘运行。但上个月赚了 3%,这个月亏了 5%——是正常波动还是策略出了问题?需要一个“仪表盘”,用具体指标替代焦虑。
> **上下文**读者跑完第 7 章,策略已经可以进入日常跟踪。但上个月赚了 3%,这个月亏了 5%,这是正常波动还是策略出了问题?需要一个“仪表盘”,用具体指标替代焦虑。
>
> **任务**:在 notebook 中跑基准回测后,用 `StrategyMonitor` 构建监控仪表盘,画三个滚动指标的时间序列图,并自动检测恶化时段。
> **任务描述**:在 `q8-iteration.ipynb` 中跑基准回测后,用 `StrategyMonitor` 构建监控仪表盘,画三个滚动指标的时间序列图,并自动检测恶化时段。
> 📌 **要点**:运营 spec 的上下文要交代“心智切换”。前七章的 spec 是“建一个新功能”,这一章开始是“看一个已有功能的健康状态”。学员如果带着建造者的姿态去做运维,看了 30 个图也不知道在干嘛——上下文一句话点明“用指标替代焦虑”,就是给学员(也给 AI)定调子:这次的产出不是新代码,是健康判断。
> 📌 **要点**:运营 spec 的上下文要交代“心智切换”。前七章的 spec 是“建一个新功能”,这一章开始是“看一个已有策略的健康状态”。读者如果带着建造者的姿态去做运维,看了 30 个图也不知道在干嘛上下文一句话点明“用指标替代焦虑”,就是给读者(也给 AI)定调子:这次的产出不是新代码,是健康判断。
#### 起草要求:周期性运营 spec 的四要素
#### 第三段:任务要求
要求段是这份 spec 的主体——重点是把“什么时候触发监控、多久跑一次、什么数值算异常、异常了怎么输出”四件事**全部显式写明**
任务要求段是这份 spec 的主体重点是把“什么时候触发监控、多久跑一次、什么数值算异常、异常了怎么输出”四件事全部显式写明:
> **要求**
> **任务要求**
>
> 1. 跑基准回测(`SimBroker` + 风险平价 + 21 天调仓 + 10% 止损),拿到 `result_base`
> 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. 自动检测恶化时段——告警规则显式声明:
> 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 步
> - **输出形态**:遍历 `monitor.bad_periods`,每段一行打印起止日期、持续天数、平均滚动夏普
> 5. 打印结尾过渡句:“2022 年集中出现恶化,原因是什么?”把读者推向第 2 步
> 📌 **要点**:周期性运营 spec 的告警四要素——触发条件 / 频率 / 阈值 / 输出形态——必须全部显式。建造类 spec 的“成功标志”是“代码跑通、图渲染出来”;运营类 spec 的“成功标志”是“在该报警的时候报了警、不该报的时候没报”。**任何一个要素藏在 oxq 库默认参数里、没写进 spec,半年后阈值变了你也不知道**——这是运营 spec 区别于 q1-q7 建造 spec 的核心方法点。
> 📌 **要点**:周期性运营 spec 的告警四要素触发条件、频率、阈值、输出形态必须全部显式。建造类 spec 的“成功标志”是“代码跑通、图渲染出来”;运营类 spec 的“成功标志”是“在该报警的时候报了警、不该报的时候没报”。任何一个要素藏在 oxq 库默认参数里、没写进 spec,半年后阈值变了你也不知道这是运营 spec 区别于第 1 章到第 7 建造 spec 的核心方法点。
> 📌 **要点**:关键常量旁边要写“为什么是这个数”。`roll_window=63` 不是随便选的——它是行业惯例(约 3 个月交易日),理由也要进 spec。运营场景里的每一个数字都是阈值,阈值的合理性比数值本身更重要。
> 📌 **要点**:关键常量旁边要写“为什么是这个数”。`roll_window=63` 不是随便选的它是行业惯例(约 3 个月交易日),理由也要进 spec。运营场景里的每一个数字都是阈值,阈值的合理性比数值本身更重要。
#### 起草结果呈现
#### 第四段:验收标准
> **结果呈现**:四行仪表盘图正常渲染 + 恶化时段列表非空(2022 年应有多个时段)+ `equity``daily_ret``result_base``monitor` 变量已定义(后续 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,弹窗选「允许」
完整示例 spec 见配套仓库的 [`q8-iteration/specs/spec-01-monitoring.md`](https://github.com/xingwudao/xquant-learning/blob/main/q8-iteration/specs/spec-01-monitoring.md)。参考示例后,确认自己的 spec,再复制给 AI
AI 助手执行完毕后,你的 notebook 里应该出现了基准回测结果和一张四行的监控仪表盘图。
AI 编程工具执行完毕后,你的 notebook 里应该出现了基准回测结果和一张四行的监控仪表盘图。
这个实验做了什么?先用 第四、七章 同款的风险平价策略(沪深300ETF + 纳指100ETF + 黄金ETF21 天调仓 + 10% 止损)跑一次从 2021 年到现在的完整回测,拿到净值曲线。然后把净值喂给 `StrategyMonitor`——它就是那个“体检中心”,自动计算三个滚动健康指标(滚动夏普、滚动回撤、相对基准超额收益),并标注出指标持续恶化的时段。
这个实验做了什么?先用第 4 章、第 7 章同款的风险平价策略(沪深300ETF + 纳指100ETF + 黄金ETF21 天再平衡 + 10% 止损)跑一次从 2021 年到现在的完整回测,拿到净值曲线。然后把净值喂给 `StrategyMonitor`它就是那个“体检中心”,自动计算三个滚动健康指标(滚动夏普、滚动回撤、相对沪深 300 表现),并标注出指标持续恶化的时段。
### 运行结果
先看基准回测:累计收益 86.45%,年化 12.50%,最大回撤 -16.62%,夏普 1.17,共 184 笔交易。
先看基准回测:累计收益 86.45%,年化 12.50%,最大回撤 -16.62%简化夏普 1.17,共 184 笔交易。
监控仪表盘的整体效果如图 8-1 所示。
![监控仪表盘——四个指标同时恶化的时段就是“危险区”](images/q8-iteration/01-monitoring-dashboard.png)
![监控仪表盘四个指标同时恶化的时段就是“危险区”](images/q8-iteration/01-monitoring-dashboard.png)
图 8-1 第一行是净值曲线,整体向上但中间有几段明显的下跌。第二行是**滚动夏普**——红色填充区域就是夏普跌破 0 的时段,说明最近 3 个月策略在亏钱。第三行是回撤,持续加深说明策略在“失血”。第四行是相对沪深300的超额收益,为负说明还不如直接买指数。
图 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 |
@@ -114,15 +114,15 @@ AI 助手执行完毕后,你的 notebook 里应该出现了基准回测结果
| 2023-10-18 | 2023-11-14 | 20 天 | -0.84 |
| 2023-12-05 | 2024-01-03 | 21 天 | -0.91 |
2022 年集中出现了 4 段恶化,最长的一段持续了 68 天,平均夏普 -2.25。
2022 年集中出现了 4 段恶化,最长的一段持续了 68 天,平均滚动夏普 -2.25。
### 结果解读
仪表盘让你从“盯着净值焦虑”变成“看指标判断”。
滚动夏普跌破 0,说明最近 3 个月策略在亏钱——这是最直接的“发烧”信号。回撤持续加深说明策略在“失血”。相对沪深300的超额收益为负,说明你还不如直接买指数。
滚动夏普跌破 0,说明最近 3 个月策略在亏钱这是最直接的“发烧”信号。回撤持续加深说明策略在“失血”。相对沪深 300 表现为负,说明你还不如直接买指数。
三个指标同时恶化的时段就是需要关注的“危险区”。2022 年出现了多段集中恶化——但**发现问题不等于知道原因**。
三个指标同时恶化的时段就是需要关注的“危险区”。2022 年出现了多段集中恶化但**发现问题不等于知道原因**。
是执行出了问题?市场变了?还是策略本身有缺陷?
@@ -130,98 +130,98 @@ AI 助手执行完毕后,你的 notebook 里应该出现了基准回测结果
## 8.2 问题出在哪?
第 1 步告诉你“最近不对劲”——但不对劲有很多种原因。
第 1 步告诉你“最近不对劲”但不对劲有很多种原因。
在开始诊断之前,先认识一个本节的关键概念:
**市场状态(regime** ——市场不是一成不变的。有时候波动大几乎所有资产一起剧烈起伏“高波动”),有时候很平静“低波动 / 正常”。就像天气有晴天和台风天,同一件衣服在不同天气穿感受完全不同。策略也一样——一个在正常市场表现不错的策略,到了高波动状态可能完全失灵。**诊断三里有两都和它相关**——所以我们前置介绍它。
**市场状态(regime**市场不是一成不变的。有时候波动大几乎所有资产一起剧烈起伏,我们叫“高波动”有时候很平静,我们叫“低波动”或“正常”。就像天气有晴天和台风天,同一件衣服在不同天气穿感受完全不同。策略也一样一个在正常市场表现不错的策略,到了高波动状态可能表现很差。**诊断三里有两都和它相关**所以我们介绍它。
就像身体不舒服,你不会直接吃药。先量体温排除感冒,再查血排除感染,最后做 CT 看看有没有更深的问题。策略诊断也一样,我们由外到内排查三
就像身体不舒服,你不会直接吃药。先量体温排除感冒,再查血排除感染,最后做 CT 看看有没有更深的问题。策略诊断也一样,我们由外到内排查三类原因
1. **是执行的问题吗?** —— 回测和实盘之间的差距有没有异常放大
2. **是市场状态regime变了吗?** —— 市场的波动水平有没有发生大的变化
3. **是策略本身的设计问题吗?** —— 参数在不同时段的表现是否稳定
1. **是执行的问题吗?** 回测和模拟交易之间的差距有没有异常放大
2. **是市场状态变了吗?** 市场的波动水平有没有发生大的变化
3. **是策略本身的设计问题吗?** 参数在不同时段的表现是否稳定
### 动手实验 2:诊断三问
我们一起把这份 spec 写出来。这次重点看两件新东西:**一份 spec 包多步诊断的取舍**——前七章每份 spec 只做一件事,这份一次进三步诊断;以及**阈值事前锁定**——什么样的数字算“通过”、什么样算“失败”,必须在跑诊断之前定好,不能跑完了再看图说话。
我们一起把这份 spec 写出来。这次重点看两件新东西:**一份 spec 包多步诊断的取舍**前七章每份 spec 只做一件事,这份一次进三步诊断;以及**阈值事前锁定**什么样的数字算“通过”、什么样算“失败”,必须在跑诊断之前定好,不能跑完了再看图说话。
#### 起草上下文 + 任务
#### 第一、二段:上下文和任务描述
接续型 spec 链的上下文要写明前置 spec 留下了哪些变量、本份 spec 在叙事里的位置:
> **上下文**:在 `q8-iteration.ipynb` 中已有第 1 步监控仪表盘和恶化时段检测。第 1 步发现了多段恶化时段(2022 年集中出现),现在需要搞清楚原因。由外到内排查三:执行 → 市场 → 策略。
> **上下文**:在 `q8-iteration.ipynb` 中已有第 1 步监控仪表盘和恶化时段检测。第 1 步发现了多段恶化时段(2022 年集中出现),现在需要搞清楚原因。由外到内排查三类原因:执行 → 市场 → 策略。
>
> **任务**:完成三步诊断——排查执行落差、检测市场状态变化、检查参数稳定性。这三步是不可拆分的方法论,必须按“由外到内”的顺序执行。
> **任务描述**:完成三步诊断排查执行落差、检测市场状态变化、检查参数稳定性。这三步是不可拆分的方法论,必须按“由外到内”的顺序执行。
> 📌 **要点**q1-q7 的写法是“一份 spec 一件事”,这份是“一份 spec 三件事”——叙事完整性优先于任务原子性。诊断方法论本身就是一个不可拆分的整体,拆成三份 spec 学员会丢掉“先排外因再查内因”的节奏。但代价是 spec 体积变大,所以**必须在任务段第一句就明示“这三步必须按顺序”**——给 AI 也给学员定下“这是一个流程,不是三个独立任务”的预期
> 📌 **要点**第 1 章到第 7 的写法是“一份 spec 一件事”,这份是“一份 spec 三件事”叙事完整性优先于任务原子性。诊断方法论本身就是一个不可拆分的整体,拆成三份 spec,读者会丢掉“先排外因再查内因”的节奏。但代价是 spec 体积变大,所以必须在任务描述段第一句就明示“这三步必须按顺序”。这是给 AI 也给读者定下预期:这是一个流程,不是三个独立任务。
#### 起草要求:阈值事前锁定
#### 第三段:任务要求
要求段是这份 spec 的主体——三步诊断每一步都要把“通过 / 失败”的判定阈值事前写明,绝不能“看图说话”:
任务要求段是这份 spec 的主体三步诊断每一步都要把“通过 / 失败”的判定阈值事前写明,绝不能“看图说话”:
> **要求**
> **任务要求**
>
> 1. **诊断 1——执行落差排查**
> - 跑“理想回测”(收盘价 + 佣金,无滑点)和“模拟实盘”(次日开盘 + 佣金 + 千分之一滑点)
> 1. **诊断 1执行落差排查**
> - 跑“理想回测”(收盘价 + 佣金,无滑点)和“模拟交易”(次日开盘 + 佣金 + 千分之一滑点)
> - 计算两条净值曲线的滚动执行落差(63 日滚动均值)
> - **判定阈值**:落差走势平稳(每 63 日变化标准差 < 0.5%)→ 排除执行问题;若突然放大则结论改为“需进一步检查滑点 / 佣金模型”
> - 把落差放到学员能理解的语境中:N 年赚了 X%,其中 Y% 被执行成本吃掉
> - 把落差放到读者能理解的语境中:N 年赚了 X%,其中 Y% 被执行成本吃掉
>
> 2. **诊断 2——市场状态检测**
> 2. **诊断 2市场状态检测**
> - 用 `MarketStateDetector(result_base)` 检测市场状态
> - 分状态打印策略表现(年化收益、夏普、天数)
> - **判定阈值**:高波动状态下夏普 < 0 且与第 1 步恶化时段重叠 ≥ 70% → 结论“市场状态变了,不是策略 bug”
> - 分状态打印策略表现(年化收益、简化夏普、天数)
> - **判定阈值**:高波动状态下简化夏普 < 0 且与第 1 步恶化时段重叠 ≥ 70% → 结论“市场状态变了,不是策略 bug”
> - 画两行子图:净值曲线 + 高波动红色阴影 / 市场波动率 + 中位数线 + 高波动阈值线
> - `detector``high_vol_mask``market_vol``HIGH_VOL_LINE` 变量必须已定义(第 3 步接续使用)
>
> 3. **诊断 3——参数稳定性检查**
> 3. **诊断 3参数稳定性检查**
> - 把数据分成前后两半(按交易日中点切分)
> - 对 5 种调仓频率(10/15/21/42/63 天)分别在两半上跑回测
> - **判定阈值**:用 `scipy.stats.kendalltau` 计算前后两半夏普排序的 tau(Kendall tau 是衡量两组排序一致性的统计量,取值 -1 到 1,越接近 1 说明前后两段的最优参数排序越像);tau ≥ 0.7 → 排序稳定,参数没过拟合;tau < 0.7 → 参数对时段敏感
> - 打印对比表:频率、前半段年化、前半段夏普、后半段年化、后半段夏普
> - 对 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 的核心是“事前锁定阈值”绝不能“看图说话”。建造类 spec 的失败长什么样很明显代码报错、图渲染不出来诊断类 spec 的失败是隐式的你跑完看到一个图,凭直觉说“看起来没问题”。只要阈值不事前锁定,诊断就退化成 confirmation bias你想看到什么结论就能从图里读出什么结论。每一步诊断都必须配一个“通过 X、失败 Y”的数字阈值。
> 📌 **要点**:spec 不能给 AI“二选一”。如果你写“用 Kendall tau 或简单比较都行”,AI 选哪种你就拿到哪种结果,下次重跑可能换一种实现,结果不可比。**spec 是契约不是协商**——能确定一种方案就锁死一种,留 AI 自由发挥的余地越小,诊断越可复现。
> 📌 **要点**:spec 不能给 AI“二选一”。如果你写“用 Kendall tau 或简单比较都行”,AI 选哪种你就拿到哪种结果,下次重跑可能换一种实现,结果不可比。spec 是契约不是协商能确定一种方案就锁死一种,留 AI 自由发挥的余地越小,诊断越可复现。
#### 起草结果呈现
#### 第四段:验收标准
> **结果呈现**:三步诊断各一组图 + 数字结论;`detector``high_vol_mask``market_vol` 变量已定义供第 3 步使用。
> **验收标准**:三步诊断各一组图 + 数字结论;`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,弹窗选「允许」
完整示例 spec 见配套仓库的 [`q8-iteration/specs/spec-02-diagnostics.md`](https://github.com/xingwudao/xquant-learning/blob/main/q8-iteration/specs/spec-02-diagnostics.md)。参考示例后,确认自己的 spec,再复制给 AI
AI 助手执行完毕后,你的 notebook 里应该出现了三步诊断的完整结果。
AI 编程工具执行完毕后,你的 notebook 里应该出现了三步诊断的完整结果。
这个实验做了什么?由外到内排查三原因:
这个实验做了什么?由外到内排查三原因:
- **诊断 1(执行)**:跑两次回测——一次用“理想条件”(收盘价成交、只有佣金),一次用“模拟实盘”(次日开盘价 + 佣金 + 滑点),对比两条净值曲线的差距是否在正常范围内。
- **诊断 1(执行)**:跑两次回测一次用“理想条件”(收盘价成交、只有佣金),一次用“模拟交易”(次日开盘价 + 佣金 + 滑点),对比两条净值曲线的差距是否在正常范围内。
- **诊断 2(市场)**:用 `MarketStateDetector` 自动检测市场的波动率水平,把每一天标记为“高波动”或“正常”,然后分别统计策略在两种状态下的表现。
- **诊断 3(参数)**:把整段回测数据按时间中点切成前后两半(前半段大致覆盖 2021-2023 上半年的高波动市场,后半段覆盖 2023 下半年至今的恢复期),在两半上分别测试 5 种调仓频率,看最优参数在不同时段是否稳定。
- **诊断 3(参数)**:把整段回测数据按时间中点切成前后两半(前半段大致覆盖 2021-2023 上半年的高波动市场,后半段覆盖 2023 下半年至今的恢复期),在两半上分别测试 5 种再平衡频率,看最优参数在不同时段是否稳定。
### 运行结果
按“由外到内”的顺序看三步诊断的输出。
#### 诊断 1执行落差排查
#### 诊断 1执行落差排查
理想回测收益 92.13%,模拟实盘收益 83.54%,执行落差累计 8.59%,每年约 1.72%。
理想回测收益 92.13%,模拟交易收益 83.54%,执行落差累计 8.59%,每年约 1.72%。
换个方式理解:策略 5 年累计赚了 92%,其中 8.6% 被佣金、滑点等执行成本吃掉了。相当于每年的执行成本约 1.7%——对于月度调仓的策略来说,这是正常水平。
换个方式理解:策略 5 年累计赚了 92%,其中 8.6% 被佣金、滑点等执行成本吃掉了。相当于每年的执行成本约 1.7%对于月度调整持仓的策略来说,这是正常水平。
更重要的是执行落差的走势是平稳的,没有突然放大——说明执行层面没有异常。如果这条线突然变陡(落差加速扩大),才说明执行出了问题。
更重要的是执行落差的走势是平稳的,没有突然放大,说明交易执行没有异常。如果这条线突然变陡(落差加速扩大),才说明执行出了问题。
排除执行问题。
#### 诊断 2市场状态检测
#### 诊断 2市场状态检测
回顾 8.2 节首讲过的**市场状态(regime)**——这一步我们用 `MarketStateDetector` 把每一天自动标记为高波动 / 正常 / 低波动,再看策略在不同状态下的表现差异。结果三档分得很开——表 8-4 一目了然。
回顾 8.2 节首讲过的**市场状态(regime)**这一步我们用 `MarketStateDetector` 把每一天自动标记为高波动 / 正常 / 低波动,再看策略在不同状态下的表现差异。结果三档分得很开表 8-4 一目了然。
策略在不同市场状态下的表现差异如表 8-4 所示。
**表 8-4 策略在不同市场状态下的表现**
| 市场状态 | 年化收益 | 夏普 | 天数 |
| 市场状态 | 年化收益 | 简化夏普 | 天数 |
|----------|----------|------|------|
| 高波动 | -1.44% | -0.08 | 260 天 |
| 正常 | 16.62% | 1.80 | 940 天 |
@@ -229,19 +229,19 @@ AI 助手执行完毕后,你的 notebook 里应该出现了三步诊断的完
策略净值与高波动时段的叠加如图 8-2 所示。
![策略净值 + 高波动时段——恶化和高波动高度吻合](images/q8-iteration/02-market-state.png)
![策略净值 + 高波动时段恶化和高波动高度吻合](images/q8-iteration/02-market-state.png)
图 8-2 的红色阴影区域就是高波动时段。和第 1 步检测到的恶化时段一对比,高度吻合——策略在高波动环境下年化收益 -1.44%,夏普 -0.08,在亏钱。而正常时段年化 16.62%,夏普 1.80,表现优秀。
图 8-2 的红色阴影区域就是高波动时段。和第 1 步检测到的恶化时段一对比,高度吻合策略在高波动环境下年化收益 -1.44%,简化夏普 -0.08,在亏钱。而正常时段年化 16.62%,简化夏普 1.80,表现优秀。
恶化的原因是市场状态变了,不是策略的 bug。
#### 诊断 3参数稳定性检查
#### 诊断 3参数稳定性检查
把数据按时间中点切成前后两半,对 5 种调仓频率分别跑回测,结果如表 8-5 所示。
把数据按时间中点切成前后两半,对 5 种再平衡频率分别跑回测,结果如表 8-5 所示。
**表 8-5 5 种调仓频率在前后两半段的表现**
**表 8-5 5 种再平衡频率在前后两半段的表现**
| 频率 | 前半段年化 | 前半段夏普 | 后半段年化 | 后半段夏普 |
| 频率 | 前半段年化 | 前半段简化夏普 | 后半段年化 | 后半段简化夏普 |
|------|-----------|-----------|-----------|-----------|
| 10 天 | +3.30% | 0.40 | +25.78% | 2.17 |
| 15 天 | +1.91% | 0.25 | +23.76% | 2.02 |
@@ -249,7 +249,7 @@ AI 助手执行完毕后,你的 notebook 里应该出现了三步诊断的完
| 42 天 | +2.88% | 0.37 | +24.82% | 1.95 |
| 63 天 | +5.34% | 0.65 | +24.56% | 1.87 |
不同时段的年化收益差异很大(前半段 2-5%,后半段 23-26%),说明策略在不同市场环境下表现差异大。但参数排序有变化——前半段 63 天最优,后半段 10 天最优。这意味着:策略的框架是对的,但对某些市场状态没有应对方案。
不同时段的年化收益差异很大(前半段 2-5%,后半段 23-26%),说明策略在不同市场环境下表现差异大。但参数排序有变化前半段 63 天最优,后半段 10 天最优。这意味着:策略的框架是对的,但对某些市场状态没有应对方案。
### 结果解读
@@ -259,17 +259,17 @@ AI 助手执行完毕后,你的 notebook 里应该出现了三步诊断的完
| 诊断 | 问题 | 结论 |
|------|------|------|
| 执行 | 回测和实盘的差距有没有异常放大? | 没有。执行落差稳定,排除 |
| 执行 | 回测和模拟交易的差距有没有异常放大? | 没有。执行落差稳定,排除 |
| 市场 | 市场波动水平有没有大的变化? | 有。恶化时段和高波动高度吻合 |
| 策略 | 参数在不同时段是否稳定? | 排序有变化,但策略框架没问题,对某些市场状态没有应对方案 |
问题定位清楚了——不是执行的问题,不是参数过拟合,而是**策略对高波动的市场状态没有应对方案**。
问题定位清楚了不是执行的问题,不是参数过拟合,而是**策略对高波动的市场状态没有应对方案**。
把这套诊断流程压成一句以后能脱口而出的口诀——
把这套诊断流程压成一句以后能脱口而出的口诀
> **🔧 归因 SOP 口诀:先排执行落差(外),再查市场状态(中),最后看策略本身(内)。**
> **归因 SOP 口诀:先排执行落差(外),再查市场状态(中),最后看策略本身(内)。**
这个由外到内的顺序不能反过来。先动策略本身,你大概率会“为了一个市场状态变化”而推翻整个框架,下一轮市场恢复正常你又要再推翻一次——这正是缺乏纪律的迭代最常见的死循环。三步走完之后再决定改什么、改哪一层,每一步都有数据支撑。
这个由外到内的顺序不能反过来。先动策略本身,你大概率会“为了一个市场状态变化”而推翻整个框架,下一轮市场恢复正常你又要再推翻一次这正是缺乏纪律的迭代最常见的死循环。三步走完之后再决定改什么,每一步都有数据支撑。
那怎么改?推翻重来?还是在现有策略上做小步改进?
@@ -277,72 +277,72 @@ AI 助手执行完毕后,你的 notebook 里应该出现了三步诊断的完
## 8.3 怎么改?
诊断结果出来了——执行没问题,是市场状态变了,策略对高波动没有应对。
诊断结果出来了执行没问题,是市场状态变了,策略对高波动没有应对。
你可能想直接调参数试试。但改策略不是拍脑袋——你需要像做实验一样:**先提出假设和判断标准,再做对照实验,最后看结果确认还是推翻假设。**
你可能想直接调参数试试。但改策略不是拍脑袋你需要像做实验一样:**先提出假设和判断标准,再做对照实验,最后看结果确认还是推翻假设。**
就像医生看病——不是看到头疼就开止疼药,而是先做检查确认病因,再开药。
就像医生看病不是看到头疼就开止疼药,而是先做检查确认病因,再开药。
每次迭代都走这个闭环
每次迭代都走这个流程
```
观察(看到了什么)—— 假设(猜原因)—— 验证标准(怎么判断对错)—— 实验(改一个变量)—— 结论(确认/推翻)
观察(看到了什么) 假设(猜原因) 验证标准(怎么判断对错) 实验(改一个变量) 结论(确认 / 推翻)
```
### 动手实验 3:假设驱动的对照实验
我们一起把这份 spec 写出来。这次重点看两件新东西:**让“假设”跟着 Strategy 跑**——open-xquant 的 `Strategy` 类在设计时就内置了 `hypothesis``objectives` 两个空,创建 Strategy 时填两个空(假设、判定标准),跑出来的 Strategy 自己背着这张假设卡;以及**验证标准事前锁定具体数字**——绝不写“看情况调整”这种橡皮条款。
我们一起把这份 spec 写出来。这次重点看两件新东西:**让“假设”跟着 Strategy 跑**open-xquant 的 `Strategy` 类在设计时就内置了 `hypothesis``objectives` 两个空,创建 Strategy 时填两个空(假设、判定标准),跑出来的 Strategy 自己背着这张假设卡。另一个重点是**验证标准事前锁定具体数字**绝不写“看情况调整”这种橡皮条款。
#### 起草上下文 + 任务
#### 第一、二段:上下文和任务描述
> **上下文**:在 `q8-iteration.ipynb` 中已有第 1 步监控 + 第 2 步诊断的完整变量。诊断结论:策略对高波动的市场状态没有应对方案。
>
> **任务**:完成两轮迭代实验(调仓频率 + 波动率过滤),用 Strategy 的 `hypothesis`/`objectives` 字段展示完整的实验工作流,最后用 `ExperimentLog` 记录迭代历史。
> **任务描述**:完成两轮迭代实验(再平衡频率 + 波动率过滤),用 Strategy 的 `hypothesis`/`objectives` 字段展示完整的实验工作流,最后用 `ExperimentLog` 记录迭代历史。
#### 起草要求:假设跟着 strategy 跑 + 验证标准事前锁定
#### 第三段:任务要求
要求段是这份 spec 的灵魂——把“假设 → 验证标准 → 实验 → 结论”的方法挂到 Strategy 自己身上的两个空(`Strategy.hypothesis``Strategy.objectives`),让 strategy 跑到哪假设卡跟到哪:
任务要求段是这份 spec 的核心。它把“假设 → 验证标准 → 实验 → 结论”的方法挂到 Strategy 自己身上的两个空(`Strategy.hypothesis``Strategy.objectives`),让 strategy 跑到哪假设卡跟到哪:
> **要求**
> **任务要求**
>
> 1. **迭代 1——调仓频率对照实验**(注定被推翻的一轮):
> - 创建 Strategy 时填写 `hypothesis="缩短调仓频率能改善高波动期表现"`
> - 同时填写 `objectives={"high_vol_sharpe": "above_baseline", "max_dd_limit": dd_limit}`——验证标准事前锁定
> 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 天)和最优频率的高波动夏普,判断假设确认还是推翻
> - 计算每种频率在高波动时段的简化夏普(用 `high_vol_mask` 筛选)
> - 假设验证:对比基准(21 天)和最优频率的高波动简化夏普,判断假设确认还是推翻
>
> 2. **迭代 2——波动率过滤对照实验**(注定被确认的一轮):
> - 创建 Strategy 时填写 `hypothesis="高波动时降仓位能显著降低回撤"`
> 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 揣摩“最佳”的含义
> - **“最佳配置”选择函数显式锁定**`best_label = argmax(dd_improvement)`不能让 AI 揣摩“最佳”的含义
> - 假设验证:检查最佳配置是否同时满足两个事前标准
>
> 3. **迭代记录表**
> - 用 `ExperimentLog(name="第章迭代实验")` 创建记录
> - 用 `ExperimentLog(name="第 8 章迭代实验")` 创建记录
> - 用 `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 自己背着这张假设卡。**
> 📌 **要点**:让 hypothesis 跟着 strategy 跑`hypothesis``objectives` 这两个空是 open-xquant 在 `Strategy` 类设计时就内置好的。spec 要求“创建 Strategy 时填写”这两个空,意思是把“假设驱动”从写在注释里、口头讲讲的方法,挂到 Strategy 自己身上跑出来的 Strategy 对象自带 `.hypothesis` 标签,`ExperimentLog` 自动取走填进表格。以前你的假设写在心里、写在备忘录里现在 Strategy 自己背着这张假设卡。
> 📌 **要点**:验证标准必须事前锁定到具体数字。“高波动夏普优于基准”是数字“回撤改善 ≥ 30% 且夏普 ≥ 1.0”是数字——绝不写“看情况调整”“效果好就采纳”这种橡皮条款。**事后改标准就是过拟合**——你跑完看到 25% 改善,临时把标准从 30% 改到 20%,那不是验证假设,是在为已有结果找说辞。事前锁定阈值是迭代 spec 区别于“调参 spec”的本质差异。
> 📌 **要点**:验证标准必须事前锁定到具体数字。“高波动简化夏普优于基准”是数字“回撤改善 ≥ 30% 且简化夏普 ≥ 1.0”是数字绝不写“看情况调整”“效果好就采纳”这种橡皮条款。事后改标准就是过拟合你跑完看到 25% 改善,临时把标准从 30% 改到 20%,那不是验证假设,是在为已有结果找说辞。事前锁定阈值是迭代 spec 区别于“调参 spec”的本质差异。
> 📌 **要点**:注意章节的节奏——迭代 1 故意安排成“假设被推翻”,迭代 2 是“假设被确认”。这是教学设计:先让学员体会“推翻不是失败推翻是省下了实盘的学费”,再示范一个被确认的假设长什么样。spec 也要配合这个节奏——迭代 1 不要写得“温柔一点让它更容易通过”,要让结果如实推翻。
> 📌 **要点**:注意章节的节奏迭代 1 故意安排成“假设被推翻”,迭代 2 是“假设被确认”。这是教学设计:先让读者体会“推翻不是失败推翻是省下了真钱试错的成本”,再示范一个被确认的假设长什么样。spec 也要配合这个节奏迭代 1 不要写得“温柔一点让它更容易通过”,要让结果如实推翻。
#### 起草结果呈现
#### 第四段:验收标准
> **结果呈现**:两轮指标表 + 净值对比图 + 假设验证结论 + 2 行迭代记录表(iter1 rejected + iter2 confirmed+ Strategy 的 hypothesis / objectives 字段值在输出中可见。
> **验收标准**:两轮指标表 + 净值对比图 + 假设验证结论 + 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,弹窗选「允许」
完整示例 spec 见配套仓库的 [`q8-iteration/specs/spec-03-iteration.md`](https://github.com/xingwudao/xquant-learning/blob/main/q8-iteration/specs/spec-03-iteration.md)。参考示例后,确认自己的 spec,再复制给 AI
AI 助手执行完毕后,你的 notebook 里应该出现了两轮对照实验的完整结果和迭代记录表。
AI 编程工具执行完毕后,你的 notebook 里应该出现了两轮对照实验的完整结果和迭代记录表。
这个实验做了什么?两轮对照实验,每轮都遵循“假设 —— 验证标准 —— 实验 —— 结论”的流程:
这个实验做了什么?两轮对照实验,每轮都遵循“假设 验证标准 实验 结论”的流程:
- **迭代 1调仓频率)**:假设“缩短调仓频率能改善高波动期表现”。保持策略逻辑不变,只改调仓周期——分别测试 5/10/15/21/42/63 天六种频率,重点比较各频率在高波动时段(第 2 步检测出的那些红色阴影区域)的夏普。
- **迭代 2(波动率过滤)**:假设“高波动时降低仓位能显著减少回撤”。保持调仓频率不变,用 `VolFilteredOptimizer` 包装 `RiskParityOptimizer`——当市场波动率超过阈值时,自动将权重缩放 0.5(仓位减半)。分别测试无过滤、阈值 10%/15%/20% 四种配置,看回撤改善了多少、付出了多少收益代价。
- **迭代 1再平衡频率)**:假设“缩短再平衡频率能改善高波动期表现”。保持策略逻辑不变,只改多久调整一次持仓。分别测试 5/10/15/21/42/63 天六种频率,重点比较各频率在高波动时段(第 2 步检测出的那些红色阴影区域)的简化夏普。
- **迭代 2(波动率过滤)**:假设“高波动时降低持仓比例能显著减少回撤”。保持再平衡频率不变,用 `VolFilteredOptimizer` 包装 `RiskParityOptimizer`当市场波动率超过阈值时,自动将权重缩放 0.5,也就是少持有一半资产,多留现金。分别测试无过滤、阈值 10%/15%/20% 四种配置,看回撤改善了多少、付出了多少收益代价。
open-xquant 的 `Strategy` 支持在创建时填写 `hypothesis`(假设)和 `objectives`(验证目标),让“假设驱动”从口号变成 Strategy 自己背着的两个标签。`ExperimentLog` 则帮你自动记录每次迭代的假设、标准、结果和结论。
@@ -350,15 +350,15 @@ open-xquant 的 `Strategy` 支持在创建时填写 `hypothesis`(假设)和
两轮迭代的产出按“先被推翻、后被确认”的节奏展开。
#### 迭代 1 — 调仓频率
#### 迭代 1:再平衡频率
观察:高波动时段策略表现最差。假设:21 天调仓太慢,缩短频率能改善。验证标准:高波动时段夏普优于基准。
观察:高波动时段策略表现最差。假设:21 天再平衡太慢,缩短频率能改善。验证标准:高波动时段简化夏普优于基准。
6 种调仓频率的全时段与高波动指标对比如表 8-7 所示。
6 种再平衡频率的全时段与高波动指标对比如表 8-7 所示。
**表 8-7 迭代 1——6 种调仓频率的指标对比**
**表 8-7 迭代 16 种再平衡频率的指标对比**
| 频率 | 年化收益 | 最大回撤 | 夏普比率 | 高波动夏普 |
| 频率 | 年化收益 | 最大回撤 | 简化夏普 | 高波动简化夏普 |
|------|----------|----------|----------|------------|
| 5 天 | +13.01% | -14.47% | 1.25 | 0.22 |
| 10 天 | +14.55% | -12.57% | 1.39 | 0.56 |
@@ -367,40 +367,40 @@ open-xquant 的 `Strategy` 支持在创建时填写 `hypothesis`(假设)和
| 42 天 | +14.09% | -15.98% | 1.29 | 0.44 |
| 63 天 | +14.71% | -11.49% | 1.31 | 0.84 |
不同调仓频率的净值曲线对比如图 8-3 所示。
不同再平衡频率的净值曲线对比如图 8-3 所示。
![迭代 1:不同调仓频率的净值对比——图例中是高波动时段的夏普](images/q8-iteration/03-iter1-rebal-freq.png)
![迭代 1:不同再平衡频率的净值对比图例中是高波动时段的简化夏普](images/q8-iteration/03-iter1-rebal-freq.png)
注意看表 8-7 最后一列——“高波动夏普”,这才是我们验证假设的指标。全时段夏普最高的是 10 天(1.39),但那不是我们的验证目标。高波动时段表现最好的是 63 天(0.84)——**更长而非更短**。
注意看表 8-7 最后一列“高波动简化夏普”,这才是我们验证假设的指标。全时段简化夏普最高的是 10 天(1.39),但那不是我们的验证目标。高波动时段表现最好的是 63 天(0.84)**更长而非更短**。
**假设被推翻。** 缩短调仓频率并没有改善高波动期表现。
**假设被推翻。** 缩短再平衡频率并没有改善高波动期表现。
这告诉我们什么?改“多久做一次决策”没有用——因为每次做的决策(风险平价满仓分配)没变。问题不在于你多久看一次天气预报,而在于看到暴雨预警后有没有带伞。
这告诉我们什么?改“多久做一次决策”没有用因为每次做的决策(风险平价满仓分配)没变。问题不在于你多久看一次天气预报,而在于看到暴雨预警后有没有带伞。
#### 迭代 2波动率过滤
#### 迭代 2波动率过滤
观察:频率不是关键,真正的问题是高波动时策略依然满仓暴露在风险中。假设:高波动时降仓位,能显著降低回撤。验证标准:回撤改善 ≥ 30%,夏普 ≥ 1.0。
观察:频率不是关键,真正的问题是高波动时策略依然持有完整仓位,暴露在风险中。假设:高波动时降低持仓比例,能显著降低回撤。验证标准:回撤改善 ≥ 30%,简化夏普 ≥ 1.0。
4 种波动率过滤配置的指标对比如表 8-8 所示。
**表 8-8 迭代 2——4 种波动率过滤配置的指标对比**
**表 8-8 迭代 24 种波动率过滤配置的指标对比**
| 配置 | 年化收益 | 最大回撤 | 夏普比率 | 回撤改善 |
| 配置 | 年化收益 | 最大回撤 | 简化夏普 | 回撤改善 |
|------|----------|----------|----------|----------|
| 无过滤(基准) | +12.50% | -16.62% | 1.17 | |
| 无过滤(基准) | +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)
![迭代 2:波动率过滤的净值对比高波动时仓减半](images/q8-iteration/04-iter2-vol-filter.png)
最佳配置是“过滤>10%”:回撤从 -16.62% 改善到 -8.70%(改善 47.6%),夏普 1.18。两个验证标准都达标。
最佳配置是“过滤>10%”:回撤从 -16.62% 改善到 -8.70%(改善 47.6%),简化夏普 1.18。两个验证标准都达标。
**假设被确认。** 波动率过滤有效。
但年化收益从 +12.50% 降到 +6.57%,代价 5.93%。你用 5.9% 的收益换来了 48% 的回撤改善——这就是**取舍(trade-off)**。没有“最好”的参数,只有“适合你风险承受能力”的选择。
但年化收益从 +12.50% 降到 +6.57%,代价 5.93%。你用 5.9% 的收益换来了 48% 的回撤改善这就是**取舍(trade-off)**。没有“最好”的参数,只有“适合你风险承受能力”的选择。
### 结果解读
@@ -410,20 +410,20 @@ open-xquant 的 `Strategy` 支持在创建时填写 `hypothesis`(假设)和
| | 改了什么 | 学到了什么 |
|---|---|---|
| 迭代 1 | 执行参数(多久做一次) | 问题不在频率——策略对市场状态没有反应 |
| 迭代 1 | 执行参数(多久做一次) | 问题不在频率策略对市场状态没有反应 |
| 迭代 2 | 策略逻辑(做什么) | 让策略感知市场状态并调整行为才有效 |
假设被推翻不是失败——这是最有价值的学习。如果你凭直觉觉得“调快点应该更好”,然后不做验证就上线,可能越改越差。**对照实验的意义就在于此:用数据而不是直觉做决策。**
假设被推翻不是失败这是最有价值的学习。如果你凭直觉觉得“更频繁调整应该更好”,然后不做验证就直接放进运行中的策略,可能越改越差。**对照实验的意义就在于此:用数据而不是直觉做决策。**
**“执行参数”和“策略逻辑”是两件事。** 调仓频率是“多久行动一次”,但每次行动的内容(满仓风险平价)没变。如果问题出在行动的内容上,改频率没用——你需要改策略的行为本身。
**“执行参数”和“策略逻辑”是两件事。** 再平衡频率是“多久行动一次”,但每次行动的内容(满仓风险平价)没变。如果问题出在行动的内容上,改频率没用你需要改策略的行为本身。
---
## 8.4 回头看:你刚才做了什么?
三步实验,回答了同一个核心问题——**策略跑起来之后怎么办?**
三步实验,回答了同一个核心问题**策略跑起来之后怎么办?**
第 1 步:建监控,发现“最近不对劲”(滚动夏普跌破 0)。第 2 步:做诊断,定位“是市场状态变了”(由外到内三层排查)。第 3 步:做迭代,改频率没用(推翻),改逻辑有效(确认)。
第 1 步:建监控,发现“最近不对劲”(滚动夏普跌破 0)。第 2 步:做诊断,定位“是市场状态变了”(由外到内排查三类原因)。第 3 步:做迭代,改频率没用(推翻),改逻辑有效(确认)。
从今以后,策略出了问题你都可以套这套 SOP 应对,三步对照清单如表 8-10 所示。
@@ -431,8 +431,8 @@ open-xquant 的 `Strategy` 支持在创建时填写 `hypothesis`(假设)和
| 步骤 | 怎么做 | 好的信号 | 坏的信号 |
|------|--------|----------|----------|
| 监控 | 看滚动夏普、回撤、超额 | 指标波动但整体健康 | 多个指标同时持续恶化 |
| 诊断 | 由外到内:执行、市场、策略 | 能定位到具体原因 | 三都有问题(可能需要重新设计) |
| 监控 | 看滚动夏普、回撤、相对沪深 300 表现 | 指标波动但整体健康 | 多个指标同时持续恶化 |
| 诊断 | 由外到内:执行、市场、策略 | 能定位到具体原因 | 三类原因都有问题(可能需要重新设计) |
| 迭代 | 假设、验证标准、实验、结论 | 假设被确认或被有价值地推翻 | 没有假设就开始改参数 |
---
@@ -441,95 +441,85 @@ open-xquant 的 `Strategy` 支持在创建时填写 `hypothesis`(假设)和
### 策略进化路径
章把策略推上了实盘,第八章把“上线之后怎么办”补完——三步进化路径如表 8-11 所示。
7 章把策略接到交易执行这一步,第 8 章补上“跑起来以后怎么办”三步进化路径如表 8-11 所示。
**表 8-11 第章三步进化路径**
**表 8-11 第 8 章三步进化路径**
| 步骤 | 做了什么 | 关键发现 |
|------|---------|---------|
| 第 1 步 监控 | 滚动指标 + 自动标恶化 | 2022 年集中出现 4 段恶化 |
| 第 2 步 诊断 | 由外到内三层排查 | 不是 bug,是市场状态变了 |
| 第 2 步 诊断 | 由外到内排查三类原因 | 不是 bug,是市场状态变了 |
| 第 3 步 迭代 | 两轮假设驱动对照 | 改频率没用,改逻辑才有效 |
章产出“从回测到实盘的完整闭环”,第章在它之上多走一步——产出“策略迭代的完整方法”加上“用数据做决策”的认知。
7 章产出“从回测到交易执行的完整流程”,第 8 章在它之上多走一步产出“策略迭代的完整方法”加上“用数据做决策”的认知。
### 概念速查表
本章涉及的核心概念汇总如表 8-12 所示,方便随时回查。
**表 8-12 第章核心概念速查**
**表 8-12 第 8 章核心概念速查**
| 概念 | 含义 | 类比 |
|------|------|------|
| 滚动夏普 | 最近一段时间(如 3 个月)的风险调整收益,衡量策略“现在”的健康度 | 体温计——数值正常说明没发烧 |
| 市场状态(regime) | 市场的波动水平和整体环境,不同时期差异很大 | 天气——晴天和台风天出门穿的衣服不一样 |
| 假设驱动迭代 | 先提出可验证的猜想和判断标准,再做实验,而不是凭感觉改 | 医生看病——先检查确认病因,再开药 |
| 滚动夏普 | 最近一段时间(如 3 个月)的风险调整收益,衡量策略“现在”的健康度 | 体温计数值正常说明没发烧 |
| 市场状态(regime) | 市场的波动水平和整体环境,不同时期差异很大 | 天气晴天和台风天出门穿的衣服不一样 |
| 假设驱动迭代 | 先提出可验证的猜想和判断标准,再做实验,而不是凭感觉改 | 医生看病先检查确认病因,再开药 |
| 对照实验 | 只改一个变量、其他不变,看效果 | 同一道菜只改盐的量,尝尝区别 |
| 执行参数 vs 策略逻辑 | “多久做一次”和“做什么”是两件事 | 多久看一次天气预报 vs 看到暴雨预警后是否带伞 |
| 迭代记录表 | 每次改动的假设、标准、结果、结论,形成可追溯的改进历史 | 病历本——记录每次看病的诊断和治疗 |
| 迭代记录表 | 每次改动的假设、标准、结果、结论,形成可追溯的改进历史 | 病历本记录每次看病的诊断和治疗 |
### 飞轮第一圈完整闭环
### 四个阶段回顾
**到这里,你完成了第一圈完整的飞轮——本章的结论会反过来喂给下一圈飞轮的“候选 → 组合 → 执行”。** 候选(第章)→ 组合层(第三、四章)→ 执行层(第七章)→ 归因(第五、六、八章)→ 反哺起点。三字要点(做 / 看 / 疑)也走了完整一遍:做 = 把每一层都写成可重复执行的规则;看 = 用指标判断好坏;疑 = 不止信回测,也质疑实盘的每一段
到这里,你已经完整走过四个阶段。确定候选(第 2 章)→ 制定规则(第 3、4 章)→ 执行交易(第 7 章)→ 评估归因(第 5、6、8 章)。第 8 章的结论会反过来提醒你:下一次再确定候选、制定规则时,要提前考虑市场状态
下一圈飞轮要让它转得更快——这就是第九章的事
做、看、疑三个环节也走了一遍:做,是把监控和迭代写成可重复执行的规则;看,是用指标判断策略状态;疑,是不只相信回测,也检查执行、市场状态和策略本身
接下来,第 9 章会让这个研究过程更快:持续寻找新的因子和新的策略改进机会。
### 本章核心认知
走完三步之后,本章最值得带走的九条认知如表 8-13 所示。
**表 8-13 第章核心认知**
**表 8-13 第 8 章核心认知**
| 认知 | 来源 |
|------|------|
| 策略亏钱不一定是坏了,可能是正常波动 | 第 1 步 |
| 持续监控比偶尔看一眼净值重要得多 | 第 1 步 |
| 诊断要由外到内——先排执行,再看市场,最后看策略 | 第 2 步 |
| 诊断要由外到内先排执行,再看市场,最后看策略 | 第 2 步 |
| 市场状态会变,策略在不同状态下表现不同是正常的 | 第 2 步 |
| 改策略前先定好验证标准,不能事后改规则 | 第 3 步 |
| 改“多久行动”和改“怎么行动”是两件事 | 第 3 步 迭代 1 |
| 假设被推翻是好事——省下了实盘亏钱的学费 | 第 3 步 迭代 1 |
| 假设被推翻是好事省下了真钱试错的成本 | 第 3 步 迭代 1 |
| 让策略感知市场状态并调整行为,才是有效的改进 | 第 3 步 迭代 2 |
| 每次改进都是取舍——用收益换安全,或反过来 | 第 3 步 迭代 2 |
| 每次改进都是取舍用收益换安全,或反过来 | 第 3 步 迭代 2 |
### 带走的问题
我们已经走完了从选标的(第章)到持续改进(第章)的完整闭环。你现在掌握了监控、诊断和迭代的方法
我们已经走完了从选标的(第 2 章)到持续改进(第 8 章)的完整流程。你现在掌握了监控、诊断和迭代的方法。
但你可能已经感受到:每次迭代都要构造假设、改参数、跑回测、对比指标、记录结论……这个过程不算轻松。
策略不完美没关系,碰到问题也没关系——只要迭代得够快,从错误和失败中学习得够快,你就可以保持进化。**迭代速度本身就是竞争力。**
策略不完美没关系,碰到问题也没关系只要迭代得够快,从错误和失败中学习得够快,你就可以保持进化。**迭代速度本身就是竞争力。**
有没有办法让这个循环转得更快?—— 第九章
有没有办法让这个过程更快?这就是第 9 章要回答的问题。
> 本章所有代码的可运行版本见 `notebooks/q8-iteration.ipynb`
> 本章所有代码的可运行版本见配套仓库的 [`q8-iteration/notebooks/q8-iteration.ipynb`](https://github.com/xingwudao/xquant-learning/blob/main/q8-iteration/notebooks/q8-iteration.ipynb)
---
## 8.6 拓展阅读
本章正文聚焦“监控 / 诊断 / 迭代”三个策略层面的动作。下面这段是动手之外的“软功”——读完正文后回头看,决定你能不能把这套方法长期跑下去。
本章正文聚焦“监控 / 诊断 / 迭代”三个策略运行相关的环节。下面这段是动手之外的“软功”读完正文后回头看,决定你能不能把这套方法长期跑下去。
### 迭代自己:耐心 / 纪律 / 记录
前面三步解决的是策略层面的问题,但还有一问题藏在你自己身上。
前面三步解决的是策略运行的问题,但还有一问题藏在你自己身上。
**耐心(对应第 1 步 监控)。** 策略有波动是常态。不要每天盯着净值焦虑——你已经有了监控仪表盘,定期检查就够了。如果你发现自己每小时都在看净值,那不是在监控,是在焦虑。仪表盘的意义不只是发现问题,也是让你在没有问题的时候安心。
**耐心(对应第 1 步 监控)。** 策略有波动是常态。不要每天盯着净值焦虑你已经有了监控仪表盘,定期检查就够了。如果你发现自己每小时都在看净值,那不是在监控,是在焦虑。仪表盘的意义不只是发现问题,也是让你在没有问题的时候安心。
**纪律(对应第 3 步 验证标准)。** 定好了验证标准就不要改。如果实验结果不好就改标准,这和第章讲的过拟合是一回事——你在“事后”调整规则来让结果看起来好,而不是真的在改进。假设被推翻了?接受它,记录下来,想下一个假设。
**纪律(对应第 3 步 验证标准)。** 定好了验证标准就不要改。如果实验结果不好就改标准,这和第 6 章讲的过拟合是一回事你在“事后”调整规则来让结果看起来好,而不是真的在改进。假设被推翻了?接受它,记录下来,想下一个假设。
**记录(对应第 3 步 迭代记录表)。** 每次迭代都填迭代记录表。三个月后回头看,你会发现大部分假设都被推翻了——但你从每次推翻中学到的东西比确认更多。这些记录就是你的成长轨迹。
**记录(对应第 3 步 迭代记录表)。** 每次迭代都填迭代记录表。三个月后回头看,你会发现大部分假设都被推翻了但你从每次推翻中学到的东西比确认更多。这些记录就是你的成长轨迹。
**一句话:** 策略层面的方法你已经有了;接下来三年里能不能跑通,取决于你“耐心 / 纪律 / 记录”这三条软纪律守得有多严。这是带得走的“心态地图”。
---
## 反馈与读者群
如果你发现本章有错别字、概念不清或实验结果不一致,欢迎
[提交 GitHub Issue](https://github.com/xingwudao/xquant-beginner/issues/new/choose)。
课程、正式书和后续更新见
[xquant.shop/courses](https://xquant.shop/courses)。
添加作者微信 `xquanter` 进入读者群。请备注:`XQuant 读者`
**一句话:** 策略运行的方法你已经有了;接下来三年里能不能跑通,取决于你“耐心 / 纪律 / 记录”这三条软纪律守得有多严。这是带得走的“心态地图”。
+160 -172
View File
@@ -1,6 +1,6 @@
# 第章:持续寻找盈利机会:因子研究入门
# 第 9 章:持续寻找机会:因子研究入门
前面章,我们从零搭建了一个完整的量化策略——选标的、定仓位、择时买卖、验证有效性、避免过拟合、接入执行、持续迭代。策略跑起来了。
前面 8 章,我们从零搭建了一个完整的量化策略选标的、定权重、制定买卖规则、验证有效性、避免过拟合、接入执行、持续迭代。策略跑起来了。
**然后呢?**
@@ -8,23 +8,23 @@
答案是:**因子研究**。
回头看前面章的每一个关键决策——用 GDP 选国家用动量排名买卖——背后都有一个共同逻辑:找到一个数据指标,用它做决策。这个数据指标,就是**因子(Factor**。
回头看前面 8 章的每一个关键决策用 GDP 选国家用动量排名买卖背后都有一个共同逻辑:把投资直觉翻译成可计算变量,再用它做决策。这个变量,就是**因子(Factor**。
量化交易员的日常工作,就是不断寻找新的因子、评估它们的预测能力、组合成更好的信号、改进策略。这一章,我们就来体验这个过程。
量化研究的日常工作,就是不断寻找新的因子、评估它们的预测能力,再把有用的发现变成策略改进。这一章,我们就来体验这个过程。
### 路线图
**完整跑通一个策略(第章)→ 选什么标的(第章)→ 每个买多少(第章)→ 什么时候买卖(第章)→ 怎么验证有效(第章)→ 如何避免自欺欺人(第章)→ 怎么真正执行(第章)→ 如何持续改进(第章)→ 持续找下一个 alpha(第九章本章)**
**完整跑通一个策略(第 1 章)→ 选什么标的(第 2 章)→ 每个买多少(第 3 章)→ 什么时候买卖(第 4 章)→ 怎么验证有效(第 5 章)→ 如何避免自欺欺人(第 6 章)→ 如何真正执行(第 7 章)→ 如何持续改进(第 8 章)→ 持续寻找机会(第 9 章本章)**
本章从“回顾已有决策”出发,正式引入因子概念,用三个步骤体验量化研究员的日常工作,三步的问题与方法对照如表 9-1 所示。
**表 9-1 第章三步实验问题与方法对照**
**表 9-1 第 9 章三步实验问题与方法对照**
| 步骤 | 问题 | 方法 |
|------|------|------|
| Step 1 | 前面的决策靠的是什么? | 回顾 → 引入因子概念 → IC 评估方法 → GDP 因子实验 |
| Step 2 | 动量因子到底靠不靠谱? | 25 只 ETF × 多个前向窗口 → IC 分析 → 分组诊断 |
| Step 3 | 因子评估能改进策略吗? | 补充因子 → 发现问题 → 波动率过滤 → 回测验证 |
| 第 1 步 | 前面的决策靠的是什么? | 回顾 → 引入因子概念 → IC 评估方法 → GDP 因子实验 |
| 第 2 步 | 动量因子到底靠不靠谱? | 25 只 ETF × 多个前向窗口 → IC 分析 → 分组诊断 |
| 第 3 步 | 因子评估能改进策略吗? | 补充因子 → 发现问题 → 波动率过滤 → 回测验证 |
(操作流程见前言“怎么使用这本书”。)
@@ -38,10 +38,10 @@
| 章节 | 决策 | 用了什么数据 | 因子名称 | 因子类别 |
|------|------|-------------|---------|---------|
| 第章选什么 | 选哪个国家的市场 | GDP 总量/增速 | GDP 因子 | 宏观因子 |
| 第章买多少 | 涨多买、跌不买 | 风险调整动量 | 动量因子 | 动量因子 |
| 第 2 章选什么 | 选哪个国家的市场 | GDP 总量/增速 | GDP 因子 | 宏观因子 |
| 第 3 章买多少 | 涨多买、跌得多不买 | 风险调整动量 | 动量因子 | 动量因子 |
你可能没意识到,第二章和第三章里你已经在用因子了。**因子(Factor)**就是你做投资决策时参考的那个数据——GDP 高的国家更值得投涨得好的多买一点。就像你挑餐厅会看评分、距离、价格,这些“参考指标”就是因子。
你可能没意识到,第 2 章和第 3 章里你已经在用因子了。**因子(Factor)**是描述资产或市场特征的可计算变量。它可以用来解释风险和收益,也可以用来预测表现、辅助决策。比如 GDP 高的国家更值得投涨得好的资产多买一点。就像你挑餐厅会看评分、距离、价格,这些“参考指标”就是因子。
量化研究的日常工作,就是不断寻找和评估这些因子。
@@ -49,9 +49,9 @@
一个因子好不好,最直接的检验方式是:它的打分排名,和实际收益排名有多像?
这就是 **ICInformation Coefficient,信息系数)** 的核心思想。打个比方——IC 就像考试估分准不准:你预估的前 5 名跟实际前 5 名的重合度越高,说明你的判断越靠谱。
这就是 **ICInformation Coefficient,信息系数)** 的核心思想。打个比方IC 就像考试估分准不准:你预估的前 5 名跟实际前 5 名的重合度越高,说明你的判断越靠谱。
计算方法:每一期,把所有标的按因子值排名,再按下一期的实际收益排名,然后算两个排名的 Spearman 相关系数。Spearman 是相关系数的一种,专门看排名而不是数值——就像比较两次考试的“班级第几名”是否一致,而不是具体分数。相关系数范围从 -1 到 +1:+1 表示因子排名和收益排名完全一致(完美预测),-1 表示完全相反,0 表示毫无关系。
计算方法:每一期,把所有投资对象按因子值排名,再按下一期的实际收益排名,然后算两个排名的 Spearman 相关系数。Spearman 是相关系数的一种,专门看排名而不是数值。它比较的是两次考试的“班级第几名”是否一致,而不是具体分数。相关系数范围从 -1 到 +1:+1 表示因子排名和收益排名完全一致(完美预测),-1 表示完全相反,0 表示毫无关系。
**IC 均值**是多期 IC 的平均值,反映因子是否长期有效:
@@ -59,45 +59,45 @@
- 绝对值 > 0.05:算不错的因子
- 绝对值 > 0.1:非常强的因子(实际中很罕见)
**ICIRIC 信息比率)** = IC 均值 / IC 标准差,衡量 IC 的稳定性。不仅要均值高,还要稳定偶尔猜中一次的因子不算好,每年都稳定有效才算)——类似夏普比率的思路
**ICIRIC 信息比率)** = IC 均值 / IC 标准差,衡量 IC 的稳定性。不仅要均值高,还要稳定偶尔猜中一次的因子不算好,每年都稳定有效才算。这和第 3 章讲简化夏普时的思路类似:不只看平均收益,也要看波动
### 动手实验 1:GDP 因子 IC 分析
我们一起把这份 spec 写出来。这次重点看两件新东西:**评估工具先讲清楚再用**,以及**因子计算的关键参数要点名到函数级**。
#### 起草上下文 + 任务
#### 第一、二段:上下文和任务描述
这是收官章第一份 spec,承接前章的所有实验。上下文段把“学员已经在用因子但没意识到”这个起点交代清楚:
这是收官章第一份 spec,承接前 8 章的所有实验。上下文段把“读者已经在用因子但没意识到”这个起点交代清楚:
> **上下文**学员已完成第二至八章。本 spec 对应 notebook `q9-daily-work.ipynb` Step 1,从零引入因子概念和 IC 评估方法,并用 10 国 GDP 数据做第一个因子评估实验。
> **上下文**读者已完成第 2 章到第 8 章。本 spec 对应 notebook `q9-daily-work.ipynb`第 1 步,从零引入因子概念和 IC 评估方法,并用 10 国 GDP 数据做第一个因子评估实验。
>
> **任务**:在 notebook 中创建 Step 1 的所有单元格——导入库、因子回顾表、IC 方法说明、GDP 因子 IC 实验、排名对比图、GDP 分析、因子分类全景表。
> **任务描述**:在 notebook 中创建 1 的所有单元格导入库、因子回顾表、IC 方法说明、GDP 因子 IC 实验、排名对比图、GDP 分析、因子分类全景表。
> 📌 **要点**:收官章 spec 把“评估工具”前置于“评估实验”。要求段先单独要求一个 IC 方法说明 markdown(讲 Spearman、阈值、ICIR),再要求 GDP 实验——把全章共用的“测量尺”建起来,后面所有 spec 都引用同一套语言。
> 📌 **要点**:收官章 spec 把“评估工具”前置于“评估实验”。任务要求段先单独要求一个 IC 方法说明 markdown(讲 Spearman、阈值、ICIR),再要求 GDP 实验。先把全章共用的“测量尺”建起来,后面所有 spec 都引用同一套语言。
#### 起草要求:函数签名精确 + 参数全显式
#### 第三段:任务要求
要求段是这份 spec 最长的一节(11 条)。两个最关键的细节:
任务要求段是这份 spec 最长的一节(11 条)。两个最关键的细节:
> **要求**(摘要):前两条规定整体结构,3-5 条是具体的 oxq 模块调用细节。
> **任务要求**(摘要):前两条规定整体结构,3-5 条是具体的 oxq 模块调用细节。
>
> 1. IC 方法说明 markdown 必须放在 GDP 实验之前——先讲 Spearman 相关系数、IC 阈值(0.03 / 0.05 / 0.1)、ICIR 概念
> 1. IC 方法说明 markdown 必须放在 GDP 实验之前先讲 Spearman 相关系数、IC 阈值(0.03 / 0.05 / 0.1)、ICIR 概念
> 2. GDP 数据下载用 `WorldBankFetcher()` + `FactorDownloader(fetcher, sub="macro")` + `dl.download("gdp", start="2010", end="2024", countries=countries)`
> 3. ETF 价格用 `YFinanceDownloader()` + `LocalMarketDataProvider().get_bars()`,时间范围 2011-01-01 至 2026-12-31
> 4. IC 计算:每个“因子年→收益年”配对,用 `scipy.stats.spearmanr` 算相关系数;同时跑 GDP 总量因子和 GDP 增速因子(`gdp_df.pct_change()`
> 5. 视觉契约:1×2 柱状图(figsize 14×5),正相关蓝色、负相关红色,加 IC 均值虚线;slope chart 选 IC 最大那一年,中美红色粗线高亮、其他灰色
> 📌 **要点**:量化研究 spec 必须把“算什么”具体到函数。“算 IC”不够——`spearmanr` 还是 `pearsonr`?前者看排名相关后者看数值相关,因子评估默认用 spearman。**任何统计计算都要点名具体函数,避免 AI 在等价函数间随机选。**
> 📌 **要点**:量化研究 spec 必须把“算什么”具体到函数。“算 IC”不够`spearmanr` 还是 `pearsonr`?前者看排名相关后者看数值相关,因子评估默认用 spearman。任何统计计算都要点名具体函数,避免 AI 在等价函数间随机选。
> 📌 **要点**:复用现成模块时,spec 第一条要点名所有 import 路径——AI 不知道哪些轮子已经存在,名字写错就重复造轮子。本节涉及 `WorldBankFetcher``FactorDownloader``read_factor``Momentum``RollingVolatility` 等 7 个模块,全部按官方写法显式列出。
> 📌 **要点**:复用现成模块时,spec 第一条要点名所有 import 路径AI 不知道哪些轮子已经存在,名字写错就重复造轮子。本节涉及 `WorldBankFetcher``FactorDownloader``read_factor``Momentum``RollingVolatility` 等 7 个模块,全部按官方写法显式列出。
#### 起草结果呈现
#### 第四段:验收标准
> **结果呈现**:GDP 数据表(万亿美元) + 10 国 ETF 覆盖表 + 1×2 IC 柱状图 + IC 均值/ICIR 打印 + 排名对比 slope chart + GDP 分析 markdown + 6 类因子分类全景表 + 过渡文案。
> **验收标准**:GDP 数据表(万亿美元) + 10 国 ETF 覆盖表 + 1×2 IC 柱状图 + IC 均值/ICIR 打印 + 排名对比 slope chart + GDP 分析 markdown + 6 类因子分类全景表 + 过渡文案。
完整 spec 在 [`specs/spec-01-factor-intro.md`](https://github.com/xingwudao/xquant-learning/blob/main/q9-daily-work/specs/spec-01-factor-intro.md)——复制给 AI,弹窗选「允许」
完整示例 spec 见配套仓库的 [`q9-daily-work/specs/spec-01-factor-intro.md`](https://github.com/xingwudao/xquant-learning/blob/main/q9-daily-work/specs/spec-01-factor-intro.md)。参考示例后,确认自己的 spec,再复制给 AI
AI 助手执行完毕后,你的 notebook 里应该出现了 GDP 数据表、IC 柱状图和排名对比图。
AI 编程工具执行完毕后,你的 notebook 里应该出现了 GDP 数据表、IC 柱状图和排名对比图。
这个实验做了什么?先下载 10 个国家的 GDP 数据和对应的指数 ETF 价格数据,然后分别用两个 GDP 因子(总量和增速)来预测下一年的指数收益排名,计算每一年的 IC。
@@ -109,19 +109,19 @@ AI 助手执行完毕后,你的 notebook 里应该出现了 GDP 数据表、IC
GDP 总量因子:IC 均值 = 0.1272ICIR = 0.3158。GDP 增速因子:IC 均值 = -0.0390ICIR = -0.1177。
GDP 总量因子的 IC 均值为正,说明 GDP 越大的国家,下一年股市表现确实有略好的倾向。虽然很弱、不稳定,但方向是对的。这就呼应了第章的选择逻辑——我们根据 GDP 总量挑选了最大的两个经济体(中国和美国),用沪深 300 和纳斯达克来构建候选池,背后是有数据支撑的。
GDP 总量因子的 IC 均值为正,说明 GDP 越大的国家,下一年股市表现确实有略好的倾向。虽然很弱、不稳定,但方向是对的。这就呼应了第 2 章的选择逻辑我们根据 GDP 总量挑选了最大的两个经济体(中国和美国),用沪深 300 和纳斯达克来构建标的池,背后是有数据支撑的。
而 GDP 增速因子的 IC 更差,说明“增长快”并不能预测“股市涨得好”——经济增速和股市回报的关系比直觉想的要弱得多。
而 GDP 增速因子的 IC 更差,说明“增长快”并不能预测“股市涨得好”经济增速和股市回报的关系比直觉想的要弱得多。
再看 IC 最高那一年的排名对比图,如图 9-2 所示。
![IC 最高年份的 GDP 排名 vs 收益排名对比](images/q9-daily-work/01-gdp-rank-comparison.png)
左列是 GDP 总量排名,右列是下一年指数收益排名。线越平,说明两个排名越接近(IC 越高);线交叉越多说明排名差异大(IC 低)。中美两国用红色高亮——你能直观感受到“IC 高”意味着什么。
左列是 GDP 总量排名,右列是下一年指数收益排名。线越平,说明两个排名越接近(IC 越高);线交叉越多说明排名差异大(IC 低)。中美两国用红色高亮你能直观感受到“IC 高”意味着什么。
### 结果解读
GDP 因子的局限很明显:年度数据、10 个国家、十几年样本,IC 估计噪音很大。但这正好说明了 IC 的价值——不是拍脑袋觉得“有道理”就行,要用数据验证。
GDP 因子的局限很明显:年度数据、10 个国家、十几年样本,IC 估计噪音很大。但这正好说明了 IC 的价值不是拍脑袋觉得“有道理”就行,要用数据验证。
### 因子的分类
@@ -138,55 +138,55 @@ GDP 因子的局限很明显:年度数据、10 个国家、十几年样本,I
| 宏观 | 大环境 | GDP、利率 | 经济数据 |
| 另类 | 非传统信息 | 新闻情绪、卫星图像 | 另类数据 |
前面课程里我们用过宏观因子(GDP)和动量因子(风险调整动量)。接下来我们重点研究动量因子——因为它用的是每天都有的价格数据,样本量大、更新快,最适合日常研究。
前面课程里我们用过宏观因子(GDP)和动量因子(风险调整动量)。接下来我们重点研究动量因子因为它用的是每天都有的价格数据,样本量大、更新快,最适合日常研究。
GDP 因子受限于年度频率和小样本,IC 很不稳定。接下来,我们用日频价格数据更多标的来评估动量因子——这才是量化研究的日常节奏。
GDP 因子受限于年度频率和小样本,IC 很不稳定。接下来,我们用日频价格数据更多投资对象来评估动量因子这才是量化研究的日常节奏。
---
## 9.2 动量因子——涨得好的会继续涨吗?
## 9.2 动量因子涨得好的会继续涨吗?
章里我们用动量排名来决定买多少——涨得好的多买,跌的不买。当时在 3 只标的上回测效果不错。
3 章里我们用动量排名来决定买多少涨得好的多买,跌得多的不买。当时在 3 只 ETF 上回测效果不错。
但那只是 3 只标的。动量因子在更大范围内真的有效吗?它在什么环境下会失灵?要回答这些问题,我们需要扩大标的池,用 IC 来严格评估。
但那只是 3 只 ETF。动量因子在更大范围内真的有效吗?它在什么环境下会失灵?要回答这些问题,我们需要扩大标的池,用 IC 来严格评估。
### 动手实验 2:动量因子 IC 分析
我们一起把这份 spec 写出来。这次重点看两件新东西:**IC 计算函数的签名要点名到参数**,以及**最低样本数阈值要写明白**。
#### 起草上下文 + 任务
#### 第一、二段:上下文和任务描述
> **上下文**:在 `q9-daily-work.ipynb` Step 1 中已完成库导入和 GDP 因子 IC 实验。已有变量 `downloader`YFinanceDownloader)、`provider`LocalMarketDataProvider)。本 spec 对应 Step 2,把标的池从 10 只扩展到 25 只,用日频数据做动量因子的 IC 分析。
> **上下文**:在 `q9-daily-work.ipynb` 1 中已完成库导入和 GDP 因子 IC 实验。已有变量 `downloader`YFinanceDownloader)、`provider`LocalMarketDataProvider)。本 spec 对应第 2 步,把标的池从 10 只扩展到 25 只,用日频数据做动量因子的 IC 分析。
>
> **任务**:在 notebook 中新建 Step 2 的所有单元格——构建 25 只标的池、计算动量因子与前向收益、IC 分析表和图、按波动率分组的 IC 分析、问题总结。
> **任务描述**:在 notebook 中新建 2 的所有单元格构建 25 只投资对象组成的标的池、计算动量因子与前向收益、IC 分析表和图、按波动率分组的 IC 分析、问题总结。
#### 起草要求:函数签名 + 最低样本阈值
#### 第三段:任务要求
要求段的灵魂是把 IC 计算的“工程化模式”用函数签名固化下来——任何新因子来了都按这个模板跑一遍。
任务要求段的灵魂是把 IC 计算的“工程化模式”用函数签名固化下来任何新因子来了都按这个模板跑一遍。
> **要求**(摘要):
> **任务要求**(摘要):
>
> 1. 25 只标的池:22 国 ETF + 3 个大宗商品(GLD / USO / SLV),时间范围 2015-01-01 至 2025-12-31
> 1. 25 只投资对象组成的标的池:22 国 ETF + 3 个大宗商品(GLD / USO / SLV),时间范围 2015-01-01 至 2025-12-31
> 2. 因子矩阵:`Momentum().compute(df, column="close", period=20)``RollingVolatility().compute(df, column="close", period=20)`、RAM = 动量 / 波动率
> 3. 前向收益窗口 `[10, 20, 40]` 交易日:`df["close"].pct_change(h).shift(-h)`
> 4. 实现 `compute_ic_series(factor_df, fwd_df, min_assets=5)`:每个交易日取因子值和前向收益的交集标的(至少 min_assets 个),用 Spearman 算 IC
> 5. 实现 `compute_grouped_ic(factor_df, fwd_df, vol_df, min_assets=6)`:每天按波动率中位数把标的分高/低两组,组内分别算 Spearman IC;要求交集 ≥12、每组 ≥ min_assets
> 4. 实现 `compute_ic_series(factor_df, fwd_df, min_assets=5)`:每个交易日取因子值和前向收益都有数据的投资对象(至少 min_assets 个),用 Spearman 算 IC
> 5. 实现 `compute_grouped_ic(factor_df, fwd_df, vol_df, min_assets=6)`:每天按波动率中位数把投资对象分高/低两组,组内分别算 Spearman IC;要求交集 ≥12、每组 ≥ min_assets
> 6. 视觉契约:2×3 子图(figsize 18×8),柱状图 `width=2, alpha=0.2, steelblue` + 60 日滚动均值 `darkblue` + IC 均值红色虚线
> 7. 末尾保留 `ic_ts = ic_results["风险调整动量 → 20日"]``fwd_df = fwd_dfs[20]` 给 spec-03
> 📌 **要点**:IC 计算这种“对每天做一次统计、跳过样本不足的天、收集成时间序列”的工程化模式,必须封装成函数——`compute_ic_series(factor_df, fwd_df, min_assets=5)`。函数签名级精度让 AI 没有自由发挥空间,也让学员未来换标的池时有明确的修改入口。
> 📌 **要点**:IC 计算这种“对每天做一次统计、跳过样本不足的天、收集成时间序列”的工程化模式,必须封装成函数`compute_ic_series(factor_df, fwd_df, min_assets=5)`。函数签名级精度让 AI 没有自由发挥空间,也让读者未来换标的池时有明确的修改入口。
> 📌 **要点**:统计计算的阈值要写明白。`min_assets=5`(整体 IC)和 `min_assets=6`(分组 IC,配合交集 ≥12 的额外门槛)不是凭空选——Spearman 在 ≥5 时方差才可控,分组样本减半后 ≥6 才能稳定。**任何“最少多少样本”的阈值都要进 spec 的参数清单,否则 AI 可能选 3 也可能选 10。**
> 📌 **要点**:统计计算的阈值要写明白。`min_assets=5`(整体 IC)和 `min_assets=6`(分组 IC,配合交集 ≥12 的额外门槛)不是凭空选Spearman 在 ≥5 时方差才可控,分组样本减半后 ≥6 才能稳定。任何“最少多少样本”的阈值都要进 spec 的参数清单,否则 AI 可能选 3 也可能选 10。
#### 起草结果呈现
#### 第四段:验收标准
> **结果呈现**25 只标的覆盖表 + 动量因子矩阵 + 6 组 IC 对比表 + 2×3 IC 时间序列子图 + 分组 IC 表 + 高/低波动 IC 时间序列对比图 + 问题总结 markdown。
> **验收标准**25 只投资对象覆盖表 + 动量因子矩阵 + 6 组 IC 对比表 + 2×3 IC 时间序列子图 + 分组 IC 表 + 高/低波动 IC 时间序列对比图 + 问题总结 markdown。
完整 spec 在 [`specs/spec-02-momentum-ic.md`](https://github.com/xingwudao/xquant-learning/blob/main/q9-daily-work/specs/spec-02-momentum-ic.md)——复制给 AI,弹窗选「允许」
完整示例 spec 见配套仓库的 [`q9-daily-work/specs/spec-02-momentum-ic.md`](https://github.com/xingwudao/xquant-learning/blob/main/q9-daily-work/specs/spec-02-momentum-ic.md)。参考示例后,确认自己的 spec,再复制给 AI
AI 助手执行完毕后,你的 notebook 里应该出现了 25 只标的的覆盖表、IC 对比表和多张 IC 时间序列图。
AI 编程工具执行完毕后,你的 notebook 里应该出现了 25 只投资对象的覆盖表、IC 对比表和多张 IC 时间序列图。
这个实验做了什么?把标的池从 第二、三章 的 3 只扩展到 25 只全球 ETF(22 个国家/地区 + 黄金/原油/白银),用 2015-2025 的日频数据,分别测试两个动量因子(20 日动量和风险调整动量)在三个前向收益窗口(10/20/40 交易日)上的 IC 表现。然后按每只标的自身的波动率分组,看动量因子在高波动和低波动标的上的表现差异。
这个实验做了什么?把标的池从第 2、3 章的 3 只 ETF 扩展到 25 只全球 ETF(22 个国家/地区 + 黄金/原油/白银),用 2015-2025 的日频数据,分别测试两个动量因子(20 日动量和风险调整动量)在三个前向收益窗口(10/20/40 交易日)上的 IC 表现。然后按每只投资对象自身的波动率分组,看动量因子在高波动和低波动投资对象上的表现差异。
### 运行结果
@@ -194,26 +194,26 @@ AI 助手执行完毕后,你的 notebook 里应该出现了 25 只标的的覆
**表 9-4 两个动量因子 × 三个前向窗口的 IC 对比**
| 因子 —— 前向窗口 | IC 均值 | IC 标准差 | ICIR | IC>0 |
| 因子 / 前向窗口 | IC 均值 | IC 标准差 | ICIR | IC>0 |
|-----------------|--------|----------|------|------|
| 20日动量 —— 10日 | -0.0263 | 0.2794 | -0.0941 | 45.7% |
| 20日动量 —— 20日 | -0.0290 | 0.2731 | -0.1062 | 45.7% |
| 20日动量 —— 40日 | -0.0436 | 0.2794 | -0.1560 | 43.4% |
| 风险调整动量 —— 10日 | -0.0161 | 0.2730 | -0.0589 | 46.0% |
| 风险调整动量 —— 20日 | -0.0193 | 0.2705 | -0.0714 | 46.4% |
| 风险调整动量 —— 40日 | -0.0335 | 0.2770 | -0.1208 | 45.7% |
| 20日动量 / 10日 | -0.0263 | 0.2794 | -0.0941 | 45.7% |
| 20日动量 / 20日 | -0.0290 | 0.2731 | -0.1062 | 45.7% |
| 20日动量 / 40日 | -0.0436 | 0.2794 | -0.1560 | 43.4% |
| 风险调整动量 / 10日 | -0.0161 | 0.2730 | -0.0589 | 46.0% |
| 风险调整动量 / 20日 | -0.0193 | 0.2705 | -0.0714 | 46.4% |
| 风险调整动量 / 40日 | -0.0335 | 0.2770 | -0.1208 | 45.7% |
两个因子在三个窗口上的 IC 均值都接近零——动量因子的截面预测能力很弱。
两个因子在三个窗口上的 IC 均值都接近零动量因子的截面预测能力很弱。
2×3 的 IC 时间序列图如图 9-3 所示。
![两个动量因子 × 三个前向收益窗口 IC 对比](images/q9-daily-work/02-momentum-ic-grid.png)
图 9-3 每个格子是一个“因子 × 前向窗口”组合的 IC 随时间变化。蓝色柱子是每日 IC,深蓝线是 60 日滚动均值,红色虚线是 IC 均值。可以看到 IC 在零附近大幅波动,正负交替——说明动量因子的预测能力非常不稳定。
图 9-3 每个格子是一个“因子 × 前向窗口”组合的 IC 随时间变化。蓝色柱子是每日 IC,深蓝线是 60 日滚动均值,红色虚线是 IC 均值。可以看到 IC 在零附近大幅波动,正负交替说明动量因子的预测能力非常不稳定。
关键发现来自**按波动率分组的 IC 分析**——按标的自身波动率分组的 IC 表现如表 9-5 所示。
关键发现来自**按波动率分组的 IC 分析**。按投资对象自身波动率分组的 IC 表现如表 9-5 所示。
**表 9-5 动量因子在高波动 vs 低波动标的上的 IC 表现**
**表 9-5 动量因子在高波动 vs 低波动投资对象上的 IC 表现**
| 因子 | 整体 IC | 高波动组 IC | 低波动组 IC | Spread |
|------|--------|-----------|-----------|--------|
@@ -222,63 +222,63 @@ AI 助手执行完毕后,你的 notebook 里应该出现了 25 只标的的覆
直观对比如图 9-4 所示。
![风险调整动量因子:高波动 vs 低波动标的 IC 对比](images/q9-daily-work/02-grouped-ic-comparison.png)
![风险调整动量因子:高波动 vs 低波动投资对象 IC 对比](images/q9-daily-work/02-grouped-ic-comparison.png)
### 结果解读
分组分析揭示了一个重要规律:**同一天内,高波动标的的动量 IC 明显差于低波动标的**。
分组分析揭示了一个重要规律:**同一天内,高波动投资对象的动量 IC 明显差于低波动投资对象**。
波动率大的资产,价格噪音更大,动量信号就更容易被噪音淹没,指错方向。风险调整动量(RAM = 动量 / 波动率)在低波动组表现更好,说明除以波动率确实有帮助——但对高波动标的依然救不了。
波动率大的资产,价格噪音更大,动量信号就更容易被噪音淹没,指错方向。风险调整动量(RAM = 动量 / 波动率)在低波动组表现更好,说明除以波动率确实有帮助但对高波动投资对象依然救不了。
结论:**一个因子不够**。动量因子在高波动环境下失灵,我们需要补充能感知波动率环境的因子,在高波动时调整策略行为。
---
## 9.3 从因子到策略——让数据说话
## 9.3 从因子到策略让数据说话
我们已经知道了动量因子的弱点。现在的问题是:这个发现能不能帮我们改进策略?
### 动手实验 3:补充因子 + 策略改进
我们一起把这份 spec 写出来。这次重点看两件新东西:**用包装类把因子的弱点翻译成过滤规则**,以及**教学简化声明**——哪些参数是为了演示效果而非工程最优。
我们一起把这份 spec 写出来。这次重点看两件新东西:**用包装类把因子的弱点翻译成过滤规则**,以及**教学简化声明**,也就是哪些参数是为了演示效果而非工程最优。
#### 起草上下文 + 任务
#### 第一、二段:上下文和任务描述
> **上下文**:在 `q9-daily-work.ipynb` Step 2 中已完成 25 只标的的动量因子 IC 分析和分组分析。已有变量:`all_data``vol_df``fwd_dfs``ic_results``compute_ic_series` 函数、`downloader`。本 spec 对应 Step 3——补充因子、总结发现、改进策略并回测对比。
> **上下文**:在 `q9-daily-work.ipynb` 2 中已完成 25 只投资对象的动量因子 IC 分析和分组分析。已有变量:`all_data``vol_df``fwd_dfs``ic_results``compute_ic_series` 函数、`downloader`。本 spec 对应第 3 步:补充因子、总结发现、改进策略并回测对比。
>
> **任务**:在 notebook 中新建 Step 3 的所有单元格——补充因子 IC、因子评估发现、策略改进框架、第三章三只标的基准回测、波动率过滤改进、对比分析。
> **任务描述**:在 notebook 中新建 3 的所有单元格补充因子 IC、因子评估发现、策略改进框架、第 3 章三只 ETF 的基准回测、波动率过滤改进、对比分析。
#### 起草要求:包装类 + 教学简化声明
#### 第三段:任务要求
要求段把“研究 → 工程”这条路具象化:因子评估发现“动量在高波动时容易出错”,怎么把这个发现翻译成代码里能跑的过滤规则?答案是**包装一个 optimizer**——保留原 optimizer 的所有行为,只在末尾“打补丁”。
任务要求段把“研究 → 工程”这条路具象化:因子评估发现“动量在高波动时容易出错”,怎么把这个发现翻译成代码里能跑的过滤规则?答案是**包装一个 optimizer**保留原 optimizer 的所有行为,只在末尾“打补丁”。
> **要求**(摘要):
> **任务要求**(摘要):
>
> 1. 补充因子 IC:复用 `vol_df`(波动率因子)+ 新建 `rev_df = -df["close"].pct_change(5)`(短期反转因子),用 `compute_ic_series` 算 20 日前向 IC,三因子对比表
> 2. 第三章三只标的510300.SS / 513100.SS / 518880.SS+ 基准策略:`Threshold` 信号、`TopNRankingOptimizer(score_col="ram", n=3, filter_negative=True)``RebalanceFrequencyRule(interval_days=21)`、data_start=“2020-06-01”
> 2. 第 3 章三只 ETF510300.SS / 513100.SS / 518880.SS+ 基准策略:`Threshold` 信号、`TopNRankingOptimizer(score_col="ram", n=3, filter_negative=True)``RebalanceFrequencyRule(interval_days=21)`、data_start=“2020-06-01”
> 3. 实现 `VolFilteredOptimizer` 包装类:接受 `base_optimizer``vol_threshold``market_vol` 参数;`optimize()` 先调用 base 拿到权重、再从 indicators 取最新日期查 market_vol、若 > 阈值则把所有持仓权重减半、多余分配给 CASH;暴露 `name``required_indicators`
> 4. 阈值取市场平均波动率的中位数(教学场景刻意“过滤狠”——50% 时间减仓,让对比效果显著)
> 5. 视觉契约:净值曲线对比图(figsize 14×7),基准蓝 #3498DB、过滤橙 #E67E22,归一化到起点 = 100,图例标注夏普比率
> 4. 阈值取市场平均波动率的中位数(教学场景刻意“过滤狠”50% 时间降低持仓比例,让对比效果显著)
> 5. 视觉契约:净值曲线对比图(figsize 14×7),基准蓝 #3498DB、过滤橙 #E67E22,归一化到起点 = 100,图例标注简化夏普
> 📌 **要点**:把研究发现的因子弱点翻译成代码里的过滤规则时,首选**包装一个新类**——保留原 optimizer 的所有行为,只在末尾“打补丁”。这样原 optimizer 不动、新行为可插可拔,未来想换过滤逻辑只换包装。
> 📌 **要点**:把研究发现的因子弱点翻译成代码里的过滤规则时,首选**包装一个新类**保留原 optimizer 的所有行为,只在末尾“打补丁”。这样原 optimizer 不动、新行为可插可拔,未来想换过滤逻辑只换包装。
>
> 📌 **要点**:包装类要复用原类的契约接口——`VolFilteredOptimizer` 接收一个 `base_optimizer` 引用,复用它的 `required_indicators`,只在 `optimize()` 末尾插入波动率检查。这样调用方完全感知不到差别,可以无痛切换。
> 📌 **要点**:包装类要复用原类的契约接口`VolFilteredOptimizer` 接收一个 `base_optimizer` 引用,复用它的 `required_indicators`,只在 `optimize()` 末尾插入波动率检查。这样调用方完全感知不到差别,可以无痛切换。
> 📌 **要点**:教学场景的参数选择必须在 spec 里声明“是简化、不是最优”。波动率阈值取中位数(50% 时间减仓)、仓位减半(不是 0.7× 也不是 0.3×)——都是为了让对比效果显著的教学选择。**spec 写明这一点,学员仿写时才知道哪些数字可以、应该被改。**
> 📌 **要点**:教学场景的参数选择必须在 spec 里声明“是简化、不是最优”。波动率阈值取中位数(50% 时间降低持仓比例)、持仓比例减半(不是 0.7× 也不是 0.3×)都是为了让对比效果显著的教学选择。spec 写明这一点,读者仿写时才知道哪些数字可以、应该被改。
#### 起草结果呈现
#### 第四段:验收标准
> **结果呈现**:三因子 IC 对比表 + 因子评估发现 markdown + 策略改进方向 markdown + 第三章三只标的覆盖信息 + 基准回测指标 + 阈值与高波动天数占比 + 过滤策略回测指标 + 指标对比表 + 净值曲线对比图 + 动态分析文本 + 小结 markdown。
> **验收标准**:三因子 IC 对比表 + 因子评估发现 markdown + 策略改进方向 markdown + 第 3 章三只 ETF 覆盖信息 + 基准回测指标 + 阈值与高波动天数占比 + 过滤策略回测指标 + 指标对比表 + 净值曲线对比图 + 动态分析文本 + 小结 markdown。
完整 spec 在 [`specs/spec-03-factor-to-strategy.md`](https://github.com/xingwudao/xquant-learning/blob/main/q9-daily-work/specs/spec-03-factor-to-strategy.md)——复制给 AI,弹窗选「允许」
完整示例 spec 见配套仓库的 [`q9-daily-work/specs/spec-03-factor-to-strategy.md`](https://github.com/xingwudao/xquant-learning/blob/main/q9-daily-work/specs/spec-03-factor-to-strategy.md)。参考示例后,确认自己的 spec,再复制给 AI
AI 助手执行完毕后,你的 notebook 里应该出现了三因子 IC 对比表、基准策略和波动率过滤策略的回测结果,以及净值曲线对比图。
AI 编程工具执行完毕后,你的 notebook 里应该出现了三因子 IC 对比表、基准策略和波动率过滤策略的回测结果,以及净值曲线对比图。
这个实验做了什么?分三步走:
1. **补充因子**:在 25 只 ETF 上计算波动率因子和短期反转因子的 IC,与动量因子对比
2. **基准回测**:回到第章的三只标的(沪深300、纳指100、黄金),用 oxq 复刻第章的动量排名策略作为基准
3. **改进回测**:基于因子评估的发现(高波动时动量预测力下降),给策略加上波动率过滤——高波动时自动降低仓位
2. **基准回测**:回到第 3 章的三只 ETF(沪深 300、纳指 100、黄金),用 oxq 复刻第 3 章的动量排名策略作为基准
3. **改进回测**:基于因子评估的发现(高波动时动量预测力下降),给策略加上波动率过滤高波动时自动降低持仓比例
### 运行结果
@@ -292,96 +292,96 @@ AI 助手执行完毕后,你的 notebook 里应该出现了三因子 IC 对比
| 波动率 | 0.0020 | 0.2643 | 0.0076 | 49.1% |
| 短期反转 | 0.0183 | 0.2786 | 0.0659 | 52.6% |
三个因子各有侧重——动量看趋势、波动率看风险、反转看短期超跌。它们在不同环境下各有优势。
三个因子各有侧重动量看趋势、波动率看风险、反转看短期超跌。它们在不同环境下各有优势。
### 因子评估的发现
关键发现是:**动量因子在高波动标的上 IC 为负,在低波动标的上接近零**——波动率是动量因子失灵的主要原因。这个发现直接指向一个改进方向:在使用动量因子做决策时,应该考虑波动率环境。
关键发现是:**动量因子在高波动投资对象上 IC 为负,在低波动投资对象上接近零**波动率是动量因子失灵的主要原因。这个发现直接指向一个改进方向:在使用动量因子做决策时,应该考虑波动率环境。
### 因子评估的发现能改进策略吗?
我们在 25 只 ETF 上做了因子评估,现在回到 第二、三章 的三只标的(沪深300、纳指100、黄金),看看这些发现能不能改进策略。
我们在 25 只 ETF 上做了因子评估,现在回到第 2、3 章的三只 ETF(沪深 300、纳指 100、黄金),看看这些发现能不能改进策略。
回顾一下贯穿全书的两层选择框架:
回顾一下贯穿全书的两步筛选框架:
- **第一GDP 因子选候选**——在 Step 1 中,GDP 因子帮我们从全球市场中筛出了有潜力的国家/资产类别
- **第二:动量因子做择时**——在候选池内,用动量排名决定买谁、买多少
- **第一GDP 因子确定标的**。在第 1 中,GDP 因子帮我们从全球市场中筛出了有潜力的国家/资产类别
- **第二:动量因子制定买入规则**。在标的池内,用动量排名决定买谁、买多少
因子评估告诉我们,动量在高波动时容易出错。如果我们能让策略在高波动时自动降低仓位,应该能改善表现。
因子评估告诉我们,动量在高波动时容易出错。如果我们能让策略在高波动时自动降低持仓比例,应该能改善表现。
基准策略与改进策略的回测对比如表 9-7 所示。
**表 9-7 基准(动量排名)vs 改进(波动率过滤)策略对比**
| 策略 | 累计收益 | 年化波动率 | 最大回撤 | 夏普比率 |
| 策略 | 累计收益 | 年化波动率 | 最大回撤 | 简化夏普 |
|------|---------|----------|---------|---------|
| 基准(动量排名) | +113.36% | 15.01% | -13.32% | 1.09 |
| 改进(波动率过滤) | +92.96% | 11.00% | -10.17% | 1.25 |
净值曲线对比如图 9-5 所示。
![因子评估 —— 策略改进基准 vs 波动率过滤](images/q9-daily-work/03-strategy-comparison.png)
![因子评估策略改进基准 vs 波动率过滤](images/q9-daily-work/03-strategy-comparison.png)
基准策略夏普 1.09,最大回撤 -13.32%。波动率过滤后夏普 1.25,最大回撤 -10.17%,回撤改善 23.7%。因子评估告诉我们高波动时动量 IC 翻负,加上波动率过滤后回撤确实下降了——这就是因子评估带来的 Alpha不是靠猜,而是靠数据发现问题、改进策略。
基准策略的简化夏普 1.09,最大回撤 -13.32%。波动率过滤后,简化夏普提高到 1.25,最大回撤降到 -10.17%,回撤改善 23.7%。因子评估告诉我们高波动时动量 IC 翻负,加上波动率过滤后回撤确实下降了。这不是靠猜,而是靠数据发现问题、改进策略。
### 结果解读
波动率过滤带来了明显的改善:夏普比率从基准的 1.09 提升到 1.25,最大回撤从 -13.3% 降低到 -10.2%——回撤改善了约 24%。
波动率过滤带来了明显的改善:简化夏普从基准的 1.09 提升到 1.25,最大回撤从 -13.3% 降低到 -10.2%回撤改善了约 24%。
代价是什么?累计收益从 113% 降到了 93%——高波动时降仓位,自然少赚了一些。但风险调整后的收益(夏普)更好了。这正是因子评估带来的价值:**不是让你赚更多,而是让你在承担同样风险的情况下赚得更稳**。
代价是什么?累计收益从 113% 降到了 93%高波动时降低持仓比例,自然少赚了一些。但风险调整后的收益(简化夏普)更好了。这正是因子评估带来的价值:**不是让你赚更多,而是让你在承担同样风险的情况下赚得更稳**。
这个改进不是靠猜测,而是因子评估告诉我们的——IC 分析发现高波动时动量 IC 翻负,我们据此加了波动率过滤,回测验证了确实有效。这就是因子研究带来的 Alpha
这个改进不是靠猜测,而是因子评估告诉我们的IC 分析发现高波动时动量 IC 翻负,我们据此加了波动率过滤,回测验证了确实有效。这就是因子研究带来的改进价值
---
## 9.4 量化交易员的一周长什么样
到这里你已经掌握了“找因子 → 评估 → 改进策略”的完整流程。但每天打开电脑,到底先做什么、再做什么?光知道“应该会做”还不够,章名《量化交易员的日常工作》得给你一份能打印贴在桌面的作业日历。
到这里你已经掌握了“找因子 → 评估 → 改进策略”的完整流程。但每天打开电脑,到底先做什么、再做什么?光知道“应该会做”还不够。收官章还要给你一份能打印贴在桌面的作业日历。
把前八章的动作串成一条**可执行的研究流**——一个量化交易员典型的一周长什么样
把前 8 章的内容串成一条**可执行的研究流**一个量化交易员典型的一周长什么样
### 每个交易日(≤ 30 分钟)
**开盘前(5 分钟)。** 看一眼策略的实盘运行状态。监控仪表盘(第八章 Step 1)告诉你“是不是坏了”——滚动夏普还在 0 以上吗?回撤超过你预设的阈值了吗?相对沪深300超额收益还为正吗?三个指标全绿,今天没事;任意一个亮红灯,列入待诊断清单。
**开盘前(5 分钟)。** 看一眼策略的运行状态。监控仪表盘(第 8 章第 1 步)告诉你“是不是坏了”滚动夏普还在 0 以上吗?回撤超过你预设的阈值了吗?相对沪深 300 表现还好吗?三个指标都正常,今天没事;任意一个亮红灯,列入待诊断清单。
**盘后(10-15 分钟)。** 拉今日的成交回报,跑一次执行对账(第七章 Step 4)——回测口径下应该成交什么、实盘实际成交了什么滑点和佣金加起来是不是在你的预算内。差距稳定 = 健康;差距突然放大 → 列入待诊断清单。
**盘后(10-15 分钟)。** 拉今日的成交回报,跑一次执行对账(第 7 章第 4 步):回测口径下应该成交什么,模拟交易或真实交易里实际成交了什么滑点和佣金加起来是不是在你的预算内。差距稳定 = 健康;差距突然放大 → 列入待诊断清单。
**晚间(10 分钟)。** 把今天的关键事件写进迭代记录表(第八章 Step 3)。“今天恶化时段触发”、“某只 ETF 触发止损”、“市场状态切到高波动”——三个月后这张表是你最值钱的资产。
**晚间(10 分钟)。** 把今天的关键事件写进迭代记录表(第 8 章第 3 步)。“今天恶化时段触发”、“某只 ETF 触发止损”、“市场状态切到高波动”三个月后这张表是你最值钱的资产。
### 每周(2-3 小时,建议周末)
**周一上午:监控扫描 + 诊断分流。** 把过去一周的滚动夏普、回撤、超额收益拉出来,对比上周——稳定还是恶化?如果有恶化时段,跑诊断三问(第八章 Step 2):先排执行落差(外)、再查市场状态(中)、最后看策略本身(内)。
**周一上午:监控扫描 + 诊断分流。** 把过去一周的滚动夏普、回撤、相对沪深 300 表现拉出来,对比上周稳定还是恶化?如果有恶化时段,跑诊断三问(第 8 章第 2 步):先排执行落差(外)、再查市场状态(中)、最后看策略本身(内)。
**周末半天:因子探索 / 假设实验。** 两种典型节奏二选一:
- **因子探索(第章流程)**:观察市场最近的特征 → 提一个新因子假设 → 跑 IC + 分组分析 → 决定是否纳入策略候选池
- **假设驱动实验(第八章 Step 3**:写下假设和验证标准 → 跑对照回测 → 看结果确认还是推翻假设。**纪律:标准定下来就不能事后改。**
- **因子探索(第 9 章流程)**:观察市场最近的特征 → 提一个新因子假设 → 跑 IC + 分组分析 → 决定是否纳入后续研究清单
- **假设驱动实验(第 8 章第 3 步**:写下假设和验证标准 → 跑对照回测 → 看结果确认还是推翻假设。**纪律:标准定下来就不能事后改。**
### 每月(半天)
**月度复盘。** 拉本月的全部迭代记录,问三个问题:①假设确认率多少?②哪些假设被推翻、原因是什么?③下个月的研究方向?复盘的目的不是论功行赏,是让“疑”持续运转——上个月你以为对的东西,本月数据是不是还支持?
**月度复盘。** 拉本月的全部迭代记录,问三个问题:①假设确认率多少?②哪些假设被推翻、原因是什么?③下个月的研究方向?复盘的目的不是论功行赏,是让“疑”持续运转上个月你以为对的东西,本月数据是不是还支持?
**工具升级。** 数据源更新了吗?open-xquant 有新模块要试吗?哪条 spec 你写第三遍了——可以模板化吗?这一步是给“做”持续松绑——让单位时间能做的实验更多。
**工具升级。** 数据源更新了吗?open-xquant 有新模块要试吗?哪条 spec 你写第三遍了可以模板化吗?这一步是给“做”持续松绑让单位时间能做的实验更多。
### 每季度(1-2 天)
**季度策略检阅(第五 + 六章工具全用上)。** 跑一次完整的策略体检:基准对比、分年拆解、收益分布、参数敏感性、Walk-forward、交叉验证、规则负担——五把铲子(含第五章敏感性)每把都挥一次。任意一项亮红灯,启动重新设计或退役流程。
**季度策略检阅(第 5 章和第 6 章工具全用上)。** 跑一次完整的策略体检:基准对比、分年拆解、收益分布、参数敏感性、Walk-forward、交叉验证、规则负担。把第 5、6 章的检查工具完整跑一遍。任意一项亮红灯,启动重新设计或退役流程。
**视野扩展。** 读 1-2 篇论文 / 行业报告 / 优秀公开策略——不是为了立刻抄,是为了看看世界变到哪了。一个量化交易员退化的标志,是他几个月没读过任何外部输入。
**视野扩展。** 读 1-2 篇论文 / 行业报告 / 优秀公开策略不是为了立刻抄,是为了看看世界变到哪了。一个量化交易员退化的标志,是他几个月没读过任何外部输入。
### 一张可贴桌面的作业速查
把上面的节奏压成一张速查表,对应飞轮和三字要点的位置如表 9-8 所示。
把上面的节奏压成一张速查表,对应四个阶段和三个环节的位置如表 9-8 所示。
**表 9-8 量化交易员的作业速查**
| 周期 | 主要动作 | 飞轮位置 | 三字要点 |
| 周期 | 主要事项 | 对应阶段 | 对应环节 |
|------|---------|---------|---------|
| 每日 | 监控仪表盘 + 执行对账 + 写迭代记录 | 归因 + 执行 | 看 |
| 每周 | 监控扫描 + 诊断分流 + 因子/假设实验 | 归因 + 候选/组合层 | 看 + 疑 + 做 |
| 每月 | 月度复盘 + 工具升级 | 归因层(反哺起点) | 疑 + 做 |
| 每季度 | 五把铲子全检阅 + 视野扩展 | 全飞轮 | 做 + 看 + 疑 |
| 每日 | 监控仪表盘 + 执行对账 + 写迭代记录 | 评估归因 + 执行交易 | 看 |
| 每周 | 监控扫描 + 诊断分流 + 因子/假设实验 | 评估归因 + 确定候选 / 制定规则 | 看 + 疑 + 做 |
| 每月 | 月度复盘 + 工具升级 | 评估归因,反过来影响下一轮研究 | 疑 + 做 |
| 每季度 | 策略完整体检 + 视野扩展 | 四个阶段全部检查 | 做 + 看 + 疑 |
**节奏比“努力”重要。** 量化不是靠加班赚钱——是靠**让飞轮持续转、让坏假设尽早被推翻、让好假设尽快上线**。一周一次因子实验、一个月一次复盘、一个季度一次大检阅——只要持续做,三年后的你会让现在的你认不出来。
**节奏比“努力”重要。** 量化不是靠加班赚钱,而是靠**让研究流程持续转、让坏假设尽早被推翻、让好假设尽快被验证**。一周一次因子实验、一个月一次复盘、一个季度一次大检阅只要持续做,三年后的你会让现在的你认不出来。
---
@@ -391,57 +391,57 @@ AI 助手执行完毕后,你的 notebook 里应该出现了三因子 IC 对比
三步走,每一步都有明确的产出:
- **Step 1**:因子概念 + IC 评估方法(从直觉到数据验证)
- **Step 2**:动量因子 IC 分析(发现高波动是弱点)
- **Step 3**:因子评估改进策略(波动率过滤提升夏普)
- **第 1 步**:因子概念 + IC 评估方法(从直觉到数据验证)
- **第 2 步**:动量因子 IC 分析(发现高波动是弱点)
- **第 3 步**:因子评估改进策略(波动率过滤提升简化夏普)
### 概念速查表
本章涉及的核心概念汇总如表 9-9 所示,方便随时回查。
**表 9-9 第章核心概念速查**
**表 9-9 第 9 章核心概念速查**
| 概念 | 含义 | 类比 |
|------|------|------|
| 因子(Factor | 用来预测资产收益的数据指标 | 挑餐厅时看的评分、距离、价格 |
| 因子(Factor | 描述资产或市场特征的可计算变量,可用于解释风险和收益、预测表现或辅助决策 | 挑餐厅时看的评分、距离、价格 |
| IC(信息系数) | 因子排名和收益排名的相似度,衡量因子预测能力 | 考试预测分和实际分的排名对比 |
| ICIR | IC 均值 / IC 标准差,衡量 IC 的稳定性 | 夏普比率的因子版——不仅要准,还要稳 |
| 截面分析 | 同一时间点比较不同标的 | 同一次考试比较不同学生的成绩 |
| 两层选择框架 | GDP 因子选候选池 + 动量因子做择时 | 先选城市(宏观),再选餐厅(微观) |
| ICIR | IC 均值 / IC 标准差,衡量 IC 的稳定性 | 因子版的“性价比”:不仅要准,还要稳 |
| 截面分析 | 同一时间点比较不同投资对象 | 同一次考试比较不同学生的成绩 |
| 两步筛选框架 | GDP 因子确定标的池 + 动量因子制定买入规则 | 先选城市(宏观),再选餐厅(微观) |
### 本章核心认知
走完三个 Step 后,本章最值得带走的三条 q9 独有认知如表 9-10 所示——其余共通认知由 9.6 飞轮 + 三字要点收束
走完三个步骤后,本章最值得带走的三条认知如表 9-10 所示。
**表 9-10 第章核心认知**
**表 9-10 第 9 章核心认知**
| 认知 | 来源 |
|------|------|
| 波动率是动量因子的关键弱点——高波动标的的 IC 明显更差 | Step 2 |
| 动量因子的截面预测力接近零,回测有效不等于因子有效 | Step 2 |
| 因子评估带来的 Alpha 来自发现因子的弱点,并把弱点转化为过滤规则 | Step 3 |
| 波动率是动量因子的关键弱点高波动投资对象的 IC 明显更差 | 第 2 步 |
| 动量因子的截面预测力接近零,回测有效不等于因子有效 | 第 2 步 |
| 因子评估的价值来自发现因子的弱点,并把弱点转化为过滤规则 | 第 3 步 |
> 本章所有代码的可运行版本见 `notebooks/q9-daily-work.ipynb`
> 本章所有代码的可运行版本见配套仓库的 [`q9-daily-work/notebooks/q9-daily-work.ipynb`](https://github.com/xingwudao/xquant-learning/blob/main/q9-daily-work/notebooks/q9-daily-work.ipynb)
---
## 9.6 写给未来的你
这是全书的最后一章。回头看看你走过的路——把整本书带给你的东西浓缩成一张图:**飞轮 + 三字要点 + 一句口号**。
这是全书的最后一章。回头看看你走过的路把整本书带给你的东西浓缩成三件事:**四个阶段、三个环节、一种工作方式**。
**飞轮的四层你都过。** 候选(第章选 ETF)→ 组合层(第三章定仓位 / 第四章加规则)→ 执行层(第七章下单)→ 归因(第五、六、八章评估 / 防过拟合 / 监控诊断)这一章的因子研究反过来回到候选与组合层,进入下一圈。这就是“飞轮转完一圈”——每转一圈都比上一圈聪明一点
**四个阶段你都过。** 确定候选(第 2 章选 ETF)→ 制定规则(第 3 章定权重 / 第 4 章加买卖规则)→ 执行交易(第 7 章下单)→ 评估归因(第 5、6、8 章评估 / 防过拟合 / 监控诊断)这一章的因子研究反过来帮助你确定新的候选、制定新的规则。研究不是走到第 9 章就停止,而是带着新问题回到下一轮
**三字要点你都练过。**
**做、看、疑三个环节你都练过。**
- **做** —— 把动作写成 spec,让 AI 把它变成可重复执行的规则。从第章的均线、第三章的三种信号、第四章的止损止盈、到第八章的对照实验,每一次都是“做”。
- **看** —— 不允许“感觉还行”,所有判断都要落到指标上。年化、波动率、最大回撤、夏普 / 卡玛 / 索提诺、IC、ICIR——你已经收集了一整套尺子。
- **疑** —— 永远怀疑你的指标。第章撞墙、第六章四把铲子、第八章诊断三问、第章 IC 分组——你已经把“疑”从感觉训练成了流程。
- **做**:把想法写成 spec,让 AI 把它变成可重复执行的规则。从第 1 章的均线,到第 3 章的权重分配方法、第 4 章的止损止盈、第 8 章的对照实验,每一次都是“做”。
- **看**不允许“感觉还行”,所有判断都要落到指标上。年化、波动率、最大回撤、简化夏普、卡玛、索提诺、IC、ICIR你已经收集了一整套尺子。
- **疑**永远怀疑你的指标。第 1 章撞墙、第 6 章防过拟合检查、第 8 章诊断三问、第 9 章 IC 分组分析,你已经把“疑”从感觉训练成了流程。
**一句口号你能复述了:「做要规则、看要指标、还要怀疑你的指标。」** 会做、会看,但不会疑,最容易在某些时候把赚到的钱加倍还给市场。这正是为什么“疑”和“做”“看”在本书同等重要。
会做、会看,但不会疑,最容易把漂亮结果当成真规律。这正是为什么“疑”和“做”“看”在本书同等重要。
**你没有写过一行代码**——每一步都是通过撰写清晰的任务说明书(spec,让 AI 来执行。这不是偷懒,这是未来量化的工作方式。**Spec 写作能力本身就是本书最重要的一份带走品**:从第一章的“复制临摹”,到第三、四章的“改参数”,到第章起的“自己拆四段式”,到第章的“独立设计研究流程”——你的 spec 能力已经走完了三个台阶。
**你不需要把重点放在手写代码上。** 每一步都是通过撰写清晰的 spec,让 AI 来执行。这不是偷懒,而是一种新的工作方式。**Spec 写作能力本身就是本书最重要的一份带走品**:从第 1 章的“照着写”,到第 3、4 章的“改参数”,到第 6 章起的“自己拆四段式”,到第 9 章的“独立设计研究流程”你的 spec 能力已经走完了三个台阶。
量化交易的门槛正在快速降低。以前需要金融学位 + 编程能力 + 大量资金现在你只需要:清晰的思考 + 数据验证的习惯 + 一个好的 AI 助手
量化交易的门槛正在快速降低。以前需要金融学位编程能力大量资金现在你至少可以用更低的成本开始练习:清晰的思考数据验证的习惯一个好的 AI 编程工具
但门槛降低不等于赚钱变容易了。市场不会因为工具变好就多给你钱。真正的竞争力在于:
@@ -449,36 +449,36 @@ AI 助手执行完毕后,你的 notebook 里应该出现了三因子 IC 对比
- **更快的迭代(看 + 疑)**:从假设到验证的周期越短越好,让“看 / 疑”快到几乎和市场同步
- **更强的纪律(疑)**:不被情绪左右,按数据做决策,不让“疑”被自己的乐观腐蚀
这三件事,恰好可以交给一个被研究纪律约束好的 AI Agent 去高速放大——具体怎么入门见本章末「拓展阅读」。
这三件事,恰好可以交给一个被研究纪律约束好的 AI Agent 去放大。具体怎么入门见本章末「拓展阅读」。
**飞轮在你手里——下一圈,让它转得更快、更稳、更聪明。** 祝你在量化的路上走得远。
**下一轮研究还在你手里。** 让它更快、更稳、更聪明。祝你在量化的路上走得远。
---
## 拓展阅读
### 让 Agent 你跑因子研究
### 让 Agent 辅助你跑因子研究
整章你做的因子研究流程是高度结构化的——找因子、查 IC、看分组、改策略,每一步做什么、看什么指标都有明确标准。**结构化的流程最适合交给 AI Agent 执行。**
整章你做的因子研究流程是高度结构化的找因子、查 IC、看分组、改策略,每一步做什么、看什么指标都有明确标准。**结构化的流程最适合交给 AI Agent 执行。**
但通用 AgentOpenClaw、Cursor Agent 等)什么都能做但什么都做不精——拿来做量化研究容易跳过 IC 评估用不可复现的方式处理数据对 IC=0.01 的因子说“还不错”。这些是研究纪律问题。
但通用 Agent 什么都能做,也容易什么都不够守规矩。拿来做量化研究时,它可能跳过 IC 评估用不可复现的方式处理数据,或者对 IC=0.01 的因子说“还不错”。这些不是能力问题,而是研究纪律问题。
**Harness Engineering(缰绳工程)** —— 给 Agent 装上身份约束、操作流程、工具限制和技能模板,让“什么都能做”变成“按规矩做”。比喻来自驯马:马很强壮但需要缰绳,Agent 能力强但需要研究纪律。
你要做的不是让 Agent “自由发挥”,而是给它清楚的身份约束、操作流程、工具限制和技能模板,让“什么都能做”变成“按规矩做”。
### 用 open-xquant Agent 接管你的因子研究
### 用 open-xquant 约束 Agent 的研究流程
open-xquant 在 `agent/bootstrap/` 目录提供了一套量化研究专用的 harness
open-xquant 在 `agent/bootstrap/` 目录提供了一套量化研究专用的 Agent 约束文件
- **SOUL.md** —— Agent 的身份和底线。最核心的约束是 **“不可复现 = 不可信 = 不可交易”**,通用 Agent 不会自带这条量化纪律
- **AGENT.md** —— 研究 SOP(5 阶段:业务约束 → 目标 → 5 要素假设 → 数据准备 → 逐层构建),每一步都要你确认才推进,不会偷跑
- **TOOLS.md** —— 工具清单(通过 MCP Server 暴露 62 个 SDK 工具)
- **19 个 skill 文件** —— 覆盖从策略构建、因子评估到实盘交易的全流程
- **SOUL.md**Agent 的身份和底线。最核心的约束是“不可复现 = 不可信 = 不可交易”
- **AGENT.md**研究 SOP,每一步都要你确认才推进
- **TOOLS.md**:工具清单,告诉 Agent 哪些工具能用、该怎么用
- **skill 文件**:把策略构建、因子评估、交易执行等流程写成可复用技能
**怎么用?** `agent/bootstrap/` 覆盖 OpenClaw 默认文件 → 让 Agent 自行安装 open-xquant skills → 配置 MCP Server。然后你用自然语言提研究需求,Agent 按 SOP 引导你完成。具体安装步骤见 [open-xquant 仓库](https://github.com/xingwudao/open-xquant)。
**怎么用?** 先阅读 open-xquant 仓库里的 `agent/bootstrap/` 说明,再按你的 Agent 工具配置对应文件。然后你用自然语言提研究需求,Agent 按 SOP 引导你完成。具体安装步骤见 [open-xquant 仓库](https://github.com/xingwudao/open-xquant)。
### 更多因子、更复杂的组合——机器学习的入口
### 更多因子、更复杂的组合机器学习的入口
本课程只用了三个因子做演示。实际的量化研究中,因子数量可能是几十甚至上百个。当因子多了,手工写 if-else 规则来组合就不现实了——阈值怎么定?因子之间的交互关系怎么处理?
本课程只用了三个因子做演示。实际的量化研究中,因子数量可能是几十甚至上百个。当因子多了,手工写 if-else 规则来组合就不现实了阈值怎么定?因子之间的交互关系怎么处理?
这时候就需要机器学习:
@@ -486,16 +486,4 @@ open-xquant 在 `agent/bootstrap/` 目录提供了一套量化研究专用的 ha
- **梯度提升树(XGBoost / LightGBM**:量化行业最常用的多因子组合方法
- **神经网络**:能捕捉更复杂的非线性关系,但需要更多数据
核心思路都一样:把因子当作特征,未来收益当作标签,让模型学出最优的组合方式。这是你未来进入实战后可以深入探索的方向——但本书的主线已经够你跑很久了,**先把第章的迭代闭环跑顺,再考虑加机器学习**。
---
## 反馈与读者群
如果你发现本章有错别字、概念不清或实验结果不一致,欢迎
[提交 GitHub Issue](https://github.com/xingwudao/xquant-beginner/issues/new/choose)。
课程、正式书和后续更新见
[xquant.shop/courses](https://xquant.shop/courses)。
添加作者微信 `xquanter` 进入读者群。请备注:`XQuant 读者`
核心思路都一样:把因子当作特征,未来收益当作标签,让模型学出更合适的组合方式。这是你未来进入实战后可以深入探索的方向但本书的主线已经够你跑很久了,**先把第 8 章的迭代流程跑顺,再考虑加机器学习**。
+2 -2
View File
@@ -18,8 +18,8 @@ spec 与 notebook 不放在这里,统一维护在:
## 开始阅读
- [前言](book/preface.md)
- [开始动手之前](book/getting-started.md)
- [第 1 章:第一次跑通一个完整策略](book/q1-how-to-profit.md)
- [准备工作](book/getting-started.md)
- [第 1 章:跑通一个策略](book/q1-how-to-profit.md)
完整目录见 [SUMMARY.md](SUMMARY.md)。