连续竞价里,成交是逐笔发生的:你的买单进来,立刻吃对手方的档。集合竞价完全是另一种时空观:9:15-9:25 之间进来的委托不成交,只排队,到 9:25:00 那一刻,全部委托一次性撮合,产生一个统一的开盘价。定价目标不是"满足某一方",而是监管明文规定的:最大成交量。这篇先把"最大成交量定价"推导成算法,再讲系统为什么要在开盘前反复"模拟撮合"。
最大成交量定价的推导
9:25 瞬间,簿上积了 10 分钟的委托。设可能的价格为 $p$(所有委托报价的离散集合,最小变动价位的整数倍)。在价格 $p$ 上:
- 所有买价 ≥ p 的买单有资格成交(他们愿意付 p 或更多),累计买量记为 $B(p)$;
- 所有卖价 ≤ p 的卖单有资格成交,累计卖量记为 $S(p)$;
- 该价位的成交量是两者取小:$V(p) = \min(B(p),\, S(p))$。
$B(p)$ 随 $p$ 单调不增(价格越高,肯买的买单越少——注意 $B(p)$ 是"买价 ≥ p"的累计,$p$ 上升时这个集合缩小),$S(p)$ 随 $p$ 单调不减。$V(p)$ 是一条先升后降的单峰曲线,定价就是找它的顶点:
$$p^* = \arg\max_{p}\ V(p)$$
顶点可能不唯一(多个价位成交量同为最大),A 股的tie-break规则:取最接近前收盘价的那个;还有离得一样近的,取较低价(沪)——规则细节交易所文件里都有,关键是系统要实现"并列时按既定规则收敛到唯一价",否则同一天两个系统算出两个开盘价。
按价格离散化之后,计算量是平凡的:候选价最多几百个档,每个档的 $B(p)$、$S(p)$ 用累计和(从两边各扫一遍前缀和)就算完,$O(P + N)$,$P$ 是档数、$N$ 是委托数。真正的工作量在边界处理:
- 在 $p^*$ 上,买价 > $p^*$ 的买单和卖价 < $p^*$ 的卖单全部成交(他们有价格优势,优先保障);
- 买价 = 卖价 = $p^*$ 的委托按时间优先部分成交,先来的先成,恰好配平 $V(p^*)$;
- 剩下排不上的一律不成交,委托继续留在簿上(在 A 股,未成交的竞价委托自动转入连续竞价——转入的规则沪市深市略有差异,系统按市场配置)。
定价之后:一次性批量撮合
价格确定,剩下的撮合就是上一篇逐档吃单循环的批量版,但要按竞价特有的顺序:先清"价格有优势的"(买价 > p* 的全部、卖价 < p* 的全部),再在 p* 档上按时间优先配平。批量撮合的输出是一笔开盘价 + 批量成交记录,行情系统把它作为开盘价快照广播——这就是为什么开盘价在 9:25:00 是"突然出现"的:它不是某笔逐笔成交的价,是 10 分钟委托的均衡解。
实现上批量撮合和普通撮合共用订单簿和撮合循环,只是触发器不同(时钟触发 vs 委托触发)和定价逻辑不同(先定价后撮合 vs 边撮合边定价)。不要在竞价实现里另起一套簿——9:25 之后未成交委托要原地转入连续竞价,两套簿意味着一次全量搬迁,搬迁中途来新委托就是并发灾难。
虚拟撮合:9:15-9:25 系统每 3 秒演练一遍
集合竞价有个产品需求:客户盯着屏幕,想知道"现在如果我下单,大概什么价能成交"。交易所的做法是下发虚拟集合竞价参考价(A 股通常每 3 秒一次),这是用当时的簿快照跑一遍完整的定价算法得出的演练结果。虚拟撮合和真撮合的区别只有一条:只定价、不成交、不改簿——它是只读的算法重放。
这个设计的高明之处:算法只有一份,真伪两种模式。9:15-9:25 每 3 秒跑一次只读模式输出参考价;9:25:00 跑一次读写模式产生真实开盘价。算法一致保证了参考价和开盘价同源——如果参考价用一个简化算法(比如忽略某些委托类型),真开盘时客户会质疑"参考价 10.05 怎么开盘 10.08",那就是系统信誉问题。
工程上有两个坑。一是虚拟撮合的簿视图必须一致:定价要读完整的买卖盘,读取期间新委托正在进来,要么快照复制(簿不大,毫秒级的事),要么单线程模型下把虚拟撮合排在委托处理之间。读到一半的旧簿算出的参考价会跳变,行情端表现为参考价闪烁。二是9:20-9:25 撤单限制:A 股在 9:20 之后不接受竞价撤单(防操纵参考价),簿从 9:20 起只进不出——系统要在委托接入层按时钟硬编码这个闸门,而不是靠运营自觉。
和连续竞价的衔接:两个容易出错的时刻
9:25:00-9:30:00 的真空期。 开盘价出来后到 9:30 连续竞价开始之间,委托还在进来(通常允许挂入)。这段时间的委托按连续竞价规则排队但不撮合(市场未开),簿的状态要保持"竞价遗留 + 新挂委托"的合并视图。深市 9:25-9:30 的处理细节和沪市不同(深市这段时间进连续竞价簿),做双市场系统时这类差异全部配置化,代码里找不到一个写死的市场名。
收盘集合竞价(14:57-15:00,深市)。 深市收盘也是集合竞价,定价算法相同,但收盘价的 tie-break 规则和开盘略有差异,且收盘价同时是当日的结算参考价(清算要用)。收盘价的正确性直接影响日终清算——收盘价错了,全市场的估值和清算全错。所以对收盘定价要加一道保护性校验:定价结果偏离前收盘 ±X%(X 按品种历史波动率定)时熔断式告警转人工,宁可延迟公布,不可错发。
最大成交量定价是"规则即算法"的范本:监管一句话,推导出一个 $O(N)$ 的算法,再衍生出参考价、撤单闸门、收盘保护一串工程需求。连续竞价和集合竞价共用一张订单簿、共用一个定价内核的不同模式——这套设计做到了规则复杂、代码简单,是撮合系统里少有的漂亮结构。