3. Tick数据详解

在事件回放与撮合仿真中,Tick 是最小、最不可再分的一条市场事件记录。它既不是K线,也不是快照,而是交易所行情系统对外发布的“原子级”增量消息。理解 Tick 的定义与字段结构,是后续做回放、重演订单簿、验证撮合逻辑的前提。本节围绕四个问题展开:Tick 到底是什么、一条 Tick 里有哪些字段、时间戳精度如何影响回放、逐笔成交与逐笔委托有什么区别与联系。

一、核心原理:Tick 是增量事件,不是状态快照

很多初学者把 Tick 理解为“某一时刻的盘口状态”,这是最常见的误区。实际上,快照(Snapshot)描述的是“当前状态”,而 Tick 描述的是“刚刚发生了什么变化”。例如某只股票最新价从 10.00 变为 10.01,快照会告诉你“现在最新价是 10.01”,而 Tick 会告诉你“有一笔成交,价格 10.01,数量 300 手,买方主动”。

因此,Tick 的本质是事件流。要还原任意时刻的订单簿,必须从初始快照出发,按时间顺序逐条应用 Tick。这也是“事件回放”名称的由来:回放的不是价格曲线,而是事件序列。撮合仿真同样如此——仿真引擎消费 Tick,重建市场状态,再让策略订单与重建出的订单簿交互。

关键结论:Tick 是增量(Delta),快照是全量(Full)。回放系统 = 初始快照 + 有序 Tick 流 + 确定性状态机。

二、字段结构:一条 Tick 通常包含什么

不同交易所的 Tick 字段命名不同,但语义高度一致。以逐笔成交为例,典型字段包括:

  • 证券代码:标的唯一标识,如 600519.SH。
  • 交易所/频道:区分上交所、深交所,或不同行情频道。
  • 时间戳:事件发生时间,精度可能是秒、毫秒、微秒甚至纳秒。
  • 序号:交易所发布的连续序列号,用于检测丢包与乱序。
  • 成交价:本笔成交价格。
  • 成交量:本笔成交数量,单位可能是股或手。
  • 成交金额:部分行情源提供,等于价×量。
  • 买卖方向:主动买、主动卖或中性,反映谁吃掉了谁。
  • 成交类型:集合竞价、连续竞价、大宗交易等。

逐笔委托的字段则不同,它描述的是“订单进入订单簿”这一事件,典型字段包括委托价格、委托数量、买卖方向、订单类型(限价/市价)、订单编号等。下面用一段伪代码展示一条 Tick 的典型结构:

struct Tick {
    string   symbol;        // 证券代码
    string   exchange;      // 交易所
    int64    timestamp;     // 事件时间,单位由精度决定
    int64    seq;           // 交易所序号
    char     type;          // 'T'=成交, 'O'=委托, 'C'=撤单
    double   price;         // 成交价或委托价
    int64    volume;        // 数量
    char     side;          // 'B'=买, 'S'=卖, 'N'=中性
    string   orderId;       // 委托/成交关联的订单号
};

三、时间戳精度:回放确定性的隐形杀手

时间戳精度直接决定回放能否复现真实市场。若原始 Tick 是微秒级,而回放系统只保留毫秒,那么同一毫秒内的多条事件顺序就会丢失,订单簿重建结果可能与真实情况不一致。常见精度层级如下:

精度典型来源回放影响
秒级早期行情或日线衍生同秒事件无法排序,回放失真严重
毫秒级多数 Level-2 快照同毫秒内需依赖序号排序
微秒级逐笔委托/逐笔成交可较真实还原事件顺序
纳秒级部分交易所内部或高频源精度最高,但存储与计算成本大
误区:认为“时间戳越精确越好”。实际上,若同一时间戳内存在多条事件,必须结合交易所序号(seq)做二级排序。只按时间戳排序,回放结果可能不可复现。

工程上建议:回放排序键使用 (timestamp, seq) 二元组,而不是单独使用 timestamp。这样即使时间戳相同,也能保证顺序稳定。

四、逐笔成交与逐笔委托:区别与联系

逐笔成交(Trade)和逐笔委托(Order)是两类不同事件,但共同构成订单簿演化的完整输入。

  • 逐笔委托:描述“新订单进入”。它改变订单簿的深度,是订单簿的“增量来源”。
  • 逐笔成交:描述“订单被撮合”。它减少订单簿中的挂单,是订单簿的“消耗事件”。
  • 撤单:描述“订单被撤销”,同样减少挂单,常与逐笔委托配套出现。

两者的联系在于:成交必然由委托驱动。没有委托,就没有可成交的订单;没有成交,委托只是挂在订单簿上。回放时,若只有逐笔成交而没有逐笔委托,就无法重建完整订单簿,只能重建“成交价序列”。反之,若只有逐笔委托而没有成交,订单簿会不断增厚却永不消耗,同样失真。

实战建议:做撮合仿真时,优先使用“逐笔委托 + 逐笔成交 + 撤单”三类事件。若数据源只提供逐笔成交,则只能做价格回放,不能做订单簿级仿真。

下面用一张图总结 Tick 数据从交易所到回放引擎的链路,以及逐笔委托与逐笔成交在订单簿中的不同作用。

Tick 数据链路与订单簿事件关系 交易所行情源 逐笔委托/成交/撤单 行情网关 解析/序号校验/落盘 Tick 存储 按 (ts, seq) 排序 回放引擎 状态机重建 逐笔委托 Order 新增挂单 → 订单簿增厚 字段:价/量/方向/订单号 作用:提供可成交订单 缺失后果:无法重建深度 逐笔成交 Trade 撮合发生 → 订单簿消耗 字段:价/量/方向/成交号 作用:反映真实成交 缺失后果:深度只增不减 订单簿状态 买盘/卖盘挂单集合 由委托 + 成交 + 撤单驱动 回放目标:逐事件一致 校验:与快照比对

五、案例与复盘:一次回放失真排查

某团队在回放某日深市逐笔数据时,发现 10:00:00.123 这一毫秒内订单簿出现异常:卖一价突然消失,随后又恢复。排查后发现,该毫秒内共有 5 条事件,原始数据按交易所序号排列,但回放程序只按时间戳排序,导致撤单事件被排到了成交事件之前。修正方案是将排序键改为 (timestamp, seq),并增加序号连续性校验:

# 回放前校验序号连续性
prev_seq = None
for tick in ticks:
    if prev_seq is not None and tick.seq != prev_seq + 1:
        log.warning("seq gap: %s -> %s", prev_seq, tick.seq)
    prev_seq = tick.seq

# 排序键
ticks.sort(key=lambda t: (t.timestamp, t.seq))

复盘要点有三:第一,时间戳精度不足时,序号是唯一可靠的顺序依据;第二,逐笔委托与逐笔成交必须成对使用,缺一不可;第三,回放结果应与交易所快照定期比对,发现偏差立即定位到具体事件。

本节核心记忆点:Tick 是增量事件;字段围绕“谁、何时、什么价、多少量、什么方向”;时间戳精度决定回放上限,序号决定回放下限;逐笔委托增厚订单簿,逐笔成交消耗订单簿,二者共同驱动撮合仿真。