委托簿重建:从零开始构建Order Book

说实话,做量化交易这几年,我最大的感触就是——Order Book才是市场的灵魂。K线可以造假,成交量可以拆分,但委托簿里的每一笔挂单,都是真金白银的态度。今天咱们就来聊聊,怎么从零开始,手撸一个能用的Order Book。

Order Book的本质是什么?

说白了,就是一个价格优先、时间优先的排队系统。买方想低价买,卖方想高价卖,两边各自排队。谁出的价好,谁就排在前面。价格一样?那就看谁先来。

我刚开始做这个的时候,以为很简单。不就是两个列表嘛,一个买一个卖。结果一跑实盘数据,直接崩了。为什么?因为每秒几千笔的撤单、改单、成交,普通的数据结构根本扛不住。

核心要点:Order Book = 买方队列 + 卖方队列,每个队列按价格排序,同价按时间排序。

数据结构怎么选?

我个人习惯用红黑树或者跳表来实现。为什么?因为需要频繁的插入、删除、查找操作。数组?太慢。链表?查找太慢。平衡树?刚刚好。

不过,如果你只是做教学或者回测,用有序字典(SortedDict)也够用。Python里可以用sortedcontainers库,C++直接用std::map

# 一个简化的Order Book结构
class OrderBook:
    def __init__(self):
        self.bids = SortedDict(lambda x: -x)  # 买盘,价格降序
        self.asks = SortedDict()              # 卖盘,价格升序
        self.orders = {}                      # 订单ID -> 订单详情
    
    def add_order(self, order):
        # 价格优先:直接插入到对应价格队列
        # 时间优先:同价格下按到达顺序处理
        pass
    
    def cancel_order(self, order_id):
        # 撤单:从价格队列中移除
        pass
    
    def match(self):
        # 撮合:检查买卖双方能否成交
        pass

我的经验:千万别用Python原生的dict来存价格队列。我曾经踩过这个坑,回测时好好的,一上实盘,撤单多了直接OOM。后来改用deque + defaultdict,内存占用降了60%。

买卖盘口五档/十档的实时更新

行情软件里看到的「买一、买二、买三...」,其实就是Order Book的前几层。实时更新意味着:每来一笔订单,都要重新计算这五档或十档的数据

怎么做?很简单:

  1. 维护好完整的Order Book
  2. 每次有变化时,取前N个价格档位
  3. 每个档位汇总该价格上的总委托量

嗯,这里要注意:不是每次变化都要全量推送。那样带宽扛不住。实际做法是增量更新——只推送变化的那一档。

档位 价格 委托量 委托笔数
买一 10.00 1,200 15
买二 9.99 800 10
买三 9.98 500 8
卖一 10.01 1,000 12
卖二 10.02 600 6

避坑指南:我曾经在实盘里直接推送全量十档,结果行情一波动,每秒推送几百次,交易所直接把我限流了。后来改成「快照+增量」模式,问题解决。记住:增量更新是实盘的生命线

价格优先与时间优先原则

这个原则听起来简单,但实现起来有不少细节。我举个例子:

买方A出价10.00,买方B也出价10.00,但A比B早到1毫秒。这时候如果有一笔卖单以10.00成交,谁先成交?A先,因为时间优先。

但如果买方C出价10.01,比A和B都高。那不管C什么时候来的,C先成交,因为价格优先。

实现的时候,我建议这样处理:

def match_order(self, incoming_order):
    # 如果是买单
    if incoming_order.side == 'buy':
        # 找卖盘中价格 <= 买入价的
        for price, orders in self.asks.items():
            if price > incoming_order.price:
                break
            # 同价格下,按时间顺序处理
            for order in orders:
                # 撮合逻辑
                pass
    # 如果是卖单,同理

关键点:价格优先体现在遍历顺序上(买盘从高价到低价,卖盘从低价到高价)。时间优先体现在同价格队列的FIFO顺序上。

实战中的几个坑

  • 精度问题:股票价格是两位小数,但期货可能是四位。我建议统一用整数(最小变动价位)来存储,避免浮点误差。
  • 冰山订单:有些大单只显示一部分。处理起来要额外维护「隐藏量」字段。
  • 闪电崩盘:极端行情下,Order Book可能瞬间被清空。要做好异常处理,别让程序崩溃。

你想想看,如果这些细节没处理好,实盘跑起来会是什么后果?我见过有人因为浮点精度问题,导致撮合逻辑死循环,直接把服务器搞挂了。

一张图看懂Order Book重建流程

下面这张图,是我自己画的核心流程。你看一遍,基本就明白整个逻辑了。

Order Book 重建与更新流程 逐笔委托数据流 解析:新订单 / 撤单 / 成交 价格优先 时间优先:FIFO队列维护 输出:五档/十档盘口 + 成交明细 核心原则 1. 价格优先:高价买/低价卖优先 2. 时间优先:同价先到先得 3. 增量更新:只推送变化 ⚠️ 注意 冰山订单需特殊处理 浮点精度用整数规避 极端行情做好熔断

这张图里,从上到下就是一次完整的委托处理流程。你仔细看「价格优先」那个菱形判断框——它决定了订单该往哪个队列塞。后面的「时间优先」队列,保证了同价订单的公平性。

我的小技巧:实盘里,我会在内存里维护两份Order Book。一份是「当前快照」,一份是「增量缓存」。每100毫秒同步一次。这样即使某次更新出问题,也能从快照恢复,不会丢失数据。

好了,关于委托簿重建的核心内容就这些。说白了,就是两个队列 + 两条规则 + 一套增量更新机制。代码实现上,数据结构选对了,性能就不是问题。剩下的,就是实盘里那些坑,一个一个踩过去就熟了。

专注资料整理