2. 逐笔数据解析与标准化

逐笔特征批量计算的第一步不是写特征公式,而是把交易所推送的原始逐笔数据变成一张字段统一、时间可比、可被批量程序稳定读取的标准化表。如果这一步做不扎实,后续的成交量分布、大单占比、主动买卖差等特征都会因为字段错位、时间戳口径不一致或重复记录而系统性失真。本节围绕三类常见逐笔数据——Level-2 逐笔成交、逐笔委托、以及行情快照中的逐笔衍生字段——讲清楚它们的格式差异、字段含义、时间对齐方法和清洗存储方案。

三类逐笔数据的格式与字段含义

沪深市场的逐笔数据通常以二进制报文或压缩后的结构化文件形式提供,不同数据商在字段命名上略有差异,但核心语义一致。逐笔成交(Trade)记录每一笔撮合成交,关键字段包括:证券代码、成交时间(精确到毫秒或十毫秒)、成交价格、成交数量、成交金额、买卖方向标识(主动买/主动卖/未知)、买方委托序号与卖方委托序号。逐笔委托(Order)记录每一笔新订单的进入与撤单,关键字段包括:委托时间、委托价格、委托数量、买卖方向、委托类别(限价/市价/本方最优等)、委托序号,以及撤单记录中的原始委托序号。Level-2 快照则提供十档买卖盘价格与数量、最新价、累计成交量额等,常被用来补充逐笔数据之外的盘口状态。

需要特别注意的是“方向标识”的口径。部分数据源直接给出主动买卖标志,部分数据源只给出买卖委托序号,需要结合委托表反推:若成交的买方委托序号晚于卖方委托序号进入撮合,则通常判定为主动买。这一判断依赖委托表与成交表的正确关联,是标准化阶段必须显式处理的逻辑,不能默认数据源已经给对。

逐笔数据解析与标准化主链路 原始数据源 逐笔成交报文 逐笔委托报文 Level-2 快照 二进制解析 按报文头取长度 字段偏移映射 价格×10000 转定点 清洗与对齐 去重(委托序号) 时间戳统一到毫秒 方向标识补全 标准化存储 Parquet 按日分区 按代码排序 成交表关键字段映射 原始字段 标准字段 类型 TradeTime trade_ts int64 毫秒 TradePrice price decimal(10,4) TradeVolume volume int64 股 BidOrderNo buy_order_id int64 BSFlag side enum(买/卖/未知) 时间戳对齐口径 09:30:00.000 09:30:03.000 委托 t1 委托 t2 成交 t3 成交 t4 对齐规则:成交归属到其成交时间所在毫秒桶; 委托与成交跨表关联时,以委托序号为主键, 时间仅用于排序与窗口切分,不用于等值连接。

时间戳对齐:最容易出错的一环

逐笔数据的时间字段存在三种常见口径:交易所撮合时间、数据商转发时间、本地接收时间。特征计算必须统一到“交易所撮合时间”,否则跨数据源合并时会出现因果倒置。实操中建议把所有时间统一转换为自当日 00:00:00 起的毫秒整数(int64),既避免浮点误差,也便于做窗口聚合。

对齐的关键原则是:委托表与成交表之间用委托序号关联,不用时间做等值连接。因为同一毫秒内可能有多笔委托和成交,时间只用于排序和划分时间窗口。例如计算“每 3 秒主动买入成交量”,正确做法是先按 trade_ts 做毫秒桶聚合,再在桶内按 side 汇总;而不是把委托表和成交表按时间戳 join。此外,集合竞价阶段(09:15–09:25、14:57–15:00)的逐笔数据时间分布特殊,批量计算时应单独标记或剔除,避免污染连续竞价的统计口径。

数据清洗:去重、补全与异常处理

原始逐笔数据常见的质量问题有四类。第一是重复记录:同一委托序号在断线重连后可能被重复推送,需按 (证券代码, 委托序号, 记录类型) 去重,保留首条。第二是方向缺失:当数据源未给 BSFlag 时,用委托序号反推——买方委托序号大于卖方委托序号则判为主动买,反之主动卖,无法判断时置为“未知”并在特征中单独计数。第三是价格与数量异常:价格为 0、数量为 0 或价格为负的记录直接丢弃;涨跌幅超过当日涨跌停限制的记录需核对是否属于除权日。第四是撤单记录的处理:撤单本身不产生成交,但会影响未成交委托量,计算“委托撤单率”类特征时必须保留,计算“成交量”类特征时应排除。

清洗阶段建议保留一份“被丢弃记录数 / 总记录数”的统计,按证券代码和日期输出。若某只股票某日丢弃率超过 0.5%,应回查原始报文是否截断,而不是直接进入特征计算。

标准化存储方案

标准化后的逐笔数据建议采用列式存储(Parquet),按“交易日 + 证券代码”两级分区,每个分区内按 trade_ts 升序排列。这样批量特征计算时可以只读取目标股票目标日期的文件,避免全量扫描。表结构上,成交表与委托表分开存储,各自保留原始委托序号作为关联键,并额外增加一列 data_source 标识数据商,便于多源比对。

下面是一个可直接参考的成交表标准化建表与写入示例(以 Python + PyArrow 为例,参数为示例值):

import pyarrow as pa
import pyarrow.parquet as pq

schema = pa.schema([
    ("symbol", pa.string()),
    ("trade_ts", pa.int64()),        # 当日 00:00 起毫秒
    ("price", pa.decimal128(10, 4)),
    ("volume", pa.int64()),          # 股
    ("amount", pa.decimal128(16, 4)),
    ("side", pa.string()),           # 买 / 卖 / 未知
    ("buy_order_id", pa.int64()),
    ("sell_order_id", pa.int64()),
    ("data_source", pa.string()),
])

# 分区路径示例:trade_date=20240603/symbol=600000/part-0.parquet
pq.write_table(table, "trade_date=20240603/symbol=600000/part-0.parquet",
               compression="snappy")

写入前应完成三件事:时间转毫秒整数、价格转定点小数、side 枚举值统一为中文或固定英文码。写入后做一次轻量校验——每个分区的记录数、最早与最晚 trade_ts、买卖方向计数,与数据商提供的日终统计做比对,偏差在千分之一以内即可认为解析与清洗链路正常。这一步校验通过后,逐笔数据才真正具备进入批量特征计算流水线的条件。