2. 字段结构总览

逐笔成交(Trade-by-Trade,常简称逐笔成交或 Tick 成交)是行情数据中最细粒度的成交记录,每一条记录对应交易所撮合引擎中一次真实发生的撮合结果。理解它的字段结构,是后续高效读取、过滤与聚合的前提。本节不讨论具体文件格式的字节布局,而是先建立一张“字段地图”:一条逐笔成交记录通常由哪些字段组成、它们各自回答什么问题、命名上有哪些约定俗成的规范,以及哪些字段最容易在解析时被误读。

一条逐笔成交记录回答的四个问题

无论数据来自上交所、深交所的 Level-2 逐笔成交,还是券商柜台或第三方数据商的标准化推送,一条成交记录本质上都在回答四个问题:谁在什么时间、以什么价格、成交了多少、方向如何。围绕这四个问题,典型字段可以归为四组:

  • 标识与定位组:证券代码、市场类型、成交序号(逐笔序号/成交编号)、频道号或应用标识。它们用于唯一确定“这是哪只股票的第几笔成交”,是排序与去重的依据。
  • 时间组:成交时间(精确到毫秒或微秒)、部分数据源还带时间戳类型标记。时间字段决定事件顺序,也是回放与延迟分析的基础。
  • 价格与数量组:成交价格、成交数量(股数或手数)、成交金额(部分数据源不直接给出,需价格×数量推算)。
  • 方向与属性组:买卖方向标识(主动买/主动卖/未知)、成交类型(如集合竞价成交、连续竞价成交、盘后固定价格成交)、内外盘标志等。

需要强调的是,不同数据源对“方向”的定义并不一致:有的用 BS 标志(B/S),有的用买卖盘序号(BidOrderNo/AskOrderNo)反推,有的直接给出“主动方”字段。解析前必须先确认该字段的语义,否则后续的主动买卖统计会整体反向。

逐笔成交记录字段结构总览 一条记录 = 标识定位 + 时间 + 价量 + 方向属性 单条逐笔成交记录 Trade Record ① 标识与定位 证券代码 SecurityID 市场类型 MarketType 成交序号 TradeIndex 频道号 ChannelNo ② 时间 成交时间 TradeTime 精度:毫秒/微秒 时间戳类型标记 (交易所本地时间) ③ 价格与数量 成交价格 Price 成交数量 Volume 成交金额 Amount (或由价×量推算) ④ 方向与属性 买卖方向 BSFlag 成交类型 TradeType 买/卖盘序号 内外盘标志 命名规范要点 • 驼峰/下划线风格随数据源而定,需按文档映射 • 同一含义不同名:TradePrice / Price / LastPx • 单位差异:股 vs 手,元 vs 分,需统一换算 • 序号字段是排序与去重的唯一可靠依据 解析易错点 • BSFlag 语义反向:B 是主动买还是买盘挂单? • 时间字段含日期偏移或时区,跨日回放会错序 • 集合竞价成交与连续竞价成交混在同一序列 • 数量为 0 或价格为 0 的占位记录需过滤

字段命名规范:同名不同义、同义不同名

逐笔成交字段没有全球统一的命名标准,实际工程中最大的坑往往不是“字段缺失”,而是“字段名相同但含义不同”或“含义相同但字段名不同”。常见情况有三类:

第一类是命名风格差异。交易所原始接口多用缩写,如 SecurityID、TradeTime、TradePrice、TradeVolume、BSFlag;而部分数据商标准化后改为下划线风格,如 symbol、trade_time、price、volume、side。读取时应先建立一张“源字段 → 标准字段”的映射表,而不是在代码里到处硬编码字段名。

第二类是单位差异。成交数量有的以“股”为单位,有的以“手”为单位(1 手 = 100 股);价格有的以“元”为单位,有的以“分”为整数存储。若不做统一,后续计算成交额会出现 100 倍或 10000 倍的偏差。建议在解析层就统一为“股”和“元”,并在映射表中显式标注换算系数。

第三类是语义差异。最典型的是 BSFlag:在部分数据源中,B 表示“主动买入”(即买方主动吃掉卖盘),S 表示“主动卖出”;而在另一些数据源中,它表示“该笔成交对应的是买盘队列还是卖盘队列”。两者在统计主动买卖净额时结果可能完全相反。工程上的做法是:不要凭字段名猜语义,而是用一笔已知方向的成交(例如涨停板上的大单)做一次小样本校验,确认后再批量解析。

字段之间的依赖关系

这些字段并非彼此独立,而是存在明确的依赖与推导关系,理解这一点对高效读取很重要:

  • 成交序号依赖证券代码与市场类型:序号通常只在“同一市场、同一证券”内连续,跨证券比较序号没有意义。因此排序键应是(市场,代码,序号)三元组,而不是单独一个序号。
  • 成交金额可由价格与数量推导:Amount = Price × Volume(单位统一后)。若数据源同时给出金额,可用它做一致性校验,偏差超过阈值说明单位或精度处理有误。
  • 方向可由买卖盘序号反推:当数据源不直接给 BSFlag 时,可通过比较买盘序号与卖盘序号的大小关系,结合逐笔委托数据推断主动方,但这要求成交与委托数据能按序号对齐。
  • 成交类型影响时间语义:集合竞价阶段的成交时间往往统一标记为开盘或收盘时刻,若与连续竞价成交混排,按时间排序会失真,需先按成交类型分组。

一个可复用的字段映射示例

下面给出一个简化的字段映射配置示例,用于把不同来源的逐笔成交字段统一到标准结构。示例中的字段名与换算系数属于教学假设,实际应以所用数据源的官方文档为准。

# 逐笔成交字段映射(示例,非实测配置)
standard_fields:
  symbol:      { source: ["SecurityID", "symbol"],        type: string }
  market:      { source: ["MarketType", "exchange"],      type: string }
  trade_index: { source: ["TradeIndex", "trade_seq"],     type: int64  }
  trade_time:  { source: ["TradeTime", "trade_time"],     type: time   }
  price:       { source: ["TradePrice", "price"],         type: float, unit: yuan }
  volume:      { source: ["TradeVolume", "volume"],       type: int64, unit: share, factor: 100 }
  amount:      { source: ["TradeAmount", "amount"],       type: float, unit: yuan, optional: true }
  bs_flag:     { source: ["BSFlag", "side"],              type: string, enum: {B: 主动买, S: 主动卖, N: 未知} }
  trade_type:  { source: ["TradeType", "trade_type"],     type: string }

这张映射表的价值在于:读取层只认标准字段名,源字段差异被隔离在配置中;单位换算(如 factor: 100 表示源数据以手为单位、需乘 100 转为股)集中在一处,避免散落在业务代码里。实际使用时,建议先用少量样本数据跑一遍映射,检查 Amount 与 Price × Volume 是否吻合、BSFlag 的枚举值是否落在预期集合内,再放开全量读取。

判断字段映射是否正确,最省力的三个校验是:① 成交额与价×量的一致性;② 同一证券的成交序号是否单调递增且无重复;③ 主动买卖统计在已知行情(如单边上涨日)下方向是否符合直觉。三项都通过,基本可以认为字段语义解析正确。

把字段结构理清之后,后续的读取效率优化才有明确的着力点:知道哪些字段是定长、哪些是变长,哪些字段可以延迟解析,哪些字段必须参与排序与去重。字段地图不是背下来的,而是在映射表与校验中逐步固化的。