4. 盘口快照重建与校验

历史回放系统里最容易被低估的一环,是“任意时刻的盘口到底长什么样”。交易所只会在特定时点推送快照,而逐笔委托与逐笔成交是连续流。要回放涨停封板瞬间的十档挂单结构,就必须用逐笔数据自己重建盘口,再用官方快照做校验,否则回放出来的封单量、撤单节奏都不可信。本节围绕“重建—校验—定位—修复”这条主线展开,重点讲清重建算法与偏差排查方法。

一、重建对象与数据前提

重建的目标是任意时刻 t 的十档盘口:买一至买十、卖一至卖十的价格与数量,以及由它们派生的委比、封单量等指标。输入有两类:逐笔委托(含委托序号、方向、价格、数量、委托类型)与逐笔成交(含成交序号、买卖方向、价格、数量)。前提是数据必须按交易所时间戳与序号严格有序,且委托与成交能通过序号或订单号关联。

这里要先明确一个约定:盘口数量指“未成交的挂单量”,成交会从对应价位的挂单中扣减,撤单会直接移除。方向约定为:买方向委托进入买方队列,卖方向委托进入卖方队列,成交则同时影响两侧(主动方扣减、被动方扣减)。全文与图示均按此约定。

二、重建算法:订单簿增量维护

核心思路是维护一个“价位→挂单量”的订单簿,按事件流逐条应用增量,而不是每来一个快照就整体重算。具体做法:

  1. 初始化空订单簿,记录起始时间点(通常为开盘集合竞价结束或某官方快照时刻)。
  2. 逐条读取事件,按时间戳排序;同一时间戳内按委托序号、成交序号排序,保证确定性。
  3. 遇到新增委托:在对应方向、对应价位累加数量;若价位不存在则插入并维持价格排序。
  4. 遇到成交:从主动方与被动方对应价位分别扣减成交量;若某价位数量归零则移除该档。
  5. 遇到撤单:从对应价位扣减撤单量,归零则移除。
  6. 每应用完一个事件,若需要输出该时刻盘口,则取买卖各前十个价位,形成十档快照。

实现上建议用有序映射(如按价格排序的平衡树或数组+二分)维护价位,保证插入、删除、取前十档都是对数或常数级。下面是一段示意性的伪代码,重点在事件应用顺序与扣减逻辑:

book = {bid: {}, ask: {}}   # 价位 -> 数量
for ev in sorted(events, key=(ts, seq)):
    if ev.type == 'order':
        side = 'bid' if ev.dir == 'B' else 'ask'
        book[side][ev.price] = book[side].get(ev.price, 0) + ev.qty
    elif ev.type == 'trade':
        # 主动方与被动方分别扣减
        book[ev.active_side][ev.price] -= ev.qty
        book[ev.passive_side][ev.price] -= ev.qty
        cleanup_zero(book)
    elif ev.type == 'cancel':
        book[ev.side][ev.price] -= ev.qty
        cleanup_zero(book)
    if need_snapshot(ev.ts):
        emit(top10(book.bid), top10(book.ask))

注意两个易错点:一是成交的主动方与被动方价位可能不同(主动方按成交价,被动方按其挂单价),必须分别扣减;二是集合竞价阶段的成交与连续竞价规则不同,重建起点应选在规则切换之后,避免把竞价撮合结果重复计入。

三、校验规则:与官方快照逐档比对

重建完成后,用官方快照做校验。校验不是简单看“总数对不对”,而要逐档比对价格与数量。建议设置以下规则:

  • 档位一致性:买卖各十档的价格序列必须与官方快照完全一致,任一档价格错位即判失败。
  • 数量容差:每档数量允许极小误差(如 0~2 手),超出即标记偏差;涨停封单等关键档位应零容差。
  • 交叉校验:买一价必须小于卖一价(连续竞价),若出现交叉说明事件应用顺序或方向有误。
  • 总量守恒:某时段内买方新增委托量 − 成交量 − 撤单量 应等于期末买方挂单总量,用于发现漏事件。

校验结果建议输出为“时刻—档位—重建值—官方值—差值”的明细表,便于定位。下面这张图展示了从逐笔事件流到十档盘口、再到校验与偏差定位的完整链路,以及三类典型偏差的归因路径。

逐笔事件流 新增委托 / 成交 / 撤单 按 (时间戳, 序号) 排序 方向:B 入买 / S 入卖 订单簿增量维护 价位 → 未成交挂单量 成交双向扣减 / 撤单移除 归零档位清理 十档盘口快照 买一~买十 / 卖一~卖十 价格 + 数量 派生:封单量、委比 官方快照 同刻十档基准 逐档比对 容差判定 校验判定 档位一致 · 数量容差 · 无交叉 · 总量守恒 偏差 A:档位错位 事件排序错误 / 同刻乱序 修复:按序号稳定排序 重放并复核价格序列 偏差 B:数量偏大 成交未双向扣减 / 漏撤单 修复:补被动方扣减 核对撤单事件是否入流 偏差 C:数量偏小 重复扣减 / 竞价成交重复计 修复:调整重建起点 去重成交序号

四、偏差定位与修复

校验失败时,不要急着改代码,先定位偏差类型。上图给出了三类典型偏差的归因路径,实践中可按以下顺序排查:

第一步,看偏差是“档位错位”还是“数量偏差”。若价格序列本身对不上,多半是事件排序问题——同一毫秒内委托与成交的先后顺序会影响价位插入与扣减,必须用交易所序号做稳定排序。若价格序列一致但数量有差,则进入数量排查。

第二步,看偏差方向。重建值系统性偏大,常见原因是成交只扣了主动方、漏扣被动方,或撤单事件未纳入事件流;重建值系统性偏小,常见原因是同一笔成交被重复扣减,或把集合竞价撮合结果又按连续竞价扣了一次。定位方法是取偏差首次出现的时刻,回看该时刻前后若干条事件,逐条手工推演订单簿变化,与重建结果对照。

第三步,用总量守恒做交叉验证。对每个校验区间,计算“买方新增委托量 − 买方成交量 − 买方撤单量”,与期末买方挂单总量比较。若不等,说明区间内存在漏事件或重复事件,可进一步缩小到具体事件序号。

建议把校验做成可重复执行的脚本:输入一段逐笔数据与对应官方快照,输出偏差明细与首次偏差时刻。这样每次修改重建逻辑后都能快速回归,避免“改一处、错一处”。

五、一个可复用的校验清单

把上述规则固化成检查项,便于日常回放任务直接套用:

检查项判定条件不通过时的优先排查方向
事件排序按 (时间戳, 序号) 严格递增同刻乱序、序号缺失
档位价格买卖十档价格与官方完全一致价位插入/删除逻辑
档位数量各档差值在容差内,关键档零容差成交双向扣减、撤单入流
买卖不交叉买一价 < 卖一价方向字段解析、事件应用顺序
总量守恒新增 − 成交 − 撤单 = 期末挂单漏事件、重复事件

需要强调的是,校验的目的不只是“证明重建对”,更是“让偏差可解释”。当每一处偏差都能对应到具体事件与具体规则时,回放系统才具备可信度。涨停封板场景下,封单量的每一次增减都对应真实的委托与撤单,重建偏差会直接扭曲封板强度判断,因此这一环节的严谨程度决定了后续事件标注的质量。

最后给出一个实操建议:重建与校验应作为回放流水线中的独立环节,输出“重建盘口序列 + 校验报告”两份产物。前者供回放与标注使用,后者作为数据质量凭证留存。这样即使后续更换数据源或调整算法,也能通过校验报告快速判断新旧结果是否一致,避免历史标注被静默污染。