第 14 课 · 回测进阶:滑点、流动性与涨跌停

回测幻觉的最后一层——你以为能成交的价格,实盘里根本不存在

预计用时:35 分钟 上一课:第 13 课 · 多 Agent 团队

这节课你将得到什么

在第 4 课的回测框架上加"现实摩擦三件套":①滑点(买入 +0.1%、卖出 -0.1%);②涨跌停不可成交(当日涨跌幅 ≥ 9.8% 视为触板,涨停禁买、跌停禁卖,信号顺延);③一张对比表——第 4 课基础版 vs 加入摩擦后的现实版,看收益又被吃掉多少。

心智模型只有一句话:回测里的"价格存在"不等于实盘里的"成交存在"。第 4 课你学会了审回测的四个要素,这节课把其中"成交规则"这一格挖到实盘的深度。

一、回测幻觉的三个层次

到这个系列的后半程,该把"回测幻觉"整理成一个完整的分层模型了——以后任何人(包括你的 Agent 团队)交给你一份回测,你按这三层从上往下审:

层次幻觉内容哪一课治的
1. 未来函数信号用到了成交时刻不可能知道的数据(当日收盘出信号、当日收盘成交)第 4 课:收盘出信号,次日开盘成交
2. 成本佣金、印花税不算,高频来回的盈利全靠漏算它第 4 课:佣金万 2.5(最低 5 元)+ 印花税 0.05% 仅卖出
3. 流动性假设"有价格 = 能按这个价格成交"——忽略滑点、冲击成本、涨跌停封板本课

第 7 课讲的过拟合属于另一个维度(研究方法的幻觉),这三层则是成交建模的幻觉:哪怕你的信号是真的、没有未来函数,只要成交价格与成交能力是编的,收益就是编的。一个有意思的规律:层次越往下,幻觉越"诚实"——未来函数是明显的错误,而流动性幻觉往往来自"懒得建模",杀伤力却一样大。

二、摩擦一:滑点——你的成交价总比理想价差一点

回测里写"次日开盘价成交",隐含假设是:你挂的单能恰好按开盘价成交。实盘里做不到,原因有三个:

工程上不用精确还原这三个机制,一个保守的固定滑点就够用(脚本 scripts/realistic_backtest.py 里的写法,注释是给你看的,不用敲):

SLIPPAGE = 0.001  # 滑点:买入 +0.1%,卖出 -0.1%
price = row["开盘"] * (1 + SLIPPAGE)   # 买入:成交价 = 开盘价 × 1.001
price = row["开盘"] * (1 - SLIPPAGE)   # 卖出:成交价 = 开盘价 × 0.999

注意方向:两个方向的滑点都对你不利——买得更贵、卖得更便宜。这是刻意的保守:回测宁可把摩擦估高一点,给策略留安全边际。如果你的策略连 0.1% 的单边滑点都扛不住,它大概率本来就活不到实盘。

三、摩擦二:涨跌停——流动性不是变贵,是消失

复习第 1 课的硬规则(规则速查):主板涨跌幅限制 ±10%。涨停时发生了什么?价格被顶在涨停板,所有想买的人排队挂在涨停价,而持有者惜售——卖盘几乎消失。你的买单排在几万手封单后面,全天可能一股都买不到。跌停镜像对称:想卖的人排队,买盘消失,你的止损单挂出去没人接。

这就是第三层幻觉的核心:涨跌停那天,K 线上明明印着开盘价、收盘价,但那些价格对你来说不可成交。回测若照常按开盘价买入,等于假设你能插到封单队伍的最前面——这是比未来函数更隐蔽的造假。

本课的建模(阈值留 0.2% 余量,涨幅 ≥ 9.8% 即视为触板):

if pending == "buy" and row["涨跌幅"] >= LIMIT_PCT:
    # 涨停:买单无法成交,信号顺延到下一个可成交日
    defer_buy += 1
elif pending == "sell" and row["涨跌幅"] <= -LIMIT_PCT:
    # 跌停:卖单无法成交,信号顺延
    defer_sell += 1
为什么阈值用 9.8% 而不是 10%

涨到 +9.8% 时卖盘通常已经枯竭、板上封单已成形,等精确到 +10.00% 再判定会漏掉大量"接近封死"的日子。回测建模的原则和滑点一样:宁可保守,不可乐观。同理,ST 股 ±5%、创业板/科创板 ±20% 的阈值要按标的调整——本课用主板茅台,取 9.8%。

四、动手:跑起来

脚本完全离线,数据还是第 2 课落地的 data/600519_qfq_2024.csv。运行:

cd "$HOME/AI Project/Quant Learning"
source .venv/bin/activate
python scripts/realistic_backtest.py

你会看到(我已在你的环境实测通过,交易明细较长,中间省略):

数据:600519_qfq_2024.csv,242 个交易日(前复权,口径见第 2 课)
信号:金叉 9 次,死叉 8 次
触板日(|涨跌幅|≥9.8%):0 天(最大单日涨幅 9.29%,最大跌幅 -7.42%)

【现实版交易明细】信号次日开盘成交 + 滑点 + 涨跌停禁交易
  买入 2024-02-08  成交价  1564.59  600 股  金额 938,756  费用 234.69
  卖出 2024-03-11  成交价  1520.61  600 股  金额 912,365  费用 684.27  本笔盈亏 -27,310
  ……
  买入 2024-09-30  成交价  1571.00  500 股  金额 785,500  费用 196.37
  卖出 2024-10-29  成交价  1445.54  500 股  金额 722,772  费用 542.08  本笔盈亏 -63,467
  ……
  买入 2024-12-13  成交价  1439.16  500 股  金额 719,579  费用 179.89

