行情数据接入:美股行情协议(ITCH/OUCH)、多播数据解析、数据包重组、快照与增量处理
做高频交易,第一关就是行情数据。你策略再牛,模型再准,拿不到干净、低延迟的行情数据,一切都是白搭。今天我们就来聊聊美股行情接入的那些硬核技术。
美股行情协议:ITCH 与 OUCH
纳斯达克提供了两套核心协议:ITCH 和 OUCH。说白了,ITCH 是行情协议,负责告诉你市场发生了什么;OUCH 是订单协议,负责让你去市场里搞事情。
ITCH(直连行情协议),我习惯叫它「市场广播员」。它通过多播方式,把每一笔订单的进入、修改、撤销、成交,全部实时推送给订阅者。你想想看,这就像坐在交易所的交易大厅里,能听到每一个交易员的喊价声。
我个人经验是,ITCH 协议最核心的数据结构是 OrderBook 的增量更新。它不会每次都给你全量的买卖盘口,而是告诉你:「有人在 150.20 这个价位加了一单 500 股」或者「有人把 150.25 的 200 股撤了」。你的任务,就是把这些增量信息,拼凑出完整的订单簿。
OUCH(直连订单协议),则是你用来下单的通道。它比 FIX 协议更轻量、更快速。我记得第一次从 FIX 切换到 OUCH 时,单笔订单的确认延迟从几百微秒降到了几十微秒。嗯,这个差距在毫秒必争的高频世界里,就是生与死的区别。
| 协议 | 方向 | 特点 | 延迟 |
|---|---|---|---|
| ITCH | 行情接收(只读) | 多播、增量更新、高吞吐 | 微秒级 |
| OUCH | 订单发送(读写) | 点对点、轻量、低延迟 | 亚微秒级 |
多播数据解析:从网络流到结构化数据
美股行情用的是 UDP 多播。为什么不用 TCP?因为 TCP 的重传机制在高频场景下是灾难。你想想看,行情数据每秒几百万条更新,如果因为丢包就重传,网络瞬间就炸了。
多播的解析,第一步是处理 数据包分片。ITCH 协议的消息大小不一,但 UDP 包最大只有 1500 字节左右(MTU 限制)。一条大的 ITCH 消息,可能会被拆成多个 UDP 包发送。
我曾经踩过一个坑:某个 ITCH 消息的头部在前一个包,主体在后一个包,但两个包到达的顺序颠倒了。如果直接按顺序解析,就会得到一堆乱码。解决方案是维护一个 重组缓冲区,根据消息序列号来排序。
解析代码大致长这样:
// 伪代码:ITCH 消息解析
void onUdpPacket(byte[] packet) {
// 1. 提取消息序列号
uint64 seqNum = readUint64(packet, 0);
// 2. 检查是否有序
if (seqNum != expectedSeqNum) {
// 丢包或乱序,放入重组缓冲区
reassemblyBuffer.put(seqNum, packet);
return;
}
// 3. 解析消息体
MessageType type = (MessageType) packet[8];
switch (type) {
case ADD_ORDER:
parseAddOrder(packet);
break;
case ORDER_EXECUTED:
parseOrderExecuted(packet);
break;
// ... 其他消息类型
}
// 4. 处理重组缓冲区中积压的消息
processReassemblyBuffer();
}
数据包重组:应对乱序和丢包
多播的痛点就是乱序和丢包。你无法保证所有数据包按顺序到达,也无法保证每个包都能到达。
我的做法是维护一个 滑动窗口。窗口大小根据历史丢包率动态调整,一般在 64 到 256 之间。窗口内的数据包按序列号排序,窗口外的直接丢弃——因为行情数据时效性极强,过时的数据没有意义。
重组逻辑的核心是:
- 收到新包,检查序列号是否在窗口内
- 如果在,插入到正确位置
- 从窗口起始位置开始,按顺序取出连续的消息
- 如果窗口起始位置缺失,等待超时或收到后续包后,直接跳过
嗯,这里要注意:超时时间不能太长。我一般设 100 微秒,超过这个时间还没等到,就认为这个包丢了。
快照与增量处理:维护实时订单簿
这是整个行情接入中最核心的部分。交易所会在每天开盘前发送一次 全量快照,告诉你当前所有价位的挂单情况。之后,就只发送增量更新。
你的任务就是:
- 用快照初始化本地订单簿
- 用增量更新实时维护订单簿状态
- 定期(比如每 10 秒)请求一次新的快照,用于校正
为什么需要定期校正?因为增量更新可能会累积误差。比如你漏掉了一个撤单消息,那么本地订单簿里就会多出一笔不存在的挂单。时间长了,误差会越来越大。
我习惯用 红黑树 来维护订单簿的买卖盘口。每个价位是一个节点,节点内部用链表存储该价位的所有订单。这样,增删改查的时间复杂度都是 O(log n)。
快照和增量的处理流程,我画了一张图:
图中可以看到,快照初始化后,进入增量处理循环。每次增量更新后,都要做一次一致性校验。如果发现异常,立即请求新的快照进行校正。
最后说一句:行情接入是整个高频交易系统的地基。地基不稳,上层建筑再漂亮也没用。我见过太多团队在策略上花了大把时间,结果因为行情数据延迟高了几个微秒,策略完全跑不出预期效果。所以,把行情接入做扎实,比什么都重要。
公众号:蓝海数据掘金营,微信deep3321