3、STEP协议详解:基于FIX协议的STEP协议结构、消息头、消息体、消息尾
好,咱们今天来聊聊STEP协议。说实话,很多刚接触交易所开发的同学,一看到“协议”两个字就头大。我当年也一样,觉得这东西太抽象了。但干久了你会发现,协议说白了就是“双方约定好的数据格式”。你按这个格式发,我按这个格式收,谁也别乱来。
STEP协议,全称是 Securities Trading Exchange Protocol。它其实是基于FIX协议(Financial Information eXchange)做的一个“本土化改造”。FIX协议在国际上用得很多,但咱们国内交易所觉得它太啰嗦,有些字段用不上,有些规则又不够灵活。所以深交所带头搞了STEP,上交所搞了类似的叫做“上证所数据接口规范”。
嗯,这里要注意:STEP不是完全另起炉灶,它保留了FIX的核心骨架。你如果懂FIX,学STEP就是分分钟的事。反过来,你直接啃STEP,也能理解FIX的精髓。
核心要点:STEP协议的消息结构 = 消息头 + 消息体 + 消息尾。三者缺一不可,顺序也不能乱。
3.1 消息结构总览
咱们先看一张图,把整体结构印在脑子里。我习惯用这种“三段式”结构来理解所有基于FIX的协议——不管是STEP、FIX还是国内的CTP,本质都一样。
你看,消息头在最前面,消息体在中间,消息尾在最后。这个顺序是固定的,解析的时候也是按这个顺序来。我见过有新手把消息头和消息体搞反了,结果解析出来的数据全是乱码。嗯,这种坑踩过一次就记住了。
3.2 消息头(Header)详解
消息头是协议的“身份证”。它告诉接收方:这条消息是谁发的、发给谁、什么类型、多长、什么时候发的。没有消息头,后面的数据根本没法解析。
STEP协议的消息头包含以下必填字段:
| 标签(Tag) | 字段名 | 说明 | 示例值 |
|---|---|---|---|
| 8 | BeginString | 协议版本标识,STEP固定为"STEP1.0.0" | STEP1.0.0 |
| 9 | BodyLength | 消息体的长度(字节数),不包括消息头和消息尾 | 128 |
| 35 | MsgType | 消息类型,如D=新订单、8=成交回报 | D |
| 49 | SenderCompID | 发送方标识,通常是交易所分配的会员代码 | MEMBER001 |
| 56 | TargetCompID | 目标方标识,如交易所代码"SSE"或"SZSE" | SZSE |
| 34 | MsgSeqNum | 消息序列号,用于去重和顺序校验 | 10001 |
| 52 | SendingTime | 发送时间,格式YYYYMMDD-HH:MM:SS.sss | 20250115-14:30:00.123 |
避坑指南:我曾经在对接某家券商的STEP接口时,发现对方发来的消息头里MsgSeqNum(34)居然跳号了。按协议要求,序列号必须连续递增,否则说明有丢包。我当时排查了半天,最后发现是对方网关重启后没重置序列号。所以,解析时一定要校验MsgSeqNum的连续性,这是保证数据完整性的第一道防线。
3.3 消息体(Body)详解
消息体是协议的核心,承载着真正的业务数据。比如你要下单,订单的股票代码、买卖方向、价格、数量都在消息体里。
消息体的字段根据MsgType(35)的不同而变化。举个例子:
- MsgType=D(新订单):包含ClOrdID(11)、Symbol(55)、Side(54)、OrderQty(38)、Price(44)、OrdType(40)等
- MsgType=8(成交回报):包含OrderID(37)、ExecID(17)、ExecType(150)、LastQty(32)、LastPx(31)等
- MsgType=5(登出):消息体可以为空,或者只带一个Text(58)字段说明原因
消息体的解析逻辑其实很简单:按Tag=Value的格式,用SOH(0x01)分隔。每个字段都是“标签=值”的形式,字段之间用SOH字符隔开。整个消息体结束后,用SOH标记结束,然后进入消息尾。
我个人习惯在解析时先根据MsgType查表,确定当前消息体应该包含哪些字段。这样解析效率高,而且能提前发现字段缺失的问题。
3.4 消息尾(Trailer)详解
消息尾只有一个字段:CheckSum(10)。它的作用是校验整条消息的完整性。
CheckSum的计算方式:
1. 从消息头的第一个字符开始,到消息尾的最后一个SOH之前(不包括CheckSum字段本身)
2. 对每个字节取ASCII码,累加求和
3. 对256取模
4. 结果转为3位十进制数,不足补前导零
举个例子,假设一条消息的原始字节流(不含CheckSum字段)的累加和为1234,那么:
CheckSum = 1234 % 256 = 210
最终写入消息尾的值为:10=210
重要提醒:CheckSum的计算范围是从消息头的第一个字节开始,到消息尾的SOH分隔符之前结束。注意,不包括CheckSum字段本身。我见过有人把整个消息包括CheckSum都算进去了,结果永远对不上。另外,SOH字符(0x01)本身也要参与累加计算,别漏了。
3.5 一个完整的STEP消息示例
咱们来看一个实际的新订单消息(MsgType=D),这样更直观:
8=STEP1.0.09=12835=D49=MEMBER00156=SZSE34=1000152=20250115-14:30:00.123
11=ORD20250115000155=00000154=138=100044=10.5040=2
10=210
注意看,消息头和消息体之间、消息体和消息尾之间,都是用SOH()分隔的。整个消息以CheckSum字段结束,后面没有多余的SOH。
解析的时候,我一般这样处理:
- 先找到第一个SOH,提取BeginString(8),确认协议版本
- 再找到第二个SOH,提取BodyLength(9),知道消息体有多长
- 根据BodyLength截取消息体内容,逐字段解析
- 最后提取CheckSum(10),重新计算校验和,验证消息完整性
这个流程看起来简单,但实际开发中坑不少。比如BodyLength计算的是消息体的字节数,不是字符数。如果消息体里有中文,一个中文字符占3个字节(UTF-8编码),你按字符数去算长度,肯定对不上。
3.6 协议解析的常见陷阱
最后分享几个我踩过的坑:
- SOH字符的处理:SOH是0x01,不是'\n'也不是'\r'。很多新手用字符串分割函数时,习惯用逗号或换行符,结果死活解析不出来。记住,STEP协议的分隔符是SOH。
- 字段顺序:虽然协议没有强制要求消息体里的字段必须按Tag大小排序,但实际中大部分交易所会按Tag升序排列。我建议解析时不要依赖顺序,而是用哈希表或字典来存储字段。
- 重复字段:有些消息里同一个Tag可能出现多次(比如多腿订单的LegSymbol)。解析时要注意处理重复字段,通常用数组或列表来存储。
- CheckSum的验证时机:我建议在解析完消息头和消息体之后、处理业务逻辑之前,先验证CheckSum。如果校验失败,直接丢弃这条消息,不要做任何后续处理。
好了,STEP协议的结构就讲到这里。说白了就是“头+体+尾”三段式,每个部分各司其职。你只要把消息头里的版本、长度、类型、序列号、时间这些关键字段解析对了,消息体里的业务数据自然就水到渠成。消息尾的CheckSum是最后一道保险,别偷懒不校验。