← 返回模块
4.6.3.2beta 可读 · 未来付费校验通过内容版本 2026-05-28

交易所连接、FIX 协议与行情数据

4.6.3 · 实盘交易与运营 · 量化全流程

交易所连接、FIX 协议与行情数据

09:20 上海,周一早上。一只新的多空股票策略今天 09:30 上线。报盘到 国信证券 主经纪商的 FIX 等价会话处于 LogonSent,没有 Logon Accepted 回执。算法被堵在外面发不出报单。基金经理在微信群里追问怎么回事。运维工程师正在网关日志里找最近一次成功 Logon —— 生产侧出向序号 4,521,003,券商上次确认到 4,521,001。券商拒掉了这次 Logon,因为会话应当从 4,521,002 接续,而我们的网关送出了 4,521,003:相差一位。本应在今早 04:00 上海触发的冷启动序号重置失败了。修复办法是手工发送 SequenceReset 35=4 同时 36=4521002,通过侧通道知会券商的 FIX 工程师;会话重连;交易台在 09:32 开盘 —— 晚了两分钟,没有 PnL 损失。本课结束时你应当能读懂 FIX 日志、走完会话状态机、诊断序号缺口、识别在延迟敏感链路上替代 FIX 的交易所专用协议,并讲清楚行情接入(feed handler)与 EMS 的契约。

本课讲连接层,即 L1 命名为订单生命周期第 7、8、9 步的部分 —— 把子单送到交易所、把成交回带回来的线上比特。下面分五块。

(A) FIX 报文格式与七条会话消息

FIX(Financial Information eXchange,金融信息交换)是行业标准的标签=值、分隔符分割的盘前与交易协议(国内对应协议 STEP / OES / CTP 的语义近似)。一条消息长这样:

8=FIX.4.4|9=148|35=D|49=BUYSIDE|56=SELLSIDE|34=1003|52=20260528-14:30:00.000|11=ORD0001|55=510300|54=1|38=1000000|40=2|44=4.50|59=0|10=047|

生产环境用 SOH(\x01)作字段分隔符(这里为可读用 | 代替)。每条消息开头必有报头字段:8 BeginString(FIX 版本)、9 BodyLength、35 MsgType、49 SenderCompID、56 TargetCompID、34 MsgSeqNum、52 SendingTime,结尾是 10 CheckSum。

下面这段内联代码列表固定七条标准的会话层消息:

(1) 35=A  Logon
(2) 35=0  Heartbeat
(3) 35=1  TestRequest
(4) 35=2  ResendRequest
(5) 35=4  SequenceReset
(6) 35=5  Logout
(7) 35=3  Reject

35=A Logon 启动会话,携带 EncryptMethod 98HeartBtInt 108,并可选 ResetSeqNumFlag 141=Y 用于冷启动重置序号。35=0 Heartbeat 在 HeartBtInt 秒空闲时发出以确认存活。35=1 TestRequest 在心跳缺失时发出,对端用 Heartbeat 回填 TestReqID 11235=2 ResendRequest 在检测到序号缺口时发出,携带 BeginSeqNo 7EndSeqNo 1635=4 SequenceReset 手工调整预期序号,携带 NewSeqNo 36,可选 GapFillFlag 123=Y35=5 Logout 优雅关闭会话。35=3 Reject 是应用层的拒绝(报文格式错误),携带 RefSeqNum 45RefTagID 371SessionRejectReason 373

(B) 四条应用消息与最常用的十二个字段

下面这段内联代码列表固定四条标准的应用层消息:

(1) 35=D  NewOrderSingle
(2) 35=F  OrderCancelRequest
(3) 35=G  OrderCancelReplaceRequest
(4) 35=8  ExecutionReport (ExecType 150 and OrdStatus 39 encode the state transition)

35=D NewOrderSingle 发出新单,必填 11 ClOrdID、55 Symbol、54 Side、38 OrderQty、40 OrdType、44 Price(限价时)、59 TimeInForce(0=当日、1=GTC、3=IOC、4=FOK)。35=F OrderCancelRequest 撤一笔挂单,必填新的 1141 OrigClOrdID(原始单的 ClOrdID)、555460 TransactTime。35=G OrderCancelReplaceRequest 改价或改量,必填 1141555438404435=8 ExecutionReport 覆盖所有状态变化 —— 交易所在每次变化时发一条:接收(ExecType=0)、部分成交或全部成交(ExecType=F,4.4 版本;150=1 是 4.2 的 PartialFill)、撤单(ExecType=4)、改单(ExecType=5)、拒单(ExecType=8)、当日完成(ExecType=3)。

