3. 撮合引擎正确性验证:限价单、市价单、冰山订单的撮合逻辑测试

撮合引擎,说白了就是回测系统的心脏。心脏跳得对不对,直接决定了你的策略回测结果靠不靠谱。我见过太多人花大把时间优化策略逻辑,结果撮合引擎里藏着个bug,白忙活一场。

今天咱们就聊聊怎么验证三种最核心的订单类型:限价单、市价单、冰山订单。嗯,这里要注意,测试不是随便跑几个例子就完事,得有一套系统的方法论。

3.1 限价单撮合逻辑验证

限价单是最基础的订单类型。它的规则很简单:买入时价格不高于指定价,卖出时价格不低于指定价。但简单归简单,坑可不少。

核心验证点:

  • 价格优先级:买单价格高的先成交,卖单价格低的先成交
  • 时间优先级:同价格下,先到先得
  • 部分成交:订单可以部分成交,剩余部分留在订单簿
  • 价格穿越:买单价格高于卖一价时,按对手方最优价成交

我个人习惯用边界值测试法。举个例子,假设当前卖一价是10.00元,你挂一个10.01元的买单。这时候应该怎么撮合?

// 测试用例:买单价格穿越
// 当前订单簿:
// 卖一:10.00  数量100
// 卖二:10.02  数量200
// 卖三:10.05  数量150

// 测试1:限价买单 10.01,数量50
// 预期:以10.00成交50股,订单簿卖一变为10.00数量50

// 测试2:限价买单 10.01,数量150
// 预期:先以10.00成交100股,再以10.02成交50股
// 剩余0股,订单簿卖一变为10.02数量150

// 测试3:限价买单 9.99,数量100
// 预期:无法成交,订单进入买单队列

我在项目中遇到过一个问题:某个回测系统在处理价格穿越时,直接把买单价格改成了对手方价格。这其实不对。你想想看,如果买单价格是10.01,卖一价是10.00,成交价应该是10.00,而不是10.01。这个细节搞错了,回测结果会差很多。

避坑指南:我曾经在测试中发现,有些撮合引擎在处理限价单时,忽略了「价格优先」原则。比如两个买单,一个10.00,一个9.99,结果9.99的先成交了。原因是什么?订单到达顺序搞反了。所以测试时一定要同时验证价格和时间两个维度。

3.2 市价单撮合逻辑验证

市价单,说白了就是「不管价格多少,我就要成交」。它的验证重点和限价单完全不同。

市价单的核心规则:

  • 立即成交,不进入订单簿
  • 按对手方最优价格逐级成交,直到完全成交或对手方订单耗尽
  • 如果对手方流动性不足,剩余部分如何处理?这取决于系统设计

我建议用以下测试矩阵来验证市价单:

测试场景 订单簿状态 市价单数量 预期结果
完全成交 卖一10.00(100), 卖二10.02(200) 买入150 成交100@10.00, 50@10.02
部分成交 卖一10.00(100) 买入200 成交100@10.00, 剩余100取消
空订单簿 无对手方订单 买入100 立即取消,无成交
大单穿透 卖一10.00(50), 卖二10.05(30), 卖三10.10(20) 买入200 成交50@10.00, 30@10.05, 20@10.10, 剩余100取消

这里有个容易踩的坑:市价单的成交均价计算。我记得有一次,一个同事写的撮合引擎在计算市价买单的成交均价时,直接用了(10.00+10.02)/2=10.01。这显然不对。正确的算法应该是加权平均:(100*10.00 + 50*10.02) / 150 = 10.0067。

小技巧:测试市价单时,我习惯先构造一个「阶梯状」的订单簿,这样能清晰验证每一档的成交价格和数量是否正确。说白了,就是让测试结果一目了然。

3.3 冰山订单撮合逻辑验证

冰山订单,也叫隐藏订单。它只暴露一部分数量,剩下的藏在「冰山下」。这种订单在实盘中很常见,大资金不想暴露真实意图嘛。

冰山订单的验证要点:

  • 显示数量:订单簿上只显示这一部分
  • 隐藏数量:不显示,但参与撮合
  • 冰山刷新:当显示数量全部成交后,从隐藏部分再拿出一个显示数量
  • 价格不变:整个冰山订单只有一个价格

