1. 课程导入与问题定义

做订单簿回测时,最容易踩的坑不是策略逻辑写错,而是“回测里看到的盘口”和“当时真实存在的盘口”不是同一个东西。原始行情通常以逐笔委托、逐笔成交或快照的形式到达,而策略需要的是一个随时间连续演化、买卖档位完整、价格与数量自洽的订单簿。重建订单簿,就是把这几种原始消息按时间顺序重放,维护出一份可查询的限价订单簿状态;历史一致性回测,则是保证这份状态在每一个决策时刻都与真实市场一致,并且策略只能使用该时刻之前已经到达的信息。

本节先明确两个核心目标。第一是状态正确:任意时刻的买一卖一、各档深度、订单数量都应与交易所真实簿一致。第二是时序正确:策略在时间 t 做出的决策,只能基于时间戳不晚于 t 的消息,不能出现“未来函数”。这两个目标共同决定了整条链路的工程约束。

完整链路:从原始消息到可回测订单簿

一条典型的链路包含四段。第一段是消息接入与规范化:把不同交易所的字段统一为“消息类型、订单号、方向、价格、数量、时间戳”这样的内部结构,并处理精度与单位。第二段是排序与去重:按交易所序号或时间戳稳定排序,剔除重复推送。第三段是簿状态维护:新增委托插入对应价位,撤单按订单号删除,成交按数量扣减,并同步维护价位聚合量。第四段是快照与查询:在策略需要的时刻输出前 N 档,供撮合或信号计算使用。

原始行情 → 可回测订单簿:链路与一致性检查点 原始消息流 逐笔委托/成交 或周期快照 规范化 统一字段/精度 方向与单位对齐 排序与去重 按序号稳定排序 剔除重复推送 簿状态维护 插入/撤单/成交扣减 价位聚合量同步 快照输出 前N档 供策略查询 检查点① 字段缺失/精度错位 检查点② 乱序/重复/跨日 检查点③ 负数量/价位残留 检查点④ 交叉盘口 一致性判据(示例) 买一价 < 卖一价;任一价位聚合量 ≥ 0;订单号唯一且撤单可追溯; 策略在 t 时刻只能读取时间戳 ≤ t 的消息;快照档位与逐笔重放结果逐笔对账一致。

常见的一致性陷阱

第一类是乱序与重复。交易所推送可能因网络抖动出现序号跳跃或重复,若直接按到达顺序处理,撤单可能先于委托到达,导致“撤不存在的单”。工程上应以交易所序号为主键做稳定排序,并记录缺口区间,缺口未补齐前不推进状态。第二类是精度与单位。价格用浮点数累加会产生 0.0000001 级别的漂移,使同一价位被拆成两个键;应统一转为整数最小变动单位(tick)存储。第三类是成交与撤单的扣减语义。部分成交只减数量不删订单,全成交才删除;若把部分成交当撤单处理,簿深度会系统性偏薄。

第四类是快照与逐笔混用。快照是某一时刻的切片,逐笔是增量;若在快照基础上叠加更早的逐笔,会重复计入。正确做法是以快照为基准点,只重放时间戳严格晚于快照的增量消息。第五类是交叉盘口,即买一价大于等于卖一价,通常意味着漏消息或排序错误,应作为硬性断言触发告警而非静默通过。

一个可执行的最小示例

假设某标的 tick 为 0.01 元,收到三条消息:委托 A 买 10.00 元 500 股;委托 B 卖 10.02 元 300 股;A 部分成交 200 股。以整数 tick 存储,10.00 元记为 1000,10.02 元记为 1002。重放后买方 1000 档剩余 300 股,卖方 1002 档 300 股,买一 1000 小于卖一 1002,通过一致性断言。若误将部分成交当撤单,买方档位会被清空,盘口立即失真——这正是回测中“信号看起来很好、实盘却对不上”的典型来源。

建议在重建器内部维护三个计数器:已处理消息数、检测到的序号缺口数、触发的断言失败数。回测开始前先跑一遍全量重放,三个计数器应分别为总消息数、0、0,否则先修数据再谈策略。