一、订单簿结构:交易系统的核心数据结构

订单簿,说白了就是一张实时更新的买卖挂单表。我刚开始接触交易系统时,觉得这东西不就是个价格排序列表吗?后来才发现,它的设计直接决定了撮合引擎的性能天花板。

1.1 订单簿的基本组成

一个标准的订单簿包含两部分:

  • 买单队列(Bid Queue):按价格从高到低排列
  • 卖单队列(Ask Queue):按价格从低到高排列

每个价格档位上,可能挂着一堆订单。这些订单按到达时间排队,也就是所谓的「价格优先、时间优先」原则。

核心要点:订单簿的本质是一个双向优先队列。买单侧价格越高越优先,卖单侧价格越低越优先。

1.2 我在项目中踩过的坑

我曾经在一个FPGA加速项目中,直接用数组实现订单簿。结果呢?当订单量达到10万笔时,插入操作耗时飙升到微秒级。对于高频交易来说,这简直是灾难。

后来我改用跳表(Skip List)结构,插入和查询都稳定在纳秒级。你想想看,同样的数据量,性能差了三个数量级。

1.3 订单簿的硬件友好设计

FPGA实现订单簿,有几个关键点:

  • 并行比较器:用硬件并行查找价格档位,比软件二分查找快得多
  • 双端口RAM:同时支持读写操作,避免流水线停顿
  • 流水线设计:把插入、删除、查询操作拆成多级流水

我的建议:刚开始做FPGA订单簿时,别追求全并行。先做一个简单的单周期操作版本,跑通了再优化。我见过太多人一上来就想做全流水,结果调试了三个月还没跑起来。

二、撮合引擎原理:从订单到成交的完整链路

撮合引擎,就是订单簿的消费者。它负责把买单和卖单匹配起来,生成成交记录。

2.1 撮合的基本流程

  1. 接收订单:从网络或API接口拿到新订单
  2. 价格检查:判断能否与对手方队列成交
  3. 数量匹配:按价格优先顺序逐笔撮合
  4. 生成成交:输出成交价格、数量、时间戳
  5. 更新订单簿:删除已成交订单,插入未成交部分

嗯,这里要注意:撮合引擎必须保证原子性。要么全部成交,要么全部不成交,不能出现部分成交后系统崩溃的情况。

2.2 连续竞价与集合竞价

类型 特点 适用场景
连续竞价 订单实时匹配,立即成交 正常交易时段
集合竞价 收集一段时间订单,统一撮合 开盘、收盘、临时停牌

我记得有一次做期货交易所项目,客户要求同时支持两种模式。FPGA实现时,我用了状态机切换,连续竞价用流水线,集合竞价用批处理。两种模式共用一套订单簿存储,节省了不少资源。

2.3 撮合引擎的FPGA实现要点

避坑指南:我曾经在撮合引擎里用了全局复位信号,结果每次复位都会把订单簿清空。生产环境下一复位,所有挂单全没了,客户差点投诉到证监会。后来我改成只复位控制逻辑,数据存储用独立电源域。

FPGA撮合引擎的核心是状态机设计:

  • IDLE:等待新订单
  • PARSE:解析订单类型和参数
  • MATCH:执行撮合逻辑
  • UPDATE:更新订单簿
  • OUTPUT:发送成交结果

每个状态控制在1-2个时钟周期内完成,这样才能达到纳秒级的处理延迟。

三、市场数据协议:交易系统的通信语言

市场数据协议,就是交易所和交易者之间的「方言」。不同的交易所用不同的协议,但核心内容都差不多。

3.1 常见的市场数据协议

  • FIX协议:金融信息交换协议,文本格式,可读性好但带宽利用率低
  • Binary协议:二进制格式,带宽利用率高,解析速度快
  • ITCH协议:纳斯达克专用,逐笔数据,信息量大
  • OUCH协议:订单管理协议,用于提交和撤销订单

我个人习惯用Binary协议做FPGA加速。为什么?因为FPGA擅长处理固定长度的二进制数据包,解析起来比文本协议快得多。

3.2 协议解析的硬件实现

FPGA解析市场数据协议,通常分三步:

  1. 帧同步:找到数据包的起始位置
  2. 头部解析:提取消息类型、序列号、时间戳
  3. 负载解析:根据消息类型解析具体字段

关键点:协议解析必须处理异常情况。比如数据包损坏、序列号跳跃、重复包等。我见过一个系统,因为没处理重复包,导致同一笔订单被撮合了两次,最后亏了几百万。

3.3 市场数据的压缩与解压缩

交易所每秒发送几百万条市场数据,带宽压力很大。常用的压缩方法有:

  • 增量编码:只传输价格变化量,不传全量
  • 字典压缩:用短编码代替长字符串
  • 快照+增量:定期发送全量快照,中间只传增量

FPGA实现解压缩时,我建议用查找表(LUT)做字典解码,比用BRAM快一个时钟周期。别小看这一个周期,在高频交易里,1纳秒的延迟可能就是几百万的利润差距。

四、交易指令类型:订单的「身份证」

交易指令类型,决定了订单怎么被处理。不同的指令类型,在FPGA里对应不同的处理逻辑。

4.1 基础指令类型

指令类型 说明 FPGA处理特点
市价单 立即以当前最优价格成交 直接匹配对手方最优档位
限价单 指定价格,不达到不成交 需要插入订单簿对应档位
止损单 价格触发后转为市价单 需要监控价格变化
冰山单 只显示部分数量,隐藏真实量 需要维护隐藏队列

4.2 高级指令类型

除了基础指令,还有几种高级类型:

  • FOK(Fill or Kill):要么全部成交,要么全部撤销
  • IOC(Immediate or Cancel):立即成交剩余部分,未成交的撤销
  • GTD(Good Till Date):指定有效期,过期自动撤销

我曾经在FPGA里实现FOK指令时,遇到一个难题:如何判断「能否全部成交」?如果按顺序逐笔检查,延迟太大。后来我用了并行求和器,同时计算所有对手方档位的总数量,一个时钟周期就出结果。

4.3 指令类型的FPGA编码

在FPGA里,指令类型通常用枚举编码:

// 指令类型编码
typedef enum logic [3:0] {
  MARKET     = 4'b0001,
  LIMIT      = 4'b0010,
  STOP       = 4'b0100,
  ICEBERG    = 4'b1000
} order_type_t;

// 指令标志位
typedef struct packed {
  logic        is_fok;
  logic        is_ioc;
  logic        is_gtd;
  logic [31:0] expire_time;
} order_flags_t;

嗯,这里要注意:编码要留足余量。我见过有人用3位编码,结果后来交易所新增了两种指令类型,整个FPGA代码都得重写。建议至少用4位,预留扩展空间。

我的经验:指令类型的FPGA实现,核心是「分类处理」。用一个case语句区分指令类型,每种类型对应一个处理模块。这样代码清晰,也方便后续增加新类型。

知识体系总览

下面这张图,展示了本章四个核心概念之间的关系:

交易系统核心概念关系图 订单簿 Order Book 撮合引擎 Matching Engine 市场数据协议 Market Data 交易指令类型 Order Types 读取 输出 定义行为 更新 订单簿是数据核心,撮合引擎是处理核心,指令类型定义规则,市场数据协议负责通信

这张图你看懂了吗?订单簿是存储层,撮合引擎是处理层,指令类型定义了处理规则,市场数据协议负责对外通信。四者缺一不可。