[开源] 用 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
- 只接受限价单,市价单会被拒绝
交易阶段通过 MarketPhase 和 SessionRunner 驱动,核心阶段包括:
PreOpenAuctionOrderEntryAuctionFrozenAuctionMatchingContinuousTradingTradingHaltClosed
协议层目前实现的是固定 64 字节 packet 编码,包含:
- 下单
- 撤单
- 成交广播
- 状态广播
- 错误回报
这部分现在主要是 core 边界和 wire format 的雏形,还没有实现真实 socket I/O。
为什么 core 要保持窄
我目前的判断是:撮合核心越窄越好。
比如下面这些能力我暂时都放在外围,而不是塞进 core:
- 午间休市、夜盘、节假日
- 市场特有交易制度
- 冰山单、止损单、TWAP 等策略型订单
- 风控、保证金、持仓、强平
- 产品上市、退市和 session 编排
core 只保留“没有它就无法完成基础撮合”的东西:
- 订单簿
- phase routing
- tick / dense range 校验
- 集合竞价池
- 基础成交结果
- 基础二进制消息格式
外围系统可以把复杂订单拆成普通订单,把复杂交易日历转换成 phase transition,把风控事件转换成普通平仓单。这样 core 的行为更容易测试,也更容易替换外围策略。
一个简化的运行流程
当前 demo 大概是这样跑的:
- 读取 CLI 参数,生成
AppConfig - 根据参数创建
dense或sparse订单簿 - 初始化
EngineState - 通过
SessionRunner执行开盘集合竞价,进入连续竞价 - 加载测试订单簿
- 循环提交买卖单,统计内部撮合耗时
- 打印最后一次撮合结果和延迟分位数
可以直接运行:
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。这个后面会整理。
我想请教社区的几个问题
- 对于撮合核心,大家会倾向保持现在这种“窄 core + 外围编排”的边界吗?还是某些市场制度应该下沉到 core?
dense订单簿用数组档位换速度,sparse用树结构换通用性,这个拆分是否合理?还有没有更推荐的数据结构?- 集合竞价的价格计算目前基于候选价格扫描和累计量计算,有没有更好的实现方式或边界 case 需要特别注意?
- 固定 64 字节 packet 这种 wire format,在 Rust 里大家会更推荐手写 codec,还是用 zerocopy/bytemuck 这类方式?
- 对这种低延迟 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 的边界和正确性打牢,再逐步补外围组件。
评论区
写评论还没有评论