4. 限价单撮合机制深度解析
限价单与市价单最本质的差别,不在于“价格是否指定”,而在于限价单进入订单簿后并不保证立即成交。它可能立刻与对手方撮合,也可能剩余部分留在盘口等待,还可能被后续行情“追上”而成交。理解限价单,必须把它看成一条在订单簿中持续存在的“挂单生命线”:从提交、进入队列、排队等待、部分成交、剩余量继续排队,直到全部成交或被撤销。本节围绕这条生命线,讲清价格-时间优先级的具体实现与边界条件。
一、限价单的两种命运:立即成交与挂单等待
限价单到达撮合引擎时,先做“可成交性判断”。以买单为例,若其限价 P_buy 大于等于当前卖一价 P_ask,则具备立即成交条件,按卖盘价格从优到劣依次吃单;若 P_buy < P_ask,则无法成交,整单进入买盘订单簿,成为挂单。卖单方向对称:P_sell <= P_bid 可立即成交,否则挂入卖盘。
关键点在于:限价单的成交价由对手方挂单价决定,而不是由自己的限价决定。例如卖一价 10.00 元有 500 股,你提交限价 10.05 元的买单 800 股,成交价是 10.00 元而非 10.05 元,成交 500 股后剩余 300 股,因买价 10.05 元高于当前卖一价(假设变为 10.02 元),会继续吃单;若卖盘被吃空或剩余卖价高于 10.05 元,剩余 300 股才转为挂单,挂在买盘 10.05 元价位。这个“先吃单、后挂单”的顺序,是限价单撮合的第一条实现规则。
二、价格-时间优先级:订单簿的排序内核
订单簿对同一方向的所有挂单维护严格排序,规则是价格优先、时间优先。买盘按价格从高到低排,卖盘按价格从低到高排;同价位内按进入队列的时间先后排,先到者在前。撮合时,新来的对手单总是先与“最优价、最早时间”的队列头部成交。
实现上,价格档位通常用有序结构(如平衡树、跳表或价格数组)维护,每个价位挂一条按时间排序的订单链表(FIFO 队列)。新单进入某价位时追加到队尾;撮合从队头取单。这里有一个容易被忽略的边界:部分成交后,剩余量是否保留原时间优先级? 主流实现是保留——订单在队列中的位置不变,只是数量减少,它仍然排在后来者前面。这意味着“早挂的单即使被部分吃掉,剩余部分依旧优先”。
三、部分成交与撤单的实现细节
部分成交是限价单最常见的中间状态。假设上图中新单剩余 300 股挂在 10.05 元队尾,随后来了一笔限价 10.05 元的卖单 500 股。撮合引擎从买盘最优价 10.05 的队头开始:先成交 A 单 200 股,再成交 B 单 150 股,最后成交新单 150 股,卖单剩余 0 股。此时新单从 300 股变为 150 股,仍留在 10.05 价位队尾,等待后续卖单。这就是“部分成交不改变时间优先级”的具体表现。
撤单逻辑看似简单,实则有边界。撤单请求到达时,引擎需先定位订单所在价位与链表节点,将其从队列摘除,并更新该价位总量;若价位总量归零,则从价格结构中删除该档位。需要特别处理的是撤单与成交的竞态:若撤单到达时订单正在被撮合,通常以“先到先处理”为准——已进入撮合流程的订单不可撤,未进入的可以撤。工程上常用订单状态机约束:新建 → 部分成交 → 全部成交 或 新建 → 已撤,部分成交 可继续转为 全部成交 或 已撤,但 全部成交 与 已撤 是终态,不可再变。
四、必须明确的边界条件
第一,同价位时间戳精度。若多笔订单时间戳相同(高并发下常见),需用序列号做次级排序,保证 FIFO 确定性。第二,自成交防范。同一账户的买卖单若在同一价位相遇,部分市场会拒绝或先撤后撮,这会影响队列顺序,需在撮合前处理。第三,价格笼子与涨跌停。超出有效价格范围的限价单可能被拒单而非挂单,判断发生在入簿之前。第四,冰山单与隐藏单。若支持冰山单,显示量成交后需从隐藏量补足,其时间优先级通常按“补量后重新排队”或“保留原位置”由规则约定,二者结果差异显著,必须在实现前明确。
把上述规则串起来,限价单撮合的核心就是:先按价格找最优档,再按时间找队头,成交后剩余量保留队列位置,撤单则精确摘除节点。价格-时间优先级不是抽象口号,而是由有序价格结构、同价 FIFO 链表、订单状态机和竞态处理共同落实的工程规则。理解这些边界,才能在分析成交回报、排查“为什么我的单没成交”时,准确判断是价格未到、队列靠后,还是已被部分成交后继续等待。