4、订单簿管理:本地订单簿重建与维护,增量更新与快照机制,多交易所订单簿合并
订单簿管理,说白了就是高频做市商的心脏。你想想看,每秒几千笔订单进来,如果本地订单簿跟交易所对不上,那后果就是一连串的废单、错单,甚至穿仓。我个人习惯把订单簿管理拆成三个核心问题:怎么重建、怎么更新、怎么合并。咱们一个一个聊。
4.1 本地订单簿重建:从零开始的信任
做市系统启动时,或者网络断开重连后,第一件事就是重建本地订单簿。你不能假设本地缓存是干净的——我在项目中遇到过,有一次系统重启后直接用了旧快照,结果挂了半小时的废单。
重建的标准流程是这样的:
- 请求交易所的全量快照(Snapshot)
- 在请求快照的同时,开始缓存增量更新(Incremental Update)
- 收到快照后,丢弃快照之前的所有增量
- 按顺序回放快照之后的增量
- 验证订单簿的完整性(比如检查买卖盘口价差是否合理)
核心原则:快照永远是最新基准,增量必须严格按序列号排序。乱序的增量比没有增量更可怕。
这里有个坑——我曾经因为忽略了快照请求和增量缓存之间的竞态条件,导致快照和增量重叠了。后来我加了一个「序列号屏障」机制:请求快照时记录当前最新的增量序列号N,快照到达后,只应用序列号大于N的增量。嗯,这个细节救了我好几次。
4.2 增量更新与快照机制:谁才是主角?
增量更新和快照,其实是互补的关系。快照是「全量状态」,增量是「变化事件」。我个人建议采用「定期快照 + 实时增量」的双轨策略。
为什么不能只用增量?因为增量流可能丢包、乱序、重复。你想想看,如果依赖纯增量,一旦中间断了一笔,后面所有数据都废了。快照就是用来「校准」的锚点。
我常用的策略是这样的:
| 策略 | 快照频率 | 增量缓存深度 | 适用场景 |
|---|---|---|---|
| 保守型 | 每30秒 | 保留最近10000条 | 低延迟、高可靠性要求 |
| 激进型 | 每5分钟 | 保留最近50000条 | 追求极致性能,网络稳定 |
| 自适应型 | 根据丢包率动态调整 | 根据内存压力动态调整 | 我目前在用的方案 |
我的小技巧:增量更新不要直接修改订单簿,而是先写入一个「环形缓冲区」。主线程只从缓冲区批量拉取增量,这样能避免锁竞争。我在一个项目中用这个方案,把订单簿更新延迟从微秒级降到了纳秒级。
4.3 多交易所订单簿合并:统一视角的挑战
做市商通常同时在多个交易所交易。每个交易所的订单簿结构、更新频率、数据格式都不一样。你需要一个统一的「合并订单簿」来支持策略决策。
合并的核心逻辑其实不复杂:
- 每个交易所维护独立的本地订单簿
- 按价格对齐买卖盘口
- 合并时按价格聚合数量
- 处理不同交易所的精度差异(比如币安支持小数点后8位,OKX只支持6位)
但真正麻烦的是时间同步。不同交易所的快照时间戳可能差了几百毫秒,直接合并会导致「伪套利机会」。我曾经因为这个原因,写了一个策略以为发现了跨所套利,结果实际执行时全是滑点。
避坑指南:合并订单簿时,一定要给每个交易所的订单簿打上「本地时间戳」。不要依赖交易所返回的时间戳——它们可能来自不同的时钟源。我习惯在收到每条增量时,用本机的纳秒级时钟打戳。
下面这张图是我设计的合并订单簿架构,你可以看看整体流程:
4.4 实战中的几个关键细节
讲完了理论,我分享几个实际项目中踩过的坑:
- 增量去重:同一个序列号的增量可能收到两次。我习惯用哈希集合记录已处理的序列号,重复的直接丢弃。
- 快照压缩:全量快照可能很大(比如BTC/USDT的深度有5000档)。我建议用Protocol Buffers或者FlatBuffers序列化,比JSON快10倍以上。
- 合并时的价格精度:不同交易所的最小价格变动单位不同。我统一用「价格档位」来对齐,比如把0.01和0.001都映射到0.01的网格上。
一句话总结:订单簿管理的本质是「用快照做锚点,用增量做更新,用合并做统一」。这三件事做好了,你的做市系统就稳了一半。
嗯,关于订单簿管理,今天就聊这么多。记住,本地订单簿是策略的眼睛——眼睛花了,再好的策略也白搭。