3. FIX协议基础:报文结构、标签与值、会话层与应用层、登录与心跳机制

做港股量化交易,FIX协议是绕不开的坎。说实话,我刚入行那会儿,看到FIX报文里一堆数字标签,头都大了。后来踩过几次坑,才慢慢摸清楚它的脾气。

今天咱们就把FIX协议的核心骨架拆开来看。你只要掌握了报文结构、会话层和应用层的区别,以及登录心跳这套机制,港股交易接口的大门就算推开了一半。

3.1 FIX协议报文结构

FIX报文说白了就是一段纯文本。每个字段由「标签=值」组成,字段之间用SOH字符(ASCII码0x01)分隔。嗯,这里要注意,SOH不可见,很多新手调试时以为字段间是空格,结果解析失败。

一个典型的FIX报文长这样:

8=FIX.4.4|9=78|35=D|49=CLIENT1|56=BROKER1|34=2|52=20240325-10:00:00.000|11=ORD001|55=00700.HK|54=1|38=1000|40=2|44=500.00|10=123|

(这里的|代表SOH分隔符,实际报文里看不到)

我习惯把报文拆成三部分来看:

  • 头段(Header):标签8、9、35、49、56、34、52等,负责路由和会话控制
  • 主体(Body):业务数据,比如下单的股票代码、价格、数量
  • 尾段(Trailer):标签10,校验和,用于完整性检查

核心规则:标签3(重复组)和标签10(校验和)必须放在报文最后。我曾经因为顺序搞反,被交易所拒单了三次才反应过来。

3.2 标签与值的体系

FIX协议定义了上千个标签。但港股交易常用的,其实就几十个。我整理了一份核心标签表:

标签 字段名 说明 示例值
8 BeginString 协议版本 FIX.4.4
35 MsgType 消息类型 D(新订单)
49 SenderCompID 发送方ID CLIENT1
56 TargetCompID 接收方ID BROKER1
55 Symbol 股票代码 00700.HK
54 Side 买卖方向 1(买)/2(卖)
38 OrderQty 订单数量 1000
44 Price 价格 500.00
10 CheckSum 校验和 123

个人经验:标签值尽量用字符串表示,比如Side=1而不是Side='1'。有些老系统对类型检查很严格,我吃过这个亏。

3.3 会话层 vs 应用层

FIX协议分两层:会话层和应用层。说白了,会话层管「怎么传」,应用层管「传什么」。

  • 会话层:负责建立连接、登录、心跳、序列号管理、重传等。消息类型包括:A(登录)、0(心跳)、5(登出)、2(重传请求)等。
  • 应用层:负责业务数据,比如下单、撤单、成交回报。消息类型包括:D(新订单)、F(订单取消)、8(执行报告)等。

你想想看,会话层就像快递员,他负责把包裹安全送到;应用层就是包裹里的商品。快递员可以换,但商品内容不变。

我画了一张图,帮你理清这两层的关系:

会话层(Session Layer) 登录(A) | 心跳(0) | 登出(5) | 序列号管理 重传请求(2) | 测试请求(1) 负责连接可靠性、消息顺序、重传机制 应用层(Application Layer) 新订单(D) | 订单取消(F) | 订单修改(G) 执行报告(8) | 市场数据(W) | 报价(R) 负责业务逻辑:下单、撤单、查询、成交回报 会话层为应用层提供可靠传输

避坑指南:我曾经在开发时,把应用层的订单消息直接塞到会话层里发,结果对方解析失败。记住,每个消息都必须有正确的MsgType(标签35),会话层消息和应用层消息不能混用。

3.4 登录与心跳机制

登录和心跳,是FIX会话层最核心的两个动作。说白了,就是告诉对方「我来了」和「我还活着」。

3.4.1 登录流程

登录消息(MsgType=A)必须包含以下标签:

  • 98(EncryptMethod):加密方式,0表示不加密
  • 108(HeartBtInt):心跳间隔,单位秒。港股通常设为30秒
  • 141(ResetSeqNumFlag):是否重置序列号,Y/N

一个典型的登录请求:

8=FIX.4.4|9=65|35=A|49=CLIENT1|56=BROKER1|34=1|52=20240325-09:30:00.000|98=0|108=30|141=Y|10=200|

如果登录成功,对方会回复一个同样MsgType=A的消息,表示接受。如果失败,会回复一个MsgType=5(登出)并带上错误原因。

个人习惯:登录时我会把ResetSeqNumFlag设为Y,这样每次重连后序列号从1开始,调试起来方便很多。但要注意,生产环境有些券商要求必须设为N,得提前确认。

3.4.2 心跳机制

心跳消息(MsgType=0)很简单,就是一个空的消息体,只包含头段和尾段。它的作用是:

  • 检测连接是否还活着
  • 防止NAT网关或防火墙超时断开连接
  • 同步序列号

心跳间隔由登录时的HeartBtInt决定。比如设为30秒,那么:

  • 如果30秒内没有其他消息,就必须发一个心跳
  • 如果超过HeartBtInt * 1.2秒没收到任何消息,就认为连接断了

我建议你实现一个心跳定时器,逻辑如下:

# 伪代码示例
last_sent_time = 当前时间
last_received_time = 当前时间

while 连接未断开:
    当前时间 = 获取当前时间()
    if 当前时间 - last_sent_time >= HeartBtInt:
        发送心跳消息(MsgType=0)
        last_sent_time = 当前时间
    if 当前时间 - last_received_time > HeartBtInt * 1.2:
        触发重连逻辑()
        break
    休眠(1秒)

我曾经踩过的坑:有一次我把心跳间隔设成了60秒,但券商网关的防火墙30秒就会断开空闲连接。结果每59秒断一次,断完重连,重连完又断。后来改成25秒才稳定。所以,心跳间隔一定要比网关的超时时间短,最好留出50%的余量。

3.5 序列号管理

每个FIX会话都有两个序列号:发送序列号(MsgSeqNum,标签34)和接收序列号。序列号从1开始,每发一条消息递增1。

序列号的作用:

  • 保证消息顺序
  • 检测消息丢失
  • 支持重传

如果发现序列号跳号(比如收到34=5,但上次收到的是34=3),说明中间丢了一条消息。这时候需要发送重传请求(MsgType=2),要求对方重发丢失的消息。

核心要点:序列号是FIX会话的命根子。我见过有人把序列号写死成1,结果每次重连都报错。记住,序列号必须严格递增,不能重复,不能跳号(除非重置)。

3.6 小结

FIX协议其实不复杂。你只要记住:

  • 报文结构:头段 + 主体 + 尾段,用SOH分隔
  • 标签与值:每个字段都是「标签=值」的形式
  • 两层分离:会话层管连接,应用层管业务
  • 登录心跳:建立连接后,用心跳维持
  • 序列号:保证消息不丢不乱

把这些基础打牢了,后面写代码的时候就会顺手很多。下一节咱们会动手实现一个FIX客户端,到时候这些概念都会用上。


公众号:蓝海资料掘金营,微信deep3321