订单簿重建:数据结构、增量更新与快照重建

做市商的核心工作,说白了就是跟订单簿打交道。你想想看,我们每天盯着盘口,看到的那些买一卖一、深度数据,背后都是订单簿在支撑。今天我就带你把这个底层逻辑彻底搞明白。

订单簿的数据结构设计

我个人习惯用字典来存储订单簿。为什么?因为订单簿本质上就是个价格到数量的映射。买盘和卖盘分开存,结构清晰,查询也快。

class OrderBook:
    def __init__(self):
        self.bids = {}  # 买盘: {price: volume}
        self.asks = {}  # 卖盘: {price: volume}
        self.timestamp = None
        self.last_update_id = None

这里有个细节要注意:价格用浮点数做key?千万别。我在项目中遇到过浮点数精度问题,两个看起来一样的价格,字典里却当成两个不同的key。我建议用整数,比如把价格乘以10000再存。

避坑指南:我曾经因为浮点数精度问题,导致订单簿重建时多出来一堆"幽灵"档位。排查了整整一个下午才发现是key的问题。从那以后,所有价格我都用整数存储。

增量更新 vs 快照重建

交易所推送数据有两种方式:快照和增量。快照就是给你当前订单簿的全量数据,增量就是告诉你"刚才发生了什么变化"。

增量更新的逻辑其实很简单:

def apply_update(self, update):
    side = update['side']  # 'bid' or 'ask'
    price = update['price']
    volume = update['volume']
    
    if side == 'bid':
        if volume == 0:
            # 删除这个价格档位
            self.bids.pop(price, None)
        else:
            self.bids[price] = volume
    else:
        if volume == 0:
            self.asks.pop(price, None)
        else:
            self.asks[price] = volume

嗯,这里要注意:volume为0表示删除这个档位。这是交易所的通用约定,别搞反了。

快照重建的时机

为什么需要快照重建?因为增量更新可能会丢数据。网络抖动、程序重启、或者增量序列号对不上,都会导致订单簿状态不一致。

我一般这样设计重建策略:

  • 每5分钟强制拉一次快照
  • 检测到增量序列号跳跃时立即重建
  • 程序启动时先拉快照再订阅增量
核心原则:增量更新保证实时性,快照重建保证一致性。两者配合使用,才能既快又准。

订单簿深度计算

深度计算,说白了就是算"在某个价格区间内,有多少买单和卖单"。

def calculate_depth(self, levels=10):
    bid_prices = sorted(self.bids.keys(), reverse=True)[:levels]
    ask_prices = sorted(self.asks.keys())[:levels]
    
    bid_depth = sum(self.bids[p] for p in bid_prices)
    ask_depth = sum(self.asks[p] for p in ask_prices)
    
    return {
        'bid_depth': bid_depth,
        'ask_depth': ask_depth,
        'bid_prices': bid_prices,
        'ask_prices': ask_prices
    }

这里有个小技巧:买盘按价格降序排列,卖盘按价格升序排列。这样取前N个就是离当前价格最近的档位。

买卖盘口分析

盘口分析不只是看买一卖一的价格差。我通常会关注这几个指标:

指标 计算方式 含义
买卖价差 ask_price_1 - bid_price_1 市场流动性最直接的体现
深度不平衡 (bid_depth - ask_depth) / (bid_depth + ask_depth) 正数表示买方力量强,负数反之
加权平均价格 Σ(price * volume) / Σvolume 大单成交的参考价格

举个例子,如果深度不平衡突然从0.2变成-0.3,说明卖压突然增大。这时候我可能会调整做市策略,减少买单挂单量。

实战经验:我在做BTC永续合约做市时,发现深度不平衡指标比单纯看价格更有参考价值。有时候价格没怎么动,但深度不平衡已经悄悄变化了,这就是机会信号。

订单簿的完整生命周期

让我用一张图来展示订单簿从初始化到持续更新的完整流程:

订单簿生命周期流程图 初始化空订单簿 拉取全量快照 订阅增量更新流 正常增量更新 检测到异常 订单簿状态一致 触发快照重建

这张图把整个流程串起来了。你想想看,从初始化到持续运行,核心就是快照和增量的配合。一旦发现异常,马上回到快照那一步重新来。

性能优化建议

订单簿操作频率很高,性能优化很重要。我分享几个经验:

  • 用OrderedDict:Python 3.7+的字典已经有序,但OrderedDict在频繁增删时性能更好
  • 预分配内存:如果知道最大档位数,提前分配好内存空间
  • 批量处理:增量更新可以攒一批再处理,减少锁竞争
性能数据:在我之前的项目中,优化后的订单簿处理延迟从平均50微秒降到了8微秒。别小看这几十微秒,在高频场景下就是天壤之别。

嗯,订单簿重建这块内容就这些。核心就是数据结构要选对,增量更新和快照重建要配合好,深度计算和盘口分析要能落地到策略里。你把这些搞明白了,做市策略的基础就扎实了。

最后提醒一句:别光看代码,一定要自己动手跑一遍。我在教学时发现,很多同学看懂了但写不出来。代码这东西,敲一遍比看十遍都管用。

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