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)。

快照消息体包含三个部分:

  1. 交易所代码(2字节)——标识是上交所还是深交所
  2. 证券代码(8字节)——比如"600519"
  3. 订单簿数据(变长)——买盘和卖盘的深度信息

订单簿数据这块,结构稍微复杂一点。它包含:

  • 买盘档位数(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字节) StartFlag 2字节 MsgType 1字节 BodyLength 4字节 Sequence 8字节 Timestamp 8字节 Checksum 2字节 消息体(变长) 交易所代码 2字节 证券代码 8字节 订单簿数据(变长) N字节 买盘档位数 2字节 卖盘档位数 2字节 买盘列表 N*(8+8)字节 卖盘列表 N*(8+8)字节 图:Step协议消息结构示意图

这张图把Step协议的消息结构拆得很清楚了。消息头固定25字节,消息体根据消息类型变化。快照消息体里,订单簿数据是核心,买盘和卖盘各自带着价格和数量。

4.6 实战中的几个要点

最后,分享几个我在实战中总结的经验:

  • 缓冲区管理:Step消息是流式传输的,可能一次收到多条消息,也可能一条消息分多次到达。我习惯用环形缓冲区来暂存数据,等凑够一条完整消息再解析。
  • 性能优化:解析时尽量用切片操作,别用循环逐字节处理。Python的struct.unpack底层是C实现的,速度很快。
  • 异常处理:网络数据不可靠,校验和不对就丢弃,别尝试修复。我曾经见过有人试图"纠正"错误数据,结果把正确的行情也改错了。

一个小建议:刚开始做Step协议解析时,先写一个简单的单元测试,用已知的二进制数据验证解析结果。等解析逻辑稳定了,再接入真实行情源。这样能省去很多调试时间。

好了,Step协议的基础就讲到这里。协议本身不复杂,但每个细节都值得认真对待。下一节咱们会深入聊增量更新的处理逻辑,那个更有意思。


蓝海数据掘金营,专注资料整理