限价单处理逻辑:从入场到成交的完整旅程

限价单,说白了就是「我只愿意在这个价格买/卖,高了/低了我不要」。这是撮合引擎里最基础、也最核心的订单类型。我做了这么多年交易系统,处理过的限价单少说也有几十亿笔,今天就把它的完整处理流程掰开揉碎了讲给你听。

限价单进入撮合流程

当一个限价单到达撮合引擎,它要经历三个关键步骤。嗯,这里要注意,顺序搞错了就会出大问题。

  1. 价格校验:检查价格是否在合理范围内。我记得有一次,一个订单的价格小数点错了六位,差点把整个市场打穿。
  2. 方向判定:是买单还是卖单?这决定了它要去吃哪边的对手盘。
  3. 进入队列:如果当前无法成交,就进入订单簿等待。

核心原则:限价单的价格必须优于或等于对手方的最优价格,才能触发撮合。买单价格 ≥ 卖一价,卖单价格 ≤ 买一价。

部分成交与完全成交

限价单进入撮合后,有两种命运:要么一次性吃饱,要么吃个半饱。我习惯把这两种情况叫做「一口闷」和「慢慢啃」。

完全成交(一口闷)

当限价单的数量小于或等于对手盘第一档的可用量时,就会发生完全成交。举个例子:

// 当前订单簿状态
卖一:100.50 元,数量 500 股
买一:100.30 元,数量 300 股

// 新进入的买单:限价 100.50 元,数量 300 股
// 结果:完全成交,吃掉卖一 300 股
// 卖一剩余:200 股

这种情况最理想,订单瞬间完成,延迟极低。我在优化撮合引擎时,专门针对这种场景做了快速路径处理,能省掉 80% 的上下文切换开销。

部分成交(慢慢啃)

当限价单的数量大于对手盘第一档的可用量时,就会发生部分成交。这时候需要「扫盘」—— 沿着价格梯度一路吃下去。

// 当前订单簿状态
卖一:100.50 元,数量 200 股
卖二:100.60 元,数量 300 股
卖三:100.70 元,数量 500 股

// 新进入的买单:限价 100.70 元,数量 800 股
// 第一步:吃掉卖一 200 股,剩余 600 股
// 第二步:吃掉卖二 300 股,剩余 300 股
// 第三步:吃掉卖三 300 股(卖三剩余 200 股)
// 结果:部分成交,共成交 800 股

个人经验:我曾经遇到过一个极端情况,一个限价单需要扫 50 层盘口才能完成。如果每层都做一次完整的锁和事务,性能会惨不忍睹。我的解决方案是:在内存中预计算扫盘路径,一次性批量处理。

剩余数量处理

部分成交后剩下的数量怎么办?这取决于订单的「有效期限」属性。我见过太多新手在这里踩坑了。

订单类型 剩余数量处理方式 典型场景
当日有效(GFD) 进入订单簿,等待后续撮合 普通散户交易
立即成交或撤销(IOC) 剩余部分直接撤销,不进入订单簿 高频交易、套利策略
全部成交或撤销(FOK) 如果无法完全成交,整单撤销 大额机构交易

避坑指南:我曾经在开发一个期货交易系统时,把 IOC 订单的剩余数量错误地放入了订单簿。结果导致大量「幽灵订单」堆积,盘口数据完全失真。排查了整整两天才找到原因,从那以后我每次处理剩余数量都会加三层校验。

核心流程图:限价单的完整生命周期

下面这张图是我自己画的,把限价单从入场到结束的整个流程串起来了。你仔细看,每个分支都对应着不同的处理逻辑。

限价单处理流程图 限价单到达 价格校验 能否 成交? 执行撮合 (扫盘) 完全 成交? 订单完成 剩余数量 处理 订单 类型? GFD 进入订单簿 IOC/FOK 撤销剩余部分 直接进入 订单簿等待

实际项目中的优化技巧

讲完了理论,我分享几个实战中总结出来的优化点。这些可都是真金白银换来的经验。

  • 批量处理:当限价单需要扫多层盘口时,不要逐层加锁。我习惯的做法是:先快照当前盘口,然后一次性计算出所有可能的成交,最后统一更新状态。
  • 内存对齐:订单对象在内存中的布局会影响缓存命中率。我曾经把一个订单的大小从 128 字节压缩到 64 字节,性能提升了 30%。
  • 无锁队列:对于 IOC 这类不需要进入订单簿的订单,可以用无锁队列直接处理,避免锁竞争。

核心要点回顾

  • 限价单处理三步骤:价格校验 → 撮合尝试 → 剩余处理
  • 完全成交 vs 部分成交:取决于订单数量与对手盘深度的关系
  • 剩余数量处理:GFD 进订单簿,IOC/FOK 直接撤销
  • 性能优化:批量处理、内存对齐、无锁队列

限价单的处理逻辑,说白了就是「能成交就成交,不能成交就等着」。但背后的细节,从价格校验到扫盘策略,从内存布局到并发控制,每一个环节都值得深挖。我见过太多系统因为忽略了这些细节,在压力测试时直接崩溃。


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