FIX 协议入门

"FIX 是金融界的 HTTP——你不需要完全理解它,但你需要知道它如何影响你的订单。"


一、什么是 FIX 协议?

1.1 定义

FIX(Financial Information eXchange):金融信息交换协议,是电子交易的行业标准通信协议。

你的交易系统 ←──FIX消息──→ 券商/交易所

发送: "我要买 100 股 AAPL,限价 $185"
接收: "订单已收到,编号 12345"
接收: "50 股成交于 $185.00"
接收: "剩余 50 股成交于 $185.01"

1.2 为什么需要 FIX?

问题没有 FIX有 FIX
接入新券商重新开发对接换配置即可
多券商交易N 套代码1 套代码
订单状态同步各家格式不同标准化消息
故障排查各家日志不同统一协议分析

1.3 FIX 协议版本

版本发布年份主要用途
FIX 4.01996历史版本
FIX 4.22000仍有使用
FIX 4.42003最常用
FIX 5.02006新功能
FIXT 1.12008传输层分离

大多数券商和交易所支持 FIX 4.4


二、FIX 消息结构

2.1 消息格式

FIX 消息是键值对的序列,用 SOH(ASCII 01)分隔:

8=FIX.4.4|9=176|35=D|49=SENDER|56=TARGET|34=2|52=20240101-09:30:00.000|
11=ORD001|21=1|55=AAPL|54=1|60=20240101-09:30:00.000|38=100|40=2|44=185.00|
59=0|10=123|

人类可读版本

8=FIX.4.4        # 协议版本
9=176            # 消息体长度
35=D             # 消息类型(D=新订单)
49=SENDER        # 发送方 ID
56=TARGET        # 接收方 ID
34=2             # 消息序号
52=20240101-09:30:00.000  # 发送时间
11=ORD001        # 客户订单 ID
21=1             # 执行指令(1=自动)
55=AAPL          # 股票代码
54=1             # 买卖方向(1=买)
60=20240101-09:30:00.000  # 交易时间
38=100           # 订单数量
40=2             # 订单类型(2=限价)
44=185.00        # 限价价格
59=0             # 有效期(0=当日有效)
10=123           # 校验和

2.2 消息分层

┌─────────────────────────────────────────────────────────────┐
                     FIX 消息结构                             
├─────────────────────────────────────────────────────────────┤
                                                             
  ┌─────────────────────────────────────────────────────┐   
   Header(消息头)                                        
     8=版本  9=长度  35=类型  49=发送方  56=接收方         
     34=序号  52=时间                                      
  └─────────────────────────────────────────────────────┘   
                                                           
  ┌─────────────────────────────────────────────────────┐   
   Body(消息体)                                          
     根据消息类型(Tag 35)不同而不同                      
     订单消息: 55=标的 54=方向 38=数量 44=价格...          
  └─────────────────────────────────────────────────────┘   
                                                           
  ┌─────────────────────────────────────────────────────┐   
   Trailer(消息尾)                                       
     10=校验和                                            
  └─────────────────────────────────────────────────────┘   
                                                             
└─────────────────────────────────────────────────────────────┘

三、核心消息类型

3.1 会话层消息

MsgType (35)名称用途
ALogon建立会话
5Logout断开会话
0Heartbeat心跳检测
1TestRequest测试连接
2ResendRequest请求重发
4SequenceReset序号重置

3.2 应用层消息(订单相关)

MsgType (35)名称方向用途
DNewOrderSingle客户→券商提交新订单
FOrderCancelRequest客户→券商撤销订单
GOrderCancelReplaceRequest客户→券商修改订单
8ExecutionReport券商→客户订单状态/成交回报
9OrderCancelReject券商→客户撤单拒绝

3.3 市场数据消息

MsgType (35)名称用途
VMarketDataRequest订阅行情
WMarketDataSnapshotFullRefresh行情快照
XMarketDataIncrementalRefresh行情增量更新

四、关键 Tag 详解

4.1 订单方向 (Tag 54 - Side)

含义
1Buy(买入)
2Sell(卖出)
5Sell Short(卖空)
6Sell Short Exempt(豁免卖空)

4.2 订单类型 (Tag 40 - OrdType)

含义必填 Tag
1Market(市价单)
2Limit(限价单)44=价格
3Stop(止损单)99=触发价
4Stop Limit(止损限价单)44=限价, 99=触发价
PPegged(锚定单)特定字段

4.3 订单有效期 (Tag 59 - TimeInForce)

含义说明
0Day当日有效
1GTC直到取消
2OPG开盘竞价
3IOC立即成交或取消
4FOK全部成交或取消
6GTD指定日期前有效

4.4 订单状态 (Tag 39 - OrdStatus)

含义
0New(已接受)
1Partially Filled(部分成交)
2Filled(全部成交)
4Canceled(已撤销)
8Rejected(被拒绝)
CExpired(已过期)

4.5 执行类型 (Tag 150 - ExecType)

含义
0New(新订单确认)
FTrade(成交)
4Canceled(撤销确认)
8Rejected(拒绝)
CExpired(过期)

五、典型消息流

5.1 正常下单流程

