ch04 Pool、Book 与撮合源码精读
本章目标
- 读懂
Pool下单入口、Book撮合循环、Ordermaker 状态、Fill成交结果和OrderInfotaker 生命周期。 - 能解释 bid、ask、price level、queue priority 如何编码到
order_id和BigVector<Order>中。 - 能推演限价单、市价单、IOC、FOK、post-only、自成交策略、取消和修改订单的状态变化。
- 能把链上事件和源码中的状态字段对应起来,服务前端订单查询和 indexer 落库。
本章学习阶梯
- L2 先用三档订单簿手工推演 bid、ask、price level 和 fill。
- L3 再读订单 ID、撮合循环、剩余挂单和事件生成。
- L4 能解释 post-only、IOC、FOK、自成交策略的失败边界。
- L5 能把撮合结果还原成前端订单状态和 Indexer 数据。
关键定义卡片
撮合章先分清 Order、OrderInfo、Fill:
public struct Order has drop, store {
balance_manager_id: ID,
order_id: u128,
client_order_id: u64,
quantity: u64,
filled_quantity: u64,
fee_is_deep: bool,
order_deep_price: OrderDeepPrice,
epoch: u64,
status: u8,
expire_timestamp: u64,
}
Order 是可以留在订单簿里的 maker 状态。它有 filled_quantity、status 和 expire_timestamp,所以取消、过期、部分成交都能从这里更新。
public struct OrderInfo has copy, drop, store {
pool_id: ID,
order_id: u128,
balance_manager_id: ID,
client_order_id: u64,
trader: address,
order_type: u8,
self_matching_option: u8,
price: u64,
is_bid: bool,
original_quantity: u64,
executed_quantity: u64,
cumulative_quote_quantity: u64,
fills: vector<Fill>,
status: u8,
market_order: bool,
order_inserted: bool,
timestamp: u64,
}
OrderInfo 是一次新订单的生命周期结果。完全吃单的市价单可能只返回 OrderInfo,不会插入簿成为 Order。这也是前端和 Indexer 容易写错的地方。
public struct Fill has copy, drop, store {
maker_order_id: u128,
execution_price: u64,
balance_manager_id: ID,
base_quantity: u64,
quote_quantity: u64,
taker_is_bid: bool,
taker_fee: u64,
maker_fee: u64,
}
Fill 是一笔 taker 与一个 maker 的成交切片,不是一整笔订单。一个 taker 订单可能产生多个 Fill。
源码地图
- packages/deepbook/sources/pool.move:
place_limit_order、place_market_order、modify_order、cancel_order、cancel_live_order等外部入口。 - packages/deepbook/sources/book/book.move:
Book字段、create_order、match_against_book、inject_limit_order、cancel_order、modify_order。 - packages/deepbook/sources/book/order.move:
Order字段、generate_fill、calculate_cancel_refund、locked_balance。 - packages/deepbook/sources/book/order_info.move:
OrderInfo字段、validate_inputs、assert_execution、match_maker、事件生成。 - packages/deepbook/sources/book/fill.move:
Fill字段和 maker 结算方向。 - packages/deepbook/sources/helper/utils.move:
encode_order_id、decode_order_id。 - packages/deepbook/sources/order_query.move:分页读取 bids/asks 的查询接口。
小节目录
- 01 bid、ask、price level、queue priority
- 02 Order 结构和生命周期
- 03 订单 ID 的生成、排序和查询
- 04 Book 如何保存买卖两侧订单
- 05 限价单进入撮合引擎的完整路径
- 06 市价单和吃单资金检查
- 07 post-only、IOC、FOK、自成交策略
- 08 Fill 如何表示一次成交
- 09 OrderInfo 如何生成订单和成交事件
- 10 部分成交、完全成交、剩余挂单
- 11 取消单和批量取消路径
- 12 order query 如何服务前端查询
- 13 撮合边界条件阅读方法
- 14 三档订单簿手工推演
本章代码
code/s01-orderbook-simulator/:用 TypeScript 实现最小订单簿模拟器。code/s02-match-trace/:输入订单列表,输出逐步撮合日志。code/s03-order-query/:查询链上订单并与本地模拟排序规则对比。
Move 高阶穿插点
- 撮合源码要按“输入校验、订单构造、撮合循环、剩余处理、事件输出、资金结算”六段读。
- 订单簿里的整数不要急着换成小数显示;先保留链上原始单位,最后一层 UI 再格式化。
OrderInfo和Order要分开理解:前者描述本次交易结果,后者描述可留在簿上的状态。
常见错误
- 错误:把市价单当成没有价格。源码中市价买单使用 max price,市价卖单使用 min price,并强制 IOC。
- 错误:用 JavaScript number 保存 u128 order id。必须使用字符串或 BigInt。
- 错误:认为部分成交普通限价单的状态一定是 live。
OrderInfo可是 partially_filled,同时剩余部分以Order进入 Book。 - 错误:取消订单只删 Book。实际还要更新 account open orders、计算 refund、通过 Vault 结算。
- 错误:只监听
OrderFilled。订单生命周期还需要OrderInfo、OrderPlaced、OrderFullyFilled、OrderCanceled、OrderExpired。
本章检查清单
- 能解释 encoded order id 的 bit 布局。
- 能说明 bid/ask 分别从哪一侧、哪个价格方向开始撮合。
- 能写出限价单进入 Book 的完整函数链。
- 能解释 Fill 中 expired/completed 的不同含义。
- 能推演 IOC、FOK、post-only 和自成交策略的行为。
- 能说明取消订单如何退还锁定资产。
进阶练习
- 练习 1:用 BigInt 写一个
decodeOrderId,输入链上 order id 后输出 side、price、sequence。 - 练习 2:构造一个 maker 过期场景,解释为什么
Fill.expired = true但它仍会参与移除订单和释放锁定资产。 - 练习 3:设计一个包含 4 个 maker 的订单簿,分别推演普通限价、IOC 和 FOK taker 的最终状态。