委托簿重建:从零开始构建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的前几层。实时更新意味着:每来一笔订单,都要重新计算这五档或十档的数据。
怎么做?很简单:
- 维护好完整的Order Book
- 每次有变化时,取前N个价格档位
- 每个档位汇总该价格上的总委托量
嗯,这里要注意:不是每次变化都要全量推送。那样带宽扛不住。实际做法是增量更新——只推送变化的那一档。
| 档位 | 价格 | 委托量 | 委托笔数 |
|---|---|---|---|
| 买一 | 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。一份是「当前快照」,一份是「增量缓存」。每100毫秒同步一次。这样即使某次更新出问题,也能从快照恢复,不会丢失数据。
好了,关于委托簿重建的核心内容就这些。说白了,就是两个队列 + 两条规则 + 一套增量更新机制。代码实现上,数据结构选对了,性能就不是问题。剩下的,就是实盘里那些坑,一个一个踩过去就熟了。