第四章 订单簿数据结构:市场脉搏的数字化映射

做市引擎的核心,说白了就是跟订单簿打交道。你想想看,整个市场的买卖意愿,都浓缩在这张「价格-数量」的表格里。我个人习惯把订单簿比作市场的「心电图」——每一笔订单的进入、撤单、成交,都是市场的一次心跳。

4.1 订单簿的构成:bids 与 asks

订单簿分两边:买方和卖方。买方出价叫 bid,卖方要价叫 ask。这俩词儿你得刻在脑子里,因为后面所有的策略逻辑都绕不开它们。

举个例子,比特币当前市场是这样的:

asks (卖单):
  价格: 50002.0  数量: 1.5
  价格: 50001.5  数量: 2.0
  价格: 50001.0  数量: 0.8

bids (买单):
  价格: 50000.5  数量: 3.2
  价格: 50000.0  数量: 1.0
  价格: 49999.5  数量: 2.5

看到没?asks 从低到高排列,bids 从高到低排列。中间那个 gap 就是「买卖价差」,做市商赚的就是这个差价。我在项目中遇到过新手把 bids 和 asks 搞反,结果策略直接反向交易,亏了不少钱。嗯,这里要注意:bids 是别人愿意出的最高价,asks 是别人愿意卖的最低价

4.2 价格-数量阶梯:市场的深度画像

光看最优报价是不够的。你得知道每个价位上有多少「弹药」。这就是价格-数量阶梯,也叫市场深度。

我习惯用这样的数据结构来表示:

class OrderBookLevel:
    def __init__(self, price: float, size: float, order_count: int):
        self.price = price      # 价格
        self.size = size        # 该价位总数量
        self.count = order_count  # 该价位订单数

class OrderBook:
    def __init__(self):
        self.bids = []  # 按价格降序排列
        self.asks = []  # 按价格升序排列
        self.update_time = 0

为什么要有 order_count?我在做流动性分析时发现,同样 100 个比特币,如果分散在 50 个订单里,说明这个价位是散户堆出来的,容易被击穿;如果只有 1 个大单,那可能是机构在护盘。这个细节,很多教材不会讲。

核心指标:

  • 价差 (spread) = ask[0].price - bid[0].price
  • 中间价 (mid price) = (ask[0].price + bid[0].price) / 2
  • 深度 (depth) = 某个价格区间内的累计数量

4.3 增量更新 vs 全量快照

这是订单簿数据流里最关键的决策点。你想想看,如果每次变化都把整个订单簿发过来,那网络带宽和 CPU 都得炸。所以交易所通常提供两种模式:

特性 全量快照 增量更新
数据量 大(整个订单簿) 小(仅变化部分)
实时性 低(通常几秒一次) 高(每笔变化都推送)
可靠性 高(完整状态) 低(可能丢包导致状态不一致)
使用场景 初始化、状态恢复 日常实时更新

实际项目中,我通常的做法是:

  1. 启动时先拉一次全量快照,建立基准状态
  2. 之后订阅增量更新流,逐笔应用变化
  3. 每隔 N 秒或 N 笔更新后,再拉一次快照做校验

我曾经踩过的坑:有一次增量更新丢了一笔,导致订单簿里凭空多出来一个价位。做市策略以为出现了套利机会,疯狂下单,结果全被吃掉。从那以后,我强制要求每 100 毫秒做一次快照校验,发现不一致立即重建。

增量更新的数据格式通常长这样:

{
  "type": "snapshot",  // 或 "update"
  "bids": [["50000.5", "3.2"], ["50000.0", "1.0"]],
  "asks": [["50001.0", "0.8"], ["50001.5", "2.0"]]
}

注意:增量更新里,如果某个价位的数量为 0,表示这个价位被删除了。这个约定很重要,我见过有人忘记处理这个逻辑,导致订单簿越积越大。

4.4 订单簿的本地维护

在内存里维护订单簿,我推荐用红黑树或者跳表。Python 里可以用 sortedcontainers 库,但做高频交易时建议自己实现一个简单的跳表,因为第三方库的 GC 开销不可控。

核心操作就三个:

  • 添加/更新:某个价位上增加或减少数量
  • 删除:某个价位数量归零时移除
  • 查询:获取最优报价、某个价位的深度等

我自己的实现里,还会维护一个「累计深度」的缓存,这样计算某个价格区间的总数量时,时间复杂度能从 O(n) 降到 O(log n)。

小技巧:做市策略里,别每次都遍历整个订单簿。维护一个「最近活跃价位」的缓存,大部分时候市场只在最优报价附近波动,这样能省不少 CPU。

4.5 知识体系总览

下面这张图,是我对订单簿数据结构的理解框架。你可以把它当作本章的「思维导图」:

订单簿数据结构 bids / asks 价格-数量阶梯 增量 vs 全量 买方出价 卖方要价 市场深度 价差计算 全量快照 增量更新 实践建议:快照初始化 + 增量维护 + 定期校验 图:订单簿数据结构知识体系 关键指标:spread | mid price | depth | order imbalance 数据结构:跳表 / 红黑树 / sortedcontainers

嗯,看到这张图你应该能感受到,订单簿不只是简单的价格列表。它背后涉及数据结构的选型、网络协议的取舍、以及状态一致性的保障。我个人觉得,能把订单簿维护好,做市引擎就成功了一半。

最后说一句:别小看订单簿的本地维护。我见过不少团队,策略逻辑写得花里胡哨,结果订单簿更新慢了 1 毫秒,整个策略就废了。记住,做市是毫秒级的博弈,数据结构的效率直接决定你的竞争力


公众号:蓝海资料掘金营,微信deep3321