> ## Documentation Index
> Fetch the complete documentation index at: https://docs.intention.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# 撮合

> 订单簿、价格-时间优先如何表示，以及订单有效期与自成交防护如何在其上生效。

撮合是[内核执行](/zh/protocol/architecture/kernel)里的一个阶段，不是链去调用的服务。它按已提交的次序取出本区块的订单，逐笔与订单簿相碰，产出成交。它不移动任何人的余额——那是[清算所](/zh/protocol/architecture/clearinghouse)的事，发生在撮合完成之后。

把这两件事分开，正是引擎可测试的原因。撮合回答的是*什么与什么成交了*；清算所回答的是*这要花多少钱、现在谁欠谁*。

<h2 id="the-book">
  订单簿
</h2>

每个品种有自己的订单簿，在内存中由三个相互配合的结构承载：

<div className="dg" data-dg="matching-book">
  <div className="dg-c" style={{aspectRatio:"720 / 302"}}>
    <svg className="dg-w" viewBox="0 0 720 302" aria-hidden="true">
      <path className="dg-wire dg-soft" d="M 348.60 82.00 L 352.60 82.00" />

      <path className="dg-wire dg-soft" d="M 438.20 82.00 L 442.20 82.00" />

      <path className="dg-wire dg-soft" d="M 527.80 82.00 L 531.80 82.00" />

      <path className="dg-wire dg-soft" d="M 617.40 82.00 L 621.40 82.00" />

      <path className="dg-wire dg--blue" d="M 205.00 68.00 L 238.60 68.00" />

      <path className="dg-head dg--blue" d="M 245.00 68.00 L 238.60 72.40 L 238.60 63.60 Z" />

      <path className="dg-wire dg--sky" d="M 205.00 162.00 L 238.60 162.00" />

      <path className="dg-head dg--sky" d="M 245.00 162.00 L 238.60 166.40 L 238.60 157.60 Z" />
    </svg>

    <div className="dg-band" style={{left:"34.7222%",top:"9.9338%",width:"65.2778%",height:"56.2914%"}}><span className="dg-cap">Slab arena — 一块预先分配的订单槽位区</span></div>
    <div className="dg-b dg--blue" style={{left:"0.0000%",top:"9.9338%",width:"27.7778%",height:"25.1656%"}}><span className="dg-t">价位表</span><span className="dg-s">有序映射，价格 → 价位</span><span className="dg-n">盘口只需走到端点就能找到，不必扫描</span></div>
    <div className="dg-b dg--sky" style={{left:"0.0000%",top:"41.0596%",width:"27.7778%",height:"25.1656%"}}><span className="dg-t">订单索引</span><span className="dg-s">订单 ID → 槽位</span><span className="dg-n">撤单和改单是常数时间</span></div>
    <div className="dg-b dg--yellow" style={{left:"36.9444%",top:"19.5364%",width:"11.0556%",height:"15.2318%"}}><span className="dg-t">订单</span></div>
    <div className="dg-b dg--yellow" style={{left:"49.3889%",top:"19.5364%",width:"11.0556%",height:"15.2318%"}}><span className="dg-t">订单</span></div>
    <div className="dg-b dg--yellow" style={{left:"61.8333%",top:"19.5364%",width:"11.0556%",height:"15.2318%"}}><span className="dg-t">订单</span></div>
    <div className="dg-b dg--yellow" style={{left:"74.2778%",top:"19.5364%",width:"11.0556%",height:"15.2318%"}}><span className="dg-t">订单</span></div>
    <div className="dg-b dg--yellow" style={{left:"86.7222%",top:"19.5364%",width:"11.0556%",height:"15.2318%"}}><span className="dg-t">订单</span></div>
    <div className="dg-b dg-plain dg-left" style={{left:"36.9444%",top:"41.3907%",width:"60.8333%",height:"15.2318%"}}><span className="dg-s">一条按到达次序串起订单的双向链表，因此同一价位内的优先级由位置决定，而不是算出来的。</span></div>
    <div className="dg-b dg-dashed dg-left" style={{left:"0.0000%",top:"74.8344%",width:"100.0000%",height:"20.5298%"}}><span className="dg-t">槽位复用次序为什么是固定的</span><span className="dg-s">被释放的槽位按固定次序复用，索引也用固定种子。这不是性能选择：两个验证者以不同次序复用槽位就会分叉。</span></div>
    <div className="dg-lbl" style={{left:"31.2500%",top:"17.8808%",width:"15.2778%",whiteSpace:"normal"}}>各价位的队首</div>
    <div className="dg-lbl" style={{left:"31.2500%",top:"62.9139%",width:"15.2778%",whiteSpace:"normal"}}>直接查找</div>
  </div>
