Files
xquant-beginner/book/q4-when-to-trade.md
T

459 lines
36 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 第四章:给策略加规则:调仓、止损、止盈
第三章我们用三种信号解决了“买多少”——等权、风险平价、动量排名,各有各的性格。但有个细节你可能没注意:不管用哪种信号,策略都是每 10 个交易日自动调仓一次——重新把持仓比例调回目标权重。为什么是 10 天?5 天行不行?一个月呢?
更关键的问题是:**如果在两次调仓之间,某只 ETF 突然暴跌 20%,你只能干等到下一个调仓日才能反应。** 这合理吗?
> **🧭 本章边界**:章名虽然叫“什么时候买卖”,但本章**不做入场择时**——什么时候第一次进场、用什么信号判断“该买了”,那些是第三章信号已经回答的事。本章聚焦的是**已经持仓之后**的三个执行节奏:多久做一次再平衡(4.1)、跌到什么程度先撤(4.2)、涨到什么程度走人(4.3)。
>
> **🧱 一个新元概念:规则(Rule)。** 第三章信号说的是“想怎么分钱”——是策略的大脑。本章引入的是**规则**——策略的手脚:感知持仓状态(涨了多少、跌了多少、上次调仓多久了),按预设条件触发动作。信号管“想怎么动”,规则管“什么时候动手”。两者分工明确,缺一不可。从本章起,你会反复看到 `RebalanceFrequencyRule` / `StopLossRule` / `TakeProfitRule` 这些 Rule 类——它们就是规则的具体实现。
### 路线图
我们正在走一条完整的策略构建之路:
**选什么标的(第二章 已完成)→ 每个买多少(第三章 已完成)→ 什么时候买卖(第四章 本章)→ 怎么验证有效(第五章)**
**这一章你仍在飞轮的“组合层”,主练“做”——把“什么时候动手”也写成可重复执行的规则。** 本章解决“什么时候买卖”,分三步揭示三个认知,最后你会发现:不是所有“听起来合理”的规则都有效。三节的问题与方法对照如表 4-1 所示。
**表 4-1 第四章三节问题与方法对照**
| 节 | 问题 | 方法 |
|----|------|------|
| 4.1 | 调仓频率怎么选? | 周/月/季度对比 + 引入交易成本 |
| 4.2 | 亏了要不要先跑? | 止损规则 |
| 4.3 | 赚了要不要走? | 止盈规则 |
本章使用第三章的风险平价(RiskParity)作为基础信号——它始终持有全部 3 只 ETF,方便我们观察不同规则的效果。
(操作流程见前言“怎么使用这本书”。每一节按相同流程:阅读 → 复制 spec 给 AI → 看结果与解读。)
---
## 4.1 调仓频率有多重要?
第三章每 10 个交易日调一次仓——这个“10”是拍脑袋定的。调得更勤(每周)会更好吗?调得更懒(每季度)会更差吗?
直觉告诉我们:调得越勤,反应越快,效果应该越好。真的吗?
### 动手实验 1:不同调仓频率对比
本节用到两个新模块:`RebalanceFrequencyRule`(设定多少天调仓一次)和 `PercentageFee`(模拟交易手续费)。
我们一起把这份 spec 写出来。这次重点看两件新东西:**参数化扫描怎么列**,以及**辅助函数封装作为后续 spec 的接口**。
#### 起草上下文 + 任务
> **上下文**:本章探讨“什么时候买卖”。第三章用了三种信号(等权、风险平价、动量排名)。本章选择风险平价(RiskParity)作为基础信号——它始终持有全部 3 只 ETF,方便我们观察止损和止盈规则的效果。第三章每 10 个交易日调一次仓,但为什么是 10 天?调得更勤或更懒,结果会怎样?
>
> **任务**:在 notebook `q4-when-to-trade.ipynb` 中创建代码,对比不同调仓频率的效果,并首次引入交易成本。
#### 起草要求:参数化扫描 + 辅助函数封装
要求段是这份 spec 的灵魂——把“对比 4 种频率”和“先后跑两轮成本”两件事拧成一份 spec。
> **要求**
>
> 1. 先读 oxq 源码:`PercentageFee` / `SimBroker` / `RebalanceFrequencyRule` / `RiskParityOptimizer` / `Engine` / `Strategy`
> 2. 复用第三章的 SYMBOLS、START、RiskParity 信号等基础配置
> 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)
> 7. 分析按实际数据动态描述(不要硬写“更高”或“更低”)
> 📌 **要点**:参数化扫描的关键是把“被对比的维度”列成一个数组(`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 链的核心架构。**
#### 起草结果呈现
> **结果呈现**
>
> 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,弹窗选「允许」。
AI 助手执行完毕后,你的 notebook 里应该出现了两张对比表和一张净值图。我们来解读结果。
四种频率分别对应每周(5 个交易日)、每两周(10)、每月(21)、每季度(63)。代码跑了两轮回测:第一轮不含成本,第二轮含成本(万分之一佣金,最低 5 元)。同样的策略,唯一的差别是有没有扣手续费。
**交易成本(Transaction Cost** ——每次买卖都要付的费用,主要是付给券商的**佣金**和(部分市场)付给政府的**印花税**。本章回测里只模拟了佣金部分(万分之一,最低 5 元),没有加印花税——实际交易的成本会比回测里更高。记住这一点:**回测里的成本是乐观估计,现实只会更贵。** A 股 / 美股 / 港股具体费率有差别,详情见章末「拓展阅读」。
在之前的第二章和第三章里,我们的回测都没有考虑交易成本,就像在一个“免手续费”的理想世界里做实验。从现在开始,我们把手续费加回来,看看现实世界会怎样。
### 实验结果
不含成本时四种频率的对比如表 4-2 所示。
**表 4-2 不含交易成本时四种调仓频率的对比**
| 频率 | 累计收益率 | 年化波动率 | 最大回撤 | 夏普比率 | 交易次数 |
|------|-----------|-----------|---------|---------|---------|
| 5 | 101.22% | 10.90% | -12.32% | 1.34 | 744 |
| 10 | 106.80% | 11.19% | -12.44% | 1.36 | 372 |
| 21 | 95.22% | 11.35% | -14.33% | 1.24 | 177 |
| 63 | 105.56% | 11.85% | -14.79% | 1.28 | 57 |
加上手续费后的对比如表 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 元 |
| 21 | 91.93% | 11.35% | -14.50% | 1.21 | 177 | 2,052 元 |
| 63 | 104.09% | 11.86% | -14.85% | 1.27 | 57 | 926 元 |
含成本下的净值曲线对比如图 4-1 所示。
![调仓频率对比(含交易成本)](images/q4-when-to-trade/01-frequency-comparison.png)
### 读懂这些结果
先看表 4-2。四种频率的夏普比率差异不大,从 1.24 到 1.36——不含成本时,每 10 天调仓的夏普比率最高(1.36)。
再看表 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 笔。
最后看净值图。四条线走势相似,终点差异不算大——频率的选择有影响,但不是决定性的。更重要的发现是:**交易不是免费的**。每次调仓——不管是买还是卖——都要付佣金、交税(根据政策)。在不含成本的“理想世界”里看起来差不多的策略,加上成本后差距就拉开了。
**数据说了算:频率的选择是个权衡——反应速度 vs 交易成本。**
但频率不是最大的问题。想象一下:你刚调完仓,第二天某只 ETF 暴跌 15%。下一次调仓要等好几天——这段时间你只能眼睁睁看着亏损扩大。能不能加一个“紧急出口”——不管调仓日到没到,亏到一定程度就先跑?
---
## 4.2 亏了能不能先跑?
定期调仓的硬伤很明显:两次调仓之间如果市场暴跌,你只能干等。
直觉告诉我们:亏了先跑(卖出止损),总比硬扛着不动好。但“跑”也不是免费的——每次卖出再买回来,都要付手续费。到底值不值?先猜后验。
### 动手实验 2:止损规则对比
我们一起把这份 spec 写出来。这次重点看两件新东西:**累积接续怎么把上一份 spec 的最优结果流到这一份**,以及**为什么这件事必须 Rule 才能做**。
#### 起草上下文 + 任务
> **上下文**:在 `q4-when-to-trade.ipynb` 中已有 RiskParity 策略在不同调仓频率下的回测结果,学员已看到交易成本的影响,理解了“交易不是免费的”。当前问题:定期调仓有个硬伤——中间暴跌只能干等。能不能加个保护?
>
> **任务**:在 notebook 中新建代码单元格,给策略加上止损规则,对比不同止损阈值的效果。
#### 起草要求:累积接续 + Signal vs Rule
止损是 Rule 而不是 Signal——这是 oxq 框架的关键设计,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())`
> 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 链就能跟着数据自动跑。
> 📌 **要点**:信号(Signal)和规则(Rule)分工不同——这是 oxq 框架的认知基石,后面 q5 / q7 调度多种 Rule 都靠它。spec 要求第 2 条把这层区分写进去,目的是让 AI 在 notebook 里也输出这段解释,给学员第二次曝光。**核心概念应该跨 spec / notebook / book 三处复述,不能只指望 book markdown 单方面承担。** **对你的练习**:以后你 spec 里出现一个不熟的核心概念,要求 AI 在生成的 notebook 里也用一两行打印解释一下——这样你跑代码的时候,notebook 自己就成了你的第二份“小词典”。
#### 起草结果呈现
> **结果呈现**
>
> 1. StopLossRule 工作原理说明(挂单机制 + Signal vs Rule 的区别)
> 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,弹窗选「允许」。
AI 助手执行完毕后,你的 notebook 里应该出现了止损规则的工作原理说明、一张对比表和一张带回撤标注的净值图。我们来解读结果。
本节引入了两个新概念。
**止损(Stop Loss** ——预设一个底线:如果从买入价跌了 X%,自动卖出。就像你去赌场之前跟自己说“今晚最多输 500 块,输到了就走人”——止损就是这个“输到了就走”的自动化版本。“活着才能赚钱”——保住本金,才有机会等到下一次上涨。
**挂单(Pending Order** ——不是立即交易,而是“预设条件,满足了再执行”。止损规则在你持有某只 ETF 时,自动向券商挂一个条件单:“如果跌到某个价格以下,就帮我卖掉。”你不用每天盯盘,系统帮你盯着。
代码从 Step 1 的结果中自动选出含成本后夏普比率最高的频率(10 天),然后在这个基准上叠加止损规则。四组对比:无止损、5% 止损、10% 止损、15% 止损。
这里有一个关键的新概念。**规则(Rule)** ——策略的“执行层”。第三章的信号(Signal)负责判断“想怎么分”,但信号不知道你的持仓情况——它只看市场数据(波动率、动量),不知道你什么时候买的、买了多少钱、现在是赚还是亏。规则不同——规则能感知持仓状态:知道“我什么时候买的、现在亏了多少”。止损就是这种“看持仓”的判断,只有 Rule 能做。
### 实验结果
止损规则的对比结果如表 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 元 |
| 10% | 100.32% | 11.02% | -13.08% | 1.32 | 378 | 3,270 元 |
| 15% | 95.45% | 11.14% | -14.99% | 1.26 | 374 | 3,071 元 |
净值曲线对比与最大回撤标注如图 4-2 所示。
![止损规则对比(含交易成本)](images/q4-when-to-trade/02-stop-loss-comparison.png)
### 读懂这些结果
先看图 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%——分母(波动率)下降抵消了分子(收益略降)的影响。
为什么三种止损都没把回撤压住?这不是 bug,而是止损在**持续下跌的市场**中的副作用——**锯齿效应**。
2022 年市场一路缓慢下跌,不是一天暴跌 10%,而是今天跌一点、明天跌一点,连着跌好几个月。5% 止损在这种环境下反复触发,形成一个循环:持仓 —— 跌 5% 触发止损卖出(亏损 + 手续费)—— 调仓日买回来(手续费)—— 继续跌 5% 又触发止损(亏损 + 手续费)—— 调仓日又买回来......
每一轮“卖-买”付两笔手续费,但并没有真正躲开下跌——因为买回来之后市场继续跌。止损只是让你“分批亏”而非“一次亏到底”,额外的交易成本累积起来,反而让总回撤略微加深。从交易次数也能看出端倪:5% 止损多了 17 笔交易,多花了 881 元手续费——这些都是锯齿效应的代价。
**止损对一次性大跌有效(跌到位就跑),对持续缓慢下跌有害(反复卖出又买回来)。在 2021–2025 这段持续震荡下跌的数据里,三种阈值都被锯齿效应反复收割——这不代表止损永远无效,而是说在这个市场环境 + 这套基准上,紧的止损没占到便宜。**
**数据说了算:止损“保护本金”的逻辑没错——夏普比率确实因为波动率下降而抬了一点;但“保护”在这段数据上的含义是“压低波动”而不是“压低回撤”。阈值怎么选、保护的是什么,要看数据怎么说。**
止损解决的是亏损方向。涨的方向呢——下一节继续看。
---
## 4.3 赚了要不要走?
亏的方向有止损兜底了。涨的方向呢——市场给你 10%,你拿着不动,要不要主动伸手去摘?万一涨上去又跌回来,岂不是白赚一场?
直觉告诉我们:赚了就走(卖出止盈),落袋为安,总比看着利润蒸发好。真的吗?先猜后验。
### 动手实验 3:止盈规则对比
我们一起把这份 spec 写出来。这次 spec 的结构和前两份**完全一样**——一样的对比表、一样的净值图、一样的辅助函数。我们要演示的是:**同样的 spec 模板,不同的规则,结果可能截然不同**——结果是什么方向,跑完再揭晓。
#### 起草上下文 + 任务
> **上下文**:在 `q4-when-to-trade.ipynb` 中已有不同调仓频率的对比结果、止损规则的效果(保护了本金,但有成本)。当前问题:止损帮你控制了亏损。反过来——赚了一定程度,要不要主动卖掉、落袋为安?
>
> **任务**:在 notebook 中新建代码单元格,给策略加上止盈规则,对比不同止盈阈值的效果。
#### 起草要求:累积接续到第三层 + 微观证据
这份 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())`
> 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 在同一回测里共存
> 7. 分析按实际数据动态描述,要点出止损 vs 止盈的对照(止损截断亏损,逻辑清晰;止盈截断利润,越紧伤害越大),并收尾“让利润奔跑,截断亏损”这句行业老话
> 📌 **要点**:累积接续到第三层——`BEST_FREQ`spec-01+ `BEST_SL`(本份现场算)+ 4 组止盈阈值。每加一层规则,都是在前一层最优配置之上加。**接续型 spec 链有两种模式:简单复用前一份的产出,以及把前 N 份的最优结果层层叠加。后者就是“累积接续”,适合“逐步给策略加规则”这类研发场景。**
> 📌 **要点**:这是一份**对照实验 spec**——前两份 spec 给学员造的预期是“加规则就会变好”,这一份要做的是把同样的对比表、同样的净值图、同样的辅助函数复用一次,只换一种规则,看数据怎么说。**写对照实验 spec,关键是不要在 spec 里预先暗示结果方向,让数据自己说话。** **对你的练习**:写一份你不知道答案的对照实验 spec 时,反复检查任务段——里面有没有“应该哪个赢”的暗示词(比如“展示 X 的优势”、“证明 Y 有效”)。把这类词都改成中性的“对比 / 评估”,数据才能给你一个干净的答案。这是量化研发里最重要的一类 spec:用数据告诉自己有些规则该不该用。
> 📌 **要点**:要求第 6 条让学员**亲眼看到** stop / limit / market 三种 order\_type 的交易记录前 10 笔。前面所有对比都是宏观指标,这 10 行把规则机制下沉到单笔交易级别。**宏观指标 spec 应该配套微观证据——光看一个总数字不够,要把机制的痕迹露出来给学员看。**
#### 起草结果呈现
> **结果呈现**
>
> 1. TakeProfitRule 工作原理说明
> 2. 指标对比表(4 行)
> 3. 净值曲线对比图
> 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,弹窗选「允许」。
AI 助手执行完毕后,你的 notebook 里应该出现了止盈规则的工作原理说明、一张对比表、一张净值图和一段交易记录。我们来解读结果。
**止盈(Take Profit** ——预设一个目标:如果从买入价涨了 X%,自动卖出锁定利润。就像你在闲鱼上卖东西,挂一个“到了这个价就成交”的价格——止盈就是给你的持仓挂一个“到了就卖”的目标价。和止损一样,也是通过挂单机制实现的:你不用盯盘,系统帮你盯着。
基准配置是 Step 1 和 Step 2 选出的最优组合:每 10 天调仓 + 5% 止损。在这个基础上,分四组测试:不止盈、10% 止盈、20% 止盈、30% 止盈。止损和止盈可以同时生效。
### 实验结果
止盈规则的对比结果如表 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 元 |
| 20% | 83.47% | 10.04% | -13.25% | 1.26 | 409 | 5,215 元 |
| 30% | 104.32% | 10.23% | -13.25% | 1.45 | 396 | 4,306 元 |
净值曲线对比如图 4-3 所示。
![止盈规则对比(含交易成本)](images/q4-when-to-trade/03-take-profit-comparison.png)
交易记录(10% 止盈,前 10 笔)如表 4-6 所示。
**表 4-6 10% 止盈策略的前 10 笔交易记录**
| 日期 | 标的 | 方向 | 数量 | 成交价 | 订单类型 | 手续费 |
|------|------|------|------|--------|---------|--------|
| 2021-02-01 | 沪深300ETF | BUY | 5,221 | 4.8952 | market | 25.56 |
| 2021-02-01 | 纳指100ETF | BUY | 32,274 | 0.8620 | market | 27.82 |
| 2021-02-01 | 黄金ETF | BUY | 12,204 | 3.8200 | market | 46.62 |
| 2021-02-22 | 沪深300ETF | BUY | 164 | 5.0562 | market | 5.00 |
| 2021-02-22 | 纳指100ETF | BUY | 32 | 0.8974 | market | 5.00 |
| 2021-02-22 | 黄金ETF | SELL | 262 | 3.6750 | market | 5.00 |
| 2021-02-26 | 黄金ETF | SELL | 11,942 | 3.6130 | market | 43.15 |
| 2021-03-08 | 沪深300ETF | SELL | 115 | 4.5957 | market | 5.00 |
| 2021-03-08 | 纳指100ETF | SELL | 241 | 0.8378 | market | 5.00 |
| 2021-03-08 | 黄金ETF | BUY | 12,453 | 3.5190 | market | 43.82 |
### 读懂这些结果
这个结果可能出乎你的意料——**多数止盈阈值降低了收益率,但 30% 止盈反而把收益和夏普都抬了上去。**
先看表 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% 这种紧的止盈恰好在这个时候把它卖掉——**你亲手切断了正在帮你赚钱的持仓。** 阈值越紧,“切断”得越早,伤害越大。
那 30% 止盈为什么反而抬了一手?阈值放到 30% 之后,止盈极少被触发——表 4-5 里 30% 止盈的交易次数 396 笔,只比不止盈多 7 笔;多花的手续费也只有 403 元。(这 7 笔含止盈触发的卖出 + 触发后下次调仓日的买回;具体止盈触发了几次,可在 notebook 的交易记录里数 `limit` 行。)但少量触发恰好抓住了几次“涨过头又掉头”的极端波动,把波动率从 10.71% 压到 10.23%——分母(波动)小了一点,分子(收益)反而稳了一点,夏普就抬上去了。**这是一个边界值的故事**:阈值太紧,把“正在赚钱的仓”切断;阈值放得够远,止盈基本不动手,少数被触发的恰好是涨过头又掉头的几次,把波动尾部削掉了一点——这就是 30% 止盈在四组里夏普反而最高的原因。
再看手续费那列。不止盈时 3,903 元,10% 止盈跳到 6,703 元——多花了 2,800 元。止盈每触发一次就是一笔额外交易——这一项就把手续费推高了 72%。
表 4-6 的交易记录也很有说明力。注意“订单类型”那列:前 10 笔全是 `market`——也就是再平衡买卖。这意味着这个基准虽然同时挂了 5% 止损 + 10% 止盈两种条件单(一份 `stop` 挂单 + 一份 `limit` 挂单),但前 10 笔里两种都还没被触发。**学员在等的“止盈触发”——`limit` 类型——在 ETF 这种宽基上很少被命中**,但每命中一次都会切断正在涨的持仓。
对比一下 Step 2 的止损:
- **止损截断亏损**——下跌中越拿越亏,跑了是对的
- **止盈截断利润**——上涨中越拿越赚,跑了通常是错的(除非阈值放得够远)
**注意:这个结论是在 2021-2025 年这段数据 + 我们用的风险平价组合上得出的。** 行业里的老话**“让利润奔跑,截断亏损”(Let profits run, cut losses short** 也是同一个方向——你不知道还会跌多少,先跑为敬;但涨到头没人提前知道,一只 ETF 涨了 10% 后可能还会涨 50%,你在 10% 就卖了,等于亲手把机会让出去。但这句话**不是定理**——换一段时间、换一类标的(比如做短线动量、抓波段反转的策略),止盈在某些场景下可能反而帮了忙。本章给你的不是“止盈一定不要加”这种死结论,而是**“加规则之前,先用数据验证它在你的策略 + 你的时段上到底是帮忙还是添乱”**这个工作方法——这才是带得走的东西。
**数据说了算:不是所有“听起来合理”的规则都有效。每加一条规则,都要用数据验证它到底是帮忙还是添乱——而且要在不同阈值上都看一遍,避免用单一参数的结果下死结论。**
---
## 4.4 回头看:你刚才做了什么?
这一章你做了三件事——给策略加调仓节奏、加止损护栏、加止盈出口。三步走下来,每一步都让你撞了一次“加规则不一定变好”的小墙。下面把三次撞墙打包成三个认知。
三步实验,三个认知升级:
1. **调仓频率**:交易不是免费的。频率越高,交易越多,成本越大。选频率就是在“反应速度”和“交易成本”之间做权衡。
2. **止损规则**:保护本金的逻辑听起来稳——跌破底线就跑。但在 2021–2025 这段持续震荡下跌的数据里,三种阈值都没把回撤压住,只把波动压低了。代价是额外交易和手续费——而且要分清楚“保护”在你的数据上到底是压回撤还是压波动。
3. **止盈规则**:逻辑不像止损那么稳。10% / 20% 阈值都降低了收益率,但 30% 阈值反而把收益和夏普都抬了上去——同一类规则,不同阈值给出反向结论。“落袋为安”听起来合理,但数据告诉我们要谨慎。
你刚才做的事情,在量化交易的框架里叫做**规则设计(Rule Design)** ——在信号的基础上,添加执行层面的保护和约束。我们来看看现在走到了哪里:
- **第二章: 买什么** —— Universe(投资宇宙)
- **第三章: 买多少** —— Signal(信号)——告诉策略每只买百分之多少
- **第四章: 什么时候** —— Rules(规则)——决定何时执行、如何保护 **← 你在这里**
本章把“规则”补进了第三章的“信号”——信号想分钱,规则才动手。两者分工明确,缺一不可。
**一个更深的认知:不是所有“合理”的规则都有效。** 止损的逻辑听起来站得住脚——截断亏损,保护本金,但在我们这段数据上“保护”的含义是压波动而不是压回撤。止盈的逻辑就更模糊——你怎么知道涨到头了?而且换个阈值结论可能反过来。加规则之前,先问两个问题:
- 这条规则的逻辑站得住脚吗?
- 换个参数、换个时段,结论还一样吗?
第二个问题,正是下一章(第五章)要回答的。
---
## 4.5 本章总结
三步实验,从“多久调一次”到“亏了跑不跑”再到“赚了走不走”——你一步步给策略加上了执行层面的规则,也第一次体验到了交易成本的威力和“规则越多越好”这个直觉的破灭。**本章主练的还是“做”——三步都把“动还是不动”从凭感觉变成了可重复执行的规则;而止盈在不同阈值上方向反过来的那一刻,正是“看要指标、还要怀疑你的指标”在飞轮组合层的第一次小预演——下一章你会正式把“看”练成主线。**
### 策略进化路径
本章接着第三章的产出,在 RiskParity 信号上逐步叠加规则——每加一条都用数据验证,而不是凭直觉。
- **第三章产出**RiskParity 信号 + 每 10 天调仓
- Step 1: 调频率 —— 频率有影响 + 交易成本不可忽视
- Step 2: 加止损 —— 压低了波动,但回撤被锯齿效应反复收割
- Step 3: 加止盈 —— 多数阈值降低收益,30% 阈值反而抬高夏普
- **第四章产出**:完整的规则配置 + “每条规则都要在不同阈值上验证”的认知
### 概念速查表
本章涉及的核心概念汇总如表 4-7 所示,方便随时回查。
**表 4-7 第四章核心概念速查**
| 概念 | 含义 | 类比 |
|------|------|------|
| 交易成本(Transaction Cost) | 每次买卖都要付的费用——佣金(付给券商)+ 税(付给政府) | 去银行换汇要收手续费 |
| 止损(Stop Loss) | 预设底线:从买入价跌了 X% 就自动卖出 | “今晚最多输 500,输到了就走” |
| 止盈(Take Profit) | 预设目标:从买入价涨了 X% 就自动卖出 | “到了这个价就成交” |
| 挂单(Pending Order) | 预设条件,满足了再执行的订单 | “到价自动触发” |
| 锯齿效应(Whipsaw) | 在持续下跌的市场里反复“卖出—回涨—又跌—又卖”,每来回一次付一笔成本 | 像锯子来回切,越切越薄 |
| 规则(Rule) | 策略的执行层——感知持仓状态,决定何时动手 | 信号是大脑,规则是手脚 |
| 让利润奔跑,截断亏损 | 止损截断亏损(通常有帮助),别用止盈截断利润 | 交易经典格言 |
### 本章最重要的收获
走完三个实验后,本章最值得带走的核心认知归纳如表 4-8 所示。
**表 4-8 第四章核心认知**
| 认知 | 来源 |
|------|------|
| 交易不是免费的——每次买卖都有成本 | 4.1 |
| 调仓频率是反应速度与交易成本的权衡 | 4.1 |
| 止损压低了波动,但回撤可能被锯齿效应反复收割 | 4.2 |
| 信号看市场,规则看持仓——两者角色不同 | 4.2 |
| 止盈结论随阈值反向——单一参数不能下死结论 | 4.3 |
| 不是所有“合理”的规则都有效,要在不同阈值上用数据验证 | 4.3 |
| 让利润奔跑,截断亏损(经验,不是定理) | 4.2 + 4.3 对比 |
| 先猜后验,数据说了算 | 贯穿全章 |
### 带走的问题
读完本章,你应该带着两个挥之不去的疑问进入下一章——它们正是第五章要回答的:
- 策略看起来不错,但**怎么知道不是恰好在这段时间表现好?** 换个时间段结果还一样吗?→ 第五章
- 回测结果漂亮就能赚钱吗?**怎么避免自欺欺人?** → 第五章
> 本章所有代码的可运行版本见 `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 读者`