1. 订单簿基础:数据结构、买卖盘口与核心原则

做量化交易系统,订单簿是绕不开的核心。说白了,它就是交易所用来记录所有未成交订单的电子账本。我刚开始接触这个领域时,觉得订单簿不就是个价格列表吗?后来在实盘项目中吃过亏,才明白里面的门道有多深。

1.1 订单簿的数据结构

订单簿本质上是一个双向链表红黑树结构。我个人习惯用红黑树,因为它的插入、删除和查询都是 O(log n) 的复杂度,在高频场景下非常稳定。

每个订单节点通常包含以下字段:

  • 订单ID:全局唯一标识
  • 价格:浮点数或定点数(我建议用定点数,避免浮点精度问题)
  • 数量:未成交的剩余数量
  • 时间戳:订单到达时间,精确到微秒
  • 方向:买或卖
  • 状态:活跃、部分成交、已撤销

核心要点:订单簿的数据结构必须支持快速查找最优价格、快速插入新订单、快速撤销订单。这三个操作是订单簿重建的基础。

我在项目中遇到过一个问题:某个交易所的订单簿重建时,因为用了链表结构,导致撤销操作需要遍历整个链表,延迟飙升到毫秒级。后来改成红黑树,延迟降到了微秒级。嗯,这里要注意,选对数据结构能省很多事。

1.2 买卖盘口

买卖盘口,就是订单簿的「门面」。买盘(Bid)是买家愿意出的最高价,卖盘(Ask)是卖家愿意接受的最低价。两者之间的差价叫「价差」(Spread)。

一个典型的盘口结构长这样:

价格 买盘数量 卖盘数量 价格
100.50 1,200 800 100.60
100.40 2,500 1,100 100.70
100.30 3,000 1,500 100.80

你想想看,如果买盘和卖盘的价格差距很大,说明市场流动性差。我记得有一次做回测,发现某个小币种的价差经常超过 1%,结果策略在实盘时根本跑不动——因为滑点直接把利润吃掉了。

实战技巧:在订单簿重建时,一定要同时维护买盘和卖盘两个红黑树。买盘按价格降序排列,卖盘按价格升序排列。这样取最优价格就是 O(1) 的操作。

1.3 价格优先与时间优先原则

这是订单簿的「宪法」。所有交易所都遵循这个原则,只是实现细节略有不同。

价格优先:买单价格高的优先成交,卖单价格低的优先成交。说白了,谁出的价更「狠」,谁就先成交。

时间优先:在价格相同的情况下,先到订单簿的订单优先成交。这个原则保证了公平性。

为什么会这样设计?因为如果只按价格优先,那大户就可以通过频繁撤单、重下单来「插队」。加上时间优先,就杜绝了这种操作。

我曾经在开发一个做市商策略时,忽略了时间优先的细节。结果在价格相同时,我的订单总是排在后面,导致成交率极低。后来我加了一个「提前下单」的逻辑,才把成交率提上来。嗯,这个坑我踩过,你们别踩。

注意:在订单簿重建时,时间戳的精度至关重要。如果两个订单的时间戳相同,需要根据订单ID的先后顺序来决定优先级。我建议使用纳秒级时间戳,并配合全局递增的序列号。

1.4 订单簿的核心逻辑流程图

下面这张图展示了订单簿的核心工作流程。我习惯用 SVG 画这种图,清晰直观。

订单簿核心工作流程 新订单到达 买卖方向 买盘:插入买盘树 卖盘:插入卖盘树 价格匹配检查 成交处理 / 更新盘口

这张图展示了订单从到达、判断方向、插入对应树结构、到价格匹配和成交的完整流程。我在做订单簿重建时,就是按照这个流程来设计容错机制的。

1.5 实际项目中的避坑指南

我曾经在对接某个交易所的行情数据时,发现他们的订单簿快照和增量数据对不上。排查了半天,原来是他们的时间戳精度不够,导致订单顺序错乱。从那以后,我养成了一个习惯:

  • 所有订单必须携带纳秒级时间戳
  • 在订单簿重建时,先做时间戳校验
  • 如果发现时间戳异常,直接丢弃该订单,请求重新快照

我的建议:在订单簿重建时,不要完全信任增量数据。最好每隔一段时间(比如 5 分钟)请求一次全量快照,然后用增量数据做增量更新。这样即使增量数据有丢失,也能通过快照恢复。

嗯,订单簿的基础就这些。数据结构选对、盘口维护好、原则理解透,后面的容错和高可用设计才能站得住脚。


专注资料整理