用 Rust 写了一个极简撮合引擎核心:Lighting Match Engine Core
[开源] 用 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 的边界和正确性打牢,再逐步补外围组件。