数据源排查:交易所WebSocket/Feed延迟、数据丢包与重连机制、多源数据对齐
做订单簿重建,第一步不是写代码,而是搞清楚你的数据到底从哪来、怎么来、来的时候有没有「偷工减料」。我见过太多团队,花了几周时间调算法,最后发现是数据源本身就有问题——白忙一场。
说白了,数据源就是整个系统的地基。地基歪了,上面盖多漂亮的房子都没用。这一节,咱们就聊聊怎么排查数据源的问题。
交易所WebSocket/Feed延迟
先说说延迟。交易所的行情数据,通常通过两种方式推送:WebSocket 和 专用数据 Feed(比如 FIX/ITCH 协议)。
WebSocket 方便,但延迟相对高一些。专用 Feed 快,但接入成本也高。我个人习惯,做高频策略必须用专用 Feed,做中低频的话 WebSocket 也够用。
但不管用哪种,延迟都是绕不开的坎。你想想看,你的订单簿重建速度再快,如果数据本身就已经晚了 10 毫秒,那重建出来的订单簿也是「过去时」。
怎么排查延迟?我一般分三步走:
- 本地打时间戳:数据一到达就立刻打上本地时间戳,跟交易所的时间戳做对比。
- 观察延迟波动:延迟稳定不可怕,可怕的是忽高忽低。波动大说明网络或者交易所本身有问题。
- 对比不同数据源:用两个独立的数据源互相验证,看同一笔交易的到达时间差。
关键指标:对于高频交易,WebSocket 延迟通常要求在 1-5 毫秒以内,专用 Feed 要求在 100 微秒以内。如果超过这个范围,你的订单簿重建基本就是「看图说话」了。
数据丢包与重连机制
丢包,这才是真正的噩梦。我在项目中遇到过好几次,订单簿重建到一半,突然发现中间少了几笔交易。结果呢?整个订单簿的价差、深度全错了。
为什么会丢包?原因很多:网络拥堵、交易所限流、WebSocket 连接不稳定……但不管什么原因,你的系统必须能感知到丢包,并且有能力恢复。
这里我分享一个我自己用的重连机制设计思路:
# 伪代码:带序列号检测的 WebSocket 重连
class OrderBookFeed:
def __init__(self):
self.last_seq = 0
self.reconnect_attempts = 0
self.max_attempts = 5
def on_message(self, msg):
current_seq = msg['sequence']
if current_seq != self.last_seq + 1:
# 检测到丢包
self.request_snapshot() # 请求全量快照
self.rebuild_orderbook()
self.last_seq = current_seq
def on_disconnect(self):
while self.reconnect_attempts < self.max_attempts:
if self.try_reconnect():
self.request_snapshot()
break
time.sleep(2 ** self.reconnect_attempts) # 指数退避
self.reconnect_attempts += 1
嗯,这里要注意:重连之后一定要请求全量快照,不能只靠增量数据恢复。我曾经吃过这个亏——重连后只补了增量,结果订单簿一直对不上,排查了整整两天才发现是快照没更新。
避坑指南:我曾经遇到过一个交易所,它的 WebSocket 在重连后不会主动推送全量快照,需要你手动请求。如果你没做这个逻辑,那重连后的所有数据都是「空中楼阁」。
多源数据对齐
做量化交易,尤其是做套利策略,经常需要同时接入多个交易所的数据。这时候问题就来了:每个交易所的时间戳不一样,序列号格式不一样,甚至数据推送的节奏都不一样。怎么对齐?
我个人的做法是:统一时间基准。所有数据到达后,立刻打上本地 NTP 同步的时间戳。然后以这个本地时间戳为基准,做数据对齐。
具体来说,分三步:
- 时间戳归一化:把各个交易所的时间戳统一转换成 UTC 纳秒级。
- 序列号映射:建立不同交易所之间的序列号映射关系,方便交叉验证。
- 延迟补偿:根据历史延迟数据,对每个数据源做动态延迟补偿。
小技巧:我习惯在数据对齐时,额外记录一个「置信度」字段。如果某个数据源的延迟波动大,置信度就低,后续策略可以降低它的权重。
下面这张图,是我自己总结的多源数据对齐流程,你可以参考一下:
最后说一句:数据源排查这件事,没有捷径。你得一条一条消息去对,一个时间戳一个时间戳去验证。但只要你把这个基础打牢了,后面的订单簿重建就会顺风顺水。
总结:数据源排查的核心就三件事——延迟要稳、丢包要补、多源要对。这三件事做好了,你的订单簿重建就成功了一半。