下面这段内联代码列表固定十二个最常用的字段(按 tag 号背):

(1)  11   ClOrdID         (client-assigned per-session unique; primary key for grep)
(2)  37   OrderID         (exchange-assigned unique)
(3)  17   ExecID          (per-execution unique)
(4)  39   OrdStatus       (0=New, 1=PartiallyFilled, 2=Filled, 4=Canceled, 5=Replaced, 8=Rejected)
(5)  150  ExecType        (the state-change reason on this ExecutionReport)
(6)  40   OrdType         (1=Market, 2=Limit, 3=Stop, 4=StopLimit)
(7)  54   Side            (1=Buy, 2=Sell, 5=SellShort)
(8)  38   OrderQty        (original requested quantity)
(9)  44   Price           (limit price)
(10) 32   LastQty         (quantity filled in this execution)
(11) 31   LastPx          (price of this execution)
(12) 14   CumQty          (cumulative quantity filled so far)

FIX 日志的调试纪律把实盘的每个运营问题都化为一行:grep ClOrdID=XYZ fix.log | sort -k4 -t'|'。打出来的序列 —— 35=D35=8 ExecType=0、多条 35=8 ExecType=F、最后一条 35=8 OrdStatus=2 ExecType=F —— 把一笔订单从线上的一生讲清楚。

(C) 会话状态机与序号管理

FIX 会话是一条带状态的 TCP 连接,承载一条按顺序编号的报文流。状态机:NotLoggedOn →(发 Logon 35=A)→ LogonSent →(收到对端 Logon)→ LoggedOn →(每 HeartBtInt 秒一次心跳)→ … →(发 Logout 35=5)→ LogoutSent →(收到 Logout)→ NotLoggedOn

每一侧维护一个出向计数和一个预期入向计数。每一条消息携带 34 MsgSeqNum,每次成功发收都自增。缺口检测:当入向 MsgSeqNum 超过预期入向计数,接收侧发 ResendRequest 35=2 索回缺失区间。发送侧用 43=Y PossDupFlag 重放缺失消息。若缺口无法填,发 SequenceReset 35=4,36=NewSeqNo,加 123=Y GapFillFlag。

冷启动 vs 暖启动:冷启动把序号重置为 1(Logon 带 141=Y);暖启动保留昨日的出向与预期入向计数。多数主经纪商要求每日在固定时间窗(典型 03:00–06:00 本地)做一次序号重置。重置由一个定时批处理触发,它轮转当天的日志文件、把前一日的 wire 数据归档到不可变存储以满足 证监会 五年留存与 沪深 交易所 程序化交易管理实施细则 的审计 trail,并指挥 FIX 引擎在下一个会话开窗时发 141=Y 的 Logon。错过这次重置 —— 也就是本课开篇的故障模式 —— 会让引擎记着昨天的计数器而券商已经重置,产出错位一位的不匹配。常见故障:(a) 缺口无重传 —— 网线丢包接收侧没收到;表现为预期入向计数追不上;修法是手工 ResendRequest。(b) 缺口太大 —— 一次网络抖动丢了 1000+ 条;修法是与对端协调后手工 SequenceReset 到一个已知良好编号。(c) 双方对冷暖启动判断不一致 —— 开篇故障;修法即手工 SequenceReset。

下面这条 KaTeX 块公式固定 EMS 对 N 笔部分成交计算的均价:

Pˉavg=i=1NLastQtyiLastPxii=1NLastQtyi=CumQtyNAvgPxNCumQtyN\bar{P}_{avg} = \frac{\sum_{i=1}^{N} \mathtt{LastQty}_i \cdot \mathtt{LastPx}_i}{\sum_{i=1}^{N} \mathtt{LastQty}_i} = \frac{\mathtt{CumQty}_N \cdot \mathtt{AvgPx}_N}{\mathtt{CumQty}_N}

右式是 EMS 回报给 OMS 的数;左式是对账引擎独立重算的数,用于在 L1 生命周期的步骤 13 检测 EMS 与券商 drop-copy 之间的漂移。

Formula Explorer

N * (1/lambda) - rho

(D) 场所专用二进制协议与 FIX-vs-binary 取舍

