Files
2026-07-14 23:00:29 +08:00

577 lines
45 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 第 7 章:从理想到现实:执行交易
> 最新书稿已更新至 [XQuant 量化课堂页](https://xquant.shop/courses)。
> 想阅读最新版官方书稿,请前往图书页。
第 6 章你建立了一套完整的防线:参数优化、前推验证、交叉验证、规则负担检查。策略通过了所有考验,你信心满满。
**明天就去买!**
但打开券商 APP,你愣住了。券商就是帮你买卖股票和基金的机构。策略说“沪深300ETF 权重 35%,纳指100ETF 权重 25%,黄金ETF 权重 40%”,可 APP 上只能输入股数和价格。35% 是多少股?按什么价格下单?
下单,就是告诉券商“买什么、卖什么、多少股、什么价格”。成交价,就是券商实际帮你买到或卖出的价格。你想用 ¥4.27 买,最后 ¥4.30 才买到,中间差出来的部分就是滑点。
回测引擎在内部用精确的小数分配权重。你很容易误以为,回测结果就是实际交易结果。但中间隔着一整条执行链:权重要翻译成股数,价格在不停变化,下单要花佣金和印花税,成交价和预期价格总有差距。这些差距不是抽象的。本章后面你会亲眼看到,仅仅因为成交价模式不同,同一个策略最好和最差之间累计差出 6.64%;再叠加佣金、印花税和滑点,四年累计又被侵蚀 5.72%。这一章就是要看清这些“看不见的钱”都去哪了。
### 路线图
**选什么标的(第 2 章)→ 每个买多少(第 3 章)→ 什么时候买卖(第 4 章)→ 怎么验证有效(第 5 章)→ 如何避免自欺欺人(第 6 章)→ 如何真正执行(第 7 章本章)**
**这一章进入四个阶段里的执行交易阶段。重点落在“做”和“看”两个环节:把“权重 → 订单 → 成交”这条执行链上的每一步都做成可重复的规则,再用执行报告看出回测和真实交易之间的差距。** 本章从“迫不及待想操作”的状态出发,在操作中暴露四个问题,四步对应的方法如表 7-1 所示。
**表 7-1 第 7 章四步问题与方法对照**
| 步骤 | 问题 | 方法 |
|------|------|------|
| 第 1 步 | 35% 是多少股? | 目标权重 → 具体订单 |
| 第 2 步 | 价格变了怎么办? | 成交价压力测试 + 成本层叠 |
| 第 3 步 | 回测和模拟交易差距有多大? | Alpaca 模拟交易 |
| 第 4 步 | 执行得对不对? | 执行报告 + 逐笔对账 |
本章会反复提到“模拟交易”。这里指 Alpaca 模拟盘(paper trading):订单进入 Alpaca 的撮合环境,不动用真实资金。真正用钱交易,需要按所在市场的开户、入金和合规流程独立办理。本章先用模拟交易练执行流程,避免还没学会检查执行风险,就先把真钱放进去。
(操作流程见前言“怎么使用这本书”。)
---
## 7.1 35% 是多少股?
策略给出的是权重——沪深300ETF 35%、纳指100ETF 25%、黄金ETF 40%。但券商 APP 只接受“买多少股、什么价格”。
你需要一个翻译器:把目标权重翻译成具体的订单。
这就像你规划了一次旅行预算——“交通占 35%,住宿占 25%,吃饭占 40%”——但到了订票网站,你只能输入具体金额。而且不能精确到分——火车票是 139 元,不是 138.52 元。凑成整数后,实际比例和计划比例总有些差距。
买股票也一样:你只能买 100 股的整数倍。8100 股沪深300ETF 不会刚好等于 35%。
### 动手实验 1:目标权重 → 具体订单
我们一起把这份 spec 写出来。这次重点看三件新东西:**交易金额必须用 Decimal**、**lot_size 是市场制度的硬约束**,以及**单点 → 横切 → 网格的三段递进**。
#### 第一、二段:上下文和任务描述
第 7 章的 spec 必须从第一行就交代“为什么是执行交易场景”。上下文用调整持仓的日子开场,把读者从“小数权重”的舒适世界拉到“券商 APP 只接受整数股”的现实。
> **上下文**:读者跑完第 6 章,策略通过了所有检验。今天需要调整持仓,风险平价方法给出目标权重:沪深300ETF 35% / 纳指100ETF 25% / 黄金ETF 40%。打开券商 APP,只能输入股数和价格。
>
> **任务描述**:在 `q7-execution.ipynb` 中用 `generate_orders()` 把目标权重翻译成具体订单,并对比 A 股(lot_size=100)和美股(lot_size=1)的取整偏差。
> 📌 **要点**:交易金额计算必须用 `Decimal` 而非 float。spec 在 import 段写 `from decimal import Decimal`,常量定义里所有权重、价格、本金都用 `Decimal("0.35")` 字符串构造。浮点累积误差在大额账户上会到分到角的差异,这是第 7 章起的第一条领域规矩。
#### 第三段:任务要求
任务要求段把市场规则当不变量写死。A 股 100 股一手,美股 1 股一手。这不是 oxq 的设计选择,是真实世界的制度差异。然后用三段递进暴露问题。
> **任务要求**
>
> 1. 常量:A 股标的 `("510300.SS", "513100.SS", "518880.SS")`、目标权重 35%/25%/40%、模拟价格 ¥4.27/¥1.72/¥8.04(全用 Decimal 字符串构造)
> 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 在挑选实现,是市场在挑选规则。
> 📌 **要点**:三段递进(单点 → 横切 → 网格)是第 7 章整章的节奏。第 1 步跑通接口,第 2 步对比两个市场,第 3 步扫描五档资金 × 两个市场。先看到现象,再看到对比,再看到规律,避免一上来用网格扫描压垮 0 基础读者。
#### 第四段:验收标准
> **验收标准**:订单表格 + A 股 vs 美股对比表 + 五档资金量两张表 + 对比柱状图;打印投入金额和剩余现金,过渡句“订单有了,但按什么价格下单?”自然带到下一节。
完整示例 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 股一手的约束让偏差远大于美股。
### 运行结果
先看 ¥100,000 的订单清单,如表 7-2 所示。
**表 7-2 ¥100,000 资金下 A 股 ETF 的订单清单与取整偏差**
| 标的 | 方向 | 目标股数 | 预估金额 | 目标权重 | 实际权重 | 偏差 |
|------|------|----------|----------|----------|----------|------|
| 沪深300ETF | BUY | 8100 | ¥34,587.00 | 35.0% | 34.6% | -0.41% |
| 纳指100ETF | BUY | 14500 | ¥24,940.00 | 25.0% | 24.9% | -0.06% |
| 黄金ETF | BUY | 4900 | ¥39,396.00 | 40.0% | 39.4% | -0.60% |
投入金额:¥98,923.00 / 剩余现金:¥1,077.001.08% 的资金没有被使用)
A 股 100 股整手约束让偏差非常明显——黄金 ETF 想要 40%,实际只做到 39.4%,差了 0.6 个百分点。有 1,077 元趴在账户里不产生任何收益。
再看 A 股 vs 美股的对比(同样是 10 万本金),如表 7-3 所示。
**表 7-3 A 股 vs 美股 10 万本金的取整偏差对比**
| 目标权重 | A 股标的 | 股数 | 实际权重 | 偏差 | 美股标的 | 股数 | 实际权重 | 偏差 |
|----------|----------|------|----------|------|----------|------|----------|------|
| 35.0% | 沪深300ETF | 8100 | 34.6% | -0.41% | SPY | 59 | 35.0% | -0.01% |
| 25.0% | 纳指100ETF | 14500 | 24.9% | -0.06% | QQQ | 48 | 24.6% | -0.41% |
| 40.0% | 黄金ETF | 4900 | 39.4% | -0.60% | GLD | 134 | 40.0% | -0.00% |
A 股现金残余:¥1,077.001.08% / 美股现金残余:$416.520.42%
A 股的偏差明显更大,现金残余也翻了一倍多。这就是 `lot_size=100` 整手约束的代价。
资金量的影响更直观,如图 7-1 所示。
![资金量越大,取整偏差越小;A 股偏差远大于美股](images/q7-execution/01-rounding-deviation.png)
左图:A 股(红色)在 1 万人民币时偏差高达 7.8%,即使 10 万人民币也有 0.6%。而美股(蓝色)在各资金量下的最大权重偏差都很小,1 万美元时偏了 5.3%,10 万美元就只有 0.4%。
右图:现金残余的规律一样——资金量越小,越多钱闲置在账户里。A 股 1 万人民币的账户,9.6% 的资金什么都没买到。
### 结果解读
第一次看到“理想”和“现实”的差距。
回测引擎在内部用浮点数精确分配权重——沪深300ETF 就是精确的 35.000%。但你去下单时,只能买 100 股的整数倍。8100 股沪深300ETF 的实际权重是 34.6%,不是 35.0%。
**取整偏差是系统性的**:每次调整持仓都会产生,日积月累就是真实的收益偏差。A 股的 100 股整手约束让偏差尤其明显。小资金账户(比如 10 万人民币做三只 ETF 的风险平价)可能连基本的权重分布都无法实现。
还有一笔“隐形损失”:**现金残余**。那些因为凑不成整股而趴在账户里的现金,一分钱收益都没产生。
但这只是第一个问题。订单生成了,按什么价格下单?
---
## 7.2 价格变了怎么办
回测里你只看到“权重”,但真实交易里每一笔下单都要付钱给三方。先建立预算感,再做实验。
**三笔执行成本你必须先认清:**
- **佣金(Commission)** —— 付给券商的服务费,A 股通常是万分之一到万分之三,最低 5 元;美股按笔或按股;港股约万分之五。本章 SimBroker 默认填的是万分之一。
- **印花税(Stamp Duty)** —— 付给政府的,A 股**卖出**时收千分之一(买入不收);港股买卖双向各 0.13%;美股没有印花税。第 2 章拓展阅读已经预告过,本章你将在执行报告里第一次亲眼看到它把钱怎么慢慢吃掉。
- **滑点(Slippage)** —— 你下单时看到的价格和最终成交的价格之间的差。看似小,每笔 0.1%-0.3%,但乘以一年几十笔交易,累积下来不可忽视。这一节就是要让你看到它有多大。
带着这三个成本的画面感,我们再回到具体场景:第 1 步生成了订单清单,“买沪深300ETF 8100 股,按 ¥4.27”。但 ¥4.27 是昨天的收盘价。你现在下单,价格可能已经涨到 ¥4.30 了,也可能跌到 ¥4.24。
按哪个价格买?这个差异有多大影响?
回测引擎默认你在收盘价精确成交——但你不可能在收盘的那一瞬间下单。实际上,你看到收盘数据后才能做决策,最早也是第二天才能交易。那第二天的价格和昨天收盘价能差多少?
还是那张订票网站——昨晚你查到火车票 139 元,今早决定下单,价格可能已经涨到 145 元,也可能降到 132 元。同一张票,看价格的时间和真正付款的时间隔开一夜,花的钱就不一样。
我们用五种成交价假设做一次压力测试。
### 动手实验 2:成交价压力测试
我们一起把这份 spec 写出来。这次重点看两件新东西:**带宽即认知**(用 fill_between 把执行不确定性变成可见的橙色带)和**积分式归因**(成本拆四层逐层加,把“扣了 5%”分解成佣金/印花税/滑点各扣多少)。
#### 第一、二段:上下文和任务描述
> **上下文**:在 `q7-execution.ipynb` 中已有第 1 步的订单生成实验。第 1 步生成了订单“买沪深300ETF 8100 股,按 ¥4.27”。但这是昨天收盘价,现在下单价格已经变了。按哪个价格买?差异有多大影响?
>
> **任务描述**:用 `FillPriceMode` 的五种模式做压力测试,再用四层成本层叠展示佣金、印花税和滑点的持续侵蚀。
> 📌 **要点**:金融术语在 spec 内最低限度解释。第 7 章的金融术语密度高(成交价、滑点、印花税折算、归一化净值、简化夏普),spec 写作要假设读者单读 spec 也能看懂。在任务要求段开头加一段术语简释,不让 spec 完全依赖正文才能读懂。
#### 第三段:任务要求
任务要求段同时压缩了“五种 FillPriceMode 全跑”和“四层成本层叠”两条主线。第一条让读者看到执行不确定性的带宽,第二条让读者看到成本归因。
> **任务要求**
>
> 1. 数据:标的 `510300.SS / 513100.SS / 518880.SS`,起始 `2021-01-01`,用 `YFinanceDownloader` + 本地缓存
> 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,胜过千言文字。
> 📌 **要点**:**积分式归因**。成本拆四层逐层加,每层一次完整回测、四条净值曲线对比。读者看到的不是“成本扣 X%”一个数字,而是“佣金扣多少、印花税扣多少、滑点扣多少”的归因分解。这是第 5 章、第 6 章已建立的“分布优于均值”思想在执行场景的延续。
> 📌 **要点**:印花税近似要主动声明“用 PercentageFee 折算为双边费率,实际略有差异”。这是教学诚实:不把模型当真理,承认精度边界。第 7 章涉及真实金额的 spec 都要遵循这条规矩。
#### 第四段:验收标准
> **验收标准**:五种模式对比表(5 行含收益和简化夏普)+ 净值曲线图(含执行不确定性带)+ 成本层叠表(4 行成本逐层递增、收益逐层递减)+ 成本层叠图(含成本侵蚀曲线);过渡句“这些都是估算,真实执行到底差多少?”带到下一节。
完整示例 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 里应该出现了五种成交价模式的对比结果和成本层叠分析。
`FillPriceMode` 有五种模式:`CLOSE`(收盘价,回测默认假设)、`NEXT_OPEN`(次日开盘价,更接近实际交易)、`NEXT_HIGH`(次日最高价,最差买入场景)、`NEXT_LOW`(次日最低价,最好买入场景)、`MID`(最高与最低均价)。同一个策略跑五遍,唯一的区别是“假设在什么价格成交”。
成本层叠则用四层配置逐层加码:无成本 → 加佣金(万一) → 加佣金 + 印花税 → 加佣金 + 印花税 + 滑点。用“次日开盘价”作为基础,因为它更接近实际交易。**滑点(Slippage)** 是你下单时价格因市场波动而偏离预期的部分,就像你在拥挤的菜市场买菜,问价时 5 块,等你掏钱时变 5.1 了。印花税是 A 股特有的成本。每次卖出时收取 0.05%,这里用 PercentageFee 近似折算为双边费率。
### 运行结果
先看五种成交价模式的对比,如表 7-4 所示。
**表 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 |
| 次日最高价(最差买入) | 81.02% | 12.62% | -16.13% | 1.14 | 185 |
| 次日最低价(最好买入) | 76.58% | 12.06% | -17.19% | 1.08 | 184 |
五条净值曲线和执行不确定性区间如图 7-2 所示。
![五种成交价模式的净值曲线和执行不确定性区间](images/q7-execution/02-fill-price-modes.png)
先看上图的五条净值曲线——起点都是 100,终点却不一样。同一个策略,同样的买卖决策,仅仅因为成交价格不同,累计收益差了好几个百分点。
再看下图——橙色填充区域就是 NEXT_HIGH 和 NEXT_LOW 之间的差距,叫**执行不确定性区间**。这个区间越宽,说明你对执行结果的控制力越弱。
收盘价(回测理想)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 |
| + 佣金 + 印花税 | 80.00% | 12.50% | 1.12 |
| + 佣金 + 印花税 + 滑点 | 76.72% | 12.08% | 1.09 |
成本层叠的净值曲线和侵蚀分解如图 7-3 所示。
![成本层叠:每一层都在侵蚀收益](images/q7-execution/03-cost-layers.png)
上图四条线逐层下移——绿色(无成本)在最上面,红色(全成本)在最下面。下图的成本侵蚀曲线持续下行,说明成本的影响不是一次性的,而是每次交易都在积累。
成本侵蚀总计:5.72%(其中佣金 1.52%,印花税 0.92%,滑点 3.28%)。
184 笔交易,佣金吃掉了 1.52%,印花税吃掉了 0.92%,滑点吃掉了 3.28%。合计 5.72% 的收益被交易成本侵蚀。
### 结果解读
五条净值曲线讲了两件事。
**第一,回测的收盘价假设是最乐观的。** 回测默认你能在收盘价精确成交——但实际上你不可能在收盘的那一瞬间下单。次日开盘价是更现实的假设,而你的实际成交价会落在次日最低价和最高价之间的某个位置。
NEXT_HIGH 和 NEXT_LOW 之间的带状区域就是“执行不确定性”——**同一个策略决策,仅仅因为执行时机不同,结果就有这么大的差距。** 这个差距不是策略的问题,而是执行的问题。
**第二,成本是持续的侵蚀。** 佣金、印花税和滑点不是一次性的损失,而是每次交易都在发生。一年几十次调整持仓下来,成本层层叠加,侵蚀掉的收益比你想象的多得多。
你刚才看到的就是 A 股的真实成本结构。佣金万一是基础,印花税在每次卖出时额外扣 0.05%,滑点更是无法避免。三者叠加,四年累计侵蚀了 5.72% 的收益——这就是为什么低频策略往往比高频策略更适合个人投资者。
但以上都是“估算”,是用不同的成交价模式模拟出来的。真正进入交易接口以后,差距到底有多大?
---
## 7.3 回测和模拟交易,差距有多大
把前两步的具体数字摊在桌面上:佣金 + 印花税 + 滑点,**四年累计侵蚀 5.72% 的收益**;同一个策略按“最好”和“最差”成交价跑,**累计收益差距高达 6.64%**。这些不是抽象的“差距”,是真金白银从你的账户里走掉。
**这一节投资对象切换为美股 ETF(SPY、QQQ、GLD)。** 前两个实验沿用 A 股 ETF(沪深300、纳指100、黄金),和第 2 章、第 6 章保持一致。从这一节开始换成美股 ETF,因为 open-xquant 内置了 Alpaca 美股模拟交易接口,可以让你亲手体验提交订单、等待成交、查看回报。美股和 A 股面对的执行问题本质相同:取整偏差、滑点、成本。区别是 A 股有 100 股整手约束和印花税,某些问题会更明显。
### A 股桥接:为什么用美股练手,结论却双向适用
你接下来要用的是 Alpaca 美股模拟盘。选它是因为接入简单,注册即可使用,不需要开户入金。但**美股看到的所有执行问题,A 股一样存在,而且大多更明显**:
1. **取整偏差** —— A 股 100 股整手,偏差比美股大得多(美股可买 1 股)
2. **价格滑点** —— A 股同样存在,开盘竞价和收盘竞价时段尤其明显
3. **交易成本** —— A 股还要额外付印花税(卖出 0.05%)
4. **执行落差** —— 同样的策略,A 股的执行落差通常比美股更大
**“用美股模拟盘练手,再把检查方法搬回 A 股操作”是这一节的实操路径**。美股 Alpaca 让你在不用动用真实资金的情况下亲手感受成交。等你回到 A 股券商 APP 手动操作时,建议你:
- 先用 `generate_orders()` 生成订单清单(第 1 步的流程),不要凭感觉手敲数量
- 定期对比回测净值和实际账户净值,监控执行落差是否在预算内
- 如果执行落差持续放大,回到本节诊断三问:是成本问题,还是执行时机问题?
现在,我们让策略接入 Alpaca 模拟交易平台跑一遍。然后把回测净值和模拟交易净值画在同一张图上,差距一目了然。
**关键概念:** Engine 和 Strategy 完全不变,只是把数据源和交易接口换了。回测用 SimBroker + LocalMarketDataProvider,模拟交易用 LiveBroker + AlpacaMarketDataProvider。策略逻辑一行都不用改。
这就像你做菜——同一份菜谱,在家里用燃气灶做和在户外用柴火做,食材和步骤一样,但火候控制不同,最终味道总有差异。策略是“菜谱”,交易接口是“灶”。
### 动手实验 3Alpaca 模拟交易
为了体验交易摩擦,本节使用美股 ETF(SPY、QQQ、GLD)。这是因为 open-xquant 内置了 Alpaca 美股模拟交易接口,可以让你亲手感受提交订单、等待成交、查看回报,而不是只在历史数据上模拟成交。
要使用 Alpaca 模拟盘,你需要:
1. 在 alpaca.markets 免费注册一个账号
2. 在账号设置中生成 API Key 和 Secret Key
3. 在项目根目录创建 `.env` 文件,填入:
```
ALPACA_API_KEY=你的Key
ALPACA_SECRET_KEY=你的Secret
```
4. 把上面的步骤告诉 AI 编程工具,它会帮你完成配置
我们一起把这份 spec 写出来。这是第 7 章最有挑战的一份:同时要让有 Alpaca 账号的读者体验模拟交易,让没账号的读者能完成完整流程,还不能把 API key 写进任何文件。重点看三件新东西:**API key 三重防线**、**A / B-1 / B-2 三段切分**,以及**外部依赖的兜底设计**。
#### 第一、二段:上下文和任务描述
> **上下文**:在 `q7-execution.ipynb` 中已有第 1 步的订单生成(A 股 ETF)+ 第 2 步的成交价压力测试(A 股 ETF)。前两步暴露了取整偏差、滑点和成本,但都是“估算”。现在切换为美股 ETF(SPY/QQQ/GLD),接入 Alpaca 模拟交易平台让策略跑一遍。
>
> **任务描述**:完成三段不可省略的演示。A 部分:SimBroker 回测基准;B-1 部分:Alpaca 数据源做历史回测,对比数据源差异;B-2 部分:LiveBroker 提交订单到 Alpaca 模拟盘。无 Alpaca 账号时提供替代方案。
> 📌 **要点**:A 股 → 美股的投资对象切换要诚实交代。open-xquant 当前只内置 Alpaca 美股 paper trading,国内券商接口尚未集成。spec 上下文段必须说清这个限制,避免读者误解“只有美股才有模拟交易”。
#### 第三段:任务要求
要求段把 API key 处理设计成三重防线:环境变量、try/except 兜底、验证段安全断言。一道防线漏了还有两道兜着。
> **任务要求**
>
> 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. **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”。第 7 章是读者第一次面对真实可花钱的接口,即使是 paper trading,养成“绝不硬编码 key”的习惯也很关键。这三层缺一不可,可作为“涉外部 API 的 spec”的安全模板。
> 📌 **要点****A / B-1 / B-2 三段切分**承接 spec-02 的“积分式归因”。A 部分建立基准,B-1 部分揭示数据源差异,B-2 部分展示模拟下单。让读者看到两层执行落差(数据层 + 成交层),而不是一笔糊涂账。
> 📌 **要点**:**外部依赖的兜底设计**是第 7 章唯一的“双轨 spec”。第 1 章到第 6 章都假设读者能跑通核心流程,但第 7 章必须接受“少数读者没 Alpaca 账号”的现实。无账号路径用 NEXT_OPEN + 滑点的 SimBroker 模拟,让没账号的读者也能完整跑完这一节。涉及外部账号、网络或付费服务的 spec 应推广这种设计。
#### 第四段:验收标准
> **验收标准**:有 Alpaca 时输出必须含三部分(SimBroker 基准累计收益 / 数据源对比图 + Alpaca IEX 累计收益 / LiveBroker 模拟交易演示标题 + 当前持仓 + 调整持仓计划 + 最终持仓);无 Alpaca 时显示模拟交易对比图;`result_live` 必须赋值供第 4 步使用。
完整示例 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 里应该出现了回测和模拟交易的对比结果。
实验分三部分:A 部分用收盘价 + 佣金跑标准回测,作为“理想基准”;B-1 部分把价格数据从 YFinance 换成 Alpaca IEX,看“数据源不同”本身能带来多大差异;B-2 部分用 **LiveBroker** 把订单发送到 Alpaca 模拟盘。SimBroker 在本地模拟成交,LiveBroker 等待 Alpaca 返回成交回报。即使是 Paper Trading(模拟盘),订单也经过 Alpaca 的撮合环境,成交价来自市场报价。
### 运行结果
先看数据源差异,如表 7-6 所示。
**表 7-6 YFinance vs Alpaca IEX 同期数据源回测差异**
| 指标 | 数值 |
|------|------|
| SimBroker 回测(YFinance 数据) | 累计收益 118.59%184 笔交易 |
| Alpaca IEX 数据 + SimBroker 回测 | 累计收益 112.66%187 笔交易 |
| 数据源差异 | 5.93% |
两个数据源的净值曲线对比如图 7-4 所示。
![同一策略,不同数据源](images/q7-execution/04-data-source-comparison.png)
先看上图——蓝色线(YFinance 数据)和红色线(Alpaca IEX 数据)走势相似,但终点差了将近 6 个百分点。下图的执行落差从 2022 年开始持续扩大。
同一个策略、同一个 SimBroker,仅仅因为价格数据来自不同供应商,结果就差了 5.93%。你以为回测结果是“确定的”,其实连数据本身都有不确定性。不同数据源的收盘价可能有微小差异(四舍五入方式、调整方法、采样时间点),四年累积下来就是这个数字。
再看 LiveBroker 的模拟订单提交。这次运行时账户里已经有了之前建好的持仓(GLD 83 股、QQQ 41 股、SPY 51 股),策略只需要做小幅调整:卖出 1 股 QQQ。
订单提交后,由于市场已关闭,订单状态为“accepted”(已接受,等待下次开盘执行)。这就是交易执行的常态:你不能随时成交,要等市场开门。如果你在开盘时间运行,订单通常会在几秒内成交,成交价和预期价格之间的差距就是滑点。
### 结果解读
这一步揭示了两层执行落差。
**第一层:数据源差异。** 同一个策略、同一个 SimBroker,仅仅因为价格数据来自不同的供应商(YFinance vs Alpaca IEX),回测结果就不一样。不同数据源的收盘价可能有微小差异(四舍五入、调整方式、采样时间点不同)。你以为回测结果是“确定的”,其实连数据本身都有不确定性。
**第二层:成交执行差异。** 用 LiveBroker 向 Alpaca 模拟盘提交订单后,成交价和你预期的收盘价总是不一样。这就是**执行落差(Implementation Shortfall**:从“策略决定买”到“真正买到”之间的距离。差距来自三个方面:
1. **成交价偏差**——回测用收盘价,模拟交易或真实交易在某个时刻成交,价格已经变了
2. **交易成本**——佣金、印花税(A 股)
3. **市场冲击**——你的买单本身推高了价格(大资金才明显,小资金可忽略)
关键认知:**执行落差像每次下单都要交一笔过路费。它不是 bug,而是从回测走向交易执行必然会出现的成本。** 关键是知道这笔费用有多大、有没有突然变贵。你需要做的不是消除它(不可能),而是监控它的浮动是否在合理范围内,并在评估策略时预留这个空间。
还有一个重要的设计细节值得注意:**Engine 和 Strategy 一行都没改。** 从回测切换到模拟交易,只是换了数据源和交易接口。策略逻辑和执行环境完全解耦。这意味着你在回测中验证过的策略,可以接入交易接口,不用重写策略代码。
差距看到了,但具体哪笔交易差得多、哪笔差得少?
---
## 7.4 怎么知道执行得对不对
第 3 步让你看到了整体的执行落差。但“整体差 6%”这个数字不够。你还需要知道:
- 具体哪笔交易差得最多?
- 是特定标的的问题,还是特定日期的问题?
- 大部分交易的执行质量如何?
这就是**执行报告**的作用:逐笔对账,找到问题。
你可能觉得“对账”这个词很陌生,但你每天都在做类似的事——收到外卖后核对订单:点了三道菜,到了没有?价格和下单时一样吗?少了一道菜你会找客服。执行报告就是交易世界的“对账单”。
### 动手实验 4:执行报告
我们一起把这份 spec 写出来。这是第 7 章的收尾 spec。前三份让读者看到执行落差整体上有多大,这份让读者**逐笔对账**,找到落差的具体来源。重点看两件新东西:**反平均设计**(最贵的一笔比平均更值钱)和**模拟交易 vs 回测的对照表**(双图各回答一个问题)。
#### 第一、二段:上下文和任务描述
> **上下文**:在 `q7-execution.ipynb` 中已有第 1 / 2 / 3 步的产出,特别是 `result_sim` 和 `result_live` 两个结果。第 3 步让读者看到了整体的执行落差,但“整体差 6%”这个数字不够,需要知道具体哪笔差得最多、是标的问题还是日期问题。
>
> **任务描述**:用 `ExecutionReport` 逐笔对比回测和模拟交易的成交,分析执行质量分布。
> 📌 **要点**:第 7 章的 spec 链同第 1 章一样讲究“接续型 spec”。`result_live` 不论走 spec-03 的 B-2 部分(Alpaca)还是兜底路径都会赋值。spec 的上下文段必须把上游变量名、形状、来源全列出,AI 才知道直接用哪份数据,不会重新跑回测。
#### 第三段:任务要求
任务要求段是这份 spec 的核心。五步走完一条诊断流程:逐笔对比 → 汇总 → 最贵的一笔 → 分布图 → 桥接 A 股。
> **任务要求**
>
> 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%。读者只看汇总统计会得到错误结论,必须看尾部。这是第 5 章、第 6 章已建立的“分布优于均值”原则在执行场景的延续。
> 📌 **要点**:**模拟交易 vs 回测的对照表**用双图各回答一个问题。直方图回答“整体怎样”,看分布形状;箱线图回答“问题在哪只标的”,看哪个箱子异常多。两个视角不可互相替代,缺一不可。
> 📌 **要点**:桥接 A 股要克制。第 7 章的主线是认知(了解执行落差),不是工程(A 股自动化交易)。spec 用文字桥接而非要求重跑,避免给读者塞超出本章范围的负担。国内券商 API 是另一套基础设施。
#### 第四段:验收标准
> **验收标准**:逐笔对比表 ≥ 20 行 + 汇总统计(含匹配交易数和平均滑点)+ 最贵一笔具体数值 + 直方图 + 箱线图 + 桥接 A 股的操作建议。
完整示例 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` 接收两个成交记录列表:回测的和模拟交易的。它会按日期和标的匹配,逐笔对比价格差异。
### 运行结果
先看逐笔对比表(前 20 笔,节选),如表 7-7 所示。
**表 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 |
| 2021-04-05 | GLD | BUY | $161.92 | $161.91 | -0.01% | 19 | 21 | +2 |
| 2021-05-04 | GLD | SELL | $166.58 | $166.56 | -0.01% | 26 | 30 | +4 |
| ... | | | | | | | | |
大部分交易的滑点很小(0.01%-0.02%),但股数差异时有出现——因为两个数据源的价格不同,`generate_orders()` 计算出的股数也不一样。
汇总统计如表 7-8 所示。
**表 7-8 回测 vs 模拟交易成交汇总统计**
| 指标 | 数值 |
|------|------|
| 总交易笔数 | 194 |
| 匹配交易 | 177(回测和模拟交易都有) |
| 仅回测有 | 7 |
| 仅模拟交易有 | 10 |
| 平均价格滑点 | +1.6199% |
| 总手续费差异 | $-17.33 |
177 笔匹配交易,平均滑点 1.62%。还有 7 笔只在回测中出现、10 笔只在模拟交易中出现。这是因为两个数据源的价格差异导致策略在不同时间触发调整持仓。
最贵的一笔:日期 2021-02-02,标的 SPY(标普500ETF),方向 BUY,回测成交价 $356.17,模拟交易成交价 $381.39,价格滑点 +7.08%。
一笔交易的滑点高达 7.08%。这是第一笔交易,回测和模拟交易的起始价格差距最大。后续交易的滑点会小得多。
执行质量分布如图 7-5 所示。
![执行质量分布:滑点直方图和按标的分组的箱线图](images/q7-execution/05-execution-quality.png)
先看左图的直方图。橙色虚线标注了平均滑点位置。大部分交易的滑点集中在 0-2% 之间,但右侧有一条长尾——少数几笔交易的滑点特别大。
再看右图的箱线图。三只 ETF 的滑点分布差异不大,但每只都有几个“异常值”(箱子外面的点)。这些就是交易执行里的尾部风险。
执行质量统计如表 7-9 所示。
**表 7-9 美股模拟盘执行质量统计**
| 指标 | 数值 |
|------|------|
| 总交易笔数 | 177 |
| 平均滑点 | +1.620% |
| 中位数滑点 | +0.973% |
| 最小滑点 | -0.112% |
| 最大滑点 | +7.082% |
| 滑点超过 0.5% 的交易 | 99 笔(56% |
### 结果解读
执行报告是策略接入交易接口后最重要的“对账”工具。
滑点直方图讲了一个重要的事实:**大部分交易的执行质量是正常的,滑点很小。但总有少数几笔偏差特别大。** 这就是交易执行里的“尾部风险”。和策略收益的尾部风险一样,你无法消除它,但你需要知道它的存在。
箱线图按标的分组,能帮你发现:是某只 ETF 的执行质量特别差(可能流动性不够),还是所有标的都差不多?如果某只标的的滑点持续偏大,你可能需要考虑换一只流动性更好的标的,或者调整下单时机。
**交易执行后的习惯:** 每次调整持仓后,生成执行报告对一遍账。不是看一眼净值就完了,要逐笔检查,发现异常及时处理。这和开车一样:你不会只看速度表,还要看油量、水温、胎压。
---
## 7.5 本章总结
四步下来,我们从四个角度回答了同一个核心问题:回测结果能直接用吗?
第 1 步:订单生成 → 权重只能买整数股 → 取整偏差 + 现金残余 → 第 2 步:成交价压力测试 → 五种价格假设差距巨大 → 执行不确定性 → 第 3 步:Alpaca 模拟盘 → 数据源不同结果就不同 → 执行落差是常态 → 第 4 步:执行报告 → 逐笔对账 → 大部分正常,少数偏差大。
从今以后,从回测走向真实交易之前,你可以用这套流程管理执行风险,整理成检查清单如表 7-10 所示。
**表 7-10 从回测走向真实交易的执行风险检查清单**
| 检查项 | 怎么做 | 好的信号 | 坏的信号 |
|--------|--------|----------|----------|
| 取整偏差 | 用 `generate_orders()` 提前计算 | 偏差 < 0.5% | 偏差 > 2%,资金太小或 lot_size 太大 |
| 成交价敏感性 | 用五种 FillPriceMode 跑压力测试 | 五种模式差距小 | 差距大,策略对执行时机敏感 |
| 交易成本 | 逐层叠加佣金、印花税、滑点 | 成本占年化收益 < 20% | 成本吃掉大部分收益 |
| 执行落差 | 对比回测净值和模拟交易净值 | 落差稳定且可预期 | 落差持续放大 |
| 执行质量 | 生成执行报告逐笔对账 | 滑点集中在 0 附近 | 频繁出现大滑点 |
**一个更深的认知:回测收益不等于真实交易收益。** 中间隔着取整偏差、滑点、佣金、印花税。在评估策略时,回测收益减去预估的执行落差,才是你的合理预期。
### 策略进化路径
第 6 章产出:通过检验的策略,信心满满 → 第 1 步:权重转订单(取整偏差 + 现金残余)→ 第 2 步:五种成交价压力测试(滑点 + 成本全景)→ 第 3 步:回测和模拟交易对比(亲眼看到执行落差)→ 第 4 步:执行报告(逐笔对账,找到差距来源)→ 第 7 章产出:从回测到交易执行的完整流程 + “执行有成本”的认知
### 概念速查表
本章涉及的核心概念汇总如表 7-11 所示,方便随时回查。
**表 7-11 第 7 章核心概念速查**
| 概念 | 含义 | 类比 |
|------|------|------|
| 取整偏差 | 因为只能买整数股,实际权重和目标权重之间的差距 | 预算 35% 交通费,但车票只有整数价,实际花的不是精确的 35% |
| 现金残余 | 凑不成整股而闲置在账户里的零头资金 | 买完所有东西后口袋里剩的零钱 |
| 成交价模式 | 回测中假设订单在什么价格成交 | 网上标价和到店实际付款价的区别 |
| 执行不确定性 | 最好和最差成交价之间的差距区间 | 同一航班,不同时间买的票价差异 |
| 滑点 | 下单时价格因市场波动偏离预期的部分 | 菜市场问价 5 块,掏钱时变 5.1 了 |
| 执行落差 | 回测收益和模拟交易收益之间的差距 | 菜谱上说 30 分钟做好,实际花了 45 分钟 |
| LiveBroker | 把订单发送到交易接口的工具 | 从纸上订单清单到把订单递给交易系统 |
| 执行报告 | 逐笔对比回测成交和模拟交易成交的对账单 | 收到外卖后核对订单:点了三道菜,价格对不对,少没少 |
### 本章核心认知
走完四步实验后,本章最值得带走的核心认知如表 7-12 所示。
**表 7-12 第 7 章核心认知**
| 认知 | 来源 |
|------|------|
| 回测权重是理想值,实际只能买整数股 | 第 1 步实验 |
| 资金量越小、整手约束越大,偏差越大 | 第 1 步实验 |
| 同一个决策,执行时机不同,结果差距很大 | 第 2 步实验 |
| 回测的收盘价假设是最乐观的 | 第 2 步实验 |
| 交易成本(佣金+滑点+印花税)持续侵蚀收益 | 第 2 步实验 |
| 回测和模拟交易永远有差距:执行落差 | 第 3 步实验 |
| 策略逻辑和执行环境可以完全解耦 | 第 3 步实验 |
| 接入交易接口后要养成逐笔对账的习惯 | 第 4 步实验 |
| 大部分交易正常,少数几笔偏差大:执行的尾部风险 | 第 4 步实验 |
### 带走的问题
我们已经走完了从选标的(第 2 章)到执行交易(第 7 章)的完整流程。回测世界和真实交易世界之间的桥梁搭好了。
但策略跑起来之后,新的问题出现了:
- 上个月赚了 3%,这个月亏了 5%——这是正常波动,还是策略出了问题?
- 什么时候应该停下来,什么时候应该继续坚持?
- 发现问题后,怎么系统性地改进,而不是推翻重来?
**策略跑起来以后,怎么监控和改进?** → 第 8 章
到这里,你完成了执行交易阶段。第 7 章把“做”和“看”落到了真实交易的边界:“做”是把 35% 的权重翻成 8100 股的订单,把 NEXT_OPEN 当成更诚实的成交假设,再用 LiveBroker 把订单递给 Alpaca 模拟盘;“看”是用执行报告逐笔对账,看清那 1.62% 的平均滑点和 7.08% 的最贵一笔藏在哪里。下一章会继续练“看”和“疑”:策略跑起来以后,怎么判断它是正常波动,还是出了问题。
> 本章所有代码的可运行版本见配套仓库的 [`q7-execution/notebooks/q7-execution.ipynb`](https://github.com/xingwudao/xquant-learning/blob/main/q7-execution/notebooks/q7-execution.ipynb)