< 返回版块

philipgreat 发表于 2026-06-01 03:12

Tags:matching engine

[开源] 用 Rust 写了一个极简撮合引擎核心:Lighting Match Engine Core

大家好,我最近整理并开源了一个用 Rust 写的撮合引擎核心项目:

GitHub: https://github.com/philipgreat/lighting-match-engine-core

项目名字叫 lighting-match-engine-core。它不是一个完整交易所系统,而是一个尽量窄的 matching core:只关注订单簿、连续竞价、集合竞价、基础交易阶段和固定长度二进制消息编码。OMS、风控、账户、行情网关、交易时段排程、产品管理这些外围系统都不放在 core 里。

我把它发出来,一方面是想和 Rust 社区交流一下低延迟撮合系统的设计取舍,另一方面也希望有人帮忙 review:哪些抽象合理,哪些地方还有明显的性能或正确性问题。

目前已经有的能力

连续竞价订单簿目前有两种实现:

  • dense:适合价格区间和 tick 比较明确的产品,用数组价格档位换取更直接的索引访问。
  • sparse:用 BTreeMap 管价格档位,适合价格范围更稀疏或不想预分配大数组的场景。

订单匹配按价格优先、时间优先处理。买单吃卖一档,卖单吃买一档;未完全成交的限价单会进入簿内。

集合竞价单独做成了 CallAuctionPool,目前支持:

  • 开盘集合竞价
  • 可选收盘集合竞价
  • 预留波动中断集合竞价类型
  • 最大成交量优先
  • 剩余不平衡量最小
  • 平衡时的价格 tie-break
  • 只接受限价单,市价单会被拒绝

交易阶段通过 MarketPhaseSessionRunner 驱动,核心阶段包括:

  • PreOpen
  • AuctionOrderEntry
  • AuctionFrozen
  • AuctionMatching
  • ContinuousTrading
  • TradingHalt
  • Closed

协议层目前实现的是固定 64 字节 packet 编码,包含:

  • 下单
  • 撤单
  • 成交广播
  • 状态广播
  • 错误回报

这部分现在主要是 core 边界和 wire format 的雏形,还没有实现真实 socket I/O。

为什么 core 要保持窄

我目前的判断是:撮合核心越窄越好。

比如下面这些能力我暂时都放在外围,而不是塞进 core:

  • 午间休市、夜盘、节假日
  • 市场特有交易制度
  • 冰山单、止损单、TWAP 等策略型订单
  • 风控、保证金、持仓、强平
  • 产品上市、退市和 session 编排

core 只保留“没有它就无法完成基础撮合”的东西:

  • 订单簿
  • phase routing
  • tick / dense range 校验
  • 集合竞价池
  • 基础成交结果
  • 基础二进制消息格式

外围系统可以把复杂订单拆成普通订单,把复杂交易日历转换成 phase transition,把风控事件转换成普通平仓单。这样 core 的行为更容易测试,也更容易替换外围策略。

一个简化的运行流程

当前 demo 大概是这样跑的:

  1. 读取 CLI 参数,生成 AppConfig
  2. 根据参数创建 densesparse 订单簿
  3. 初始化 EngineState
  4. 通过 SessionRunner 执行开盘集合竞价,进入连续竞价
  5. 加载测试订单簿
  6. 循环提交买卖单,统计内部撮合耗时
  7. 打印最后一次撮合结果和延迟分位数

可以直接运行:

cargo run --features match-timing --release -- --prodid 7 --name AAPL --test-order-book-size 50k

也可以切换订单簿实现:

cargo run --features match-timing --release -- \
  --prodid 7 \
  --name AAPL \
  --test-order-book-size 50k \
  --order-book sparse

集合竞价 benchmark:

cargo run --release -- --prodid 7 --name AAPL --bench-call-auction-only

关于性能数字

README 里有一个本地 demo 截图:在 Apple M1 Max MacBook Pro 上,50K bids + 50K asks 的测试簿里,内部 core matching latency 曾测到过个位数 ns 级别。

这里我想明确说明一下:这个数字只代表当前 demo/benchmark 路径下的内部匹配耗时,不包含网络、序列化、风控、持久化、行情广播、真实多线程调度等成本,也不等价于生产环境端到端延迟。

我更关心的是:

  • 订单簿数据结构是否还能优化
  • dense/sparse 两种实现的边界是否清晰
  • benchmark 是否应该补更真实的分布和回放数据
  • Rust 下有哪些更好的 cache/locality/branch 优化方式

如果大家有相关经验,很欢迎拍砖。

当前测试状态

当前仓库里有单元测试覆盖:

  • 配置解析和参数校验
  • dense 订单簿价格区间和 tick 校验
  • basic buy/sell matching
  • 集合竞价价格计算和撮合
  • 集合竞价撤单
  • phase routing
  • demo session runner
  • 64 字节协议编码中的错误回报和成交批量编码

本地执行:

cargo test

当前结果是 34 个测试通过。

不过项目仍然很早期,还有不少 warning,包括一些未使用代码,以及 Cargo.toml 里 target-specific rustflags 的写法目前会被 Cargo 当成 unused manifest key。这个后面会整理。

我想请教社区的几个问题

  1. 对于撮合核心,大家会倾向保持现在这种“窄 core + 外围编排”的边界吗?还是某些市场制度应该下沉到 core?
  2. dense 订单簿用数组档位换速度,sparse 用树结构换通用性,这个拆分是否合理?还有没有更推荐的数据结构?
  3. 集合竞价的价格计算目前基于候选价格扫描和累计量计算,有没有更好的实现方式或边界 case 需要特别注意?
  4. 固定 64 字节 packet 这种 wire format,在 Rust 里大家会更推荐手写 codec,还是用 zerocopy/bytemuck 这类方式?
  5. 对这种低延迟 core,大家通常怎么设计 benchmark,才能避免自嗨式的 ns 数字?

后续计划

短期想补的东西:

  • 整理 warning 和 manifest 配置
  • 更完整的撤单路径
  • 更严谨的 benchmark 输入
  • 补充行情广播/成交输出边界
  • 把协议 codec 和 core matching 的边界再理清楚
  • 增加更多集合竞价边界用例

中长期可能会做:

  • 外围 session scheduler 示例
  • 简单 TCP/UDP gateway 示例
  • 市场数据回放 benchmark
  • 多产品多实例部署示例
  • 更完整的文档和架构图

项目地址:

https://github.com/philipgreat/lighting-match-engine-core

欢迎 review、提 issue、提 PR,也欢迎直接在帖子里指出设计问题。这个项目还处在早期阶段,我更希望先把 core 的边界和正确性打牢,再逐步补外围组件。

评论区

写评论

还没有评论

1 共 0 条评论, 1 页