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 业务消息:交易的“灵魂”
管理消息搞定了连接,接下来就是真正的交易了。业务消息里,最核心的就是NewOrderSingle和ExecutionReport。
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):交易所分配的订单IDExecType(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 消息类型关系图
下面这张图,是我自己画的。它展示了管理消息和业务消息在会话生命周期中的位置。
3.4 避坑指南
最后,分享几个我踩过的坑:
- 序列号管理:消息序列号必须严格递增。我曾经因为序列号跳变,导致对方不断发ResendRequest,消息量翻了好几倍。
- 心跳超时处理:不要只依赖心跳。我建议同时用TCP keepalive做双重保障。
- ExecutionReport的重复处理:同一个订单可能收到多条相同状态的ExecutionReport。一定要做幂等处理,否则会重复记账。
- ClOrdID唯一性:这个ID必须全局唯一。我见过有人用时间戳+随机数,结果在高并发下重复了,订单被拒绝。
我的建议:刚开始接触FIX协议时,先用模拟器把管理消息的交互流程跑通。Logon、Heartbeat、Logout这三个搞定了,再上业务消息。别一上来就下单,容易出问题。
好了,这一章就聊到这里。管理消息和业务消息的关系,说白了就是“先握手,再交易”。你只要记住这个原则,后面学起来就顺了。
公众号:蓝海资料掘金营,微信deep3321