</div>

| 结构       | 作用                                        |
| -------- | ----------------------------------------- |
| **价位表**  | 从价格到价位的有序映射，因此最优买价和最优卖价只需走到端点就能找到，不必扫描    |
| **订单链**  | 一条按到达次序串起订单的双向链表，因此同一价位内的优先级由位置决定，而不是算出来的 |
| **订单索引** | 从订单 ID 到其槽位的直接映射，因此撤单和改单是常数时间，而不是一次搜索     |

订单存放在一个 **slab arena** 中——一块预先分配好的区域，分配与释放都是常数时间。因此订单簿操作在热路径上不做内存分配，被释放的槽位也按固定次序复用，而不是分配器碰巧放到哪里就是哪里。最后这一点不是性能选择：如果两个验证者以不同次序复用槽位，任何能观察到槽位布局的东西都会发散。

订单索引也守同一条规矩：用固定种子，不用随机种子。按进程随机播种的哈希表，是抵御碰撞攻击的标准做法；但放在共识执行路径上，它就是一次分叉。

价格全程都是整数——单位是 subtick，不是小数。它与你提交的数值如何对应，见[精度](/zh/trading/precision)。

<h2 id="priority">
  优先级
</h2>

优先级先看价格，再看它在该价位链表里的位置。这里的“时间”，指的是这笔订单在已提交区块序列中的规范位置，不是它到达某个节点的时刻。

这消除了区块内的延迟竞速。同一区块中的两笔订单有一个确定的先后，每个验证者算出的结果都一样，离某个节点再近也改变不了它。在区块之间，到达时间仍然重要——但竞争的单位是区块，不是微秒。

内核的阶段次序进一步强化了这一点：一个区块之内，撤单先于主动吃单执行；挂着的报价，只要它的撤单和对方订单落在同一区块，就不会被吃掉。

<h2 id="matching-an-order">
  撮合一笔订单
</h2>

<div className="dg" data-dg="matching-walk">
  <div className="dg-c" style={{aspectRatio:"720 / 334"}}>
    <svg className="dg-w" viewBox="0 0 720 334" aria-hidden="true">
      <path className="dg-wire dg--blue" d="M 134.00 144.00 L 155.60 144.00" />

      <path className="dg-head dg--blue" d="M 162.00 144.00 L 155.60 148.40 L 155.60 139.60 Z" />

      <path className="dg-wire dg--sky" d="M 320.00 144.00 L 345.60 144.00" />

      <path className="dg-head dg--sky" d="M 352.00 144.00 L 345.60 148.40 L 345.60 139.60 Z" />

      <path className="dg-wire dg--green" d="M 510.00 144.00 L 535.60 144.00" />

      <path className="dg-head dg--green" d="M 542.00 144.00 L 535.60 148.40 L 535.60 139.60 Z" />

      <path className="dg-wire dg--sky" d="M 633.00 114.00 L 633.00 80.00 L 241.00 80.00 L 241.00 103.60" />

      <path className="dg-head dg--sky" d="M 241.00 110.00 L 236.60 103.60 L 245.40 103.60 Z" />

      <path className="dg-wire dg--green" d="M 633.00 174.00 L 633.00 213.60" />

      <path className="dg-head dg--green" d="M 633.00 220.00 L 628.60 213.60 L 637.40 213.60 Z" />

      <path className="dg-wire dg--sky" d="M 241.00 178.00 L 241.00 213.60" />

      <path className="dg-head dg--sky" d="M 241.00 220.00 L 236.60 213.60 L 245.40 213.60 Z" />
    </svg>

    <div className="dg-b dg--blue" style={{left:"0.0000%",top:"35.3293%",width:"18.0556%",height:"15.5689%"}}><span className="dg-t">新到订单</span></div>
    <div className="dg-b dg--yellow dg-round" style={{left:"23.0556%",top:"34.1317%",width:"20.8333%",height:"17.9641%"}}><span className="dg-t">与订单簿交叉？</span></div>
    <div className="dg-b dg--sky" style={{left:"49.4444%",top:"35.3293%",width:"20.8333%",height:"15.5689%"}}><span className="dg-t">吃掉对手方最优价位</span></div>
    <div className="dg-b dg--green" style={{left:"75.8333%",top:"35.3293%",width:"24.1667%",height:"15.5689%"}}><span className="dg-t">产出成交</span></div>
    <div className="dg-b dg--green" style={{left:"75.8333%",top:"67.0659%",width:"24.1667%",height:"25.7485%"}}><span className="dg-t">结束</span></div>
    <div className="dg-b dg--sky dg-left" style={{left:"10.5556%",top:"67.0659%",width:"45.8333%",height:"25.7485%"}}><span className="dg-t">按订单有效期，把剩余部分挂单或拒绝</span><span className="dg-s">GTC 挂单 · IOC 撤销剩余 · FOK 不能全部成交就一点也不执行 · 只挂单宁可被拒也不交叉</span></div>
    <div className="dg-lbl" style={{left:"60.6944%",top:"23.9521%",width:"27.7778%",whiteSpace:"normal"}}>还有剩余 — 吃下一个价位</div>
    <div className="dg-lbl" style={{left:"87.9167%",top:"58.9820%"}}>没有剩余</div>
  </div>