亚毫秒级或更低延迟(亚 10 微秒来回)的策略,FIX 的文本格式与标签解析成为瓶颈;交易所提供二进制协议以降低延迟。国内对应链路:上证 SSE STEP / 沪报盘(SSE EzPS 高速变体可达亚毫秒,标准变体 1–5 毫秒);深圳 SZSE OES(Order Entry System);中金所 CFFEX CTP / 综合交易平台(CTP 是国内期货 broker API 主流,几乎所有期货经纪商 国信 / 中信 / 海通 / 招商 都在用);上期所 SHFE + 大商所 DCE + 郑商所 CZCE + INE 上海国际能源中心(商品期货均用 CTP 变体);港交所 HKEX OCG(Orion Central Gateway,二进制,亚 10 微秒)。在 CTP 之上,易盛 ESunny(商品 EMS)与飞马 FemasAPI(亚毫秒 HFT 级)、顶点风控金仕达风控 是国内主要的经纪 API 与风控栈。

要点:国内场所早期未广泛采用 FIX —— 国内连接基本是场所自研协议(CTP、OES、STEP、OCG)。FIX 出现在跨境通道(沪港通、深港通、合资 QFII / RQFII 通过 高盛上海 / 摩根上海 给境外客户提供 FIX 入口)。

讲清楚 FIX-vs-binary 的取舍。FIX 通用、文本可调、所有 PB 与 EMS 都支持,对任何延迟容忍 >1 毫秒的策略足够。二进制场所专用、日志文件需解码器、亚 100 微秒策略必需 —— 通常是 HFT 做市、延迟套利与亚秒级统计套利。多数 4.6.1 策略目录里的策略(多空股票、日频统计套利、CTA、期权波动率)跑在 FIX 等价协议(STEP / OES / CTP)上很舒服;亚秒统计套利与 HFT 做市必须走二进制。这种判断在国内体现在 明汯 / 幻方 / 灵均 头部 私募 都在 上证 张江 数据中心 与 深圳 福田 数据中心 部署机柜并接 飞马 FemasAPI / OES 高速通道。

把术语统一一下:行情接入产出的标准化结构称为 限价订单簿(LOB),展开后逐笔的有 订单簿、订单流、队列位置、买卖价差(NBBO 上下两边)、最小变动单位(沪深 A 股 RMB 0.01,ETF 期权 RMB 0.0001)。在 LOB 上对挂单两边持续报价并赚取价差的参与者是 做市商;他们 quote 的两边受 市场冲击 与库存风险约束。

(E) 行情接入(feed handler)、托管(colo)与 drop-copy

行情接入(实现细节在 3.4 / 3.5 / 3.6.4)订阅多播 UDP 二进制行情、解析为标准化的订单簿表示、把 (last_price, NBBO, depth_n, volume_today) 流喂给 EMS 与风控系统。国内场所:上证 Level-2 / 沪行情 / 上证 Level-2(10 档行情多播,沪 张江 机柜约 5 毫秒延迟);深圳 SZSE Level-2 / 深行情(10 档行情多播);中金所 CFFEX Level-2 行情(股指期货);SHFE / DCE / CZCE / INE 商品期货 Level-2 行情;港交所 HKEX OMD-C。供应商数据源(延迟或合成):通联数据 DataYes(万得 子公司)、恒生聚源(恒生电子 聚源)、万得 Wind 数据直连(国内财务数据厂商主流)、同花顺数据东方财富 Choice。要点:量化托管基金必须用直连多播 Level-2,供应商源太慢。上证、深圳 Level-2 多播订阅需合格投资者批准并与交易所签托管协议(通过券商二级转授权)。

托管交叉连接:撮合引擎所在的物理机楼 —— 上证 上海 张江 数据中心(浦东 张江,撮合引擎与合格券商 / 基金机柜共址);深圳 SZSE 深圳 福田 数据中心(撮合 + 机柜);中金所 CFFEX 上海 张江(撮合 + 机柜,与 上证 张江 分点运营但同区);港交所 HKEX HKEX Central(中环 港交所 总部)+ 将军澳 Tseung Kwan O 备站。订阅方把自家服务器物理放进同一栋楼,用交叉连接(一根短跨线缆到交易所网络口)拿到亚微秒的物理网络延迟。成本:沪 张江 约 RMB 10–50 万 / 月 / 机柜含跨线;深圳 福田 接近。亚 10 微秒策略必须托管;亚毫秒可托管或就近数据中心(如 沪外 Equinix 节点)。明汯 / 幻方 / 灵均 / 思特奇 等头部 私募 都在 沪 张江 + 深圳 福田 落机柜;Tower 上海 / Hudson River 上海 / Optiver 上海 / IMC 上海 等外资 HFT 同样通过本地券商部署。

