2. 盘口数据基础与格式解析
盘口回放系统的第一道工程门槛,不是渲染,而是数据。逐笔委托、逐笔成交、快照行情这三类数据构成了回放的全部素材,它们的结构、时间语义和读取方式各不相同。如果在这一层没有把字段含义、时间戳口径和序列对齐关系理清,后续的回放速度控制、盘口重建和成交撮合都会出现难以定位的偏差。本节围绕三类数据的结构差异、时间与序列的对齐方法,以及 CSV、JSON、二进制三种常见数据源的读取与预处理展开。
三类数据的结构差异
逐笔委托(Order)描述的是“谁在什么时刻下了什么单”。核心字段通常包括:委托编号、证券代码、买卖方向、委托价格、委托数量、委托类型(限价/市价/本方最优等)、委托时间。逐笔委托是增量事件,一条记录只描述一次委托行为,不描述盘口状态。
逐笔成交(Trade)描述的是“什么时刻发生了成交”。核心字段包括:成交编号、证券代码、成交价格、成交数量、买卖方向(主动方)、成交时间,部分数据源还会给出买方委托号和卖方委托号,用于与委托流关联。逐笔成交同样是增量事件,且与逐笔委托之间存在因果关联:一笔成交往往对应两条委托的数量扣减。
快照行情(Snapshot)描述的是“某一时刻盘口的截面状态”。典型字段包括:时间戳、最新价、成交量、成交额、买一至买十的价格与数量、卖一至卖十的价格与数量。快照是状态数据,相邻两帧之间是覆盖关系而非累加关系。这一点是回放实现中最容易出错的地方:逐笔数据需要按事件累加重建盘口,快照数据则直接给出盘口,二者不能混用同一套更新逻辑。
时间戳与序列对齐
三类数据的时间戳口径必须统一。常见情况是:逐笔数据带毫秒级交易所时间戳,快照数据带秒级或毫秒级时间戳,而本地落库时又附加了接收时间。回放时应以交易所时间戳为主排序键,本地接收时间只用于诊断延迟,不参与回放顺序。若数据源只提供本地时间,则需要在预处理阶段明确标注,并在回放时接受由此带来的顺序不确定性。
同一时间戳内的多条记录,仅靠时间无法确定先后。此时需要引入序列号:逐笔委托和逐笔成交通常带有交易所序列号,快照则可能带有帧序号。排序键建议采用“交易所时间戳 → 序列号 → 原始行号”的三级结构,其中原始行号作为最终兜底,保证同一份数据多次回放的结果完全一致。这一点对回放调试非常重要:如果排序不稳定,同一段行情每次回放出的盘口可能不同,问题就无从复现。
另一个需要处理的问题是跨数据源对齐。逐笔成交与逐笔委托之间通过委托号关联,但并非所有数据源都提供该字段。若缺失,只能退化为按时间邻近匹配,此时应明确记录匹配的不确定性,避免在回放中把推测关系当作确定事实。
三种数据源的读取与预处理
CSV 数据源最常见,适合教学与中小规模回放。读取时需注意编码(GBK 与 UTF-8 混用是高频问题)、分隔符、空值表示以及时间列的格式。预处理阶段应把时间列统一解析为毫秒时间戳,把价格与数量转为数值类型,并丢弃或标记无法解析的行。示例读取逻辑如下:
import csv, datetime
def parse_ts(s):
# 示例格式:2024-06-03 09:30:00.120
return int(datetime.datetime.strptime(s, "%Y-%m-%d %H:%M:%S.%f").timestamp() * 1000)
rows = []
with open("orders.csv", encoding="utf-8") as f:
for i, r in enumerate(csv.DictReader(f)):
rows.append({
"seq": int(r["seq"]),
"ts": parse_ts(r["time"]),
"code": r["code"],
"side": r["side"],
"price": float(r["price"]),
"qty": int(r["qty"]),
"row": i,
})
rows.sort(key=lambda x: (x["ts"], x["seq"], x["row"]))
JSON 数据源常见于接口导出或嵌套结构的行情文件。读取时重点在于字段路径的稳定提取和数值类型的显式转换。JSON 中数字可能被解析为浮点,数量字段需转回整数;时间字段可能是字符串或毫秒整数,需按实际格式分支处理。嵌套数组(如十档盘口)应展开为固定长度的价格数组和数量数组,便于后续按档位索引。
二进制数据源吞吐最高,适合大规模历史回放。读取的关键是结构体定义与字节序。以常见的定长记录为例,需先确认字段偏移、字段类型(如 int32、int64、float)和大小端,再按记录长度切分缓冲区。二进制读取不做逐行解析,而是按块读取后批量转换,预处理阶段同样要完成时间戳归一化和排序键构建。
预处理检查要点
完成读取后,建议在进入回放引擎前做一轮轻量校验:时间戳是否单调不减(允许同一时刻多条)、序列号在同一时间戳内是否唯一、价格与数量是否落在合理区间、证券代码是否与目标标的匹配、快照帧数是否与时间跨度大致吻合。对于逐笔数据,还可统计买卖方向分布与成交量的关系,作为数据完整性的粗判依据。这些检查不追求严格正确性证明,目的是在回放前尽早暴露格式层面的问题,避免把数据错误带入盘口重建环节。
把三类数据的结构差异、时间与序列的对齐规则、以及三种数据源的读取要点固定下来之后,回放系统就拥有了一个稳定、可复现的事件序列。后续的盘口重建与回放控制,都建立在这个序列之上。