1. 逐笔行情数据特征与归档需求分析
逐笔行情是交易所撮合引擎在每一笔订单事件发生时向外推送的最细粒度市场数据。与分钟K线或快照行情相比,它记录的是"事件流"而非"状态切片":每一次委托进入、撤单、成交,都会产生一条带时间戳的记录。要设计一套可靠的历史归档与检索系统,第一步不是选数据库,而是把数据本身看清楚——它长什么样、有多快、有多大、会被谁以什么方式读取。
三类核心数据的结构与字段含义
逐笔行情通常包含三类报文,它们在归档中的角色完全不同。
逐笔委托描述订单簿的"意图侧"变化。以沪深Level-2为例,典型字段包括:证券代码、交易所通道号、委托序号(逐笔递增)、订单号、买卖方向、委托价格、委托数量、委托类型(限价/市价/本方最优等)以及委托时间。委托序号是同一通道内的严格递增序列,它是判断丢包与乱序的第一手依据。
逐笔成交描述"已实现"的撮合结果,字段包括成交序号、买方订单号、卖方订单号、成交价格、成交数量、成交时间以及成交类型(成交/撤单等)。注意逐笔成交中的买卖订单号可以与逐笔委托中的订单号关联,这一关联能力是回测中还原订单生命周期、计算排队位置的关键。
快照则是按固定频率(如每3秒)对订单簿的切片,包含十档买卖价量、最新价、成交量、成交额、委托总量等汇总字段。快照是"状态",逐笔是"增量",二者在归档中往往需要配套存储:逐笔用于精确重放,快照用于快速定位与校验。
时间精度与数据量级
逐笔行情的时间戳精度直接决定归档格式的选择。国内交易所的逐笔数据通常提供毫秒级时间戳,部分场景可到微秒;而撮合事件在同一毫秒内可能发生数十笔,因此时间戳不能作为唯一排序键。正确的排序依据是"通道号 + 委托/成交序号",时间戳只用于区间过滤和跨通道对齐。这一点在归档表设计中必须体现:分区裁剪用日期,排序与去重用序号,时间戳建辅助索引。
数据量级方面,可以做一个合理的估算示例。假设某市场单日逐笔委托约 8000 万条、逐笔成交约 4000 万条,单条记录按 40 字节估算,原始日增量约 4.8 GB;一年 250 个交易日约 1.2 TB。若叠加多路行情、多市场与快照,年增量很容易进入 5~10 TB 量级。这个规模意味着:单机文件系统可以承载,但必须做列式编码与分区裁剪,否则全表扫描一次就要数小时。
增长模式与归档挑战
逐笔数据的增长不是均匀的。开盘集合竞价、尾盘、指数调仓日、极端行情日都会出现尖峰,单日峰值可能是均值的 2~3 倍。归档系统若按均值规划容量,会在峰值日出现写入堆积。更麻烦的是乱序与缺口:网络抖动会导致报文乱序到达,通道切换会导致序号跳变。归档时若不记录缺口位置,后续回测就会在无感知的情况下"跳过"一段行情,产生错误结论。
因此归档流程中应包含三个动作:按序号排序去重、逐通道检测序号连续性、将缺口区间写入元数据表。下面是一段示意性的校验逻辑,用于说明判断依据(示例参数,非实测结果):
# 逐通道序号连续性检查(示意)
for channel in channels:
seqs = sorted(records[channel].seq)
expected = seqs[0]
for s in seqs:
if s != expected:
gaps.append((channel, expected, s - 1)) # 记录缺口区间
expected = s + 1
# 若 gaps 非空,则该交易日归档标记为 partial,回测时需显式处理
三类场景对归档的核心诉求
回测要求"顺序、完整、可重放"。它按时间区间顺序读取,对吞吐敏感、对随机点查不敏感,因此适合按交易日分区 + 列式存储,并保证同一通道内序号严格有序。审计要求"可追溯、不可篡改"。它往往从一笔成交出发,反查对应的买卖委托与前后订单簿状态,属于典型的点查与关联查询,需要订单号索引以及归档文件的哈希校验。监控复盘要求"低延迟抽样"。它只关心某只证券在某段时间的异常,需要按证券代码 + 时间段的二级裁剪能力。
三类诉求指向同一个设计结论:归档不能只存一份"原始报文流水",而应同时维护分区化的明细数据、序号与订单号索引、缺口与校验元数据。明细保证可重放,索引保证可检索,元数据保证可信。三者缺一,归档就只是"存下来了",而不是"能用了"。
把上述特征归纳起来,逐笔行情归档的本质是"用结构化方式保存一条可验证的事件流"。数据结构决定了字段与关联方式,时间精度决定了排序键的选择,数据量级与尖峰增长决定了分区与压缩策略,而回测、审计、监控三类场景则共同定义了检索接口与质量校验要求。后续的存储格式选型、分区设计与检索实现,都应回到这四个判断依据上来。