3. FIX协议消息类型:管理消息与业务消息

聊到FIX协议,消息类型是绕不开的核心。我个人习惯把FIX消息分成两大类:管理消息业务消息。说白了,管理消息负责“能不能聊”,业务消息负责“聊什么”。

你想想看,两个交易系统之间建立连接,总得先打个招呼吧?确认对方还活着吧?这就是管理消息的活。而真正下单、成交回报,那是业务消息的事。

3.1 管理消息:连接的“心跳”与“握手”

管理消息有5种,我一个个说。嗯,这里要注意,它们虽然不直接产生交易,但少了任何一个,你的系统都可能掉线。

3.1.1 Logon (35=A) —— 登录

这是会话开始的第一条消息。客户端发Logon,服务端回Logon,才算握手成功。

我在项目中遇到过,有些新手把Logon的字段填错了,比如HeartBtInt(心跳间隔)设成0,结果服务端直接拒绝连接。为什么?因为0意味着不发心跳,服务端没法判断你是否还活着。

关键字段:

  • HeartBtInt (108):心跳间隔,单位秒。一般设30或60。
  • Username (553) + Password (554):认证信息。
  • EncryptMethod (98):加密方式,0表示不加密。
// Logon消息示例(FIX 4.4)
8=FIX.4.4|9=65|35=A|49=CLIENT1|56=BROKER1|34=1|52=20240101-00:00:00|98=0|108=30|10=123|

3.1.2 Heartbeat (35=0) —— 心跳

连接建立后,双方按HeartBtInt设定的间隔互发心跳。如果一方超过一定时间没收到心跳,就会认为连接断了。

我曾经调试过一个延迟问题,发现心跳消息居然占了网络带宽的30%。后来一查,是HeartBtInt设成了1秒。太频繁了!建议生产环境设30秒以上。

我的经验:心跳间隔不要设太短。30秒是行业惯例,既能及时检测断连,又不会浪费带宽。

3.1.3 TestRequest (35=1) —— 测试请求

当你怀疑对方掉线了,可以主动发一个TestRequest。对方必须回一个Heartbeat,且TestReqID字段要一致。

这招我在排查网络抖动时经常用。如果发了TestRequest没收到回应,基本可以断定连接有问题。

3.1.4 ResendRequest (35=2) —— 重发请求

如果发现消息序列号有跳号,说明中间丢消息了。这时候发ResendRequest,要求对方重发指定范围的消息。

举个例子:你收到了序列号1、2、4,但没收到3。那就发ResendRequest,要求重发3。对方会把3以及之后的消息重新发一遍。

注意:ResendRequest不能乱发。我曾经见过一个系统,因为网络抖动频繁触发重发,结果消息量暴增,把网关打挂了。建议加个重发频率限制。

3.1.5 Logout (35=5) —— 登出

会话结束前,主动发Logout。对方收到后回一个Logout,然后关闭连接。

嗯,这里有个坑:有些系统直接断TCP,不发Logout。这会导致对方一直等心跳超时,白白浪费资源。规范的做法是:先发Logout,再断连接。

3.2 业务消息:交易的“灵魂”

管理消息搞定了连接,接下来就是真正的交易了。业务消息里,最核心的就是NewOrderSingleExecutionReport

3.2.1 NewOrderSingle (35=D) —— 新订单

这是客户端用来下新订单的消息。字段很多,但核心的就几个:

标签 字段名 说明
11 ClOrdID 客户端订单ID,必须唯一
54 Side 买卖方向:1=买,2=卖
38 OrderQty 数量
44 Price 价格(限价单必填)
40 OrdType 订单类型:1=市价,2=限价
// NewOrderSingle示例(买入100股,限价10.5)
8=FIX.4.4|9=78|35=D|49=CLIENT1|56=BROKER1|34=2|52=20240101-00:00:01|11=ORD001|54=1|38=100|44=10.5|40=2|10=234|

我个人习惯把ClOrdID设计成“时间戳+自增序号”的格式,这样既唯一又好排查。

3.2.2 ExecutionReport (35=8) —— 执行报告

这是交易所或券商返回的订单状态消息。每笔订单从提交到成交,会经历多个状态,每个状态都会触发一条ExecutionReport。

常见的状态有:

  • 0=New:订单已接收
  • 1=Partially filled:部分成交
  • 2=Filled:全部成交
  • 4=Canceled:已撤销
  • 8=Rejected:被拒绝

我在项目中遇到过,有些系统只处理Filled状态,忽略了Partially filled。结果订单部分成交后,客户端以为没成交,又补了一单,导致超买。嗯,这个坑踩过的人不少。

关键字段:

  • ClOrdID (11):关联原始订单
  • OrderID (37):交易所分配的订单ID
  • ExecType (150):执行类型(0=New, 1=Partial, 2=Fill, 4=Cancel, 8=Reject)
  • OrdStatus (39):订单状态
  • LastQty (32):本次成交数量
  • LastPx (31):本次成交价格
// ExecutionReport示例(订单已成交)
8=FIX.4.4|9=88|35=8|49=BROKER1|56=CLIENT1|34=3|52=20240101-00:00:02|11=ORD001|37=EXE001|150=2|39=2|32=100|31=10.5|10=345|

3.3 消息类型关系图

下面这张图,是我自己画的。它展示了管理消息和业务消息在会话生命周期中的位置。

FIX消息类型关系图 客户端 服务端 Logon (35=A) 管理消息(会话层) • Heartbeat (35=0) —— 定期心跳 • TestRequest (35=1) —— 主动测试 • ResendRequest (35=2) —— 重发请求 • Logout (35=5) —— 正常登出 业务消息(应用层) • NewOrderSingle (35=D) —— 新订单 • ExecutionReport (35=8) —— 执行报告 • OrderCancelRequest (35=F) —— 撤单 • OrderStatusRequest (35=H) —— 查询 管理消息保障连接稳定,业务消息完成交易功能

3.4 避坑指南

最后,分享几个我踩过的坑:

  • 序列号管理:消息序列号必须严格递增。我曾经因为序列号跳变,导致对方不断发ResendRequest,消息量翻了好几倍。
  • 心跳超时处理:不要只依赖心跳。我建议同时用TCP keepalive做双重保障。
  • ExecutionReport的重复处理:同一个订单可能收到多条相同状态的ExecutionReport。一定要做幂等处理,否则会重复记账。
  • ClOrdID唯一性:这个ID必须全局唯一。我见过有人用时间戳+随机数,结果在高并发下重复了,订单被拒绝。

我的建议:刚开始接触FIX协议时,先用模拟器把管理消息的交互流程跑通。Logon、Heartbeat、Logout这三个搞定了,再上业务消息。别一上来就下单,容易出问题。

好了,这一章就聊到这里。管理消息和业务消息的关系,说白了就是“先握手,再交易”。你只要记住这个原则,后面学起来就顺了。


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