2. 逐笔数据清洗与预处理

逐笔数据是研究撤单关联特征与成交响应的原料,但交易所下发的原始逐笔流水并不能直接用于特征提取。它至少存在三类问题:时间戳口径不统一、异常记录混入、撤单与成交之间缺乏显式关联。本节的任务,就是把原始逐笔流水加工成一条时间有序、字段规范、事件可关联的标准化事件序列,为后续特征工程提供干净输入。

一、先明确事件模型与字段口径

清洗之前必须先定义"一条事件长什么样"。逐笔数据通常包含委托(order)、成交(trade)、撤单(cancel)三类记录,不同数据源字段命名差异很大。建议统一为一张事件表,核心字段包括:event_id(清洗后重排的唯一序号)、ts(统一到毫秒的交易所时间)、seq(原始通道序号)、event_type(ORDER/TRADE/CANCEL)、order_id(委托编号)、trade_id(成交编号,撤单为空)、price、qty、side(B/S)、channel(频道号)。

这里有一个关键约定必须提前说明并全文保持一致:时间参照系统一采用交易所撮合时钟,本地接收时间只作为辅助字段保留,不参与排序。若原始数据只有本地时间,则需通过逐笔序号与快照时间做一次偏移估计,把本地时间校正到交易所时间轴上,否则后续"撤单发生在成交前多少毫秒"这类特征会系统性失真。

二、时间戳对齐:排序依据不是时间,而是序号

逐笔数据的一个常见误区是直接按时间戳排序。实际上同一毫秒内可能有多条记录,且不同频道的时间戳精度不同(有的到毫秒,有的到微秒,有的只到秒)。正确做法是:以通道序号 seq 为主排序键,时间戳为辅。同一频道内 seq 严格递增,跨频道则先按频道分组、组内按 seq 排序,再按时间戳做跨频道归并。

对齐还要处理三类具体情形:其一,时间戳精度不一致,统一向上取整到毫秒会引入最多 1ms 误差,对高频撤单研究可接受,但需在字段中标注原始精度;其二,存在重复推送,同一 seq 出现两次,按 (channel, seq) 去重保留首条;其三,存在序号跳变,说明中间有丢包,此时不能简单拼接,应在事件表中插入一条 GAP 标记记录,让后续特征计算知道此处存在数据缺口,避免把跨缺口的两条记录误判为连续行为。

三、异常值识别:四类高频脏数据

逐笔流水的异常主要集中在以下四类,建议逐类设规则而非"一刀切"删除:

  • 价格越界:价格 ≤ 0,或偏离当日快照均价超过阈值(示例取 ±30%)。这类记录多为字段错位,应标记 PRICE_ANOMALY 并隔离,不进入主序列。
  • 数量异常:数量为 0 或为负,或单笔数量超过该标的当日最大单笔成交量的若干倍。数量为 0 的撤单记录在部分数据源中表示"全撤",需结合原始字段语义判断,不能一律删除。
  • 逻辑矛盾:撤单数量大于原委托剩余量、成交数量大于委托量、同一 order_id 出现两次方向相反的委托。这类记录应标记 LOGIC_CONFLICT,并回溯其关联委托做人工抽检。
  • 时间倒挂:同一 order_id 的撤单时间早于委托时间,或成交时间早于委托时间。这通常是对齐错误而非数据错误,应优先检查时间校正逻辑,而不是直接丢弃。
异常处理的原则是"标记而非删除":在主事件序列之外维护一张异常表,记录 event_id、异常类型、原始字段值和处理动作。这样既保证主序列干净,又保留可追溯性,便于后续复盘异常对特征分布的影响。

四、撤单与成交的关联匹配

这是本节最核心的一步。撤单记录本身只带 order_id 和撤单量,要研究"撤单是否引发成交响应",必须把撤单挂回它所属的委托,并找到该委托在撤单前后发生的成交。匹配逻辑分三层:

  1. 委托—撤单匹配:以 order_id 为键,把 CANCEL 记录关联到对应的 ORDER 记录,计算 剩余量 = 委托量 − 已成交量,校验撤单量是否等于剩余量(全撤)或小于剩余量(部撤)。
  2. 委托—成交匹配:同样以 order_id 为键,把该委托下的所有 TRADE 记录聚合,得到累计成交量与加权成交价,并记录首笔成交时间与末笔成交时间。
  3. 撤单—成交时序匹配:对每条撤单,在其所属委托的成交集合中,按时间轴切分出"撤单前成交"与"撤单后成交"两组,分别统计笔数、量与时间间隔。这一步直接产出后续要用的核心变量,例如撤单前 500ms 内该委托的成交笔数、撤单后 200ms 内同价位对手方成交笔数。

需要注意,部分数据源的撤单记录不带 order_id,只带 seq 和价格数量。此时需用"同频道、同价格、同方向、时间最接近且剩余量吻合"的启发式规则做模糊匹配,并给匹配结果打置信度标签,低置信度记录不参与强关联特征计算。

逐笔数据清洗与事件序列构建流程 原始逐笔流水 ORDER / TRADE / CANCEL 多频道 · 多精度时间戳 时间戳对齐 seq 为主键排序 统一毫秒 · 插入 GAP 标记 异常值识别 价格越界 · 数量异常 逻辑矛盾 · 时间倒挂 关联匹配 委托↔撤单 委托↔成交 异常记录表(旁路) event_id · 类型 · 原始值 · 动作 标准化逐笔事件序列 event_id · ts · seq · event_type · order_id · price · qty · side 时间有序 · 字段规范 · 事件可关联 撤单—成交时序切分 撤单前成交组 撤单后成交组 特征提取输入 撤单前成交笔数 · 撤单后响应量 · 时间间隔 · 关联置信度 图例 主处理链 旁路/异常

五、构建标准化事件序列与质量校验

完成上述步骤后,把主序列按 event_id 重排输出,并做三项质量校验:一是完整性,统计 GAP 标记数量与占比,若超过 0.1% 需回查数据源;二是一致性,抽样 100 条撤单,人工核对撤单量是否等于委托量减累计成交量;三是时序合理性,检查撤单前成交组的时间戳是否全部早于撤单时间,若有违例说明对齐仍有问题。

下面给出一份可直接套用的清洗规则配置示例,字段与阈值均为教学示例,实际使用时需按标的流动性和数据源特性调整:

{
  "time_base": "exchange_clock",
  "sort_key": ["channel", "seq"],
  "dedup_key": ["channel", "seq"],
  "gap_policy": "insert_marker",
  "anomaly_rules": {
    "price_range_pct": 0.30,
    "qty_zero_action": "flag_not_drop",
    "cancel_gt_remaining": "flag_logic_conflict",
    "time_inversion": "recheck_alignment"
  },
  "match_rules": {
    "primary_key": "order_id",
    "fallback": ["channel", "price", "side", "qty"],
    "confidence_threshold": 0.8
  },
  "window_ms": {"pre_cancel": 500, "post_cancel": 200}
}

这份配置的价值在于把"清洗"从一次性脚本变成可复现的规则集:换一个交易日、换一只标的,只需调整阈值和窗口参数,匹配逻辑与输出结构保持不变。最终产出的标准化事件序列,应当满足"任意一条撤单都能在 O(1) 时间内找到其所属委托、前后成交组和关联置信度",这样后续的撤单关联特征计算才能稳定、可解释地展开。