4. 增量更新机制:逐笔成交与订单簿变化事件、序列号与数据对齐

好,咱们今天聊点实在的。

订单簿重建,说白了就是要把市场里那一堆乱七八糟的买卖单,拼回一张完整的“价格-数量”表。但问题来了——市场是活的,每秒都有几百上千笔成交和撤单。你总不能每次都把整个订单簿推倒重来吧?

嗯,这就是增量更新的用武之地。

4.1 为什么需要增量更新?

我刚开始做高频交易系统时,犯过一个低级错误:每次收到行情快照,就全量替换本地订单簿。结果呢?网络延迟稍微一高,订单簿就“跳帧”,策略直接亏钱。

你想想看,如果每秒有5000笔订单变动,你每次都全量拉取,带宽和CPU都扛不住。更关键的是——全量快照之间,你根本不知道中间发生了什么。

所以,增量更新是必须的。它只告诉你“变了什么”,而不是“现在长什么样”。

核心原则: 全量快照用于初始化,增量事件用于持续维护。两者配合,才能做到低延迟、高精度。

4.2 逐笔成交与订单簿变化事件

增量更新里,最核心的就是两类事件:

  • 逐笔成交(Trade):谁和谁成交了,价格多少,数量多少。
  • 订单簿变化(OrderBook Change):新增订单、撤销订单、修改订单。

我个人习惯把这两类事件分开处理。为什么呢?因为成交事件会影响最新价、成交量这些指标,而订单簿变化影响的是盘口深度。混在一起处理,逻辑容易乱。

举个例子,某交易所的增量事件格式可能是这样的:

{
  "type": "trade",
  "price": 100.5,
  "quantity": 200,
  "timestamp": 1699000000123
}

{
  "type": "orderbook_change",
  "side": "bid",
  "price": 100.0,
  "quantity": 150,
  "action": "add"  // 或 "remove", "update"
}

注意,有些交易所会把“修改订单”拆成“删除旧单 + 新增新单”。我在对接某家二线交易所时就踩过这个坑——他们文档里写的是“update”,实际发的是两条事件。嗯,这里一定要看文档,更要看实际数据。

4.3 序列号:数据对齐的命根子

增量事件最大的问题是什么?乱序

网络传输不是完美的。你可能会先收到第100号事件,再收到第99号。如果你直接按收到顺序处理,订单簿就乱了。

所以,每个增量事件都必须带一个单调递增的序列号

我曾经踩过的坑: 某次对接一家海外交易所,他们的序列号是全局唯一的,但重启后会重置。我没做序列号连续性检查,结果重启后订单簿直接错位,亏了一笔。从那以后,我每次都会检查序列号是否连续,不连续就请求全量快照重新同步。

序列号对齐的逻辑其实很简单:

  1. 本地维护一个 last_seq,记录最后处理的事件序列号。
  2. 收到新事件时,检查其序列号是否等于 last_seq + 1
  3. 如果相等,正常处理;如果大于,说明有事件丢失,请求全量快照。
  4. 如果小于,说明是重复事件,直接丢弃。

代码实现大概长这样:

def process_incremental(event):
    global last_seq
    if event.seq == last_seq + 1:
        apply_to_orderbook(event)
        last_seq = event.seq
    elif event.seq > last_seq + 1:
        request_snapshot()  # 丢了事件,重新同步
    else:
        pass  # 重复事件,忽略

你可能会问:为什么不直接缓存乱序事件,等前面的到了再处理?

理论上可以,但实际中我很少这么做。因为缓存逻辑会增加复杂度,而且一旦网络抖动严重,缓存会越积越多。还不如直接拉快照来得干脆。

4.4 数据对齐的完整流程

好了,我们把上面这些串起来,看看一个完整的增量更新流程长什么样。

下面这张图是我自己画的,帮你理清思路:

增量更新与数据对齐流程 1. 全量快照 初始化订单簿 2. 接收增量事件 带序列号 3. 序列号检查 是否连续? 连续 4. 应用更新到订单簿 更新 last_seq 不连续 5. 请求全量快照 重新同步 循环:全量快照 → 增量事件 → 序列号检查 → 应用更新 序列号不连续时,立即回退到全量快照

这个流程我用了好多年,基本没出过问题。核心就一句话:信任序列号,别信任顺序

4.5 实战中的几个细节

最后,分享几个我在实战中积累的小经验:

  • 序列号最好是全局单调递增的,而不是每个价格档位单独递增。否则对齐逻辑会复杂很多。
  • 收到全量快照后,要清空本地订单簿再重建,而不是在原有基础上叠加。我见过有人直接叠加,结果重复数据越积越多。
  • 增量事件里如果包含“修改”操作,一定要确认修改前后的数量。有些交易所的“修改”事件只给新数量,不给旧数量,你得自己算差值。
  • 定期请求全量快照做“健康检查”。即使序列号一直连续,也可能因为程序bug导致数据偏差。我习惯每5分钟拉一次快照做对比。
小技巧: 如果你对接的是WebSocket行情,可以在本地维护一个环形缓冲区,缓存最近1000条增量事件。一旦发现序列号不连续,可以尝试从缓冲区里找找有没有漏掉的。这样能减少全量快照的请求次数。

好了,增量更新这块就聊到这儿。说白了,就是“用序列号管住乱序,用快照兜底”。你只要把这两点做到位,订单簿的实时性基本就有保障了。


专注资料整理