3、行情数据协议解析(一):FIX协议二进制格式解析、消息头/消息体/校验和、使用FPGA解析FIX协议的关键点
各位同学,今天我们来聊聊行情数据协议解析的第一站——FIX协议。说实话,FIX协议在金融圈里是个老面孔了,从90年代诞生到现在,几乎成了电子化交易的事实标准。但咱们做FPGA硬件加速的,关心的不是它怎么在软件里跑,而是怎么在硬件里把它“吃透”。
我刚开始接触FIX协议时,第一反应是:这玩意儿字段也太多了吧?后来做多了才发现,真正在行情链路里高频传输的,其实就那么几个核心字段。咱们今天就把这些核心点拆开揉碎了讲清楚。
3.1 FIX协议二进制格式概览
FIX协议有两种编码方式:一种是传统的文本版(Tag=Value),另一种是二进制版(FAST/FIX Binary)。咱们做硬件加速,肯定选二进制版。为什么?你想想看,文本解析在FPGA里做,光是ASCII转数字就要消耗大量LUT资源,而二进制格式天然对齐硬件处理。
二进制FIX的消息结构其实很简单,就三部分:
- 消息头(Header):固定长度,包含消息类型、序列号、时间戳等元信息
- 消息体(Body):变长部分,承载具体的行情数据(如订单簿、成交记录)
- 校验和(Checksum):消息末尾的3字节校验值,用于完整性验证
我习惯把FIX消息比作一个“信封”:信封外面写着发件人、收件人(消息头),里面装着信纸(消息体),最后贴个防伪标签(校验和)。
3.2 消息头解析:固定字段的硬件友好设计
二进制FIX的消息头通常是固定长度的,比如24字节或32字节。这在FPGA里简直是“天选之子”——固定长度意味着我们可以用简单的状态机+计数器来解析,不需要复杂的可变长度处理逻辑。
一个典型的消息头包含以下字段:
| 字段名 | 字节偏移 | 长度(字节) | 说明 |
|---|---|---|---|
| MsgType | 0 | 1 | 消息类型(如'D'表示行情快照) |
| SeqNum | 1 | 4 | 消息序列号(小端序) |
| SendTime | 5 | 8 | 发送时间戳(纳秒精度) |
| BodyLength | 13 | 2 | 消息体长度(不含头和校验和) |
| Reserved | 15 | 9 | 保留字段,通常填0 |
嗯,这里要注意:不同交易所的FIX实现可能有细微差别。比如有的交易所把SeqNum放在第5字节,有的放在第1字节。我在项目中遇到过一家交易所,它的消息头长度是28字节而不是标准的24字节,当时排查了好久才发现是文档版本没对齐。
3.3 消息体解析:变长字段的挑战
消息体是变长的,长度由消息头里的BodyLength字段指定。这部分是FPGA解析的难点——因为变长意味着我们不能用固定流水线来处理。
我常用的做法是:
- 先缓存整个消息体:用BRAM或分布式RAM把BodyLength指定长度的数据存下来
- 再按字段类型解析:根据MsgType决定解析模板(比如行情快照和逐笔成交的字段结构不同)
- 字段对齐:把变长字段(如股票代码)填充到固定宽度,方便后续处理
举个例子,一个行情快照消息体可能长这样:
// 伪代码:行情快照消息体解析
// 假设BodyLength = 64字节
// 字段布局:
// [0:7] Symbol (8字节, ASCII, 不足补空格)
// [8:15] BidPrice (8字节, 定点数, 精度1e-4)
// [16:23] AskPrice (8字节, 定点数)
// [24:27] BidSize (4字节, 整数)
// [28:31] AskSize (4字节, 整数)
// [32:63] Reserved (32字节, 扩展用)
// FPGA解析逻辑(Verilog风格)
always @(posedge clk) begin
if (body_valid) begin
symbol <= body_data[0*8 +: 64]; // 取前8字节
bid_price <= body_data[8*8 +: 64]; // 取第8-15字节
ask_price <= body_data[16*8 +: 64];
bid_size <= body_data[24*8 +: 32];
ask_size <= body_data[28*8 +: 32];
end
end
你看,一旦确定了字段偏移,FPGA的位切片操作就能发挥优势——一个时钟周期就能把所有字段提取出来,比CPU逐字节解析快几个数量级。
3.4 校验和计算:硬件加速的甜点
FIX协议的校验和计算方式很简单:从消息头第一个字节到消息体最后一个字节,所有字节求和后取低8位,再与消息末尾的3字节校验值比较。
说白了就是:
checksum = (sum_of_all_bytes) & 0xFF
// 然后与消息末尾的3字节ASCII数字比较
// 比如校验和是0x5A,消息末尾就是"090"(ASCII码)
在FPGA里做这个计算,我推荐用流水线加法器树。为什么?因为求和操作可以拆成多级流水,每个时钟周期处理8字节或16字节,吞吐量轻松达到100Gbps以上。
我曾经在一个项目中,用Xilinx的DSP48E2做校验和累加,配合BRAM做数据缓存,实现了单时钟周期处理32字节的校验逻辑。整个模块只用了不到200个LUT,延迟只有3个时钟周期。
3.5 使用FPGA解析FIX协议的关键点
做了这么多年FPGA加速,我总结了几个关键点,分享给大家:
- 状态机设计要简洁:FIX解析的状态机通常只需要3-4个状态(IDLE、HEADER、BODY、CHECKSUM)。别搞复杂了,状态越多越容易出时序问题。
- 数据对齐是王道:FPGA处理64位或128位数据最拿手。如果消息头是24字节,那就用3个64位周期读完,别一个字节一个字节地读。
- 预留扩展接口:交易所经常升级协议,比如增加新字段。我习惯在解析模块里留几个“万能槽位”,遇到未知字段就自动跳过,不影响主流程。
- 错误处理要快:校验和错误或字段越界时,直接丢弃整条消息并上报错误计数器。别尝试修复,行情数据时效性极高,修复不如重传。
下面这张图是我自己画的FIX协议解析流水线结构,大家可以参考:
这张图展示了我常用的五级流水线结构。你看,校验和计算是和消息体解析并行的,这样不会额外增加延迟。错误处理模块挂在旁边,一旦发现校验失败,直接丢弃整条消息,不影响主流水线。
核心要点回顾:
- 二进制FIX协议由消息头(固定长度)、消息体(变长)、校验和(3字节)三部分组成
- 消息头解析适合用固定状态机+计数器实现,一个时钟周期可提取所有字段
- 消息体解析需要先缓存再按模板提取,变长字段要填充到固定宽度
- 校验和计算用流水线加法器树,吞吐量高且延迟低
- 预留扩展接口和快速错误处理机制,应对协议升级和异常情况
好了,关于FIX协议的二进制解析就讲到这里。下一节我们会深入另一个主流协议——STEP协议,看看它和FIX有什么异同,以及在FPGA里怎么处理它的特殊字段。各位回去可以拿一份真实的行情抓包数据练练手,纸上得来终觉浅嘛。