一、订单簿基础:数据结构、价格-时间优先与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 订单簿重建的完整流程
订单簿重建,就是把交易所发来的增量数据(增、删、改)还原成完整的盘口快照。这个过程看似简单,但细节决定成败。
从这张图可以看出,订单簿重建不是简单的数据堆砌。每一步都有优化空间:
- 协议解析:我建议用零拷贝技术,避免不必要的内存分配。比如用
std::string_view代替std::string。 - 数据校验:检查序列号是否连续。如果发现跳号,说明数据丢了,需要重新请求快照。
- 核心引擎:这里是最关键的。我习惯用内存池来管理订单对象,避免频繁的 new/delete 操作。
性能关键点:订单簿重建的延迟瓶颈通常不在算法本身,而在内存分配和缓存命中率。我做过测试:用内存池优化后,重建速度提升了3倍以上。另外,尽量保证订单簿数据在CPU缓存行内对齐,减少缓存未命中。
嗯,最后说一句:订单簿重建是量化系统的基石。你花再多时间优化都不为过。我见过太多团队在策略上投入巨大,却在底层数据上栽跟头。记住:垃圾进,垃圾出。订单簿数据不准,再好的策略也是白搭。