订单簿
每个品种有自己的订单簿,在内存中由三个相互配合的结构承载:Slab arena — 一块预先分配的订单槽位区
价位表有序映射,价格 → 价位盘口只需走到端点就能找到,不必扫描
订单索引订单 ID → 槽位撤单和改单是常数时间
订单
订单
订单
订单
订单
一条按到达次序串起订单的双向链表,因此同一价位内的优先级由位置决定,而不是算出来的。
槽位复用次序为什么是固定的被释放的槽位按固定次序复用,索引也用固定种子。这不是性能选择:两个验证者以不同次序复用槽位就会分叉。
各价位的队首
直接查找
订单存放在一个 slab arena 中——一块预先分配好的区域,分配与释放都是常数时间。因此订单簿操作在热路径上不做内存分配,被释放的槽位也按固定次序复用,而不是分配器碰巧放到哪里就是哪里。最后这一点不是性能选择:如果两个验证者以不同次序复用槽位,任何能观察到槽位布局的东西都会发散。
订单索引也守同一条规矩:用固定种子,不用随机种子。按进程随机播种的哈希表,是抵御碰撞攻击的标准做法;但放在共识执行路径上,它就是一次分叉。
价格全程都是整数——单位是 subtick,不是小数。它与你提交的数值如何对应,见精度。
优先级
优先级先看价格,再看它在该价位链表里的位置。这里的“时间”,指的是这笔订单在已提交区块序列中的规范位置,不是它到达某个节点的时刻。 这消除了区块内的延迟竞速。同一区块中的两笔订单有一个确定的先后,每个验证者算出的结果都一样,离某个节点再近也改变不了它。在区块之间,到达时间仍然重要——但竞争的单位是区块,不是微秒。 内核的阶段次序进一步强化了这一点:一个区块之内,撤单先于主动吃单执行;挂着的报价,只要它的撤单和对方订单落在同一区块,就不会被吃掉。撮合一笔订单
新到订单
与订单簿交叉?
吃掉对手方最优价位
产出成交
结束
按订单有效期,把剩余部分挂单或拒绝GTC 挂单 · IOC 撤销剩余 · FOK 不能全部成交就一点也不执行 · 只挂单宁可被拒也不交叉
还有剩余 — 吃下一个价位
没有剩余
- GTC — 剩余部分挂到订单簿上。
- IOC — 撤销剩余部分。
- FOK — 如果订单无法全部成交,就一点也不执行。
- ALO — 只挂单:这笔订单要是会吃掉流动性,就直接拒单,不让它穿过去。
自成交防护
新到的订单要是会和同一所有者挂着的流动性成交,这次撮合就被抑制,不予执行。由哪一方让路,可以配置:
这样撤掉的挂单会在撮合过程中收集起来,在同一区块内移除,订单簿上不会留着一笔已经被抑制的订单。
这项检查里的“所有者”,按订单簿跟踪的账户层级判定。交易侧的说明见自成交防护。
撮合不做什么
它不计算手续费、不实现盈亏、不调整仓位、不检查保证金。这些都发生在撮合之后的清算所里,由撮合产出的成交驱动。 它也不判断一笔订单是否被允许存在。保证金是否充足、挂单数量上限、只减仓约束,以及市价转限价,都在订单到达订单簿之前就已处理完毕。等撮合器看到一笔订单时,唯一的问题只剩它该放在订单簿的什么位置。后续阅读
清算所
成交出现之后,余额和仓位会怎样。
订单类型
交易侧的视角:你可以提交什么,每一种如何表现。
订单簿
深度、价位,以及作为交易者如何读盘。
IntentionKernel
撮合在区块执行中处于什么位置。