客户                                    券商
                                        
   ────── NewOrderSingle (35=D) ──────→ 
          11=ORD001, 55=AAPL,           
          54=1, 38=100, 40=2, 44=185    
                                        
   ←── ExecutionReport (35=8) ───────── 
       150=0 (New), 39=0 (New)          
       订单已接受                        
                                        
   ←── ExecutionReport (35=8) ───────── 
       150=F (Trade), 39=1 (PartFilled) 
       31=185.00, 32=50                 
       成交 50 股于 $185.00             
                                        
   ←── ExecutionReport (35=8) ───────── 
       150=F (Trade), 39=2 (Filled)     
       31=185.01, 32=50                 
       成交剩余 50 股于 $185.01         
                                        

5.2 撤单流程

客户                                    券商
                                        
   ── OrderCancelRequest (35=F) ──────→ 
      11=CANCEL001, 41=ORD001           
      (41=原订单 ID)                    
                                        
   ←── ExecutionReport (35=8) ───────── 
       150=4 (Canceled), 39=4           
       撤单成功                         
                                        

或者撤单失败:

   ←── OrderCancelReject (35=9) ─────── 
       102=1 (Unknown order)            
       撤单失败:订单不存在             
                                        

5.3 会话建立

客户                                    券商
                                        
   ────────── Logon (35=A) ───────────→ 
              98=0 (无加密)             
              108=30 (心跳间隔 30s)     
                                        
   ←────────── Logon (35=A) ─────────── 
               会话建立成功             
                                        
   ←───────── Heartbeat (35=0) ──────── 
                30                  
   ────────── Heartbeat (35=0) ───────→ 
                                        

六、Python 实现示例

6.1 使用 QuickFIX

import quickfix as fix
import quickfix44 as fix44

class TradingApplication(fix.Application):
    """FIX 交易应用"""

    def __init__(self):
        super().__init__()
        self.session_id = None
        self.order_id = 0

    def onCreate(self, session_id):
        """会话创建"""
        self.session_id = session_id
        print(f"Session created: {session_id}")

    def onLogon(self, session_id):
        """登录成功"""
        print(f"Logged on: {session_id}")

    def onLogout(self, session_id):
        """登出"""
        print(f"Logged out: {session_id}")

    def toAdmin(self, message, session_id):
        """发送管理消息前的回调"""
        pass

    def fromAdmin(self, message, session_id):
        """收到管理消息"""
        pass

    def toApp(self, message, session_id):
        """发送应用消息前的回调"""
        print(f"Sending: {message}")

    def fromApp(self, message, session_id):
        """收到应用消息"""
        msg_type = fix.MsgType()
        message.getHeader().getField(msg_type)

        if msg_type.getValue() == fix.MsgType_ExecutionReport:
            self._handle_execution_report(message)

    def _handle_execution_report(self, message):
        """处理执行报告"""
        exec_type = fix.ExecType()
        message.getField(exec_type)

        if exec_type.getValue() == fix.ExecType_FILL:
            # 成交回报
            order_id = fix.ClOrdID()
            last_px = fix.LastPx()
            last_qty = fix.LastQty()

            message.getField(order_id)
            message.getField(last_px)
            message.getField(last_qty)

            print(f"Fill: {order_id.getValue()} "
                  f"{last_qty.getValue()} @ {last_px.getValue()}")

    def send_new_order(self, symbol: str, side: str,
                       quantity: int, price: float):
        """发送新订单"""
        self.order_id += 1
        cl_ord_id = f"ORD{self.order_id:06d}"

        order = fix44.NewOrderSingle()

        # 必填字段
        order.setField(fix.ClOrdID(cl_ord_id))
        order.setField(fix.Symbol(symbol))
        order.setField(fix.Side(fix.Side_BUY if side == 'buy'
                                else fix.Side_SELL))
        order.setField(fix.OrderQty(quantity))
        order.setField(fix.OrdType(fix.OrdType_LIMIT))
        order.setField(fix.Price(price))
        order.setField(fix.TimeInForce(fix.TimeInForce_DAY))
        order.setField(fix.TransactTime())

        fix.Session.sendToTarget(order, self.session_id)

        return cl_ord_id

    def cancel_order(self, orig_cl_ord_id: str, symbol: str, side: str):
        """撤销订单"""
        self.order_id += 1
        cl_ord_id = f"CXL{self.order_id:06d}"

        cancel = fix44.OrderCancelRequest()

        cancel.setField(fix.ClOrdID(cl_ord_id))
        cancel.setField(fix.OrigClOrdID(orig_cl_ord_id))
        cancel.setField(fix.Symbol(symbol))
        cancel.setField(fix.Side(fix.Side_BUY if side == 'buy'
                                 else fix.Side_SELL))
        cancel.setField(fix.TransactTime())

        fix.Session.sendToTarget(cancel, self.session_id)

        return cl_ord_id

6.2 配置文件

# fix_client.cfg

[DEFAULT]
ConnectionType=initiator
ReconnectInterval=5
FileStorePath=./store
FileLogPath=./log
StartTime=00:00:00
EndTime=00:00:00
UseDataDictionary=Y
DataDictionary=./FIX44.xml
ValidateUserDefinedFields=N

