4. 限价订单的插入与档位更新
限价订单进入撮合引擎后,并不是简单地“挂到队尾”就结束了。一笔新订单要真正成为订单簿的一部分,需要依次完成价格档位查找、队列插入、档位数量更新三个动作,并在过程中处理“档位不存在需新建”“档位已存在需追加”“订单可立即成交需先撮合”等分支。本节围绕这一插入过程展开,重点讲清楚每一步在数据结构上究竟发生了什么。
一、插入前先判断:这笔订单会不会先成交
限价订单到达时,引擎首先做的是可成交性判断,而不是直接插入。以买方向为例,若新买单价格大于等于当前卖一价,则它与卖侧存在交叉,必须先进入撮合流程,成交剩余部分才可能挂入买侧订单簿;若新买单价格低于卖一价,则不存在交叉,直接进入挂单插入流程。卖方向对称:卖价小于等于买一价则先撮合,否则直接挂单。这个判断决定了后续是“先撮合再插入”还是“纯插入”,是插入流程的第一个分叉点。
需要强调的是,只有“不可立即成交”的订单才走完整的档位查找与插入路径。因此本节讨论的插入,指的是订单在对手侧无交叉、需要停留在本方订单簿中的情形。
二、价格档位查找:定位目标档位
订单簿通常按价格档位组织:每个价格对应一个档位对象,档位内部维护一个订单队列。查找的目标是判断“该价格档位是否已存在”。实现上常见两种结构:
- 有序数组/链表:按价格排序存放档位,查找可用二分或顺序扫描,适合档位数量较少的场景;
- 哈希表 + 有序索引:用哈希表以价格为键做 O(1) 定位,同时用有序结构(如平衡树、跳表)维护价格顺序以便取最优价,适合档位多、更新频繁的场景。
查找结果只有两种:命中已有档位,或未命中需要新建档位。以示例参数说明:假设当前买侧已有档位 100.00(队列含 3 笔)、99.98(队列含 1 笔),新买单价格为 99.99,则查找未命中,需要新建 99.99 档位,并把它插入到 100.00 与 99.98 之间,保持买侧价格从高到低排列。
三、队列插入:档位内部的时间优先
同一价格档位内,订单按到达时间先后排队,先到先成交,这就是价格优先、时间优先原则中的时间维度。新订单插入时一律追加到该档位队列的尾部,不允许插队。若档位是新建的,则创建空队列后把该订单作为队首放入。
队列本身可用双向链表实现,尾部追加为 O(1);也可用动态数组,但要注意删除中间订单(撤单)时的搬移成本。实际工程中常配合“订单号 → 队列节点”的映射,使得撤单时能直接定位并摘除节点,而不必扫描整个队列。
四、档位数量更新:维护聚合字段
每个档位通常维护两个聚合量:订单笔数和累计数量。新订单插入后,需要把该档位的订单笔数加 1,累计数量加上本单剩余数量。这两个字段是行情推送和最优价计算的重要输入,必须与队列内容严格一致。
以示例说明:新买单剩余数量 500 股,插入 99.99 新建档位后,该档位订单笔数由 0 变为 1,累计数量由 0 变为 500。若随后又有一笔 300 股的 99.99 买单到达,则笔数变为 2,累计数量变为 800。反过来,撤单或成交导致订单离队时,必须对称地减 1 并扣减相应数量;当笔数与累计数量同时归零时,该档位应从订单簿中移除,避免留下空档位影响最优价判断。
五、图解:一笔新买单的插入路径
下图以一笔价格 99.99、数量 500 股的买单为例,展示从到达、可成交性判断、档位查找、队列插入到档位数量更新的完整路径,并标出“命中已有档位”与“新建档位”两个分支。
六、实现要点与常见问题
把上述过程落到代码,有几个细节值得注意。第一,档位查找与新建必须保证价格顺序正确:新建档位要插入到有序索引的合适位置,买侧按价格降序、卖侧按价格升序,否则最优价会取错。第二,队列插入与档位数量更新应当原子完成,避免出现“订单已入队但笔数未加”的中间状态被行情线程读到。第三,订单剩余数量在部分成交后会变化,档位累计数量应基于“剩余数量”而非“原始数量”维护。
一个可参考的插入伪代码如下,重点看查找、插入、更新三步的衔接:
insertLimitOrder(order):
if canCross(order): # 与对手侧存在交叉
order = match(order) # 先撮合,返回剩余部分
if order.remaining == 0:
return # 全部成交,无需挂单
level = book.findLevel(order.side, order.price)
if level is None: # 未命中,新建档位
level = book.createLevel(order.side, order.price)
book.insertLevelInOrder(level) # 维持价格有序
level.queue.append(order) # 队尾追加,时间优先
level.orderCount += 1
level.totalQty += order.remaining
order.levelRef = level # 便于后续撤单/成交时回查
这段伪代码体现了本节的核心链条:可成交性判断决定是否先撮合,档位查找决定新建还是复用,队列插入保证时间优先,数量更新维护聚合字段。四者缺一不可,任何一步遗漏都会导致订单簿状态与真实委托不一致。
七、如何验证插入结果正确
插入完成后,可以从三个层面检查。其一,看档位是否存在且价格顺序正确,例如买侧应呈现 100.00、99.99、99.98 的降序排列。其二,看目标档位队列的尾部是否为刚插入的订单,且笔数与累计数量与队列内容吻合。其三,看最优价是否被意外改变:本例中 99.99 低于买一价 100.00,因此买一价不变;若新单价格高于原买一价且无交叉,则买一价应更新为新单价格。把这三个检查点固化为测试用例,能有效覆盖插入逻辑的主要分支。
至此,一笔限价订单从到达、判断、查找、插入到数量更新的完整路径已经清晰。理解这条路径,是后续分析撤单、成交扣减以及订单簿快照生成的基础。