第四章:市场数据解析——行情数据的“翻译官”
做量化交易的,都知道一句话:行情是命,数据是血。
但说实话,我刚入行那会儿,真没把行情解析当回事。觉得不就是网络上来个包,拆开看看价格嘛。直到有一次,我负责的FPGA加速卡在实盘环境中丢了一个tick,导致套利策略开了反向仓……嗯,那天晚上我对着逻辑分析仪坐了四个小时。
从那以后,我养成了一个习惯:永远不要假设行情数据是干净的。
这一章,咱们就聊聊行情数据解析那些事儿。说白了,就是怎么把交易所发来的“天书”,变成FPGA能理解的“白话”。
4.1 行情数据格式:ITCH / OUCH / FIX
目前主流的交易所,用的协议就那么几种。我挑三个最常见的讲讲。
4.1.1 ITCH——最“啰嗦”的协议
ITCH是纳斯达克搞的。它有个特点:每个消息都告诉你“发生了什么”。
比如一个订单进来,ITCH会发一条“添加订单”消息,告诉你价格、数量、订单编号。订单被成交了,再发一条“执行交易”消息。订单被撤单了,再发一条“删除订单”消息。
你想想看,这多好理解?但代价就是——数据量巨大。我测过,在高峰时段,ITCH的带宽能占到总流量的60%以上。
ITCH的消息格式很固定。每条消息的第一个字节是消息类型,后面跟着固定长度的字段。举个例子:
// ITCH 5.0 的 "Add Order" 消息 (Type 'A')
Byte 0: 消息类型 (0x41 = 'A')
Byte 1-2: 时间戳 (纳秒,低16位)
Byte 3-6: 订单编号 (uint32)
Byte 7-8: 买卖方向 (0x42='B' 买, 0x53='S' 卖)
Byte 9-12: 价格 (uint32, 价格 = 原始值 / 10000)
Byte 13-14: 数量 (uint16)
Byte 15-17: 股票代码索引 (uint24)
我在项目中遇到过一个问题:ITCH的某些字段是“可选”的,比如“交叉交易”消息里的“交叉类型”。如果你按固定偏移去解析,很容易读错。我的建议是:每个消息类型单独写一个解析状态机,别偷懒用统一模板。
4.1.2 OUCH——给“下单”用的协议
OUCH和ITCH是“兄弟协议”。ITCH用来收行情,OUCH用来下单。
OUCH的消息更短。因为它不需要告诉你市场状态,只需要告诉交易所“我要买/卖多少”。
典型的OUCH下单消息长这样:
// OUCH 4.0 的 "Enter Order" 消息
Byte 0: 消息类型 (0x4F = 'O')
Byte 1-4: 订单编号 (uint32)
Byte 5: 买卖方向 (0x42='B', 0x53='S')
Byte 6-9: 价格 (uint32)
Byte 10-11: 数量 (uint16)
Byte 12-13: 股票代码索引 (uint16)
Byte 14: 订单类型 (0x4C='L' 限价, 0x4D='M' 市价)
OUCH有个坑:它的“订单编号”是客户端自己生成的。如果你在FPGA里生成编号,一定要保证不重复。我曾经见过一个团队,因为FPGA复位后编号计数器没清零,导致生成了重复订单……交易所直接拒单,策略停了整整一个交易日。
4.1.3 FIX——金融界的“通用语”
FIX协议就更有意思了。它是个文本协议,用“标签=值”的方式组织数据。
比如一条FIX行情消息可能是这样的:
8=FIX.4.2|9=78|35=W|49=EXCHANGE|56=CLIENT|55=AAPL|44=150.25|38=1000|10=123|
你看,每个字段前面都有个数字标签。35=W表示这是“市场数据快照”消息,55=AAPL表示股票代码,44=150.25表示价格。
FIX的好处是灵活,坏处也是灵活——解析起来太慢了。在FPGA里解析FIX,我个人的经验是:先做“标签定位”,再做“值提取”。别想着一次性解析完所有字段,那会浪费大量LUT资源。
4.2 硬件解析引擎设计
好了,协议看完了。接下来聊聊怎么在FPGA里“翻译”这些协议。
硬件解析引擎,说白了就是一个状态机+数据通路的组合。我一般把它分成三级流水线:
4.2.1 第一级:帧同步
这一级负责找到消息的“边界”。
对于ITCH来说,每条消息前面有个长度字段。对于FIX来说,每条消息以“8=FIX”开头,以“10=xxx”结尾。
帧同步的逻辑很简单:
- 检测起始标志
- 读取长度字段
- 计数到长度值,确认结束
但要注意:网络包可能被拆分成多个TCP段。我在项目中遇到过,一个ITCH消息被拆成了两个TCP包。如果帧同步逻辑没处理好,就会解析出“半条消息”。
我的做法是:在帧同步模块里加一个“重组缓冲区”。收到不完整的消息时,先存着,等后续数据来了再拼起来。
4.2.2 第二级:字段提取
这一级负责从消息里“抠”出各个字段。
对于固定长度的协议(ITCH/OUCH),这步很简单——按偏移量读就行。对于变长协议(FIX),就需要“标签匹配”了。
我常用的方法是:并行比较器阵列。比如,我需要提取“价格”字段(标签44),就同时比较8个字节,看哪个字节是“44=”。找到了,就从这个位置往后读。
代码示例(Verilog风格):
// 并行标签匹配示例
always @(posedge clk) begin
for (i = 0; i < 8; i = i + 1) begin
if (data_in[i*8 +: 8] == "4" &&
data_in[i*8+8 +: 8] == "4" &&
data_in[i*8+16 +: 8] == "=") begin
tag_found <= 1;
tag_position <= i;
end
end
end
嗯,这里要注意:并行比较器很耗资源。如果你要提取10个字段,每个字段用8路并行比较,那就是80个比较器。所以,我一般只提取“关键字段”——价格、数量、买卖方向。其他字段(比如“下单员ID”)直接透传,不做解析。
4.2.3 第三级:协议转换
这一级把解析出来的字段,转成内部统一的“总线格式”。
我习惯用这样的内部格式:
// 内部行情总线格式
typedef struct packed {
logic [31:0] price; // 价格,定点数
logic [31:0] quantity; // 数量
logic [1:0] side; // 买卖方向:00=买,01=卖
logic [15:0] symbol_id; // 股票代码索引
logic [63:0] timestamp; // 时间戳,纳秒
logic [7:0] msg_type; // 消息类型
} market_data_t;
这样,不管底层是ITCH、OUCH还是FIX,到了FPGA内部,都是统一的格式。后面的策略模块、风控模块,就不用关心“数据是从哪个交易所来的”了。
4.3 数据包分类与过滤
行情数据来了,不是每条都要处理。比如,你只交易AAPL和MSFT,那其他股票的消息就可以直接扔掉。
数据包分类与过滤,就是在FPGA里做“快速筛选”。
4.3.1 分类:按股票代码分桶
我常用的方法是哈希表+桶。
具体做法:
- 维护一个“关注股票列表”,比如256只股票
- 对每只股票,分配一个“桶编号”(0~255)
- 收到行情消息后,提取股票代码,查哈希表,得到桶编号
- 如果桶编号有效,就把消息发到对应的处理通道
哈希表可以用BRAM实现。我一般用直接映射的方式:股票代码的低8位作为桶编号。这样查表只需要一个时钟周期。
但要注意:哈希冲突。如果两只股票的代码低8位相同,就会冲突。我的做法是:在软件初始化时,检查所有关注的股票,确保没有冲突。如果有,就调整哈希函数。
4.3.2 过滤:按消息类型筛选
不是所有消息类型都需要处理。比如,ITCH里的“系统事件”消息(类型'S'),对交易策略来说就没用。
过滤逻辑很简单:
- 维护一个“允许的消息类型”位图
- 收到消息后,用消息类型作为索引,查位图
- 如果位图对应位为1,放行;否则丢弃
这个位图可以用一个32位的寄存器实现。ITCH有20多种消息类型,32位足够了。
4.4 多路行情合并
做高频交易的,通常不会只连一个交易所。比如,你可能同时连了纳斯达克和纽交所。这两家的行情数据,需要合并成一路“统一行情”。
多路行情合并,核心要解决两个问题:时序对齐和冲突处理。
4.4.1 时序对齐
不同交易所的行情数据,到达FPGA的时间不一样。可能纳斯达克的AAPL行情先到,纽交所的AAPL行情后到。但它们的“时间戳”是交易所给的,不是FPGA收到的时刻。
所以,合并时不能按“到达时间”排序,要按“交易所时间戳”排序。
我的做法是:每个交易所维护一个FIFO。收到行情消息后,先存到对应的FIFO里。然后,比较所有FIFO的“队首消息”的时间戳,把时间戳最小的那条发出去。
这个比较逻辑,可以用一个多路比较器实现。比如,有4路行情,就同时比较4个时间戳,选出最小的。
4.4.2 冲突处理
如果两个交易所同时报出同一只股票的价格,怎么办?
这取决于你的策略。有的策略用“最优价”(取最高买价、最低卖价),有的策略用“加权平均价”。
我一般这样处理:
- 对于“买一价”,取所有交易所中的最大值
- 对于“卖一价”,取所有交易所中的最小值
- 对于“成交量”,直接累加
这样处理的好处是:保守。你永远不会因为合并逻辑而“看到”一个比实际更好的价格。
4.5 本章知识体系总览
说了这么多,我画了一张图,帮你把这一章的知识点串起来。
这张图展示了行情数据从“输入”到“输出”的完整流程。你想想看,每一级都做一点“减法”——解析引擎把协议转成统一格式,分类过滤把无关数据扔掉,多路合并把多路变一路。最终,FPGA内部看到的是一条干净、统一、实时的行情流。
好了,这一章的内容就到这儿。行情解析是硬件加速交易系统的“地基”。地基没打好,上面的策略再牛也白搭。下一章,咱们聊聊订单管理——怎么把“交易信号”变成“交易所能理解的订单”。