4、订单簿更新机制:增量更新 vs 快照更新,以及 Websocket 订阅
做量化交易,订单簿就是你的眼睛。眼睛要是花了,策略再牛也白搭。
今天咱们聊聊订单簿怎么更新。说白了,就是交易所怎么把最新的买卖盘口数据推给你。这里有两种主流方式:增量更新和快照更新。我个人习惯把前者比作「打补丁」,后者比作「重装系统」。
4.1 快照更新:简单粗暴,但费流量
快照更新,就是交易所每隔一段时间,把整个订单簿的深度数据一次性发给你。
比如每100毫秒,你收到一个完整的买卖10档数据。这种方式实现简单,你不需要维护本地状态,拿到数据直接用就行。
但问题也很明显——数据量巨大。你想想看,如果每秒发10次快照,每次传输几千字节,一天下来流量惊人。而且高频场景下,100毫秒的延迟可能已经错过了最佳交易时机。
4.2 增量更新:轻量高效,但需要维护状态
增量更新就聪明多了。交易所只告诉你「变了什么」,而不是「全部是什么」。
比如某笔订单被吃掉了,交易所只发一条消息:「价格100.5的卖单,数量从10变成5」。你本地维护一个订单簿副本,收到消息后更新对应位置即可。
这样做的好处是带宽占用低、延迟小。但代价是——你得自己维护订单簿状态,而且必须保证增量消息不丢、不乱序。
我曾经踩过一个坑:某次Websocket断线重连后,没重新拉快照,直接收增量。结果本地订单簿和交易所对不上,策略疯狂报错。嗯,从那以后我学乖了——断线重连必须重新拉快照。
4.3 增量更新的数据结构
增量消息通常长这样:
{
"type": "incremental",
"timestamp": 1699000000123,
"bids": [
[100.5, 10], // 价格100.5,买一数量10
[100.4, 0] // 数量为0表示删除该档位
],
"asks": [
[101.0, 5],
[101.2, 0]
]
}
注意看,数量为0表示该价格档位被清空。这是增量更新里最常见的「删除」操作。
我个人习惯在本地维护一个OrderBook类,用红黑树或跳表来存储价格档位。Python里可以用sortedcontainers库,C++用std::map,Java用TreeMap。
4.4 Websocket 订阅:实时数据的通道
不管是快照还是增量,都得靠Websocket来传输。Websocket是长连接,交易所可以主动推数据给你,不用你轮询。
订阅流程一般是三步:
- 建立连接:用Websocket客户端连上交易所的ws地址
- 发送订阅消息:告诉交易所你要订阅哪个交易对的订单簿
- 接收数据:交易所开始推送快照或增量
举个例子,订阅币安的BTC/USDT订单簿:
// 订阅消息
{
"method": "SUBSCRIBE",
"params": [
"btcusdt@depth20@100ms" // 每100ms推送一次20档快照
],
"id": 1
}
币安同时支持快照和增量两种模式。我个人建议:低频策略用快照,高频策略用增量。
4.5 增量 vs 快照:怎么选?
直接上对比表:
| 对比维度 | 快照更新 | 增量更新 |
|---|---|---|
| 带宽占用 | 高 | 低 |
| 实现复杂度 | 低 | 高 |
| 延迟 | 较高(取决于推送间隔) | 低(实时推送变化) |
| 断线重连 | 简单,直接重新拉 | 复杂,需要重新拉快照+增量 |
| 适用场景 | 低频策略、数据回放 | 高频策略、实时交易 |
你想想看,如果你的策略是看1分钟K线做交易,快照完全够用。但如果你是做市商,每秒钟要更新几十次报价,那必须用增量。
4.6 实战中的避坑指南
最后分享几个我踩过的坑:
- 乱序问题:Websocket消息可能乱序到达。我习惯在每条消息里加一个序列号,本地做排序校验。
- 心跳检测:交易所通常每30秒发一次ping。如果连续3次没收到pong,主动重连。
- 数据校验:每收到1000条增量后,主动拉一次快照做对比。如果偏差超过阈值,报警并重连。
- 内存管理:订单簿的档位可能很多(比如100档)。用数组存的话,记得预分配内存,避免频繁扩容。
4.7 知识体系图
下面这张图总结了订单簿更新的核心逻辑:
从图上可以看得很清楚:快照是「全量替换」,增量是「局部更新」。两者最终都汇聚到本地订单簿状态上。
好了,关于订单簿更新机制就聊这么多。记住一句话:快照保底,增量提效,Websocket是桥梁。下次实盘的时候,记得先拉快照再收增量,别像我一样踩坑。