3. 回测数据准备与清洗

限价单超时改价规则的回测,本质上是在历史行情上重放“挂单—等待—超时—撤单改价”这一决策循环。规则本身并不复杂,真正决定回测可信度的是输入数据:如果订单簿快照、逐笔成交和委托流水三类数据在时间、价格、数量上不能严丝合缝地对齐,那么超时判定、成交判定和改价触发都会出现系统性偏差。本节围绕“取什么、怎么对齐、怎么清洗、最终产出什么”展开,目标是得到一个可直接喂给回测引擎的输入数据集。

三类数据的分工与获取要点

回测需要三类原始数据,它们各自承担不同职责:

  • 订单簿快照(L2 深度):提供某一时刻买一至买 N、卖一至卖 N 的价格与数量,用于判断限价单挂在队列中的位置、估算排队量与成交概率,以及计算改价时的盘口参照。快照通常是定频的(如每 500ms 或每 3s 一档),需要记录快照时间戳、买卖档位价格与数量。
  • 逐笔成交(Trade/Tick):记录每一笔撮合的成交时间、成交价、成交量与主动方向(买方发起/卖方发起)。它是判断“限价单是否被成交”的最直接依据,也是估计超时窗口内价格是否穿越挂单价的关键。
  • 委托流水(Order/委托明细):记录报单、撤单、改单事件及其时间、价格、数量、方向与订单号。它用于还原真实的下单节奏,也用于校准“超时”的计时起点——超时应从委托被交易所确认(或本地发出)的时刻开始计,而不是从策略产生信号开始计。

获取时要注意三点:一是三类数据必须来自同一交易标的、同一交易日、同一时区;二是快照与逐笔成交应尽量来自同一数据源,避免不同厂商的撮合口径差异;三是委托流水若来自模拟盘或实盘导出,需确认时间字段是本地时间还是交易所时间,并保留原始字段不做覆盖。

时间戳对齐:统一到同一时间基准

时间戳对齐是清洗中最容易出错、也最影响结论的一步。常见问题包括:快照时间戳为“采集时刻”而非“交易所撮合时刻”;逐笔成交精确到毫秒而快照只到秒;委托流水使用本地时钟且存在网络延迟。处理原则是先定义统一基准,再做对齐。

建议统一采用交易所撮合时间(或数据源标注的交易所时间)作为唯一基准,精度统一到毫秒。对于只有秒级精度的快照,将其视为该秒内的“区间代表值”,在回测中按“快照时刻 ≤ 事件时刻 < 下一快照时刻”的区间归属规则使用,而不是简单取最近邻。对于委托流水,若只能拿到本地时间,应先用同一批订单的交易所回报时间做偏移估计,得到一个稳定的时钟偏差后再整体平移,而不是逐笔随意调整。

对齐后的数据应满足单调不减的时间序,并对同一时间戳的多条记录定义稳定排序:一般按“委托事件 → 逐笔成交 → 快照”的优先级处理,确保回测中“先看到成交、再看到快照”的因果顺序不被颠倒。

缺失值与异常值的识别与处理

缺失值在订单簿数据中非常普遍:某档位无挂单、快照间隔内无成交、委托流水缺少撤单回报等。处理时要区分“真缺失”和“业务上的空”。例如买五档数量为 0 可能是该档确实无挂单,应保留为 0 而不是删除;而整段快照缺失(如连续多个周期无数据)则属于采集中断,需要标记为不可用区间,回测在该区间内应暂停挂单或跳过改价判断,避免用错误盘口做决策。

异常值主要分三类:价格越界(如买一价高于卖一价、价格为 0 或远超涨跌停)、数量异常(如单档数量为负、数量级明显偏离历史分布)、时间异常(时间戳回退、重复、跨日)。对价格类异常,优先用相邻快照做交叉校验,无法修复的整条快照剔除;对数量异常,可结合逐笔成交累计量做一致性检查,偏差超过阈值(例如示例中设为 5%)则标记可疑;对时间异常,重复时间戳保留首条并记录,回退时间戳按排序后重新编号,跨日数据直接截断。

清洗不是“删得越干净越好”。每一次剔除都应记录原因和影响范围,回测报告中应能回答“因为清洗损失了多少可交易时段”,否则规则表现可能被清洗口径悄悄美化。

构建回测输入数据集

清洗完成后,需要把三类数据整合成回测引擎可直接消费的结构。推荐按“事件流 + 快照索引”的方式组织:事件流按时间排序,包含成交事件与委托事件;快照单独存为按时间索引的盘口序列,事件流中的每个事件携带其所属快照区间编号。这样回测在推进时间时,既能按事件精确触发成交与超时判断,又能随时取到当前有效盘口。

下面给出一个可直接参考的输入数据集字段设计(示例字段,实际可按引擎调整):

数据块关键字段用途
快照序列snap_ts、bid_px[1..N]、bid_qty[1..N]、ask_px[1..N]、ask_qty[1..N]盘口参照、排队估计、改价定价
成交事件trade_ts、price、qty、aggressor_side成交判定、价格穿越判断
委托事件order_ts、order_id、side、price、qty、event_type(报/撤/改)超时计时起点、改价链路还原
质量标记gap_flag、outlier_flag、source回测跳过与结果归因

整合时还要做一次“可回测性”检查:每个交易日的有效快照覆盖率是否达到阈值(示例可设为 95%)、事件流时间是否严格单调、每个委托事件是否都能找到其所属快照区间。检查不通过的数据日应单独列出,而不是混入总体回测结果。

回测输入数据集构建:三类数据 → 对齐清洗 → 事件流 + 快照索引 订单簿快照 snap_ts / 买卖N档 定频采集,秒级精度 逐笔成交 trade_ts / 价 / 量 主动方向,毫秒精度 委托流水 order_ts / 报撤改 超时计时起点 时间戳对齐与清洗 统一交易所时间,毫秒精度 区间归属:快照≤事件<下一快照 缺失标记 gap_flag 异常标记 outlier_flag 排序:委托→成交→快照 事件流(按时间排序) 成交事件 + 委托事件 每条事件携带所属快照区间号 严格单调时间序 快照索引序列 snap_ts → 买卖N档盘口 按时间索引,供事件随时取用 覆盖率 ≥ 95% 方可回测 可回测性检查 覆盖率 / 单调性 / 区间可归属 不通过的数据日单独列出 对齐原则:以交易所撮合时间为唯一基准;秒级快照按区间代表值使用, 不取最近邻;本地时钟先用同批订单回报估计偏移再整体平移。

完成上述步骤后,回测引擎拿到的就是一份“时间可对齐、盘口可取用、成交可判定、异常可追溯”的输入数据集。后续的超时判定与改价逻辑,都应建立在这份数据集之上,而不是在回测过程中临时拼接原始数据——否则规则表现将难以复现,也难以定位问题究竟来自规则本身还是数据口径。