模拟撮合引擎:七条规则,加一条回测里不存在的状态
206 笔委托、98 笔成交、108 笔被拒——而"被拒"这个概念在向量化回测里根本不存在
P4 那台撮合器已经有了七条 A 股规则。这一节把它装进系统,并且加上一条回测里根本不存在的东西:
委托状态。
回测里,委托只有两种命运:成交、或者不存在。而真实世界里,委托可以挂着、可以被拒、可以部分成交。
实测 622 个交易日:
委托 206 成交 98 被拒 108
超过一半的委托没有成交。
而这 108 笔被拒的原因分布,本身就是一份诊断报告——它告诉你策略在什么地方撞了墙。
⚠️ 这是本课唯一的撮合实现,它不连接任何真实交易通道。为什么停在这里,见 p8.10。
点外卖时,"下单"和"骑手接单"是两件事。
你点了,不代表有人接。
回测把这两件事合成了一件——所以它永远不会告诉你"没人接单"。
一、七条规则(全部继承自 P4 的单元测试)
| 规则 | 实现要点 | |
|---|---|---|
| ① | 涨跌停 | 一字涨停买不进、一字跌停卖不出 |
| ② | T+1 | locked 字段;new_day() 才解锁 |
| ③ | 整手 | 买入按 100 股向下取整 |
| ④ | 停牌 | tradable=False 直接拒单 |
| ⑤ | 佣金 | 双边 2.5bp,最低 5 元 |
| ⑥ | 印花税 | 仅卖出收 5bp |
| ⑦ | 滑点 | 买入向上、卖出向下各 5bp |
第 ⑤ 条的"最低 5 元"是很多实现会漏的:小额交易的实际成本远高于比例费率。一笔 1000 元的买入,按 2.5bp 只有 0.25 元佣金,而实际要收 5 元——真实费率是 50bp,20 倍。
二、回测里不存在的那条状态
@dataclass
class Position:
qty: int = 0
locked: int = 0 # T+1:当日买入不可卖
cost: float = 0.0
@property
def sellable(self):
return max(self.qty - self.locked, 0)
locked 这个字段在向量化回测里没有对应物——因为向量化回测里"持仓"就是一个数,没有"其中多少不能卖"这回事。
而正是这个字段,让 T+1 从一句口号变成一个会拒单的约束。
🔧 动手做:看一眼向量化回测永远给不了你的那 108 笔(5 分钟)
跑 SimMatcher(模拟撮合),看它的成交与被拒统计。
你会看到(实测):622 个交易日,206 笔委托里 98 笔成交、108 笔被拒——而"被拒"这个概念,在向量化回测里根本不存在(它默认"目标仓位一定达成")。
想明白:那 108 笔被拒(涨跌停封板、停牌、资金不足、T+1 锁定)是一份诊断报告:它告诉你策略在什么时候"想做却做不到"。向量化回测把这些失败的交易全部静默吞掉,于是你看到的净值比现实好看。能把"被拒"显式记下来,是模拟撮合相对向量化最本质的价值。
三、108 笔被拒:一份诊断报告
被拒原因的分布,比被拒数量更有信息量。
实测 108 笔全部是同一个原因:
不足一手 108 笔(100.0%)
这不是 bug,是策略状态的真实反映:仓位接近满仓后,策略仍在要求"再加一点",而剩余现金买不起一手。
而这正是 p4.1 建这套拒单记录的理由:
如果静默跳过,回测会显示"策略选择空仓"——净值曲线一模一样,结论完全不同。
一个只报成交、不报被拒的系统,会把"你没钱建仓"伪装成"策略判断如此"。
这两件事在净值上看不出区别,在改进策略时却指向完全相反的方向。
四、撮合器与回测引擎的三个差别
P4 的 run_backtest | qsys 的 SimMatcher | |
|---|---|---|
| 数据 | 一次性给全 | 一天一天喂 |
| 委托 | 立即撮合,无状态 | 有 open_orders,可挂单 |
| 账户 | 函数内部变量 | 可查询的快照(/account) |
第一条差别是纸上交易的全部价值所在:一次性给全的数据,让"我不小心用了未来数据"这件事无法被发现;而一天一天喂,结构上就堵住了这条路。
五、为什么这套撮合值得自己写
不是因为它比商业系统好,而是因为你需要知道它假设了什么。
| 你写的 | 你就知道 |
|---|---|
一字板判定用 low >= 涨停价 | 不是所有涨停都买不进,只有一字板 |
成交价用 open × (1+滑点) | 你的回测假设了"次日开盘成交" |
| 最低佣金 5 元 | 小额交易的真实费率是多少 |
locked 字段 | T+1 到底在什么时候生效 |
而用别人的系统时,这些假设通常写在文档的第 37 页,或者根本没写。
📌 免责:本课为技术教学,
SimMatcher是模拟撮合,不连接任何真实交易通道;不构成投资建议。
四件事:
- 七条 A 股规则全部继承自 P4 的单元测试。其中"最低佣金 5 元"最容易漏:一笔 1000 元的买入按 2.5bp 只要 0.25 元,而实际收 5 元——真实费率 50bp,20 倍
locked字段在向量化回测里没有对应物——向量化回测的"持仓"就是一个数,没有"其中多少不能卖"这回事。正是这个字段让 T+1 从一句口号变成一个会拒单的约束- 108 笔被拒全部是"不足一手",这不是 bug,是策略状态的真实反映。一个只报成交、不报被拒的系统,会把"你没钱建仓"伪装成"策略判断如此"——两件事在净值上看不出区别,在改进策略时却指向完全相反的方向
- 与回测的三个差别:数据一天一天喂(而非一次性给全)· 委托有状态可挂单 · 账户是可查询的快照。第一条是纸上交易的全部价值——一次性给全的数据让"用了未来数据"无法被发现,而一天一天喂,结构上就堵住了这条路
自己写撮合的理由不是它比商业系统好,而是你需要知道它假设了什么:一字板怎么判、成交价用哪个、最低佣金多少、T+1 何时生效——用别人的系统时,这些假设通常写在文档第 37 页,或者根本没写。
下一节把它跑成闭环:信号 → 委托 → 成交 → 复盘,并讲一个纸上交易当场暴露、而回测掩盖了的缺陷。
🔎 来源与核验· 1 条,点开核对
