暗池交易核心逻辑:订单簿管理
做暗池交易硬件加速,第一个绕不开的就是订单簿管理。说白了,订单簿就是一张大表,记录着所有买卖意愿。但暗池的订单簿和普通交易所不一样——它不公开,你看不到别人的挂单。
我个人习惯把订单簿分成两层:价格层和订单层。价格层维护所有活跃的价格点,订单层挂在对应的价格下面。硬件实现时,我通常用双端口BRAM来存,一个端口负责读,一个端口负责写。
核心要点:暗池订单簿必须支持纳秒级的插入、删除、修改操作。软件里用红黑树,硬件里我们用哈希表+链表。
价格层设计
价格层其实就是一个排序数组。但暗池里价格精度很高,可能是0.0001美元。直接全展开?不现实。我建议用哈希映射,把价格映射到索引上。
举个例子:
// 价格哈希映射
typedef struct {
uint64_t price; // 实际价格 * 10000
uint32_t index; // 物理索引
uint32_t order_count; // 该价格上的订单数
} price_entry_t;
// FPGA实现时,用CAM(内容寻址存储器)
// 我曾在项目里用Xilinx的UltraRAM搭过4096条目的价格表
// 查找延迟稳定在2个时钟周期
嗯,这里要注意。价格层的更新频率很高,每秒可能有几十万次。用纯组合逻辑做CAM?功耗扛不住。我建议用流水线结构,把查找和更新分开。
订单层设计
每个价格点上挂着一串订单。暗池订单有几种类型:限价单、市价单、冰山单(只显示一部分)。硬件里处理冰山单有点麻烦,我踩过坑。
我曾经设计过一个方案:每个订单用128位表示:
| 字段 | 位宽 | 说明 |
|---|---|---|
| 订单ID | 32位 | 全局唯一 |
| 价格 | 32位 | 定点数表示 |
| 数量 | 32位 | 剩余数量 |
| 类型 | 8位 | 限价/市价/冰山 |
| 时间戳 | 24位 | 纳秒级精度 |
订单链表用FPGA的Block RAM实现。每个节点存下一个节点的指针。你想想看,如果全部用寄存器,一个订单就要几百个触发器,太浪费了。
实战技巧:订单池用环形缓冲区管理。预分配好N个订单槽位,用空闲链表维护可用槽位。这样分配和释放都是O(1)操作。
撮合引擎
撮合引擎是暗池的心脏。它的任务很简单:找到匹配的买卖单,执行交易。但要在硬件里做到低延迟,门道不少。
我习惯把撮合引擎分成三个流水级:
- 价格匹配级:检查新订单能否和对手盘匹配
- 数量匹配级:确定成交数量,处理部分成交
- 结果输出级:更新订单簿,发送成交回报
为什么这么分?因为每一级的逻辑复杂度不同。价格匹配最快,一个比较器就行。数量匹配要处理各种订单类型,最慢。流水线设计能让每一级独立工作,整体吞吐量就上去了。
价格匹配逻辑
买一价 ≥ 卖一价,就能成交。硬件里实现很简单:
// Verilog伪代码
wire match = (buy_price >= sell_price) && (buy_valid && sell_valid);
// 但暗池有特殊规则
// 比如:最小交易量、价格保护等
// 我遇到过客户要求"价格滑点不超过0.01%"
// 这时候就要加一个比较器做范围检查
我记得有一次,客户要求支持"暗池内价格优先,时间优先"的规则。价格优先好办,比较器搞定。时间优先就麻烦了——得比较两个订单的时间戳。硬件里时间戳是全局计数器,比较起来很快。
数量匹配逻辑
数量匹配要处理的情况很多:
- 完全成交:买单数量 = 卖单数量
- 部分成交:买单数量 > 卖单数量,剩余部分继续挂单
- 冰山单:只显示一部分数量,实际还有隐藏量
冰山单的处理,我建议用状态机。每个冰山单维护一个"已显示量"和"总剩余量"。当显示量成交完,自动补充下一批。这个逻辑在软件里很简单,硬件里要小心死锁。
避坑指南:我曾经设计过一个撮合引擎,冰山单的补充逻辑没处理好,导致订单簿出现"幽灵订单"——显示有单,实际已经成交完了。排查了三天才发现是状态机跳转条件写错了。
价格发现机制
暗池的价格发现和公开市场不一样。公开市场看最新成交价,暗池可能参考外部市场,也可能内部撮合。
我常用的方案是"参考价格+偏移量"。比如:
// 参考价格来自外部交易所
// 暗池内允许在参考价 ±0.1% 范围内成交
uint64_t ref_price = get_external_price();
uint64_t min_price = ref_price * 999 / 1000;
uint64_t max_price = ref_price * 1001 / 1000;
// 只有在这个范围内的订单才能进入撮合
为什么要有这个机制?防止暗池价格偏离太远。你想想看,如果暗池里有人挂个极低价,那成交价就不合理了。参考价格机制能保证暗池价格和主流市场保持一致。
我个人习惯用滑动窗口来更新参考价格。每100微秒从外部行情源取一次数据,取中位数作为参考价。这样能过滤掉一些异常波动。
硬件实现要点
价格发现模块里,最核心的是除法器。计算偏移量要用到乘除法。FPGA里除法器很贵,我建议用查找表+移位的方式近似计算。
举个例子:
// 计算 0.1% 的偏移量
// 不用除法,用移位
// 0.1% ≈ 1/1024
uint64_t offset = ref_price >> 10; // 右移10位相当于除以1024
// 精度损失?对于价格发现来说足够了
// 实际误差在0.002%以内
小技巧:如果客户要求高精度,可以用DSP48模块做定点乘法。Xilinx的DSP48支持25x18乘法,一个时钟周期出结果。
流动性管理策略
流动性管理是暗池的命门。没有流动性,暗池就是个空壳。硬件加速能帮上什么忙?主要是快速识别流动性提供者,给他们更好的待遇。
我设计过一套"流动性评分"系统:
- 每个交易者有一个评分,根据历史挂单量和成交率计算
- 评分高的交易者,订单优先级更高
- 评分低的交易者,可能被限制交易量
硬件实现时,评分计算用累加器就行。每成交一笔,更新一次评分。我建议用定点数,不要用浮点——浮点运算在FPGA里太慢了。
流动性池管理
暗池里通常有多个流动性池。比如:
| 池类型 | 特点 | 硬件处理方式 |
|---|---|---|
| 公开池 | 所有人可交易 | 简单FIFO |
| 机构池 | 仅限机构客户 | 白名单过滤 |
| 暗池对暗池 | 跨暗池撮合 | 需要外部接口 |
每个池的撮合逻辑略有不同。公开池用价格优先,机构池可能用时间优先。硬件里用配置寄存器来控制策略,这样不用重新编译就能切换。
我记得有个项目,客户要求支持"动态流动性池"——根据市场情况自动调整池的参数。这个有点复杂,我用了微码引擎来实现。每个池有一小段微码,描述它的行为。这样灵活性很高,但延迟增加了几个纳秒。
核心观点:流动性管理不是纯技术问题,它和业务策略紧密相关。硬件工程师要和交易员密切配合,理解他们的需求,才能设计出真正有用的加速方案。
本章知识体系
下面这张图展示了暗池交易核心逻辑的四个模块及其关系:
这四个模块不是孤立的。订单簿给撮合引擎提供数据,价格发现给撮合引擎提供参考价,撮合结果反馈给流动性管理,流动性管理反过来影响订单簿的优先级。硬件设计时,要把它们当成一个整体来考虑。
好了,这一章的内容就到这里。暗池交易的核心逻辑,说白了就是怎么在硬件里快速、准确地完成这些操作。下一章我们会深入具体的硬件架构设计,讲讲怎么用FPGA实现一个完整的暗池交易加速器。
公众号:蓝海资料掘金营,微信deep3321