2. 逐笔数据清洗与预处理实战
逐笔成交数据是特征计算与回测的原料,但真实市场推送的逐笔流几乎不可能直接使用。交易所快照与逐笔合并、行情网关重发、网络抖动、多路订阅去重失败,都会让原始数据带上重复记录、时间戳乱序、异常价格与异常数量。本节的任务不是讲清洗的概念,而是把一份"脏"的逐笔数据真正处理成可计算、可回测的干净数据集,并明确每一步的判断依据。
先明确数据契约,再动手清洗
清洗前必须先定义目标数据的字段契约,否则"干净"没有标准。以A股逐笔成交为例,我们约定输出表包含:trade_id(交易所成交编号,唯一键)、symbol、trade_time(精确到毫秒的成交时间)、price、volume(股)、amount(元)、side(主动买卖方向,B/S/N)、seq(同一毫秒内的序号)。其中trade_id是去重主键,trade_time与seq共同构成排序键。契约一旦确定,后续每个清洗步骤都围绕它展开,而不是凭感觉删数据。
四类问题的识别与处理
重复记录。重复有两种来源:完全重复(同一trade_id出现多次)和近似重复(不同trade_id但字段几乎一致,常见于多路行情合并)。处理策略是:先按trade_id精确去重,保留首条;再对剩余数据按symbol + trade_time + price + volume + side做窗口内近似去重,窗口取1毫秒,因为同一毫秒内真实出现两笔完全相同价格数量的成交概率极低,而重复推送几乎必然落在同一毫秒。近似去重会误删真实成交,因此必须记录被删条数,若某标的单日近似去重比例超过0.5%,应回查数据源而非直接接受。
时间戳乱序。逐笔数据在传输中可能因多线程写入或网关重排而局部乱序。判断标准不是"是否严格递增",而是"乱序跨度是否在可接受范围内"。做法是:按trade_time升序、同毫秒内按seq升序排序,然后计算每条记录相对前一条的时间差,统计负值(即乱序)的条数与最大回退毫秒数。若最大回退在50毫秒以内且乱序占比低于0.1%,可视为传输抖动,排序后直接使用;若出现秒级回退,说明数据源存在批次拼接问题,需要按批次边界切分后分别排序,避免把不同批次的数据强行混排。
异常价格。价格异常包括:价格为零或负、价格超出当日涨跌停范围、价格相对前一有效成交跳变超过阈值。前两类可直接判定为无效;第三类需要谨慎,因为真实的大单冲击也会造成跳变。建议用"相对昨收的偏离"与"相对前一成交的跳变"双条件判断:偏离超过涨跌停幅度直接剔除;跳变超过5%但仍在涨跌停内,标记为待核查而非直接删除,核查方式是看该笔成交前后1秒内是否有连续同向成交支撑,有则保留,无则剔除。
异常数量。数量异常包括:数量为零、数量非整数(A股应为100股整数倍,但逐笔成交允许零股,需区分)、单笔数量超过该标的流通股本的某个比例。零数量直接剔除;非整数需结合市场规则判断,科创板允许零股成交,不能一刀切;超大单笔则标记而非删除,因为它是真实信息,只是需要在特征计算时做缩尾处理。
标准化:让字段可计算
排序与校验之后,还要做字段层面的标准化。价格统一为元、保留两位小数;数量统一为股;amount若缺失,用price × volume补齐,并校验与原始amount的偏差,偏差超过0.01元说明原始字段存在精度或单位问题,需标记。方向字段side要统一编码,把不同数据源的"买/卖/未知"映射为B/S/N,未知方向不能随意填B或S,否则会污染主动买卖特征。时间字段统一为交易所时区的毫秒时间戳,避免回测时因时区错位导致跨日边界错误。
用一份校验清单收口
清洗是否完成,不能靠"看起来没问题",而要用可复核的指标收口。建议每个标的每个交易日输出一份清洗报告,至少包含以下字段:
| 检查项 | 计算方式 | 通过标准(示例) |
|---|---|---|
| 主键唯一性 | 重复 trade_id 条数 | 0 |
| 近似重复率 | 窗口去重删除数 / 原始条数 | < 0.5% |
| 时间有序性 | 排序后负时间差条数占比 | < 0.1% |
| 价格合规率 | 价格在涨跌停内的条数占比 | 100%(剔除后) |
| 数量合规率 | 数量为正且符合市场规则的占比 | > 99.9% |
| 金额一致性 | |amount − price×volume| > 0.01 的条数 | 0 |
这份报告的价值在于:当后续特征出现异常值时,可以快速判断是特征逻辑问题还是数据清洗遗漏。例如某标的某日"大单净流入"特征突然失真,回查清洗报告发现当日近似重复率高达3%,说明多路行情合并去重失败,特征失真源于数据而非模型。
一个容易忽略的细节:跨日与集合竞价
逐笔数据在跨日边界和集合竞价时段有特殊形态。集合竞价期间可能没有连续逐笔,只有开盘/收盘一笔集中成交,若按连续竞价的时间差逻辑判断乱序,会把正常的竞价间隔误判为异常。处理方式是按交易时段分段:连续竞价段做时间差校验,集合竞价段单独保留、不做跳变判断。跨日时,前一日的最后一笔与次日第一笔之间不应计算时间差,否则会产生巨大的负值或正值,污染乱序统计。这些边界条件不处理,清洗报告看起来合格,但特征在开盘和收盘附近会系统性偏移。
完成以上步骤后,得到的数据集应满足:主键唯一、时间有序、价格与数量在合规范围内、字段单位统一、边界时段单独标记。此时它才可以交给特征计算模块,也才具备回测所需的可复现性——同样的原始数据,经过同样的清洗脚本,必须得到逐条一致的结果。