4、Step协议入门:从零开始理解行情快照
做量化交易的朋友,肯定绕不开一个东西——行情快照。说白了,就是交易所某个时刻的盘口数据。今天咱们聊的Step协议,就是专门用来传输这种快照的轻量级协议。
我第一次接触Step协议是在2018年,当时帮一家券商做行情网关。说实话,第一眼看到协议文档,我心想:这玩意儿也太简单了吧?但用起来才发现,越简单的协议,坑往往藏得越深。
4.1 Step协议是什么?
Step协议,全称是Simple Trading Exchange Protocol。名字里带个"Simple",确实没骗人。它不像FIX协议那样动辄几百页文档,Step的核心设计理念就一条:快、轻、准。
你想想看,行情数据每秒都在变。如果协议太复杂,解析时间一长,行情就滞后了。Step协议就是为了解决这个问题诞生的。
核心特点:
- 二进制编码,不是文本协议
- 固定长度消息头,解析速度快
- 消息体结构紧凑,带宽占用低
- 支持增量更新和全量快照
我在项目中遇到过最典型的场景:用Step协议接收沪深交易所的Level-2行情。每秒能处理上万笔订单簿更新,延迟控制在微秒级。嗯,这个性能,FIX协议是做不到的。
4.2 消息头结构:一切从这里开始
每条Step消息,开头都是一个固定长度的消息头。我习惯把它叫做"门牌号"——你只有读对了门牌号,才能找到正确的房间。
消息头长这样:
| 字段 | 长度(字节) | 说明 |
|---|---|---|
| StartFlag | 2 | 起始标识,固定为0xAA55 |
| MsgType | 1 | 消息类型,比如快照、增量、心跳 |
| BodyLength | 4 | 消息体长度,单位字节 |
| Sequence | 8 | 序列号,用于去重和排序 |
| Timestamp | 8 | 时间戳,纳秒精度 |
| Checksum | 2 | 校验和,验证消息完整性 |
总共25个字节。不多不少,刚刚好。
我的小技巧:解析消息头时,先校验StartFlag。如果不对,直接丢弃整条消息。别浪费时间解析后面的内容。我曾经见过有人先解析BodyLength,结果遇到乱码数据,BodyLength是个天文数字,程序直接OOM了。
4.3 消息体结构:快照数据的核心
消息头之后,就是消息体。消息体的结构取决于MsgType。咱们今天重点讲快照消息(MsgType=0x01)。
快照消息体包含三个部分:
- 交易所代码(2字节)——标识是上交所还是深交所
- 证券代码(8字节)——比如"600519"
- 订单簿数据(变长)——买盘和卖盘的深度信息
订单簿数据这块,结构稍微复杂一点。它包含:
- 买盘档位数(2字节)
- 卖盘档位数(2字节)
- 买盘列表:每个档位包含价格(8字节)和数量(8字节)
- 卖盘列表:同样每个档位包含价格和数量
举个例子,如果买盘有5档,卖盘有5档,那订单簿数据就是:2 + 2 + 5*(8+8) + 5*(8+8) = 164字节。
注意:价格字段用的是定点数,不是浮点数。具体来说,价格 = 整数值 / 10000。比如报价是168800,实际价格就是16.88元。我刚开始做的时候,直接当整数用,结果算出来的价格差了100倍。嗯,这个坑我踩过。
4.4 字段类型定义:别小看这些基础
Step协议里只有四种基本字段类型。简单到让人怀疑人生:
| 类型 | 长度 | Python对应类型 |
|---|---|---|
| UInt8 | 1字节 | int |
| UInt16 | 2字节 | int |
| UInt32 | 4字节 | int |
| UInt64 | 8字节 | int |
全是无符号整数。没有字符串,没有浮点数,没有变长字段。为什么?因为简单就是快。
解析的时候,用Python的struct模块就能搞定:
import struct
# 解析消息头
header_fmt = '!H B I Q Q H' # 大端序
header_size = struct.calcsize(header_fmt)
# 假设data是收到的字节流
start_flag, msg_type, body_len, seq, ts, checksum = \
struct.unpack(header_fmt, data[:header_size])
# 校验起始标识
if start_flag != 0xAA55:
print("非法消息,丢弃")
return
# 解析订单簿
# 买盘档位
bid_count = struct.unpack('!H', data[offset:offset+2])[0]
offset += 2
for i in range(bid_count):
price = struct.unpack('!Q', data[offset:offset+8])[0] / 10000
offset += 8
volume = struct.unpack('!Q', data[offset:offset+8])[0]
offset += 8
print(f"买{i+1}: 价格{price:.2f}, 数量{volume}")
你看,代码就这么几行。但越简单的东西,越容易在细节上出问题。
避坑指南:
- 字节序问题:Step协议统一用大端序(网络字节序)。如果你用小端序解析,数据全乱套。
- 对齐问题:有些交易所会在字段之间插入填充字节。我建议你严格按照协议文档来,别自作聪明。
- 校验和计算:通常是所有字节的累加和取低16位。但不同交易所可能有差异,务必确认。
4.5 一张图看懂Step协议
说了这么多,不如一张图来得直观。下面是我画的Step协议消息结构图:
这张图把Step协议的消息结构拆得很清楚了。消息头固定25字节,消息体根据消息类型变化。快照消息体里,订单簿数据是核心,买盘和卖盘各自带着价格和数量。
4.6 实战中的几个要点
最后,分享几个我在实战中总结的经验:
- 缓冲区管理:Step消息是流式传输的,可能一次收到多条消息,也可能一条消息分多次到达。我习惯用环形缓冲区来暂存数据,等凑够一条完整消息再解析。
- 性能优化:解析时尽量用切片操作,别用循环逐字节处理。Python的
struct.unpack底层是C实现的,速度很快。 - 异常处理:网络数据不可靠,校验和不对就丢弃,别尝试修复。我曾经见过有人试图"纠正"错误数据,结果把正确的行情也改错了。
一个小建议:刚开始做Step协议解析时,先写一个简单的单元测试,用已知的二进制数据验证解析结果。等解析逻辑稳定了,再接入真实行情源。这样能省去很多调试时间。
好了,Step协议的基础就讲到这里。协议本身不复杂,但每个细节都值得认真对待。下一节咱们会深入聊增量更新的处理逻辑,那个更有意思。