==================================================================
对比:同一个策略、同一份数据,逐步加入现实摩擦
==================================================================
               基础版(第4课)   现实版(滑点+涨跌停)  买入持有(基准)
------------------------------------------------------------------
总收益率             -23.79%               -25.39%          -5.75%
年化收益             -24.64%               -26.29%          -5.98%
最大回撤             -25.12%               -26.73%         -25.17%
交易次数                   8                     8               1
胜率                     12%                   12%              —
==================================================================
现实摩擦:收益从 -23.79% 降到 -25.39%,再吃掉 1.60%
  其中滑点累计 14,021 元;涨跌停顺延:买 0 次、卖 0 次
注:年末仍持仓(最后一个金叉后未现死叉),浮动盈亏已计入。

把这笔账和第 4 课连起来看:幻觉版 -19.81% → 第 4 课基础版 -23.79% → 本课现实版 -25.39%。同一个策略、同一份数据,三层幻觉全部挤掉后,收益又少了 5.58 个百分点。这个策略本来就在亏钱,摩擦让它亏得更多——摩擦是乘法,不是加法:它放大亏损、侵蚀盈利,方向永远对你不利。

五、解剖:1.60 个点去哪了

对比表里滑点累计 14,021 元,看最典型的一笔(9 月底追高的那次):

一年 9 买 8 卖共 17 次成交,每次被滑点咬一口,累计就是 1.4 万——比全年级数最大的一笔盈利(+8,577)还多。这就是"隐形税收":单笔看无感,全年看致命。

另一个数字同样重要:涨跌停顺延 0 次。茅台 2024 全年没有一天涨跌幅达到 9.8%(最接近的是 9 月 26 日 +9.29%)。这是大盘蓝筹的特点,不是市场的特点——换成小盘股、换成 2015 年那种极端行情,触板日会密集出现。规则必须在代码里就位,哪怕它一年都不触发一次;它就像保险丝,平时不存在,出事时保命。

六、信号顺延与 T+1 的连锁反应(合成样本演示)

真实数据上演示不了顺延机制,脚本里内置了一段教学构造的迷你行情(非真实行情):金叉次日涨停、死叉次日跌停。两个版本的分歧一目了然:

基础版(不管涨跌停,假设涨停板也能成交):
  买入 2024-12-03  成交价   110.00  9000 股  金额 990,000  费用 247.50
  卖出 2024-12-09  成交价    97.20  9000 股  金额 874,800  费用 656.10  本笔盈亏 -116,104
现实版(触板禁交易,信号顺延):
  2024-12-03 涨停(+10.00%),买单无法成交,信号顺延
  买入 2024-12-04  成交价   112.11  8900 股  金额 997,797  费用 249.45
  2024-12-09 跌停(-10.00%),卖单无法成交,信号顺延
  卖出 2024-12-10  成交价    93.91  8900 股  金额 835,763  费用 626.82  本笔盈亏 -162,910
  基础版总收益 -11.61%  vs  现实版总收益 -16.29%

两件事值得盯紧:第一,基础版那个"12 月 3 日按 110 买入"本身就是幻觉——涨停封板时你排在几万手封单后面,根本买不到;它账面上的 -11.61% 是一个你拿不到的结果。第二,顺延不是免费的,而且和 T+1 叠加成连锁反应:涨停追不进,顺延一天买到时贵了 2%(112.11 对 110);跌停卖不出,顺延一天卖出时又低了 3.4%(93.91 对 97.2),且因为 T+1,顺延买入的仓位当天还锁着不能卖。如果跌停是连续的(极端行情里常见,想想 2015 年股灾期间的千股跌停),你的止损单会一天天地顺延,风险敞口被迫持续扩大——回测里的一次"信号顺延",在实盘中就是眼睁睁看着亏损放大却动不了。

这对你的 Agent 系统意味着什么

你的 Agent 盘后出信号、生成"次日操作计划"。这节课之后,计划里必须有分支逻辑:"若明日触板涨停,则买入计划取消还是改限价排队?若触板跌停,止损无法执行,风险敞口怎么算?"一个不会处理涨跌停的 Agent,在极端行情里给出的建议是不可执行的。免费数据源在极端行情里也更容易挂(第 2 课的教训),计划里还得有"数据缺失时保守处理"的兜底。

七、为什么实盘收益永远 ≤ 回测收益

把三层幻觉都建模完之后,你的回测收益就是实盘收益的乐观上界,因为总有一些摩擦你永远建模不全:

由此得到一个极其实用的判据:回测里只赚 2~3% 的策略,实盘大概率亏钱——边际收益会被你没建模到的摩擦吃光。回测的真正用途是证伪(快速淘汰不行的策略),而不是许诺收益。这也是整个系列的基调:Agent 把"写策略 + 严谨回测"的周期压缩到一小时,意味着你一晚上能证伪十个想法——这才是它给你的真实杠杆。

自测

凭记忆作答——答错比答对更能加固记忆。

动手挑战(可选,10 分钟)

把脚本里的 SLIPPAGE = 0.001 改成 0.003(模拟一只流动性差的小盘股)重跑,看现实版收益变成多少。再想想:如果一个高频策略每天交易一次,0.3% 的单边滑点一年会吃掉多少收益?这就是为什么"回测年化 200% 的高频策略"在 A 股实盘里几乎都不成立。

延伸阅读

下一课预告

第 15 课 · 仓位管理与凯利公式:这个策略 8 笔交易 7 笔亏,但比"买什么"更生死攸关的问题是"买多少"——满仓一把梭,一次黑天鹅就能让你出局。仓位决定你能不能活到策略生效的那天。

有任何不清楚的地方(比如滑点的方向、顺延的代码逻辑、9.8% 阈值的取舍),直接问我——我是你的老师。