4. 盘口快照重建与校验
历史回放系统里最容易被低估的一环,是“任意时刻的盘口到底长什么样”。交易所只会在特定时点推送快照,而逐笔委托与逐笔成交是连续流。要回放涨停封板瞬间的十档挂单结构,就必须用逐笔数据自己重建盘口,再用官方快照做校验,否则回放出来的封单量、撤单节奏都不可信。本节围绕“重建—校验—定位—修复”这条主线展开,重点讲清重建算法与偏差排查方法。
一、重建对象与数据前提
重建的目标是任意时刻 t 的十档盘口:买一至买十、卖一至卖十的价格与数量,以及由它们派生的委比、封单量等指标。输入有两类:逐笔委托(含委托序号、方向、价格、数量、委托类型)与逐笔成交(含成交序号、买卖方向、价格、数量)。前提是数据必须按交易所时间戳与序号严格有序,且委托与成交能通过序号或订单号关联。
这里要先明确一个约定:盘口数量指“未成交的挂单量”,成交会从对应价位的挂单中扣减,撤单会直接移除。方向约定为:买方向委托进入买方队列,卖方向委托进入卖方队列,成交则同时影响两侧(主动方扣减、被动方扣减)。全文与图示均按此约定。
二、重建算法:订单簿增量维护
核心思路是维护一个“价位→挂单量”的订单簿,按事件流逐条应用增量,而不是每来一个快照就整体重算。具体做法:
- 初始化空订单簿,记录起始时间点(通常为开盘集合竞价结束或某官方快照时刻)。
- 逐条读取事件,按时间戳排序;同一时间戳内按委托序号、成交序号排序,保证确定性。
- 遇到新增委托:在对应方向、对应价位累加数量;若价位不存在则插入并维持价格排序。
- 遇到成交:从主动方与被动方对应价位分别扣减成交量;若某价位数量归零则移除该档。
- 遇到撤单:从对应价位扣减撤单量,归零则移除。
- 每应用完一个事件,若需要输出该时刻盘口,则取买卖各前十个价位,形成十档快照。
实现上建议用有序映射(如按价格排序的平衡树或数组+二分)维护价位,保证插入、删除、取前十档都是对数或常数级。下面是一段示意性的伪代码,重点在事件应用顺序与扣减逻辑:
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 手),超出即标记偏差;涨停封单等关键档位应零容差。
- 交叉校验:买一价必须小于卖一价(连续竞价),若出现交叉说明事件应用顺序或方向有误。
- 总量守恒:某时段内买方新增委托量 − 成交量 − 撤单量 应等于期末买方挂单总量,用于发现漏事件。
校验结果建议输出为“时刻—档位—重建值—官方值—差值”的明细表,便于定位。下面这张图展示了从逐笔事件流到十档盘口、再到校验与偏差定位的完整链路,以及三类典型偏差的归因路径。
四、偏差定位与修复
校验失败时,不要急着改代码,先定位偏差类型。上图给出了三类典型偏差的归因路径,实践中可按以下顺序排查:
第一步,看偏差是“档位错位”还是“数量偏差”。若价格序列本身对不上,多半是事件排序问题——同一毫秒内委托与成交的先后顺序会影响价位插入与扣减,必须用交易所序号做稳定排序。若价格序列一致但数量有差,则进入数量排查。
第二步,看偏差方向。重建值系统性偏大,常见原因是成交只扣了主动方、漏扣被动方,或撤单事件未纳入事件流;重建值系统性偏小,常见原因是同一笔成交被重复扣减,或把集合竞价撮合结果又按连续竞价扣了一次。定位方法是取偏差首次出现的时刻,回看该时刻前后若干条事件,逐条手工推演订单簿变化,与重建结果对照。
第三步,用总量守恒做交叉验证。对每个校验区间,计算“买方新增委托量 − 买方成交量 − 买方撤单量”,与期末买方挂单总量比较。若不等,说明区间内存在漏事件或重复事件,可进一步缩小到具体事件序号。
五、一个可复用的校验清单
把上述规则固化成检查项,便于日常回放任务直接套用:
| 检查项 | 判定条件 | 不通过时的优先排查方向 |
|---|---|---|
| 事件排序 | 按 (时间戳, 序号) 严格递增 | 同刻乱序、序号缺失 |
| 档位价格 | 买卖十档价格与官方完全一致 | 价位插入/删除逻辑 |
| 档位数量 | 各档差值在容差内,关键档零容差 | 成交双向扣减、撤单入流 |
| 买卖不交叉 | 买一价 < 卖一价 | 方向字段解析、事件应用顺序 |
| 总量守恒 | 新增 − 成交 − 撤单 = 期末挂单 | 漏事件、重复事件 |
需要强调的是,校验的目的不只是“证明重建对”,更是“让偏差可解释”。当每一处偏差都能对应到具体事件与具体规则时,回放系统才具备可信度。涨停封板场景下,封单量的每一次增减都对应真实的委托与撤单,重建偏差会直接扭曲封板强度判断,因此这一环节的严谨程度决定了后续事件标注的质量。
最后给出一个实操建议:重建与校验应作为回放流水线中的独立环节,输出“重建盘口序列 + 校验报告”两份产物。前者供回放与标注使用,后者作为数据质量凭证留存。这样即使后续更换数据源或调整算法,也能通过校验报告快速判断新旧结果是否一致,避免历史标注被静默污染。