11 · 撮合引擎原理:订单怎么变成成交
交易系统里最核心、最不该出错的环节,不是下单、不是行情,而是撮合——买卖双方怎么配对、按什么规则排队、谁先成交。普通交易者只需要知道「价格优先、时间优先」八个字,而做系统的工程师需要把八个字展开成数据结构、算法与工程约束。
本篇从撮合的定义讲起,覆盖撮合规则、订单簿数据结构、撮合算法、高级话题(做市、涨跌停排队、大宗、闪电崩盘)、交易所撮合 vs 区块链 AMM 的对比,最后是撮合引擎的设计要点。
1. 撮合是什么
撮合(Matching)是把买单与卖单按规则配对并确定成交价、成交量的过程。无论哪个市场,撮合的本质都是同一个问题:
给定当前订单簿(所有未成交**限价单**)与一个新进来的订单,怎么生成一组「成交」?
新买单(买 100 手 @ 4100)──▶ 撮合引擎 ──▶ 成交记录:卖一 4100 成交 50 手
│ 卖二 4101 成交 50 手
└──▶ 剩余 0 手(全部成交)| 概念 | 定义 |
|---|---|
| 订单簿(Order Book) | 所有未成交限价单按价格组织的队列集合 |
| 撮合引擎(Matching Engine) | 执行撮合规则的软件/系统,交易所的最核心资产 |
| 成交(Fill/Trade) | 一笔买单与一笔卖单配对成功,产生价格与数量 |
| 剩余量(Leaves) | 成交后订单未成交的部分,继续留在订单簿排队 |
撮合引擎的职责边界是「合法地、可复现地把订单变成成交」——它不决定价格涨跌,只决定谁先成交、以什么价成交。做市、报价、定价是市场参与者的事,撮合引擎只是规则的执行者。
2. 撮合规则:价格优先、时间优先
2.1 两条铁律
连续竞价(Continuous Trading)的核心规则,全世界统一:
| 规则 | 内容 | 通俗说法 |
|---|---|---|
| 价格优先 | 买单:出价高的先成交;卖单:出价低的先成交 | 谁给的价格好,谁先走 |
| 时间优先 | 同一价格:先挂单的先成交 | 同价先到先得 |
一个直观例子(当前订单簿):
| 档位 | 卖单 |
|---|---|
| 卖一 | 4101 × 100 |
| 卖二 | 4102 × 200 |
- 买入限价单 4101 × 150:与卖一(4101)成交 100 手,剩 50 手与卖二(4102)成交 50 手——成交价分别为 4101、4102,全部成交。
- 买入限价单 4100 × 50:价格低于卖一(4101),无法立即成交 → 进入买单队列排在 4100 价位,等价格跌下来。
- 买入市价单 × 100:直接吃卖一 4101×100,成交价 4101——市价单 = 不限价、以对手方最优价立即成交。
2.2 限价单与市价单在撮合队列中的位置
| 订单类型 | 进入队列吗? | 在队列中的位置 |
|---|---|---|
| 限价单(未成交部分) | 进入订单簿 | 按价格归档;同价按时间戳排在队尾(时间优先) |
| 市价单 | 不进入订单簿 | 只存在于「入场瞬间」,立即与对手方最优价撮合;不能立即全部成交的剩余部分按交易所规则处理(部分转为限价/直接撤销,以交易所规则为准) |
由此推出一个工程常识:市价单是「抢」,限价单是「排队」。限价单想保证成交,挂单价格要越过对手方最优价(即高于卖一/低于买一);同价排队则完全拼时间。
3. 订单簿数据结构
3.1 Level-2 盘口
交易所行情中最常见的数据结构:买一~买五、卖一~卖五(深度按交易所而定,加密所常见 20-100 档)。
| 档位 | 含义 | 数据内容 |
|---|---|---|
| 买一 | 当前最高的未成交买单价格 | 价格 + 该价总挂单量 |
| 买二~买五 | 依次更低的买价 | 同上 |
| 卖一 | 当前最低的未成交卖单价格 | 价格 + 该价总挂单量 |
| 卖二~卖五 | 依次更高的卖价 | 同上 |
盘口三个关键含义:
- 买一与卖一之差 = 点差(Spread),是**流动性**质量的直接度量;买一价就是「想立即卖出的参考价」,卖一价是「想立即买入的参考价」。
- 盘口是聚合视图(每个价位上的总量),看不到队列里的每一笔单——真正的逐笔队列属于内部数据。
- 盘口数量含冰山单隐藏部分?不含——冰山单只暴露一部分,聚合视图只能看到暴露量(以交易所规则为准)。
3.2 增量更新(add / update / delete 事件)
行情对接与撮合引擎内部都用事件模型描述订单簿变化:
| 事件 | 语义 | 例子 |
|---|---|---|
| add | 新价档出现 | 4101 价位首次出现挂单 |
| update | 某价位数量变化 | 4101 从 100 手变 80 手(部分成交/加单) |
| delete | 某价位清空 | 4101 数量**归零**,档位消失 |
- 客户端按事件流重建/维护本地订单簿;事件必须带序列号(Sequence),用于乱序检测与断线补缺口。
- 国内 CTP 行情以「五档快照」为主(无增量事件序列,直接推最新五档);加密交易所 WebSocket 的
depth频道则是标准的增量事件流(depthUpdate事件带价格+数量,数量 0 即 delete)。
3.3 订单簿重建(Rebuild)
增量事件流的致命弱点是「丢一个事件就整体错位」:一个 update 丢失,本地盘口从此与真实盘口永久偏差。因此:
- 任何深度频道都必须提供重建机制:检测到序号缺口/断线重连后,请求全量快照(REST 拉取 depth snapshot),再叠加之后的增量。
- 重建流程的经典三步:拉全量快照 → 记录快照对应的最后序号 → 丢弃序号 ≤ 该值的存量增量,仅应用更新的增量。
- 重建期间,基于盘口的策略必须暂停——拿错误的盘口下单比不下单更危险。
💀 重建期间基于盘口的策略必须暂停
重建期间,基于盘口的策略必须暂停——拿错误的盘口下单比不下单更危险。 增量事件流丢一个 update,本地盘口从此与真实盘口永久偏差;此时若策略仍按「错位盘口」报单或对冲,错的位置、错的量,全部变成实盘损失。
4. 撮合算法
4.1 简单匹配循环(限价单入场)
最朴素的核心逻辑(任何撮合引擎的雏形):
输入:新订单(方向、价格、数量)
对手方队列 = 买单则看卖队列,卖单则看买队列(按价格优先排序,头端最优)
while 新订单还有剩余量 且 对手方队列非空 且 价格可成交(买价 ≥ 卖价):
头端订单 = 对手方队列头(最优价、最早时间)
成交价 = 头端订单价格 # 挂单者的限价,而非新订单的限价
成交量 = min(新订单剩余量, 头端订单剩余量)
生成成交记录;双方剩余量各自扣减
若头端订单剩余量为 0:从队列移除(并可能触发 delete 事件)
若新订单还有剩余量:
若为限价单:加入本方队列(同价插到队尾,时间优先)
若为市价单:按规则处理剩余(部分转限价/撤销)一个关键细节:成交价始终是「队列里先挂者」的限价。新单以更优价格吃单时,成交价按对手方挂单价成交——这就是「限价单给你更好的价格」的原因,也是**滑点**分析的基础。
💀 增量事件流丢一个事件整体就永久错位
增量事件流的致命弱点是「丢一个事件就整体错位」:一个 update 丢失,本地盘口从此与真实盘口永久偏差。 重建流程的经典三步:拉全量快照 → 记录快照对应的最后序号 → 丢弃序号 ≤ 该值的存量增量,仅应用更新的增量。重建期间基于盘口的策略必须暂停——拿错误的盘口下单比不下单更危险。
4.2 连续竞价撮合(伪代码)
把 4.1 放在交易所真实约束下(以连续竞价时段为例):
while 收到新订单:
校验(价格步进、数量、风控)→ 不通过则拒绝,返回 REJECTED
执行匹配循环(见 4.1)
输出事件:OrderUpdate(订单状态变化)、Trade(成交)、DepthUpdate(盘口变化)
事件按全局递增序号广播 → 落盘为不可变事件日志关键约束:
- 先校验后撮合:非法订单(价格越限、数量不合规)直接拒单,不进入撮合。
- 事件顺序全局唯一:所有订阅方看到的事件序号一致——这是「行情一致性」的保证(详见 03-行情系统.md)。
💡 先校验后撮合先事件日志后广播
先校验后撮合:非法订单(价格越限、数量不合规)直接拒单,不进入撮合。撮合结果必须先写日志(WAL)再对外广播——事件日志是「顺序、不可变、可重放」的,它是行情广播、对账、审计、故障恢复的唯一事实来源。
- 确定性:同样的订单序列、同样的时间戳,撮合结果必须完全一致——这是回放(Replay)与对账的基础。
4.3 集合竞价(开盘/收盘:最大成交量定价)
连续竞价是「逐笔撮合」,集合竞价是「一批集中撮合」,核心原则是最大成交量定价:
1. 收集阶段(如 A 股 9:15-9:25):接受委托,不撮合
2. 定价阶段:
对所有买单/卖单按价格排序,尝试每个候选价
找出「可成交数量最大」的价格区间(最大成交量原则)
存在多个候选价时,取「偏离昨收价最近」的(次规则以交易所规则为准)
3. 撮合:所有满足「买价 ≥ 开盘价 ≥ 卖价」的订单按开盘价成交;同价按时间优先
4. 未成交部分进入连续竞价(或撤销,按规则)- 开盘价:由集合竞价产生,是「把最多订单成交掉的价格」——真实市场里开盘价的确定就这样朴素。
- 对工程的意义:集合竞价期间不允许市价类委托、报单行为与连续竞价不同(以各交易所规则为准);收盘集合竞价同理(A 股 14:57-15:00)。
5. 高级话题
5.1 做市商与最小报价单位
- 最小报价单位(Tick Size):价格必须落在最小步进的网格上(如螺纹钢 1 元/吨、加密 BTC 以 0.1 或 0.01 计),撮合引擎按网格校验与组织队列。
- 做市商:报价方,双边挂单赚点差;撮合引擎对做市商订单可能有独立队列(指定做市商优先级)与激励,但核心仍遵循价格/时间优先(特殊规则以交易所规定为准)。
5.2 涨跌停时的排队
涨跌停时,排队的意义完全不同:
- 涨停板:卖单没人接,买单全部排队等卖单出现——排队顺序 = 时间优先(先挂的先成交),此时「能不能成交」取决于你在买单队列里的位置,而非价格(价格都一样)。
- 跌停板:镜像对称。
- 对交易者的含义:涨停板上排队想卖出的人排在队列尾部,可能排几个交易日都轮不到;对系统的含义:排队位置就是一切,挂单时机比价格更重要。
5.3 大宗交易撮合(撮合外配对)
- 大宗交易:在连续竞价之外,买卖双方协商价格与数量后,向交易所申报,在专门的时段(如收盘后)按协商价成交。
- 性质:撮合外配对——交易所不参与价格发现,只做清算层面的确认与信息披露。
- 对工程的意义:大宗成交的回报、行情标识(特殊成交量标记)与普通成交不同,行情系统与对账系统要能区分。
5.4 闪电崩盘与撮合延迟
- 撮合引擎的延迟直接决定市场微观结构:延迟越低,抢跑窗口越窄;撮合排队(处理速度跟不上订单流)是高频故障形态之一。
- 闪电崩盘(Flash Crash)的典型机制:极端行情下市价单/止损单连环触发、流动性瞬间蒸发,盘口深度骤减导致价格在毫秒级暴跌后回弹——撮合引擎本身只是忠实地执行了规则,但引擎的流控与熔断机制(如波动性暂停)是防崩盘的工程手段(以各交易所规则为准)。
6. 交易所撮合 vs 区块链 AMM
6.1 中心化撮合(订单簿)回顾
| 特征 | 订单簿撮合(CEX / 期货 / 股票) |
|---|---|
| 价格发现 | 订单簿供需决定 |
| 对手方 | 需要有人与你的订单配对 |
| 成交价 | 队列中先挂者的限价 |
| 流动性 | 取决于挂单者的参与 |
6.2 区块链 AMM(自动化做市商)
DeFi 里的 Uniswap 等协议用恒定乘积公式替代订单簿:
x * y = k
x = 池中代币 A 数量,y = 池中代币 B 数量,k 为恒定常数
买入 Δx 个 A:需支付 Δy 个 B,满足 (x - Δx) * (y + Δy) = k| 特征 | AMM(如 Uniswap) |
|---|---|
| 价格发现 | 由池内两种资产的数量比例决定(无需订单簿) |
| 对手方 | 流动性池(LP 存入的两侧资产) |
| 成交价 | 随交易量滑动的公式价:你买得越多,价格越贵 |
| 流动性 | 池子深度即流动性,24×7 无休 |
6.3 滑点与无常损失
| 概念 | 定义 | 与订单簿市场的对照 |
|---|---|---|
| 滑点 | 实际成交价与期望价的偏差 | 订单簿里是「吃掉更多档位」;AMM 里是「公式定价随交易量变化」——同一笔交易越大,滑点越高 |
| 无常损失(Impermanent Loss) | LP 提供流动性的收益,与「直接持有两种代币」相比的差额损失:价格偏离存入时比例越大,损失越明显 | 订单簿市场没有直接对应物(做市商的风险是库存与点差,理念相近) |
一句话对比:订单簿撮合是「人与人」配对的精确市场,AMM 是「人与公式」配对的确定性市场。前者需要流动性供给,后者用资金池恒定公式自动定价——效率、透明与无常损失是 AMM 的三大主题。
7. 撮合引擎设计要点
如果自己实现一个撮合引擎(模拟撮合/内部撮合器,不是真实交易所),要点如下:
7.1 内存订单簿
- 订单簿必须在内存:价格队列用平衡树/跳表/优先队列(价格有序)+ 每价位的 FIFO 队列(时间优先),撮合是纯内存操作。
- 性能目标常识:每笔撮合延迟微秒级(真实撮合引擎的指标在微秒到几十微秒量级,具体以实现与硬件为准)——任何磁盘 I/O、网络同步进撮合主循环都是不可接受的。
- 单线程处理订单流(无锁或少锁):撮合逻辑的并发正确性靠「串行化订单流」保证,而不是靠锁。
7.2 事件日志(撮合记录不可丢)
- 撮合结果必须先写日志(WAL)再对外广播:事件日志是「顺序、不可变、可重放」的——它是行情广播、对账、审计、故障恢复的唯一事实来源。
- 日志落盘走顺序写(Append-Only),与撮合主循环解耦(异步批量刷盘);崩溃恢复时从日志重放重建内存订单簿。
- 审计要求:撮合记录保留时长按监管要求执行,且不可篡改。
7.3 性能指标
| 指标 | 含义 | 参考常识 |
|---|---|---|
| 每笔撮合延迟 | 订单进引擎到成交事件产出 | 微秒级(µs) |
| 撮合吞吐 | 每秒处理订单数 | 数万~数十万笔/秒(取决于实现) |
| 事件到广播延迟 | 成交到订阅方可见 | 与网络/行情链路叠加(详见 03 篇) |
| 可用性 | 引擎不宕机时间 | 交易时段内为目标 99.99%+ |
7.4 其余工程要点
- 价格/数量用整数或定点数(禁止浮点比较)——浮点误差在撮合里会变成「对不上的账」。
- 校验先行、风控前置;非法订单必须在进队列前拒绝。
- 模拟撮合器(自建 Mock)要能注入异常(拒单、乱序、断线),用于回归测试——这是 01-对接全景与角色分工.md 里「自建模拟柜台」的基础。
风险提示
⚠️ 风险提示
撮合引擎是交易系统中最不能「差不多就行」的组件:订单排序错误会让时间优先失效,浮点运算会让对账永远差一分钱,事件日志丢失会让回放与审计失去依据,而撮合逻辑与行情广播耦合则会把毫秒级故障放大成全局瘫痪。本文所述撮合规则、流控数值与性能指标均为行业通行口径,具体以各交易所规则与官方文档为准。若只是对接(而非自研撮合),请把注意力放在订单簿重建、事件序号与对账上——自研撮合引擎是大型工程,请勿在生产资金链路中直接投入使用。