3、订单簿构建:Level2数据解析、订单簿数据结构设计、增量更新与快照
做市商的核心工作,说白了就是跟订单簿打交道。你每天盯着的那个买卖盘口,背后就是订单簿在实时运转。我个人习惯把订单簿比作一个「战场地图」——你得知道敌人在哪、兵力多少、动向如何,才能排兵布阵。
这一章,我们就来拆解这个地图是怎么画出来的。
3.1 Level2数据解析:别被原始数据吓到
交易所给的Level2数据,其实是一堆结构化的二进制流或者JSON。我刚开始接触的时候,看着那密密麻麻的字段也头疼。但后来发现,核心就三样东西:价格、数量、方向。
拿沪深交易所的Level2快照举例,它长这样:
{
"timestamp": 1628000000000,
"bids": [
[10.01, 1000],
[10.00, 2000],
[9.99, 1500]
],
"asks": [
[10.02, 800],
[10.03, 1200],
[10.04, 900]
]
}
bids是买单,asks是卖单。每个数组里,第一个数是价格,第二个数是挂单量。嗯,这里要注意:价格精度和数量精度,不同交易所可能不一样。我在项目中遇到过,某次因为没处理好精度,导致订单簿价格错位,差点把策略搞崩了。
3.2 订单簿数据结构设计:选对容器,事半功倍
数据结构选不好,后面增量更新就是灾难。你想想看,每秒几千笔订单进来,如果每次都要全量排序,性能肯定扛不住。
我个人推荐用红黑树或者跳表来实现订单簿。为什么?因为这两种结构天然支持有序插入和删除,时间复杂度都是O(log n)。
下面是我常用的一个简化版设计:
class OrderBook {
constructor() {
this.bids = new Map(); // 价格 -> 数量
this.asks = new Map(); // 价格 -> 数量
this.bestBid = null; // 最优买价
this.bestAsk = null; // 最优卖价
}
// 更新某个价格档位的挂单量
update(side, price, size) {
const book = side === 'buy' ? this.bids : this.asks;
if (size === 0) {
book.delete(price); // 撤单
} else {
book.set(price, size); // 新增或修改
}
this.updateBestPrice(side);
}
}
这里用Map来存储,其实是为了快速查找。但如果你需要频繁获取「最优五档」或者「前N档」,那还是得用有序结构。我建议用SortedMap或者自己维护一个双向链表。
sortedcontainers库里的SortedDict。C++的话,直接用std::map。Java有TreeMap。别自己造轮子,除非你想练手。
3.3 增量更新与快照:别让数据「打架」
增量更新,就是只告诉你「变了什么」,而不是「全部是什么」。比如交易所发来一条消息:
{
"type": "update",
"side": "buy",
"price": 10.01,
"size": 500
}
意思是:买一价10.01,挂单量变成500。注意,是覆盖,不是累加。我之前就犯过这个错,以为增量是「加500」,结果订单簿总量越算越大,跟实际盘口对不上。
快照呢?就是交易所每隔一段时间(比如500ms)给你一份完整的订单簿。增量更新和快照要配合使用:
- 启动时:先拿快照,建立初始订单簿。
- 运行中:用增量更新来维护。
- 异常时:如果发现数据对不上,重新请求快照。
我画了一张流程图,帮你理清这个逻辑:
看到没?核心就是序列号校验。每条增量消息都带一个序列号,你本地维护一个lastSeq。如果新消息的seq != lastSeq + 1,说明中间丢了数据,这时候必须重新请求快照,否则订单簿就「脏」了。
3.4 实战中的几个坑
讲几个我踩过的坑,你遇到了可以少走弯路:
- 时间戳对齐:交易所的时钟和你的本地时钟可能有偏差。我建议用交易所的时间戳,别用本地时间。
- 重复消息:网络重传可能导致同一条增量消息收到两次。幂等处理很重要,我一般用seq去重。
- 内存管理:订单簿如果长时间运行,Map里的key会越来越多。定期清理「僵尸档位」(比如挂单量为0且超过一定时间没变动的价格),能省不少内存。
| 场景 | 推荐做法 | 不推荐做法 |
|---|---|---|
| 价格存储 | 用整数(如乘以10000) | 直接用浮点数 |
| 数据结构 | 红黑树 / 跳表 | 数组 + 每次排序 |
| 增量更新 | 覆盖模式(set) | 累加模式(add) |
| 异常恢复 | 重新请求快照 | 尝试本地修复 |
好了,订单簿这块的核心逻辑就这些。说白了,就是数据结构选对、增量更新做对、异常处理到位。你把这三点拿捏住,订单簿这块基本就稳了。