交易系统核心模块设计:硬件实现思路

做量化交易系统这么多年,我最大的感触是:软件能解决的问题,硬件能解决得更快。但前提是——你得知道哪些模块值得用硬件加速。

今天咱们聊聊交易系统里四个核心模块:行情解析、订单管理、风控检查、撮合引擎。我会结合自己的实战经验,讲讲它们在硬件上怎么落地。

一、行情解析模块:从网线到内存的闪电通道

行情解析,说白了就是把交易所发来的二进制数据包,变成咱们能用的价格、成交量、深度信息。这个环节,延迟的瓶颈往往不在CPU,而在数据搬运。

核心思路:用FPGA做线速解析,数据从网线进来,直接进硬件逻辑,不走操作系统协议栈。

我在项目中遇到过最典型的问题:用软件解析行情,从网卡中断到应用层拿到数据,延迟至少5-10微秒。而FPGA方案,这个数字可以压到纳秒级。

具体怎么做?我习惯把行情解析分成三步:

  1. MAC层过滤——只抓UDP包,丢弃其他协议
  2. 应用层解码——按交易所协议(比如CTP、FIX/FAST)解析字段
  3. 数据分发——把解析好的行情推给后续模块

嗯,这里要注意:不同交易所的协议差异很大。比如上期所的CTP协议,报文头里有个字段叫「交易日」,如果你用固定偏移去解析,遇到节假日切换就可能出问题。我曾经踩过这个坑,后来改成动态解析,才彻底解决。

// 伪代码:FPGA行情解析状态机
always @(posedge clk) begin
    case(state)
        IDLE: if(rx_valid) state <= PARSE_HEADER;
        PARSE_HEADER: begin
            // 提取交易日、合约代码、数据长度
            trade_date <= rx_data[31:0];
            contract_id <= rx_data[63:32];
            data_len <= rx_data[95:64];
            state <= PARSE_BODY;
        end
        PARSE_BODY: begin
            // 按字段解析买卖盘口、最新价等
            bid_price <= rx_data[15:0];
            ask_price <= rx_data[31:16];
            // ... 更多字段
            state <= DONE;
        end
        DONE: begin
            // 输出解析结果
            data_valid <= 1;
            state <= IDLE;
        end
    endcase
end

个人经验:行情解析的FPGA实现,建议用流水线架构。每个时钟周期处理一个字段,不要用状态机来回跳转。这样吞吐量能稳定在线速。

二、订单管理模块:状态机的艺术

订单管理,说白了就是跟踪每个订单从「已提交」到「已成交」或「已撤单」的全生命周期。这个模块在软件里通常用数据库或哈希表实现,但在硬件里,你得用状态机+片上存储。

为什么会这样?因为硬件里没有malloc,没有动态内存分配。所有订单的状态,都得提前规划好存储位置。

我建议用双端口BRAM来存订单表。一个端口给写入逻辑,一个端口给查询逻辑。这样读写不冲突,延迟可控。

订单状态 硬件编码 说明
NEW 4'b0001 新订单,等待风控检查
ACCEPTED 4'b0010 风控通过,等待撮合
PARTIAL_FILLED 4'b0100 部分成交,剩余量继续等待
FILLED 4'b1000 全部成交,订单结束
CANCELLED 4'b1111 已撤单

你想想看,如果订单量很大,比如每秒10万笔,软件里用哈希表查一次可能要几十个时钟周期。而硬件里用BRAM,一个时钟周期就能读出状态。这就是硬件的优势。

避坑指南:我曾经在订单管理模块里用过外部DDR3来存订单表,结果发现延迟太大——读一次要几十个时钟周期。后来全部改成片上BRAM,延迟直接降到1个周期。所以,能用片上资源就别用片外。

三、风控检查模块:规则引擎的硬件化

风控检查,说白了就是一堆「如果...那么...」的规则。比如:

  • 如果订单价格超过涨跌停板,拒绝
  • 如果账户资金不足,拒绝
  • 如果单笔数量超过上限,拒绝
  • 如果累计撤单次数超过阈值,拒绝

这些规则在软件里用if-else实现,但在硬件里,我习惯用并行比较器阵列。每个规则对应一个比较器,所有规则同时检查,结果用与门汇总。

举个例子:

// 并行风控检查
assign reject_price = (order_price > max_price) || (order_price < min_price);
assign reject_balance = (order_amount > account_balance);
assign reject_volume = (order_volume > max_volume);
assign reject_cancel = (cancel_count > max_cancel);

assign reject = reject_price || reject_balance || reject_volume || reject_cancel;

嗯,这里要注意:风控规则是动态的。比如涨跌停板价格每天都会变。所以这些阈值不能写死在代码里,得用寄存器存起来,方便软件更新。

个人习惯:我会在FPGA里留一组AXI-Lite寄存器,专门给软件写风控参数。这样既保证了硬件的低延迟,又保留了软件的灵活性。

四、撮合引擎:价格优先、时间优先的硬件实现

撮合引擎是交易系统的核心。它的逻辑很简单:价格优先,时间优先。但实现起来,尤其是硬件实现,有不少门道。

我见过最直接的做法:用排序网络来维护买卖盘口。每次新订单进来,插入到合适的位置,同时检查是否能和对手盘成交。

具体来说:

  1. 买盘用降序排列(最高价在最前面)
  2. 卖盘用升序排列(最低价在最前面)
  3. 新买单进来,和卖盘第一档比较
  4. 如果买单价格 ≥ 卖一价,成交
  5. 成交后更新盘口,继续比较

你想想看,这个逻辑在软件里用红黑树或跳表实现,插入和删除都是O(logN)复杂度。但在硬件里,用并行比较器+移位寄存器,可以做到O(1)的插入和删除。

下面这张图展示了撮合引擎的核心数据流:

撮合引擎硬件架构图 新订单输入 风控检查 订单管理 撮合引擎 价格优先 · 时间优先 买盘队列(降序) 卖盘队列(升序) 成交回报输出

这张图展示了数据从新订单输入,经过风控检查、订单管理,最终进入撮合引擎的完整流程。买卖盘队列分别维护,撮合引擎根据价格优先、时间优先的原则进行匹配。

避坑指南:我曾经在撮合引擎里用过全排序网络,结果发现资源消耗太大——深度10层的盘口,用了上千个比较器。后来改成部分排序+流水线,资源降了80%,性能没降。所以,别追求理论上的最优,实际够用就行。

小结

这四个模块,行情解析和风控检查相对简单,适合用FPGA做线速处理。订单管理和撮合引擎稍微复杂,需要精心设计状态机和存储结构。但不管哪个模块,核心思路都一样:用并行性换延迟,用流水线换吞吐量

我个人觉得,硬件加速不是万能的。有些场景,比如复杂的策略逻辑,还是软件更灵活。但交易系统的核心链路——从行情到订单到成交——这几个环节,硬件加速的价值是实打实的。

嗯,今天就聊到这儿。下次咱们可以深入聊聊FPGA和GPU在交易系统中的具体分工。


专注资料整理