我做行情分析有一段时间了,发现订单簿的数据其实比我们想象的要复杂。很多人用加密货币api的时候,只拉全量快照或者盯着增量消息,其实都不够。全量快照频繁拉取占用带宽,增量推送如果没有本地快照做基础,又会出现数据不完整的情况。把增量消息和本地快照结合起来,才能得到真正可靠的实时深度。
为什么要合并快照和增量
增量推送的核心是“更新现有状态”,它是基于某个快照的变化。如果直接用增量而没有初始快照,就可能出现深度表不完整的情况。我的做法是先拿一次快照作为初始状态,然后用增量消息去更新本地买卖列表,这样深度表随时都是完整的,延迟也控制得住。
本地快照如何组织
在代码里,我通常把买卖两边用字典保存,价格作为 key,数量作为 value。举个例子:
买/卖 | 价格 | 数量 |
买 | 30000 | 1.5 |
买 | 29950 | 2.0 |
卖 | 30050 | 0.8 |
卖 | 30100 | 3.0 |
这样做方便更新:增量消息来了,直接更新对应价格的数量或者删除数量为 0 的价格层。无需遍历整个列表,效率很高。
增量消息的处理方式
增量推送通常包含三种操作:新增、更新、删除。
新增:本地没有的价格层,直接插入字典。
更新:价格层存在,则覆盖数量。
删除:数量为 0 时,删除这个价格层。
有的交易所增量消息会乱序,但大部分都会提供 sequence 或 timestamp 字段,用来保证更新顺序。只要用这个字段校验,基本不会覆盖错数据。
以AllTick API 为例,我在测试时,把增量消息直接合并到本地快照里,代码很简单:
import websocket import json snapshot = {} def on_message(ws, message): data = json.loads(message) for update in data['orders']: price = update['price'] quantity = update['quantity'] side = update['side'] # 'buy' 或 'sell' if quantity == 0: snapshot[side].pop(price, None) else: snapshot.setdefault(side, {})[price] = quantity print(snapshot) ws = websocket.WebSocketApp("wss://api.alltick.co/crypto/orderbook", on_message=on_message) ws.run_forever()这段代码里,快照保存在本地字典里,增量消息来了直接处理就行。买卖深度一直保持最新。
性能优化建议
如果订单层级多,或者推送频繁,可以考虑两点:
只处理差异:有些消息更新了但本地没有变化的价格可以忽略,减少无效操作。
限制深度层数:大多数策略只关注前 20-50 层,更新全部深度没必要,占用资源。
另外,价格最好用 Decimal 或者统一数据类型,避免浮点精度问题,尤其是计算买卖价差时。
校验和稳定性
增量推送方便,但网络抖动可能会丢消息,所以建议定期拉一次全量快照做校验,频率根据交易量和策略决定,一般几秒到几十秒就够。这样可以保证深度表随时可靠。
我的体会
把增量和快照结合,实时深度几乎和交易所保持一致,延迟低又可靠。在可视化或者交易策略中,这种方法比只靠全量快照或者单独增量都稳。对于做行情相关开发的同学来说,理解增量消息和快照的关系,比盯着 API 文档里的参数更重要。