4. 逐笔行情数据清洗与标准化归档

逐笔行情是交易所按“每一笔成交/每一笔委托变更”推送的细粒度数据,典型字段包括证券代码、交易所时间戳、逐笔序号、成交价、成交量、买卖方向、委托编号等。原始数据在采集、落盘、传输过程中几乎不可能“天然干净”:多路行情源合并会造成时间乱序,断线重连会带来重复推送,网络抖动或采集进程重启会留下缺口,个别记录还会出现价格为 0、成交量为负、时间戳越界等异常。若不清洗就直接归档,后续回测、撮合还原、微观结构统计都会建立在错误基础上。本节围绕“清洗—标准化—归档—记录”这条主线,讲清每一步实际怎么做。

一、先明确清洗目标与判定口径

清洗不是“把数据改好看”,而是按明确规则把原始流转换为可信、可复现、可追溯的归档数据。建议在动手前先固定四类口径:

  • 乱序:以“交易所时间戳 + 逐笔序号”为排序键。同一时间戳内按序号升序;跨源合并时以交易所时间为准,本地接收时间仅作辅助。
  • 重复:同一证券、同一交易所、同一逐笔序号视为同一事件,保留首条,其余标记为重复。
  • 缺失:序号不连续即视为缺口。缺口可能是正常撤单未推送,也可能是丢包,需结合交易所规则判断,不能一律填补。
  • 异常:价格 ≤ 0、成交量 ≤ 0(除撤单类事件)、时间戳早于当日开盘或晚于收盘过多、字段为空等,按规则隔离而非直接删除。

口径一旦确定,就应写入配置并随归档一起保存,保证同一批原始数据在任何时候重跑都能得到相同结果。

二、清洗与标准化的处理链

下图展示了一条逐笔行情从原始落地到标准化归档的完整处理链,以及各环节的判定分支与输出物:

原始逐笔数据 多源合并 / 落盘文件 ① 排序与去重 时间戳+序号排序 同序号保留首条 ② 缺口检测 序号连续性校验 标记 gap 区间 ③ 异常值隔离 价格/量/时间越界 写入隔离区 ④ 字段标准化 时间戳统一为 UTC 纳秒 / 价格统一为最小变动单位整数 成交量统一为股 / 方向编码统一为 B/S/N ⑤ 分区归档写入 按 证券代码 / 交易日 分区 Parquet 列式存储 + 元数据文件 ⑥ 清洗日志与质量指标 重复数 / 缺口数 / 异常数 时间跨度 / 记录总数 / 校验和 异常隔离区 保留原始记录 + 异常原因标签 供人工复核,不进入主归档 关键判定规则: • 排序键 = 交易所时间戳(纳秒)+ 逐笔序号;跨源合并以交易所时间为准,本地接收时间仅辅助 • 重复判定 = 证券代码 + 交易所 + 逐笔序号 完全一致;保留首条,其余计入重复指标 • 缺口判定 = 序号不连续;需结合交易所规则区分“正常撤单未推送”与“真实丢包”

处理链中有两个容易做错的地方值得展开。第一,排序与去重必须放在缺口检测之前。如果先检测缺口,乱序数据会产生大量虚假缺口,重复数据又会让序号看起来“跳变”,导致缺口统计失真。第二,异常隔离不等于删除。被隔离的记录要连同原始字段和异常原因标签一起保存,否则一旦规则误判,原始数据已不可恢复,归档就失去了可追溯性。

三、时间戳与字段单位的统一

逐笔行情最常出问题的是时间。不同交易所、不同行情源可能分别使用本地时间、UTC、毫秒或纳秒精度,甚至同一文件内混用。标准化时应统一为“UTC 纳秒整数”存储,并在元数据中记录原始时区与精度,转换公式为:

utc_ns = (local_epoch_ms - tz_offset_ms) * 1_000_000 + sub_ms_ns

其中 tz_offset_ms 为该交易所时区相对 UTC 的偏移(如东八区为 +28800000 毫秒),sub_ms_ns 为毫秒以下的纳秒部分。价格字段建议统一转换为“最小变动单位整数”,例如某股票最小变动单位为 0.01 元,则 12.34 元存为 1234,避免浮点误差;成交量统一为“股”,若原始为“手”则乘以每手股数。方向字段统一编码为 B(买)、S(卖)、N(未知/中性),不要保留各源自定义的字符。

四、归档文件组织与写入

清洗后的数据建议按“证券代码 / 交易日”两级分区写入 Parquet 文件,目录形如 archive/600000/20240603/part-000.parquet。Parquet 列式存储对“按字段检索、按时间范围扫描”非常友好,压缩率也明显优于逐行文本。每个分区目录下同时写入一个 _meta.json,记录该分区的记录数、时间范围、字段 schema 版本、清洗规则版本和校验和。写入时采用“先写临时文件、校验通过后原子重命名”的方式,避免中途失败留下半截文件被误读。

五、清洗日志与质量指标

每次清洗任务都应产出一份结构化日志,至少包含以下指标,便于事后审计和问题定位:

指标含义示例值
raw_count原始记录总数1,284,530
dup_count重复记录数3,217
gap_count检测到的缺口区间数12
abnormal_count异常隔离记录数48
clean_count最终归档记录数1,281,265
time_span归档数据时间跨度09:30:00.000 ~ 15:00:00.000
checksum归档文件 SHA-256a3f1…(截断)

这些指标应满足基本一致性:clean_count = raw_count - dup_count - abnormal_count(缺口不删记录,只做标记)。若等式不成立,说明清洗逻辑存在漏算或重复计数,必须排查后再发布归档。日志本身也应按交易日归档,与数据文件放在同一批次目录下,形成“数据 + 元数据 + 日志”的完整交付单元。

建议把清洗规则、字段映射表和异常判定阈值全部外置为配置文件,并在日志中记录配置文件的哈希值。这样同一批原始数据在不同时间重跑,可以验证结果是否一致,也为规则迭代留下版本依据。

完成上述步骤后,归档数据才真正具备“可检索、可复现、可审计”的属性:检索时按分区直接定位文件,复现时依据日志和配置重跑,审计时通过质量指标快速判断某日数据是否可信。清洗与标准化看似琐碎,但它决定了后续所有逐笔分析的上限。