2. 盘口数据基础与采集

要研究买卖价差与成交时点的关联,第一步不是建模,而是把“数据长什么样”弄清楚。盘口数据本质上是交易所按时间顺序推送的订单簿快照与逐笔事件流,不同行情级别提供的信息粒度差异很大,直接决定了后续能计算哪些价差指标、能把成交时点对齐到什么精度。

一、Level-1 与 Level-2 的数据结构差异

Level-1 行情是最基础的快照,通常只包含最优一档买价(bid1)、最优一档卖价(ask1)及其对应委托量,外加最新成交价、成交量、成交金额和快照时间戳。它足以计算最优买卖价差,但看不到盘口深度,也无法判断价差是被单笔大单撑开的,还是多档稀薄造成的。

Level-2 行情在 Level-1 基础上扩展为多档快照(常见十档),并额外提供逐笔委托与逐笔成交明细。逐笔数据带有委托编号、方向、价格、数量和毫秒级时间戳,是研究“成交发生在什么时点、以什么价格成交、当时价差处于什么状态”的关键素材。下表对比两类数据的核心字段与用途:

维度Level-1 快照Level-2 快照 + 逐笔
盘口档位仅最优一档通常十档买卖
时间精度秒级或 3 秒切片毫秒级
成交明细仅汇总成交量/额逐笔成交价、量、方向
典型用途价差粗算、低频监控价差—成交时点关联分析

需要强调的是,快照是“某一时刻的截面”,逐笔是“事件流”,两者时间语义不同。做关联分析时,必须明确以哪一类时间戳为基准,否则会出现成交被错误归入相邻快照区间的问题。

二、买卖价差的定义与计算口径

绝对价差定义为 ask1 − bid1,反映一档之间的价格空隙;相对价差(也叫报价价差率)为 (ask1 − bid1) / 中间价,其中中间价 mid = (ask1 + bid1) / 2。相对价差消除了股价绝对水平的影响,便于跨标的、跨时段比较。若研究深度层面的价差,还可计算有效价差:以成交价与中间价之差的绝对值乘以 2,再除以中间价,衡量实际成交相对中间价的偏离程度。

计算时要注意三个口径问题:一是快照中若出现买一价为空或卖一价为空(涨跌停封死),价差不可直接计算,应标记为缺失而非填零;二是集合竞价阶段与连续竞价阶段的价差含义不同,需分时段处理;三是停牌、复牌前后的快照要单独标注,避免把异常状态混入统计。

三、成交时点的记录方式

成交时点通常有三种时间戳来源:交易所撮合时间、行情推送时间、本地接收时间。撮合时间最接近真实成交瞬间,但普通用户往往只能拿到推送时间;本地接收时间受网络延迟影响,只适合做延迟监控,不适合做微观时点分析。实践中应以交易所时间戳为主键,并保留推送时间用于延迟诊断。

把成交事件与盘口快照对齐时,常用做法是“前向填充”:对每一笔成交,取时间戳不晚于该成交时刻的最近一条快照,作为其当时的盘口状态。这样每笔成交都能关联到一个 bid1、ask1 和价差值,形成“成交—价差”配对样本。下图展示了这一对齐关系与数据流:

盘口快照与逐笔成交的时间对齐 快照流(Level-2 十档) t=10:00:00.100 t=10:00:00.400 t=10:00:00.700 t=10:00:01.000 逐笔成交流 成交A .250 成交B .520 成交C .810 成交D .950 取 t≤.250 最近快照 取 t≤.520 最近快照 取 t≤.810 最近快照 取 t≤.950 最近快照 配对样本:成交价、成交量、方向、bid1、ask1、价差、中间价 用于统计不同价差区间下的成交频率与成交时点分布

四、高频数据采集与清洗的基本方法

采集环节要解决“收得全、收得准、收得稳”。以常见的行情接入为例,程序订阅后按消息类型分流:快照消息写入快照表,逐笔成交写入成交表,逐笔委托写入委托表,每条记录保留交易所时间戳与本地接收时间。落盘建议采用按交易日分文件、按消息类型分表的组织方式,便于回放与增量校验。

清洗环节重点处理四类问题。第一是重复消息:网络重连或补发会导致同一序列号重复,应按交易所提供的序列号去重。第二是乱序:逐笔流可能因并发写入出现时间倒序,需按时间戳与序列号重排。第三是缺失与跳号:序列号不连续说明丢包,应记录缺口区间,必要时通过历史回补接口补齐。第四是异常值:价格为 0、买卖价倒挂(bid1 > ask1)、成交量超过盘口挂单量等,都应打上异常标记而非直接删除,保留可追溯性。

# 伪代码:逐笔成交与快照对齐的核心逻辑
snapshots = load_snapshots(day)          # 按时间升序
trades    = load_trades(day)             # 已去重、已重排
snapshots.sort(key=lambda s: s.ts)
idx = 0
for tr in trades:
    while idx + 1 < len(snapshots) and snapshots[idx+1].ts <= tr.ts:
        idx += 1
    snap = snapshots[idx]
    if snap.bid1 is None or snap.ask1 is None:
        tr.spread = None                 # 涨跌停等缺失情形
    else:
        tr.spread = snap.ask1 - snap.bid1
        tr.mid    = (snap.ask1 + snap.bid1) / 2
        tr.rel_spread = tr.spread / tr.mid
    tr.snap_ts = snap.ts

这段逻辑的关键在于“不晚于成交时刻的最近快照”,而不是简单取时间差最小的快照,后者可能把成交错误地关联到未来盘口。完成对齐后,建议先做一次质量核查:统计价差缺失比例、买卖倒挂条数、成交与快照时间差的中位数。若时间差中位数明显偏大,说明快照频率过低或存在推送延迟,需要重新评估数据是否满足研究精度。

教学示例说明:上文时间戳、序列号处理与伪代码均为讲解用示例,实际参数应以所接入行情源的真实字段和频率为准。清洗规则一旦确定,应在整个研究周期内保持一致,避免不同批次数据口径不一。

只有把数据结构、价差口径、时点对齐和清洗规则四件事同时定清楚,后续统计“价差扩大时成交是否更稀疏”“成交集中发生在价差收窄的哪个阶段”才有可靠基础。数据层面的一个小口径偏差,往往会在关联分析中被放大成看似显著的伪规律。