4、交易信号处理核心:行情数据解析(Level1/Level2)、订单簿数据结构设计

行情数据,说白了就是市场的「心跳」。

做量化交易,尤其是高频方向,你每天打交道最多的就是它。我刚开始接触这个领域时,总觉得行情解析不就是把网络包拆开读字段吗?后来被现实狠狠教育了一顿——解析速度慢一拍,你的策略就比别人晚进场,盈亏可能就是天壤之别

这一章,我们聊聊行情数据的两种主流规格,以及如何在FPGA里给订单簿设计一个「快、准、狠」的数据结构。

4.1 Level1 与 Level2:你看到的是同一个市场吗?

先搞清楚一个概念。Level1和Level2,不是谁比谁高级,而是信息颗粒度不同

  • Level1(快照行情):只告诉你当前最优的买一卖一,以及最新成交价、成交量。信息量小,适合做趋势跟踪或者低频策略。
  • Level2(逐笔行情):把整个订单簿的挂单情况都给你,甚至每一笔委托的到达、撤单、成交都告诉你。信息量大,适合做高频做市、套利。

我在项目中遇到过一个问题:用Level1的数据做订单簿重建,结果发现重建出来的深度跟真实市场对不上。为什么?因为Level1只给你「结果」,不给你「过程」。中间那些撤单、改价,你根本不知道。

核心结论:做高频交易,Level2是标配。Level1只能用来做辅助参考。

4.2 行情数据解析:从字节流到结构化数据

不管Level1还是Level2,数据到了FPGA里,都是一串字节流。你的任务就是:用最快的速度,把它拆成有意义的字段

举个例子,深交所的Level2行情快照,结构大致如下:

// 简化版行情快照结构(C语言风格)
typedef struct {
    uint32_t  channel_id;      // 频道ID
    uint64_t  timestamp_ns;    // 时间戳(纳秒)
    uint32_t  security_id;     // 证券代码
    uint64_t  pre_close_px;    // 昨收价
    uint64_t  open_px;         // 开盘价
    uint64_t  high_px;         // 最高价
    uint64_t  low_px;          // 最低价
    uint64_t  last_px;         // 最新价
    uint64_t  volume;          // 成交量
    uint64_t  turnover;        // 成交额
    // ... 还有买卖十档的价格和数量
} market_snapshot_t;

在FPGA里解析,我建议用流水线架构。一个时钟周期解析一个字段,而不是等全部收完再解析。你想想看,如果等整个包收完再处理,延迟至少增加几十个时钟周期。在高频场景下,这几十个周期可能就是一笔交易的胜负手。

个人经验:解析时,尽量用移位寄存器代替乘法器。比如价格字段是定点数,直接移位比做乘法快得多。我曾经把一个解析模块的延迟从12个周期优化到了3个周期,就是靠这个技巧。

4.3 订单簿数据结构设计:FPGA里的「账本」

订单簿,就是记录所有未成交委托的「账本」。在软件里,你可以用红黑树、跳表这些高级数据结构。但在FPGA里,简单粗暴才是王道

我个人习惯用价格桶(Price Bucket)结构。什么意思?就是把每个价格档位,映射到一个固定的存储位置。比如价格从1.00到100.00,每个价格对应一个桶,桶里存着这个价格上的总委托量。

为什么用桶?因为FPGA的BRAM(块内存)访问延迟固定,而且可以并行读写。你想想看,如果用一个链表,查找一个价格要遍历,延迟完全不可控。用桶,一个时钟周期就能拿到数据。

下面是一个简化版的订单簿结构设计:

// 价格桶结构(Verilog风格)
module order_book #(
    parameter PRICE_BUCKET_NUM = 10000,  // 价格桶数量
    parameter DEPTH_BITS = 64            // 每个桶的深度位宽
)(
    input  wire         clk,
    input  wire         rst_n,
    // 写端口:更新某个价格上的委托量
    input  wire [13:0]  wr_addr,         // 价格索引
    input  wire [63:0]  wr_data,         // 新的委托量
    input  wire         wr_en,
    // 读端口:查询某个价格上的委托量
    input  wire [13:0]  rd_addr,
    output reg  [63:0]  rd_data
);

    // 使用BRAM实现价格桶
    reg [63:0] bucket [0:PRICE_BUCKET_NUM-1];

    always @(posedge clk) begin
        if (wr_en) begin
            bucket[wr_addr] <= wr_data;
        end
        rd_data <= bucket[rd_addr];
    end

endmodule
注意:价格桶的数量不能无限大。BRAM资源是有限的。我一般根据策略需要的深度来定,比如只关注买卖各100档,那就只分配200个桶。别贪多,够用就行。

4.4 实战中的避坑指南

嗯,这里要重点说说我踩过的坑。

  • 时间戳对齐:不同交易所的时间戳格式不一样。有的是纳秒,有的是微秒。我曾经因为没做对齐,导致策略在回测和实盘时表现完全不一样。后来我统一在解析阶段就把所有时间戳转成纳秒。
  • 价格精度:行情数据里的价格,很多是定点数。比如深交所的价格是乘以10000后的整数。千万别当成浮点数处理,否则FPGA里会多出一堆DSP资源。
  • 乱序处理:网络传输中,行情包可能乱序到达。我建议在解析模块后面加一个重排序缓冲区,保证订单簿更新的顺序正确。否则,先到的撤单包可能把后到的挂单包给覆盖了。
一个小技巧:在FPGA里做订单簿更新时,可以用双缓冲(Double Buffering)。一个缓冲区给解析模块写,另一个给策略模块读。这样读写互不干扰,吞吐量直接翻倍。

4.5 本章知识体系总览

为了让你更直观地理解整个流程,我画了一张图。这张图展示了从行情数据到达FPGA,到最终生成订单簿快照的完整链路。

行情数据解析与订单簿生成流程 网络字节流 Level1 / Level2 流水线解析 字段提取 + 时间戳对齐 重排序缓冲区 保证更新顺序正确 双缓冲订单簿 价格桶结构(BRAM实现) 写缓冲区 ← 解析模块 读缓冲区 → 策略模块 订单簿快照输出 供策略模块直接使用 注:整个流程在FPGA内以流水线方式运行,延迟固定

这张图里,最核心的就是「双缓冲订单簿」那块。你想想看,如果没有双缓冲,解析模块在写的时候,策略模块就不能读,整个系统的吞吐量就卡住了。用了双缓冲,两边各干各的,互不干扰。

好了,这一章的内容就到这里。行情解析和订单簿设计,是FPGA加速交易信号处理的基础。把这些搞扎实了,后面的策略实现才能跑得稳、跑得快。


专注资料整理