集合竞价与撮合性能验证:清算价、分配和 Benchmark¶
30 秒版(开场)¶
集合竞价先在 collection 阶段积累订单,再在一个确定性时点统一 uncross,所有成交使用 同一 clearing price。一个常见价格优先级是:最大成交量、最小不平衡、最接近参考价, 但候选价格、market/limit 订单、price collar 和最终 tie-break 都是 venue-specific, 不能只说“取成交量最大价格”就认为结果唯一。性能验证也不能只跑空簿 QPS:先用规则 向量证明竞价/连续撮合正确,再固定订单簿深度、交叉率、订单类型和持久化边界,报告 p50/p99/p999、allocs、GC 和端到端延迟,并用多次 benchmark + benchstat 比较。
3 分钟版(精讲深度)¶
- 阶段状态机:Collection 接单/撤单但不连续成交;Opening/Uncross 冻结或排队新命令; Complete 后切回连续交易。
- 清算价格:遍历合法候选价,按版本化规则比较 executable quantity、imbalance、 reference distance 和最终 tie-break。
- 分配:优于清算价的订单优先,再处理清算价订单的 time/pro-rata 等 venue 规则。
- 可审计性:保存 indicative price、最终输入水位、规则版本、clearing result 和订单分配。
- 性能:区分状态机 CPU microbenchmark、WAL/fsync、网络和下游发布的端到端 benchmark。
可运行示例:
go test ./examples/senior/callauction/...
go test ./examples/senior/matchingengine -run '^$' -bench . -benchmem -count 10
10 分钟版(原理 + 图示)¶
stateDiagram-v2
[*] --> Collection
Collection --> Collection: accept / cancel / indicative quote
Collection --> Opening: cutoff sequence
Opening --> Uncross: freeze eligible set
Uncross --> Complete: one clearing price and allocations
Complete --> Continuous: publish trades and residual book
清算价不是一句公式¶
教学实现按以下顺序选择:
- 最大化可成交量
min(cumulativeBuy, cumulativeSell)。 - 最小化绝对不平衡
abs(cumulativeBuy-cumulativeSell)。 - 最小化与 reference price 的距离。
- 若仍相同,执行显式配置的 lower/higher final tie-break。
Nasdaq 官方材料也使用“最大成交、最小 imbalance、最接近 inside bid-ask midpoint”的 层级,但真实规则还涉及特定订单类型、inside price、阈值和监管规则。示例是 limit-order-only 教学策略,不会冒充 Nasdaq/Coinbase 的完整实现。
候选价与 indicative quote¶
- 候选价格可能来自订单限价、参考价、tick、price collar 和 venue 特定边界。
- Collection 期间的 indicative open price/size 只是当前订单集的结果,新订单和撤单会改变它。
- 最终 uncross 必须绑定一个输入 cutoff sequence;“最后一次每秒广播的 indicative quote” 不一定等于实际 opening price。
- 若没有可交叉 interest,应进入明确 no-cross/cancel/转连续交易规则,不能生成零量成交。
订单分配¶
示例按价格优先、同价 FIFO 分配:
- Buy:价格高于 clearing price 的优先,再到 at-price。
- Sell:价格低于 clearing price 的优先,再到 at-price。
- 两侧各分配 exactly executable quantity,所有 trade 使用 clearing price。
有的 venue 在清算价使用 pro-rata、size priority、imbalance-only 等规则;规则必须显式, 并用固定订单向量验证“总成交量守恒、订单不超量、单一成交价、分配确定”。
性能验证分层¶
| 层 | 测什么 | 不包含什么 |
|---|---|---|
| 数据结构 microbenchmark | add/cancel/match、档位深度、allocs/op | 网络、WAL、调度排队 |
| 状态机 benchmark | 完整命令与事件生成、STP/FOK/auction | fsync 和外部发布 |
| 单机 pipeline | sequencer → WAL → apply → event journal | 跨机网络与客户端 |
| 端到端 | gateway → ACK/trade/market data,故障与背压 | 不能用平均值掩盖尾延迟 |
工作负载必须声明¶
至少固定:
- 初始 book levels、每档订单数、热 symbol 数和 price distribution。
- add/cancel/amend/cross 比例,平均/尾部成交笔数。
- GTC/IOC/FOK/Post-only/STP/auction 比例。
- single writer 是否绑核、
GOMAXPROCS、CPU、内存、NUMA 和 Go 版本。 - WAL batch、fsync policy、网卡/内核参数与下游 backpressure。
“本机 100 万 QPS”如果没有这些条件,对架构判断没有价值。
Go Benchmark 的正确用法¶
go test ./examples/senior/matchingengine \
-run '^$' -bench . -benchmem -count 10 > before.txt
# 修改后在同一环境运行到 after.txt
benchstat before.txt after.txt
再用 -cpuprofile/-memprofile、go tool pprof、runtime trace 和操作系统 profiler 找证据。
Benchmark 中排除 fixture setup 可以看 hot path,但必须明确它不是端到端结果;不要为通过
任意阈值而删除正确性检查。工作负载还必须固定:不能让订单簿随 b.N 持续增长,再把
这个非平稳结果当成“单次挂单延迟”。示例因此使用固定 1,024 单 batch。
生产场景¶
- collection/opening 切换由权威 sequence 驱动,新命令在 cutoff 后排队到下一状态。
- 保存每次 indicative calculation 与最终 uncross 的 input hash/state hash。
- 开盘峰值前预热内存与连接,监控 command queue、WAL latency、GC pause 和 publication lag。
- 用 shadow replay 比较新旧规则的 clearing price、fills 和最终 residual book。
- 性能回归门禁使用统计比较和可解释阈值,硬件/Go 版本变化时重新建立 baseline。
排查与工具¶
竞价结果争议先重放同一 cutoff 前的命令,核对 eligible order、candidate prices、 reference、rule version 和 allocation。性能抖动则按 queueing、CPU、allocation/GC、 fsync、network、downstream 分层,不要只看总 QPS。
架构取舍¶
更复杂的数据结构可降低理论复杂度,却可能增加 pointer chasing、cache miss 和 GC; 连续价格数组缓存友好但占空间并受价格范围约束。应以真实分布 benchmark 和 profile 决策, 同时保持规则层与存储结构解耦。
深挖问答¶
- 最大成交量是否一定唯一价格? → 不一定,还需 imbalance、reference 和最终 tie-break。
- 集合竞价是否按订单到达就成交? → collection 阶段通常不连续撮合,统一在 uncross 时处理。
- Indicative price 是否可承诺? → 不是;输入集在最终 cutoff 前仍可能变化。
- Benchmark 无分配是否代表生产无 GC? → 不代表;网络解码、事件、日志和外围服务仍会分配。
- 平均 10µs 是否达标? → 需看 p99/p999、工作负载、排队和端到端持久化边界。
反模式与事故¶
- 只实现“成交量最大”,多个价格并列时依赖 map 遍历。
- collection 期间部分订单偷偷按连续撮合成交。
- 最终成交价与对外 indicative quote 没有 sequence 关联。
- 用空订单簿、无 WAL 的 microbenchmark 宣称端到端交易延迟。
- 只优化 QPS,未验证规则结果、尾延迟和恢复一致性。
代码示例¶
result, err := callauction.Uncross(orders, callauction.Policy{
ReferencePrice: 10000,
FinalTieBreak: callauction.LowerPrice, // 明确的产品规则
})
examples/senior/callauction/ 覆盖价格选择层级、显式最终 tie-break、价格时间分配与
no-cross;examples/senior/matchingengine/engine_benchmark_test.go 提供带边界说明的
microbenchmark。