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 和撤单量,要研究"撤单是否引发成交响应",必须把撤单挂回它所属的委托,并找到该委托在撤单前后发生的成交。匹配逻辑分三层:
- 委托—撤单匹配:以
order_id为键,把 CANCEL 记录关联到对应的 ORDER 记录,计算剩余量 = 委托量 − 已成交量,校验撤单量是否等于剩余量(全撤)或小于剩余量(部撤)。 - 委托—成交匹配:同样以
order_id为键,把该委托下的所有 TRADE 记录聚合,得到累计成交量与加权成交价,并记录首笔成交时间与末笔成交时间。 - 撤单—成交时序匹配:对每条撤单,在其所属委托的成交集合中,按时间轴切分出"撤单前成交"与"撤单后成交"两组,分别统计笔数、量与时间间隔。这一步直接产出后续要用的核心变量,例如撤单前 500ms 内该委托的成交笔数、撤单后 200ms 内同价位对手方成交笔数。
需要注意,部分数据源的撤单记录不带 order_id,只带 seq 和价格数量。此时需用"同频道、同价格、同方向、时间最接近且剩余量吻合"的启发式规则做模糊匹配,并给匹配结果打置信度标签,低置信度记录不参与强关联特征计算。
五、构建标准化事件序列与质量校验
完成上述步骤后,把主序列按 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) 时间内找到其所属委托、前后成交组和关联置信度",这样后续的撤单关联特征计算才能稳定、可解释地展开。