</div>

撮合器反复吃掉对手方的队首，每吃掉一个挂单方就产出一笔成交，直到新到订单用尽，或订单簿不再交叉。剩余部分怎么处理，由订单有效期决定：

* **GTC** — 剩余部分挂到订单簿上。
* **IOC** — 撤销剩余部分。
* **FOK** — 如果订单无法全部成交，就一点也不执行。
* **ALO** — 只挂单：这笔订单要是会吃掉流动性，就直接拒单，不让它穿过去。

成交在产生的同时就带上归属信息。每一笔成交都记着自己在本品种成交序列中的位置；组装输出时，这些按品种的位置归并成整个区块内的单一顺序。正因如此，日后才能把一个事件追回到引起它的那笔交易，以及区块中的那个确切位置。

<h2 id="self-trade-prevention">
  自成交防护
</h2>

新到的订单要是会和同一所有者挂着的流动性成交，这次撮合就被抑制，不予执行。由哪一方让路，可以配置：

| 模式                      | 行为              |
| ----------------------- | --------------- |
| **Expire taker**（撤销吃单方） | 新到的订单被撤销        |
| **Expire maker**（撤销挂单方） | 挂着的订单被撤销，新到订单继续 |
| **Expire both**（双方撤销）   | 两笔都被撤销          |

这样撤掉的挂单会在撮合过程中收集起来，在同一区块内移除，订单簿上不会留着一笔已经被抑制的订单。

这项检查里的“所有者”，按订单簿跟踪的账户层级判定。交易侧的说明见[自成交防护](/zh/trading/self-trade-prevention)。

<h2 id="what-matching-does-not-do">
  撮合不做什么
</h2>

它不计算手续费、不实现盈亏、不调整仓位、不检查保证金。这些都发生在撮合之后的[清算所](/zh/protocol/architecture/clearinghouse)里，由撮合产出的成交驱动。

它也不判断一笔订单是否被允许存在。保证金是否充足、挂单数量上限、只减仓约束，以及市价转限价，都在订单到达订单簿之前就已处理完毕。等撮合器看到一笔订单时，唯一的问题只剩它该放在订单簿的什么位置。

<h2 id="where-to-go-next">
  后续阅读
</h2>

<CardGroup cols={2}>
  <Card title="清算所" href="/zh/protocol/architecture/clearinghouse">
    成交出现之后，余额和仓位会怎样。
  </Card>

  <Card title="订单类型" href="/zh/trading/order-types">
    交易侧的视角：你可以提交什么，每一种如何表现。
  </Card>

  <Card title="订单簿" href="/zh/trading/order-book">
    深度、价位，以及作为交易者如何读盘。
  </Card>

  <Card title="IntentionKernel" href="/zh/protocol/architecture/kernel">
    撮合在区块执行中处于什么位置。
  </Card>
</CardGroup>
