撮合引擎:交易所是怎么把买单卖单凑成一笔交易的 书架

第 09 章 / 共 12 章

第九章 · 成交之后:回报、行情与对账

成交只是开始

撮合循环跑完一圈,簿子变了,双方的钱货要划转——但先别急着划。交易所此刻还欠全世界一个交代:把"刚刚发生了什么"准确、有序、不丢不重地通知出去。这一章讲的就是成交之后的消息体系。别小看它——真实交易所的撮合内核可能只占代码量的两成,剩下八成都在伺候"让别人知道"。

第一张网:成交回报,只给你看的私信

你的订单进门之后,它的一生会产生一串消息,这叫成交回报(execution report)——交易所发给你的私信,告诉你"你的单现在什么状态"。一张订单的状态机长这样:

已接收 → 已确认(进了簿子)→(部分成交 → 部分成交 → …)→ 全部成交
          ↘ 已撤销 / 已拒绝 / 已过期

每个状态变化都是一条回报,带上订单号、本次成交量、本次成交价、剩余量。第五章那张1500股的市价单,你会收到三条"部分成交"回报加一条"全部成交"。

设计回报流时有一个铁律:客户端必须能从回报流重建出订单的完整状态,不需要额外去问。因为"问"是有竞态的——你查询的瞬间状态又变了。流是唯一的真相来源,查询只能做兜底。这个原则在第十一章的日志重放里会放大成整个系统的架构哲学。

第二张网:行情广播,给所有人看的公告

回报是私信,行情是公告。交易所把簿子的变化广播给所有订阅者,这就是你在行情软件里看到的东西。行情分几个档次,贵的不一样:

注意一个因果方向,很多人会搞反:行情不是数据库查出来的,是撮合过程的副产品。撮合引擎每改一次簿子(新单入簿、成交、撤单),就顺带吐出一条"簿子增量"。L2行情的每一档总量,就是这些增量累加出来的。这直接决定了实现方式:不要定时截图订单簿去广播(那样要么丢变化要么发重复),要让簿子的每次修改天然产生一条增量消息。

序列号:让"丢没丢"变成可检测的

行情走网络广播,网络会丢包。怎么让接收方知道自己丢没丢?答案简单到优雅:给每条消息编一个严格递增的序列号(sequence number)。

你收到1001、1002、1004——立刻知道丢了1003,本地簿子不能信,必须修复。修复方式两种:重放(找交易所要1003这条,或从某个序号开始重发)和快照(重新拉一份当前完整簿子,再从快照对应的序列号继续接增量)。币所的WebSocket深度流就是这套机制:先拉快照,缓存增量,把序列号对上了再开始应用,对不上就重来。

你自己实现时有两个决策点:序列号是全所一个还是每个品种一个(后者扩展性好,主流做法);快照里必须带"这份快照对应到哪个序列号",否则增量接不回去。

大白话:行情流就像一本连载小说,每页有页码。页码断了,别猜,回头补那一页或者从最近的全家福重新接——绝不允许"大概齐"。金融数据的对,是"可证明的对"。

第三张网:对账,深夜里的慢功夫

白天的回报流和行情流再严密,交易所每天收盘后还要干一件笨事:对账——把每个账户"理论上应该有的钱和货"跟实际余额核一遍。理论上应该有 = 开盘余额 + 全天所有成交、费用、充提的累加。

对账的意义不是"平时会错",而是必须能证明没错。它是整个系统正确性的最终裁判:撮合引擎、清算、费用、任何一环出bug,对账都会不平。不平就是事故,停下来查。所以好的交易所系统,从第一天就把每笔成交设计成"可重算"的——这又为第十一章的事件溯源埋了线。

收拢一下

成交之后,交易所同时维持三张网:给当事人的回报流(私信、状态机)、给所有人的行情流(公告、L1/L2/L3、序列号可修复)、给自己的对账流(慢、全量、最终裁判)。三张网共用同一个数据源:撮合循环吐出来的事件。把这句话记住,下一章我们终于开始写代码——你会发现,数据结构的设计早已被前八章的规则悄悄决定了。

撮合引擎:交易所是怎么把买单卖单凑成一笔交易的 · 王建硕
费曼式写法 · 由多个 AI 代理撰写与互相审校