drop-copy 是一条只读 FIX 等价会话,把主会话上的成交全部镜像给主经纪商或结算商。券商 PB 用 drop-copy 做实时风险与隔日对账(L3 实现,L4 收尾)。drop-copy 既是审计通道,也是 L3 中头寸对账差异检测的依据。

下面这段围栅 text 代码块展示一笔完整成交的样本 FIX 日志:

35=D|11=ORD0001|55=510300|54=1|38=1000000|40=2|44=4.50|59=0
35=8|11=ORD0001|37=SSE0042|17=EXEC001|39=0|150=0|14=0|151=1000000
35=8|11=ORD0001|37=SSE0042|17=EXEC002|39=1|150=F|14=200000|151=800000|32=200000|31=4.45
35=8|11=ORD0001|37=SSE0042|17=EXEC003|39=1|150=F|14=600000|151=400000|32=400000|31=4.46
35=8|11=ORD0001|37=SSE0042|17=EXEC004|39=2|150=F|14=1000000|151=0|32=400000|31=4.47
35=8|11=ORD0001|37=SSE0042|17=EXEC005|39=2|150=3

逐行走:第 1 行 —— NewOrderSingle 发出 1,000,000 股 510300(沪深300 ETF)限价 4.50,当日有效。第 2 行 —— ExecutionReport ExecType=0(New,OrdStatus=0)09:45:01.234 应答。第 3 行 —— 部分成交 200,000 股 4.45;CumQty 200,000,LeavesQty 800,000。第 4 行 —— 再成交 400,000 股 4.46;CumQty 600,000,LeavesQty 400,000。第 5 行 —— 最后成交 400,000 股 4.47;CumQty 1,000,000,LeavesQty 0;OrdStatus 跳到 2(Filled)。第 6 行 —— DoneForDay(ExecType=3)10:15。EMS 算出的均价是 (200000 × 4.45 + 400000 × 4.46 + 400000 × 4.47) / 1000000 = 4.462

下面这段围栅 python 代码块勾勒 FIX 解析器:

# FIX 4.4 tag=value pipe-delimited parser
SOH = chr(0x01)

class FixParser:
    def parse_message(self, raw_bytes):
        """Split SOH-delimited bytes into {tag: value} dict."""
        ...

    def extract_tag(self, msg, tag_number):
        """Return value for tag_number from parsed msg dict."""
        ...

    def is_session_message(self, msg):
        """True if 35 in {A,0,1,2,3,4,5}."""
        ...

    def is_application_message(self, msg):
        """True if 35 in {D,F,G,8}."""
        ...

    def trace_clordid(self, clordid, log_file):
        """grep equivalent: return ordered list of messages for one ClOrdID."""
        ...

收束在纪律条款:实盘每一笔订单都有一条可在亚毫秒内解析的 FIX 日志 trail;运营调试从 grep 该订单的 ClOrdID 开始,按时序走一遍打出来的序列。

Exercise

某券商 FIX 等价会话(STEP / OES / CTP)09:20 上海 卡在 LogonSent;没有 Logon Accepted 回执。(i) 按顺序背诵七条会话层消息的 35= 值。(ii) 画会话状态机:从 NotLoggedOnLogonSent / LoggedOnLogoutSent 再回到 NotLoggedOn,包含心跳 / TestRequest / ResendRequest 的跳转。(iii) 诊断错位一位序号的故障:券商上次确认出向 MsgSeqNum=4521001,我们的网关在 4521003 —— 指出你会发哪条会话消息恢复,并写出精确的 35= 值和 36= NewSeqNo 字段。(iv) 走一遍买入 1,000,000 510300 的 FIX 来回日志:NewOrderSingle → ExecutionReport(New) → 部分成交 → Filled → DoneForDay,识别 35= 序列与 CumQty / LeavesQty / LastPx 的演变。(v) 命名各场所替代 FIX 的二进制协议:上证、深圳、中金所(国内三家)。

提示
七条会话消息按三类用途分组:开关会话(35=A35=5)、保活(35=035=1)、缺口恢复(35=235=435=3)。
提示
会话开窗错位一位由 SequenceReset 35=4 修复,36= 设为对端下一个期望编号。这里对端上次确认到 4,521,001,因而期望 4,521,002 —— 发 35=4|36=4521002 并带 123=Y GapFillFlag。

向 L3 的衔接:七项盘前风控检查就坐在 EMS 与 FIX 通道之间 —— 在 35=D 发出之前拦下每个子单;熔断开关触发时则把整条 FIX 会话切掉。L3 打开七项检查,L4 把它们包进交易台的日常运营中。