一、订单簿基础:数据结构、价格-时间优先与Level 1/Level 2

做量化交易,订单簿是绕不开的核心。说白了,它就是交易所用来记录所有挂单的电子账本。我刚开始接触这个领域时,觉得订单簿不就是个价格列表吗?后来在实盘中被狠狠教育过——数据结构选不对,延迟直接爆炸。

1.1 订单簿的核心数据结构

订单簿通常由两个主要部分组成:买盘(Bid)卖盘(Ask)。每个盘口下,又按价格和时间组织订单。

我个人习惯用跳表(Skip List)红黑树来维护价格层级。为什么?因为订单簿的核心操作是:插入、删除、查询最优价格。这些操作在红黑树里都是 O(log n) 的复杂度。你想想看,如果每秒有几十万笔订单进来,用链表遍历肯定扛不住。

核心数据结构对比:

数据结构 插入 删除 查询最优 适用场景
红黑树 O(log n) O(log n) O(1) 通用,我最常用
跳表 O(log n) O(log n) O(1) 并发场景更友好
数组+链表 O(n) O(n) O(1) 小规模,不推荐

嗯,这里要注意:价格-时间优先原则决定了我们的排序逻辑。价格优先是第一位的——买价高的排前面,卖价低的排前面。价格相同的情况下,先来的订单先成交。这个原则看似简单,但在实现时很容易踩坑。

1.2 价格-时间优先原则的工程实现

我在项目中遇到过一个问题:同一个价格层级下,订单按时间排序。如果用红黑树,价格作为第一key,时间戳作为第二key。但时间戳的精度问题让我吃过亏——微秒级的时间戳在极端行情下可能重复。

我的解决方案是:用全局递增的序列号代替时间戳。每个订单进来时,分配一个唯一的ID。这样既保证了时间顺序,又避免了时钟回拨的问题。

// 伪代码示例:订单簿核心结构
struct Order {
    uint64_t order_id;    // 全局递增ID
    double price;
    uint32_t quantity;
    uint8_t side;         // 0=buy, 1=sell
};

// 价格层级节点
struct PriceLevel {
    double price;
    std::deque<Order> orders;  // 同一价格下的订单队列
};

// 订单簿主结构
class OrderBook {
    std::map<double, PriceLevel, std::greater<double>> bids;  // 买盘,价格降序
    std::map<double, PriceLevel, std::less<double>> asks;    // 卖盘,价格升序
};

你可能会问:为什么用 std::map 而不是 std::unordered_map?因为我们需要按价格顺序遍历,红黑树天然支持有序性。我曾经试过用哈希表,结果在生成快照时排序开销巨大,得不偿失。

1.3 Level 1 与 Level 2 数据的区别

这个区别,说白了就是信息粒度的不同

  • Level 1:只告诉你最优买卖价和成交量。就像看冰山的一角。
  • Level 2:展示多个价格层级的挂单情况。能看到冰山水面下的部分。

我记得有一次做策略回测,只用了Level 1数据,结果实盘时滑点大得惊人。为什么?因为Level 1看不到深度——你以为最优价有100手,实际上可能只有10手是真实的,剩下的都是虚假挂单。

实战建议:做高频策略一定要用Level 2数据。但要注意,Level 2的数据量是Level 1的几十倍。我见过有人把全量Level 2数据直接塞进内存,结果16G内存瞬间爆掉。正确的做法是:只维护前10-20个价格层级,深层的订单对价格影响很小。

避坑指南:我曾经在对接某交易所时,发现它的Level 2数据更新频率比Level 1慢。如果你用Level 2做盘口分析,一定要确认数据的时间戳对齐。否则,你看到的买卖盘可能是几百毫秒前的快照,这会导致策略误判。

1.4 订单簿重建的完整流程

订单簿重建,就是把交易所发来的增量数据(增、删、改)还原成完整的盘口快照。这个过程看似简单,但细节决定成败。

订单簿重建流程 交易所原始数据 协议解析 (FIX/Protobuf/JSON) 数据校验 (序列号/时间戳) 快照 订单簿核心引擎 插入/删除/修改 价格-时间排序 Level 1 最优买卖价 Level 2 深度快照 策略/风控/监控

从这张图可以看出,订单簿重建不是简单的数据堆砌。每一步都有优化空间:

  1. 协议解析:我建议用零拷贝技术,避免不必要的内存分配。比如用 std::string_view 代替 std::string
  2. 数据校验:检查序列号是否连续。如果发现跳号,说明数据丢了,需要重新请求快照。
  3. 核心引擎:这里是最关键的。我习惯用内存池来管理订单对象,避免频繁的 new/delete 操作。

性能关键点:订单簿重建的延迟瓶颈通常不在算法本身,而在内存分配和缓存命中率。我做过测试:用内存池优化后,重建速度提升了3倍以上。另外,尽量保证订单簿数据在CPU缓存行内对齐,减少缓存未命中。

嗯,最后说一句:订单簿重建是量化系统的基石。你花再多时间优化都不为过。我见过太多团队在策略上投入巨大,却在底层数据上栽跟头。记住:垃圾进,垃圾出。订单簿数据不准,再好的策略也是白搭。


蓝海数据掘金营,专注资料整理