2. 逐笔数据基础与回放机制
逐笔回放排障的第一步,不是打开回放工具,而是弄清楚“回放的是什么”。逐笔数据是交易所行情中最细粒度的成交流水,它把每一笔成交、每一次委托变更都记录成独立事件。回放引擎则按这些事件的时间戳与序列号重新驱动一遍行情,让研究者看到与当时几乎一致的盘口演化过程。错序、缺帧、重复、时间戳漂移等问题,几乎都能在“数据结构—序列语义—回放流程”这条链路上找到根源。
逐笔成交与逐笔委托的数据结构
以常见的沪深Level-2逐笔数据为例,逐笔成交(Trade)通常包含:证券代码、成交时间(精确到毫秒或更细)、成交价格、成交数量、成交方向(主动买/主动卖或买卖盘标识)、成交编号(TradeIndex/BidApplSeqNum/AskApplSeqNum等)、以及买卖双方委托的序列号。逐笔委托(Order)则包含:委托时间、委托价格、委托数量、买卖方向、订单类型(市价/限价/本方最优等)、委托编号。两者通过“成交记录中引用的买方/卖方委托序列号”建立关联,这是回放时还原订单簿的关键。
需要特别注意的是,不同交易所、不同数据源的字段命名和语义并不完全一致。例如上交所逐笔成交中的买方委托序号与卖方委托序号,分别指向对应的逐笔委托记录;深交所则用ApplSeqNum体系串联成交与委托。排障时若把两套编号体系混用,回放出的订单簿会出现“成交找不到对应委托”的悬空状态。因此,接入数据的第一件事是建立字段映射表,明确每个编号字段指向哪一类记录、在哪个通道内唯一。
时间戳与序列号的双重语义
逐笔数据同时携带两类排序依据:时间戳和序列号。时间戳表达“事件在交易所侧发生的时刻”,序列号表达“事件在撮合系统中的先后顺序”。二者在理想情况下单调一致,但在实际数据中经常出现冲突:同一毫秒内多笔事件时间戳相同,此时必须依赖序列号定序;跨通道数据合并时,不同通道的时间戳可能存在微小偏差,序列号则能提供更可靠的全局顺序。
回放引擎的正确做法是:以序列号为主排序键,时间戳为辅。若仅按时间戳排序,同一毫秒内的多笔成交可能被错误重排,导致盘口在瞬间出现不合理的交叉或负深度。反之,若序列号出现跳变(例如从1001直接跳到1005),则说明中间存在缺帧,必须触发告警而不是静默跳过。时间戳还承担另一项职责:检测延迟与漂移。当某条记录的时间戳明显早于前一条已处理记录,或与本地接收时间的差值异常增大,往往意味着数据源侧发生了时钟回拨或补发。
回放引擎的基本流程
回放引擎的核心可以概括为四步:读取—排序—重建—输出。读取阶段从原始数据文件或消息队列中拉取逐笔成交与逐笔委托,按通道拆分;排序阶段将多通道数据按序列号归并成单一事件流,同时做跳号、重复、时间回退检测;重建阶段维护一个订单簿,逐条应用委托挂入、撤单、成交扣减;输出阶段按固定时间间隔或事件驱动方式生成盘口快照与成交序列。
一个容易被忽视的细节是“初始状态”。回放不能从零开始凭空重建订单簿,必须加载回放起点之前的订单簿快照作为基准。若快照缺失或与逐笔数据的时间点不匹配,重建出的盘口从一开始就是错的。实践中常见的做法是:选取某个整点或集合竞价结束后的快照,记录其对应的序列号水位,然后从该水位之后的第一条逐笔事件开始回放。这样既能保证起点正确,也能通过序列号连续性验证快照与流水是否衔接。
常见故障点与判断依据
回放错序与缺帧的故障点集中在几个位置。第一类是排序错误:多通道合并时未按序列号归并,或同一毫秒内按时间戳稳定排序导致顺序颠倒,表现为盘口瞬间交叉、成交量对不上。第二类是缺帧:序列号不连续,中间丢失若干条委托或成交,表现为某笔成交引用的委托号在订单簿中不存在,或买一卖一之间出现异常大的价差。第三类是重复帧:同一序列号出现两次,通常由补发或重传引起,若不去重会导致委托被重复挂入、深度虚增。第四类是时间戳异常:时间回退或跨通道偏差过大,表现为回放速度忽快忽慢、事件顺序与序列号矛盾。
判断依据可以归纳为三条可操作的检查:其一,逐条比对序列号,记录所有跳变点及其前后事件类型;其二,对每笔成交检查其引用的买方/卖方委托号是否已在订单簿中,统计悬空引用比例;其三,监控订单簿的买一卖一价差与深度,若出现负深度或价差异常扩大,回溯最近若干条事件定位原因。这三条检查不需要复杂工具,用一段脚本对回放日志做后处理即可完成,是排障时最先应该跑起来的基础校验。