3、订单簿数据结构:价格优先、时间优先原则,Level 1/2/3 数据层级
好,咱们今天聊聊订单簿的数据结构。这东西听起来有点枯燥,但说白了,它就是交易所用来撮合买卖双方的那张「记账本」。我做了这么多年量化,可以负责任地告诉你——不理解订单簿结构,你写的策略就是空中楼阁。
3.1 价格优先、时间优先:撮合的铁律
先讲规则。交易所撮合订单,遵循两个基本原则:
- 价格优先:买单出价高的排前面,卖单出价低的排前面。
- 时间优先:价格相同的情况下,先来的订单排前面。
嗯,就这么简单。但实际执行起来,细节挺多。
核心逻辑:买单从高往低排,卖单从低往高排。两边一碰,能成交的就立刻成交。
举个例子。假设当前订单簿是这样的:
| 方向 | 价格 | 数量 | 时间戳 |
|---|---|---|---|
| 卖单 | 100.05 | 200 | 09:30:01.001 |
| 卖单 | 100.04 | 100 | 09:30:00.500 |
| 卖单 | 100.03 | 150 | 09:30:00.200 |
| —— | —— | —— | —— |
| 买单 | 100.02 | 80 | 09:30:00.100 |
| 买单 | 100.01 | 300 | 09:30:00.050 |
| 买单 | 100.00 | 500 | 09:29:59.800 |
这时候来了一个市价买单,要买300股。它会怎么成交?
先打掉最便宜的卖单——100.03的150股。还剩150股,继续打100.04的100股。还剩50股,再打100.05的50股。成交完毕。
你看,价格优先体现在:卖单里价格最低的先成交。时间优先体现在:如果两个卖单价格一样,先挂单的先成交。
我的经验:做高频策略时,时间优先这个细节特别重要。我曾经因为没处理好订单的时间戳精度,导致模拟盘和实盘结果差了十万八千里。后来才发现,交易所的时间戳是纳秒级的,而我用的数据只精确到毫秒。
3.2 订单簿的数据结构:用啥来存?
订单簿本质上是一个价格-数量映射。每个价格档位上,挂了多少单,总量是多少。
我个人习惯用红黑树来实现。为什么?
- 插入和删除都是 O(log n) 复杂度
- 天然有序,可以快速找到最优买卖价
- 支持范围查询,比如查某个价格区间内的总挂单量
代码实现大概长这样:
// 用C++实现一个简单的订单簿
class OrderBook {
private:
// 买单:价格从高到低排序
std::map<double, OrderInfo, std::greater<double>> bids;
// 卖单:价格从低到高排序
std::map<double, OrderInfo, std::less<double>> asks;
public:
void addOrder(Order order) {
if (order.side == BUY) {
bids[order.price].quantity += order.quantity;
} else {
asks[order.price].quantity += order.quantity;
}
}
void removeOrder(Order order) {
// 从对应价格档位减去数量
// 如果数量归零,删除该价格档位
}
double getBestBid() {
return bids.empty() ? 0 : bids.begin()->first;
}
double getBestAsk() {
return asks.empty() ? 0 : asks.begin()->first;
}
};
当然,Python里可以用 sortedcontainers 库,或者直接用 heapq 维护优先队列。但说实话,做高频交易的话,还是C++更靠谱。
注意:千万别用普通的哈希表来存订单簿。虽然查询快,但你要频繁获取「最优价格」,哈希表没法直接做到有序遍历。我曾经见过有人用Python的dict硬搞,结果每次都要排序,性能惨不忍睹。
3.3 Level 1/2/3 数据层级:你到底需要多细?
交易所提供的数据,一般分三个层级。我刚开始做量化时,傻傻分不清,后来吃了不少亏。
Level 1:最粗的行情快照
只包含:
- 最新成交价
- 最高价、最低价
- 开盘价、收盘价
- 成交量、成交额
- 最优买卖价(即买一、卖一)
说白了,就是给你看一眼当前市场的大致情况。做长线策略够用了,但做短线?嗯,远远不够。
Level 2:深度行情
这个就详细多了。它包含:
- 买卖各档位的价格和挂单量(通常是5档、10档或50档)
- 逐笔成交数据
- 市场统计信息
我大部分策略都用Level 2数据。它能让你看到「订单簿的轮廓」——比如买一挂了1000手,但买二只有50手,说明这个支撑位其实很脆弱。
Level 3:全量订单簿
这是最细的数据。它包含:
- 所有未成交订单的详细信息
- 每个订单的ID、价格、数量、时间戳
- 订单的增删改操作流
说白了,Level 3能让你重建整个订单簿的完整历史。每一笔挂单、撤单、成交,你都能追踪到。
关键区别:Level 2告诉你「当前价格档位上有多少单」,Level 3告诉你「这些单是谁挂的、什么时候挂的、有没有撤单」。Level 3的数据量是Level 2的几十倍甚至上百倍。
我举个例子。假设你看到买一挂了1000手,但下一秒突然撤单了。用Level 2数据,你只能看到「买一消失了」。用Level 3数据,你能看到「某个机构账户在09:30:00.001挂了1000手,又在09:30:00.500撤单了」——这信息量完全不一样。
避坑指南:我曾经为了省带宽费,只订阅Level 2数据做高频策略。结果发现策略在模拟盘上跑得好好的,一上实盘就亏钱。后来排查才发现,有些大单在Level 2里根本看不到——它们挂在更深的档位,但Level 2只给你看前5档。从那以后,做高频策略我至少用Level 2+逐笔成交,有条件就上Level 3。
3.4 三种层级的对比
| 特性 | Level 1 | Level 2 | Level 3 |
|---|---|---|---|
| 数据量 | 极小 | 中等 | 极大 |
| 更新频率 | 秒级 | 毫秒级 | 微秒级 |
| 包含信息 | 最优买卖价 | 多档深度 | 全量订单 |
| 适用策略 | 长线、低频 | 中频、短线 | 高频、做市 |
| 成本 | 免费或极低 | 中等 | 昂贵 |
3.5 一张图看懂订单簿结构
下面我用SVG画了一张图,把订单簿的核心逻辑展示出来。你看完应该就明白了。
这张图把整个流程串起来了。买单和卖单分别按价格排序,撮合引擎在中间做匹配。数据层级从粗到细,你根据自己的策略需求选就行。
3.6 实际开发中的选择建议
说了这么多,到底该用哪个层级?我根据经验给个建议:
- 做日频、周频策略:Level 1就够了。别浪费钱买深度数据。
- 做日内短线:至少Level 2,最好加上逐笔成交。你能看到订单簿的「呼吸」。
- 做高频、做市商:必须上Level 3。没有全量订单簿,你就是在盲人摸象。
再次提醒:Level 3数据不是你想买就能买的。很多交易所要求你提供做市商资质,或者最低交易量。而且数据带宽消耗巨大,我见过有人买了Level 3数据,结果服务器带宽被打满,反而丢了行情。嗯,这又是一个坑。
好了,订单簿的数据结构就聊到这儿。记住一句话:数据结构决定了你能看到什么,数据层级决定了你能看到多细。选对了,事半功倍;选错了,事倍功半。
公众号:蓝海资料掘金营,微信deep3321