2. 逐笔数据源解析与字段映射
逐笔盘口回放的第一步,不是写回放引擎,而是把交易所原始逐笔数据“读懂”。同一笔成交,在逐笔委托流和逐笔成交流里各有一套字段;不同交易所对时间戳精度、编码格式、买卖方向的定义也不一致。如果这一步没有统一,后续的回放时间轴、盘口重建和撮合校验都会建立在错误的前提上。本节围绕“解析—理解—映射”三个动作展开,目标是产出一张可执行的字段映射表。
一、两类逐笔数据流的本质区别
Level-2 逐笔数据通常包含两条独立的数据流:逐笔委托(Order/Tick Order)和逐笔成交(Trade/Tick Trade)。逐笔委托记录的是“订单进入撮合系统”的事件,包含委托编号、买卖方向、价格、数量、订单类型;逐笔成交记录的是“撮合完成”的事件,包含成交编号、买卖双方委托编号、成交价格、成交数量、成交方向。二者是因果关系:委托是成交的输入,成交是委托的结果。
回放盘口时,必须同时消费两条流。只吃成交流,无法还原挂单的增减;只吃委托流,无法知道哪些委托被成交、成交在什么价位。因此解析阶段要明确:每条流各自负责重建盘口的哪一部分状态。
二、字段含义与常见陷阱
以常见的逐笔委托字段为例,需要重点核对以下几项:
- 委托编号(OrderNo/BidNo):交易所内唯一,但不同交易所编号规则不同,有的按通道递增,有的全局递增。回放时作为主键使用,不能假设跨交易所可比。
- 买卖方向(Side):常见编码为 1=买、2=卖,也有 B/S 字符或 0/1 编码。必须按数据源文档确认,不能凭经验猜测。
- 订单类型(OrdType):区分限价、市价、本方最优等。市价单在逐笔委托中可能没有价格字段,回放时需特殊处理。
- 价格与数量:注意价格单位(元还是厘)和数量单位(股还是手)。部分交易所价格字段为整数放大 10000 倍,直接当元使用会造成数量级错误。
逐笔成交字段的陷阱集中在成交方向和买卖委托编号。成交方向通常表示“主动方方向”,即买方主动成交还是卖方主动成交,这与盘口买卖压力判断直接相关。买卖委托编号用于把成交关联回委托流,若数据源不提供,则只能靠价格和数量近似匹配,回放精度会下降。
三、时间戳精度与编码格式
时间戳是回放排序的核心。常见精度有毫秒(13 位)、微秒(16 位)和纳秒(19 位)。同一交易所内两条流的时间戳精度可能不同,例如委托流为微秒、成交流为毫秒。映射时必须统一到最高精度,低精度数据在缺失位补零,而不是四舍五入。排序时若同一时间戳出现多条记录,需用交易所序号(SeqNo)作为第二排序键,保证回放顺序与真实撮合顺序一致。
编码格式方面,需确认字符集(GBK 或 UTF-8)、字段分隔符(二进制定长、CSV、JSON)以及数值是否为小端序。二进制定长结构在解析时要严格按字节偏移读取,字段错位是最常见的解析故障。
四、建立原始字段到标准字段的映射表
映射表是本节的核心交付物。标准字段建议围绕回放需求设计:事件时间、事件类型、委托编号、买卖方向、价格、数量、成交编号、买方委托编号、卖方委托编号、交易所序号。下表给出一个示例映射(字段名与编码为教学示例,实际以数据源文档为准):
| 标准字段 | 逐笔委托原始字段 | 逐笔成交原始字段 | 转换规则 |
|---|---|---|---|
| event_time | TransactTime(微秒) | TradeTime(毫秒) | 统一为微秒,毫秒值×1000 |
| event_type | 固定值 ORDER | 固定值 TRADE | 按流类型赋值 |
| order_no | OrderNo | — | 直接映射 |
| side | Side(1/2) | — | 1→BUY,2→SELL |
| price | Price(×10000 整数) | TradePrice(×10000) | 除以 10000 转为元 |
| volume | Volume(股) | TradeVolume(股) | 直接映射 |
| trade_no | — | TradeNo | 直接映射 |
| bid_order_no | — | BidOrderNo | 关联委托流 |
| ask_order_no | — | AskOrderNo | 关联委托流 |
| seq_no | SeqNo | SeqNo | 第二排序键 |
映射表落地时,建议用一段解析代码验证字段偏移和单位换算是否正确。例如读取一条委托记录后,打印原始字节与解析结果对照,确认价格除以 10000 后落在合理区间(如 5~500 元),数量为 100 的整数倍。若出现价格 0.0005 或数量 3 这类异常值,说明单位或偏移有误。
# 示例:逐笔委托单条记录解析(字段偏移为教学假设)
raw = read_record(stream) # 定长二进制记录
order = {
"order_no": int.from_bytes(raw[0:8], "little"),
"side": raw[8],
"price": int.from_bytes(raw[9:13], "little") / 10000.0,
"volume": int.from_bytes(raw[13:17], "little"),
"time_us": int.from_bytes(raw[17:25], "little"),
"seq_no": int.from_bytes(raw[25:33], "little"),
}
assert 0 < order["price"] < 10000, "价格单位异常"
assert order["volume"] % 100 == 0, "数量单位异常"
下图展示了两条原始数据流经过解析、单位换算、时间统一后,汇入标准回放事件流的结构关系。注意委托流与成交流在映射后仍保持各自的事件类型,通过委托编号建立关联,而不是合并成一条记录。
五、映射完成后的校验要点
映射表不是写完就结束,需要用真实数据做三项校验。第一,数量守恒:同一交易日内,逐笔成交的成交量总和应等于日线成交量(允许因集合竞价处理存在少量差异)。第二,关联完整率:成交记录中的买卖委托编号应能在委托流中找到对应记录,关联率低于阈值说明字段映射或数据源缺失有问题。第三,时间单调性:按 event_time 与 seq_no 排序后,时间戳不应出现大幅回退,若回退超过一个合理窗口,需检查是否混入了不同精度的时间字段。
完成以上步骤后,原始逐笔数据才真正变成“可回放”的标准事件流。映射表应作为数据集说明文档的一部分固化下来,后续每次接入新数据源,只需替换原始字段列,转换规则和校验逻辑可以复用。