4、撮合核心算法:价格优先时间优先原则、FIFO队列原理、撮合匹配逻辑

好,咱们今天聊点硬核的。撮合系统的心脏,说白了就是那套匹配逻辑。你想想看,交易所每秒要处理成千上万笔订单,凭什么给张三成交,不给李四?凭什么张三的订单排前面?这里头就是「价格优先、时间优先」这八个字在起作用。

我个人习惯把撮合算法拆成三块来看:排队规则队列结构匹配引擎。这三块搞明白了,整个撮合系统的骨架就搭起来了。

4.1 价格优先、时间优先原则

这个原则,说白了就是两条铁律:

  • 价格优先:买单出价高的排前面,卖单出价低的排前面。
  • 时间优先:价格一样的情况下,谁先来谁先成交。

嗯,听起来很简单对吧?但我在项目中遇到过一个问题:当价格相同的时候,时间戳的精度怎么定?微秒级够不够?纳秒级会不会太浪费资源?

我建议你直接用硬件时间戳,精度至少到微秒。为什么?因为软件时间戳在极端情况下会有抖动,而FPGA的时钟计数器是确定性的。你想想看,如果两个订单在同一个时钟周期到达,时间戳一样怎么办?这时候就得靠「端口优先级」或者「FIFO入队顺序」来二次裁决了。

核心要点:

  • 价格优先是「硬约束」,不可违反
  • 时间优先是「软约束」,在价格相同时生效
  • 时间戳精度决定了系统的公平性下限

4.2 FIFO队列原理

FIFO(First In First Out),先进先出。这玩意儿在FPGA里太常见了,但用在撮合系统里,有几个坑你得注意。

先看一个最简单的FIFO实现:

module fifo_simple #(
    parameter DEPTH = 256,
    parameter WIDTH = 64
)(
    input  clk,
    input  rst_n,
    input  wr_en,
    input  [WIDTH-1:0] wr_data,
    output reg [WIDTH-1:0] rd_data,
    input  rd_en,
    output reg full,
    output reg empty
);

    reg [WIDTH-1:0] mem [0:DEPTH-1];
    reg [7:0] wr_ptr, rd_ptr;
    reg [8:0] count;  // 多一位用于判断满/空

    // 写操作
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            wr_ptr <= 0;
            count <= 0;
        end else if (wr_en && !full) begin
            mem[wr_ptr] <= wr_data;
            wr_ptr <= wr_ptr + 1;
            count <= count + 1;
        end
    end

    // 读操作
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            rd_ptr <= 0;
        end else if (rd_en && !empty) begin
            rd_data <= mem[rd_ptr];
            rd_ptr <= rd_ptr + 1;
            count <= count - 1;
        end
    end

    // 满/空标志
    assign full  = (count == DEPTH);
    assign empty = (count == 0);

endmodule

这个代码看着简单,但有个问题:读和写不能同时操作同一个地址。我曾经在调试一个撮合系统时,发现订单莫名其妙丢失了,查了两天才发现是FIFO的读写冲突导致的。后来我改用双端口RAM + 独立读写指针,才彻底解决。

避坑指南:

我曾经在FIFO深度设置上吃过亏。深度设小了,订单高峰期直接溢出;设大了,资源浪费。我的经验是:深度取最大并发订单数的1.5倍,再留20%余量。

4.3 撮合匹配逻辑

匹配逻辑是撮合系统的「大脑」。它要干的事很简单:把买单和卖单对上号。但实现起来,细节多得吓人。

咱们先画个流程图,看看整个匹配过程长什么样:

撮合匹配逻辑流程图 订单到达 判断买卖方向 买单FIFO队列 卖单FIFO队列 取最高买单价格 取最低卖单价格 匹配? 成交 等待新订单

这个流程看着简单,但实际实现时,有几个关键点:

  1. 价格比较器:需要同时比较买单的最高价和卖单的最低价。我建议用并行比较器,一个时钟周期出结果。
  2. 数量匹配:价格匹配了,数量不一定匹配。这时候要处理「部分成交」的情况。
  3. 队列更新:成交后,队列头部的订单数量要扣减,如果数量归零则出队。

注意:部分成交的订单,剩余数量要重新插入队列。但插入位置不是队尾,而是根据价格优先原则重新排序。这个逻辑在FPGA里实现起来有点绕,我建议用「插入排序」的思想,每来一个新订单就扫描一次队列。

4.4 实战中的性能考量

好了,理论讲完了,咱们聊聊实际项目中怎么优化性能。

优化项 传统做法 FPGA优化做法 性能提升
价格比较 串行比较 并行比较器树 5x
队列查找 遍历查找 内容寻址存储器(CAM) 10x
匹配逻辑 状态机串行处理 流水线并行处理 8x
时间戳生成 软件打戳 硬件计数器 100x

嗯,看到这个表你可能会问:CAM是什么?说白了就是「按内容查找」的存储器。你给它一个价格,它直接告诉你这个价格在队列的哪个位置。我在一个高频交易项目里用过,查找延迟从10个时钟周期降到了1个时钟周期。

我的经验:如果你用的是Xilinx的FPGA,可以用BRAM搭一个简单的CAM。如果是Altera的,可以用MLAB。别自己用LUT去搭,资源消耗太大,划不来。

4.5 一个完整的匹配状态机

最后,咱们看一个简单的匹配状态机代码。这个代码只处理「价格匹配」和「数量匹配」两个核心逻辑,实际项目里还要加上超时、撤单等处理。

module match_fsm (
    input clk,
    input rst_n,
    input [31:0] buy_price,
    input [31:0] sell_price,
    input [31:0] buy_qty,
    input [31:0] sell_qty,
    output reg match_done,
    output reg [31:0] match_price,
    output reg [31:0] match_qty
);

    typedef enum {IDLE, COMPARE, EXECUTE, DONE} state_t;
    state_t state, next_state;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            state <= IDLE;
        else
            state <= next_state;
    end

    always @(*) begin
        next_state = state;
        case (state)
            IDLE: begin
                if (buy_price >= sell_price)
                    next_state = COMPARE;
            end
            COMPARE: begin
                // 价格匹配,比较数量
                next_state = EXECUTE;
            end
            EXECUTE: begin
                // 取较小数量成交
                match_qty = (buy_qty < sell_qty) ? buy_qty : sell_qty;
                match_price = (buy_price + sell_price) >> 1;  // 取中间价
                next_state = DONE;
            end
            DONE: begin
                match_done = 1;
                next_state = IDLE;
            end
        endcase
    end

endmodule

这个状态机看着简单,但实际项目中,COMPARE状态要处理的事情很多:检查是否有未成交的挂单、判断是否触发熔断、记录成交日志等等。我建议你把COMPARE状态拆成多个子状态,用流水线的方式处理,这样吞吐量能翻倍。

好了,关于撮合核心算法,今天就聊这么多。记住一句话:价格优先是铁律,时间优先是规矩,FIFO是基础,匹配逻辑是灵魂。把这四样东西吃透了,你就能设计出一个高性能的撮合系统。


公众号:蓝海资料掘金营,微信deep3321