你想想看,如果冰山订单的显示数量是100,隐藏数量是900,总共有1000股。对手方一个200股的买单过来,会发生什么?

// 冰山订单测试用例
// 挂单:冰山卖单,价格10.00,显示100,隐藏900

// 测试1:对手方买单 200股
// 第一步:吃掉显示部分的100股,成交价10.00
// 第二步:冰山刷新,从隐藏部分拿出100股作为新的显示
// 第三步:继续成交新的显示部分100股,成交价10.00
// 结果:总成交200股,冰山剩余显示0,隐藏800

// 测试2:对手方买单 50股
// 结果:成交50股,冰山显示剩余50股,隐藏900不变

// 测试3:对手方买单 1000股
// 结果:冰山全部成交,共1000股,成交价均为10.00

关键验证点:

  1. 冰山刷新时机:必须在显示部分完全成交后才刷新
  2. 刷新数量:每次刷新拿出一个显示数量,不是全部隐藏
  3. 价格一致性:所有成交价格必须相同
  4. 订单簿显示:任何时候只显示当前冰山的一角

我在项目中遇到过最头疼的问题是什么?冰山订单的「幽灵成交」。有一次测试发现,冰山订单的隐藏部分居然在订单簿上「闪现」了一下。虽然只有几毫秒,但在高频回测中,这个bug会导致完全不同的成交结果。

特别注意:冰山订单的测试一定要关注并发场景。比如两个市价单同时到达,一个在吃显示部分,另一个在等刷新。我曾经就因为这个场景没测到,上线后出了大问题。

3.4 综合测试框架

光测单个订单类型还不够,还得测它们之间的交互。我建议搭建一个这样的测试框架:

// 综合测试场景示例
// 初始订单簿:
// 卖一:10.00 限价单 100股
// 卖二:10.02 冰山单 显示50股,隐藏200股  
// 卖三:10.05 限价单 300股

// 测试:市价买单 400股
// 预期成交路径:
// 1. 吃掉卖一100股@10.00
// 2. 吃掉冰山显示50股@10.02,冰山刷新显示50股
// 3. 吃掉新显示50股@10.02,冰山刷新显示50股
// 4. 吃掉新显示50股@10.02,冰山刷新显示50股
// 5. 吃掉新显示50股@10.02,冰山隐藏剩余0
// 6. 吃掉卖三100股@10.05
// 总成交:400股,剩余卖三200股@10.05

这个测试场景覆盖了限价单、冰山订单、市价单的混合交互。说白了,就是模拟真实市场中的复杂情况。

我的经验:写测试用例时,别只写「正常情况」。多想想「边界情况」和「异常情况」。比如订单簿为空时下市价单、冰山订单的隐藏数量为0、限价单的价格正好等于对手方最优价等等。这些边缘场景才是bug的高发区。

3.5 自动化验证方案

手动测试终究不是长久之计。我建议把测试用例写成自动化脚本,每次修改撮合引擎后一键运行。

自动化验证的核心思路:

  • 定义标准化的测试用例格式(输入、预期输出)
  • 用脚本批量执行测试用例
  • 对比实际输出和预期输出
  • 生成测试报告,标注失败用例

嗯,这里要注意,自动化测试的覆盖率很重要。我一般会要求覆盖率达到90%以上,特别是那些「边界条件」和「异常流程」。

最后说一句,撮合引擎的验证不是一锤子买卖。随着系统迭代,新的功能可能会引入新的bug。所以,把测试用例维护好,每次改完代码就跑一遍,这才是靠谱的做法。

撮合引擎正确性验证框架 订单输入 订单类型 限价单 市价单 冰山订单 撮合引擎 价格优先 时间优先 冰山刷新 部分成交 价格穿越 成交结果 验证方法 边界值测试 价格边界/数量边界 场景测试 混合订单交互 自动化测试 批量执行/报告 回归测试 版本迭代

这张图展示了撮合引擎验证的整体框架。从订单输入到撮合核心,再到成交输出,最后用四种验证方法兜底。说白了,就是形成一个完整的验证闭环。

总结一下:限价单验证要关注价格穿越和时间优先级,市价单要关注成交均价和流动性不足的情况,冰山订单要关注刷新机制和幽灵成交。三种订单的混合测试才是真正的考验。把这些都测透了,你的回测系统才算靠谱。

公众号:蓝海资料掘金营,微信deep3321