[SESSION]
BeginString=FIX.4.4
SenderCompID=YOUR_CLIENT_ID
TargetCompID=BROKER_ID
SocketConnectHost=fix.broker.com
SocketConnectPort=9876
HeartBtInt=30

6.3 启动客户端

def main():
    settings = fix.SessionSettings("fix_client.cfg")
    application = TradingApplication()
    store_factory = fix.FileStoreFactory(settings)
    log_factory = fix.FileLogFactory(settings)

    initiator = fix.SocketInitiator(
        application, store_factory, settings, log_factory
    )

    initiator.start()

    try:
        # 等待登录
        import time
        time.sleep(2)

        # 发送订单
        order_id = application.send_new_order(
            symbol="AAPL",
            side="buy",
            quantity=100,
            price=185.00
        )
        print(f"Order submitted: {order_id}")

        # 保持运行
        while True:
            time.sleep(1)

    except KeyboardInterrupt:
        initiator.stop()

七、常见问题与排查

7.1 序号不同步

问题: "MsgSeqNum too low, expecting 100 but received 50"

原因: 客户端和服务端的消息序号不一致

解决方案:
1. 发送 SequenceReset (35=4) 重置序号
2. 或在 Logon 消息中设置 ResetSeqNumFlag (141=Y)
3. 生产环境:使用持久化存储保持序号

7.2 心跳超时

问题: 连接断开,日志显示心跳超时

原因: 网络延迟或阻塞

解决方案:
1. 检查网络连接
2. 增加 HeartBtInt 值(但不要太大)
3. 确保应用没有长时间阻塞

7.3 订单被拒绝

问题: ExecutionReport 显示 OrdStatus=8 (Rejected)

排查步骤:
1. 检查 Tag 58 (Text) 获取拒绝原因
2. 常见原因:
   - 资金不足 (Insufficient funds)
   - 标的不可交易 (Symbol not found)
   - 价格超出限制 (Price out of range)
   - 数量不符合规则 (Invalid quantity)

7.4 消息解析工具

def parse_fix_message(raw_message: str) -> dict:
    """解析 FIX 消息为字典"""
    # 替换 SOH 为可见字符
    if '\x01' in raw_message:
        raw_message = raw_message.replace('\x01', '|')

    fields = {}
    for pair in raw_message.split('|'):
        if '=' in pair:
            tag, value = pair.split('=', 1)
            fields[int(tag)] = value

    return fields


def format_fix_message(fields: dict) -> str:
    """格式化打印 FIX 消息"""
    tag_names = {
        8: 'BeginString',
        9: 'BodyLength',
        35: 'MsgType',
        49: 'SenderCompID',
        56: 'TargetCompID',
        34: 'MsgSeqNum',
        52: 'SendingTime',
        11: 'ClOrdID',
        55: 'Symbol',
        54: 'Side',
        38: 'OrderQty',
        40: 'OrdType',
        44: 'Price',
        39: 'OrdStatus',
        150: 'ExecType',
        31: 'LastPx',
        32: 'LastQty',
        10: 'CheckSum',
    }

    lines = []
    for tag, value in sorted(fields.items()):
        name = tag_names.get(tag, f'Tag{tag}')
        lines.append(f"  {tag:>3} ({name}): {value}")

    return '\n'.join(lines)

八、安全注意事项

8.1 生产环境要求

要求说明
TLS/SSL必须使用加密连接
IP 白名单券商通常限制连接 IP
证书认证部分券商要求客户端证书
序号持久化防止重启后序号冲突
消息日志所有消息必须记录备查

8.2 测试环境

1. 大多数券商提供 UAT(用户验收测试)环境
2. 先在 UAT 测试所有消息类型
3. 模拟各种异常场景(网络断开、消息乱序等)
4. 验证订单生命周期完整性

九、常见误区

误区一:FIX 协议很复杂,只有专业机构才需要

对于直接对接券商的量化交易者,理解 FIX 是必要的。即使使用封装好的 API,了解底层协议有助于排查问题。

误区二:所有券商的 FIX 实现都一样

虽然 FIX 是标准协议,但各券商可能有:

  • 自定义 Tag
  • 不同的必填字段要求
  • 特定的消息流程

误区三:可以忽略会话层管理

会话层(心跳、序号)的正确处理是稳定运行的基础。忽略这些会导致连接不稳定和消息丢失。


十、总结

要点说明
核心用途订单提交、成交回报、撤单
消息结构Header + Body + Trailer,键值对格式
关键消息D(新订单), 8(执行报告), F(撤单)
实现方式QuickFIX 是最常用的开源库
生产要求TLS、序号持久化、完整日志

延伸阅读

Cite this chapter
Zhang, Wayland (2026). FIX协议入门. In AI Quantitative Trading: From Zero to One. https://waylandz.com/quant-book/FIX协议入门
@incollection{zhang2026quant_FIX协议入门,
  author = {Zhang, Wayland},
  title = {FIX协议入门},
  booktitle = {AI Quantitative Trading: From Zero to One},
  year = {2026},
  url = {https://waylandz.com/quant-book/FIX协议入门}
}