1. 逐笔成交数据初识

逐笔成交(Tick Trade)是交易所撮合引擎在每一笔成交达成时推送的最细粒度行情记录。与分钟K线不同,它不经过任何时间聚合,保留了成交发生的先后顺序、成交价格、成交量和买卖方向等原始信息。做交易分析时,逐笔成交是微观结构研究、订单流分析、大单识别和高频策略回测的基础数据;而它的原始形态往往并不"干净",这正是异常值清洗与稳健统计要解决的问题。

一条逐笔成交记录长什么样

以国内A股常见的Level-2逐笔成交为例,一条记录通常包含以下核心字段:

字段含义示例值说明
证券代码标的唯一标识600519含市场后缀更稳妥,如 600519.SH
成交时间撮合时刻09:31:02.350毫秒级,同一毫秒可有多笔
成交序号当日成交流水号12873用于排序与去重,通常单调递增
成交价格本笔成交价1682.50单位元,需注意复权问题
成交数量本笔成交股数300A股以100股为一手,但零股成交存在
成交金额价格×数量504750可校验,也可自行计算
买卖方向主动方标识B / S / 中性不同数据商标注口径不同

需要特别说明方向字段的口径:有的数据源用"B/S"表示主动买/主动卖,有的用"买/卖盘"表示成交落在买一价还是卖一价,还有的只给一个标志位。方向判断错误会直接导致后续资金流统计反向,因此拿到数据的第一件事应是抽样核对方向字段与价格、盘口的关系,而不是急着算指标。

常见来源与格式差异

逐笔成交数据的来源大致分三类:交易所官方Level-2行情(经券商或行情商转发)、第三方数据服务商(如Wind、聚源、Tushare等)、以及自建行情网关落地的原始二进制或CSV文件。格式上,常见的有CSV/Parquet批量文件、数据库表(MySQL、ClickHouse)、以及实时推送的二进制协议流。不同来源的差异主要体现在:时间精度(秒级还是毫秒级)、方向字段口径、是否含撤单记录、复权处理方式、以及是否已做集合竞价剔除。

拿到一份逐笔数据后,先确认三件事:时间字段的精度与是否含日期、方向字段的定义、以及成交序号是否唯一且有序。这三点决定了后续清洗脚本能否正确排序和去重。

为什么必须做异常值清洗与稳健统计

逐笔成交的"脏"体现在多个层面。第一类是重复记录:行情重连或落库重试会造成同一成交序号出现多次,直接累加会虚增成交量。第二类是价格异常:集合竞价阶段的虚拟成交、涨跌停板上的撮合、以及数据源偶发的价格错位(如小数点偏移),会产生远离当日价格中枢的离群点。第三类是数量异常:零股、超大单(如单笔百万股)在统计换手率或大单占比时若不加区分,会严重扭曲均值。第四类是时间乱序:多路行情合并时时间戳可能非单调,影响按时间窗口的聚合。

传统统计量(均值、标准差、相关系数)对离群值极其敏感——一笔错误的价格记录就能把当日均价拉偏,一个超大单就能让成交量标准差翻倍。稳健统计的思路是用中位数、分位数、截尾均值、MAD(中位数绝对偏差)等对离群值不敏感的量替代,或先做异常值识别与修正再统计。下面的图示展示了从原始逐笔流到稳健统计量的完整处理链路,以及各环节的输入输出关系:

原始逐笔流 时间/序号/价/量/方向 CSV·Parquet·DB 结构校验 序号唯一·时间单调 金额=价×量核对 异常值识别 价格偏离·零股·超大单 重复序号·时间乱序 清洗与修正 去重·截尾·标记 保留原始副本 稳健统计量 中位数·分位数·截尾均值·MAD 大单占比·主动买卖净额 对离群值不敏感,可复现 直接统计(对照) 均值·标准差·相关系数 受单笔异常影响大 用于量化清洗前后的偏差 关键原则:清洗不删除原始数据,只新增"是否异常/异常类型"标记列;统计时按标记过滤或截尾,保证结果可追溯、可复算。 方向口径、时间精度、复权方式必须在清洗前统一,否则后续所有统计量都会系统性偏移。

一个可操作的最小示例

假设我们拿到某只股票某日上午的逐笔CSV,字段为:code,time,seq,price,volume,amount,direction。第一步做结构校验:检查seq是否唯一、time是否单调不减、amount是否等于price×volume(允许浮点误差)。第二步识别异常:以当日成交价的中位数为中心,用MAD计算稳健尺度,把偏离超过5倍MAD的记录标记为价格异常;把volume为0或超过当日成交量99.9分位的记录标记为数量异常;把重复seq标记为重复。第三步统计:分别计算清洗前后的成交量均值、中位数和主动买入净额,观察差异。

import pandas as pd
import numpy as np

df = pd.read_csv("tick_600519.csv")
df = df.sort_values(["time", "seq"]).reset_index(drop=True)

# 结构校验
dup = df.duplicated(subset="seq", keep=False)
amt_err = (df["amount"] - df["price"] * df["volume"]).abs() > 1e-6

# 稳健尺度:MAD
med = df["price"].median()
mad = (df["price"] - med).abs().median()
df["price_outlier"] = (df["price"] - med).abs() > 5 * 1.4826 * mad

# 数量异常:零股与极端大单
q999 = df["volume"].quantile(0.999)
df["vol_outlier"] = (df["volume"] <= 0) | (df["volume"] > q999)

# 对照统计
raw_mean = df["volume"].mean()
robust_median = df["volume"].median()
clean = df[~df["price_outlier"] & ~df["vol_outlier"]]
print("原始均值:", raw_mean, "稳健中位数:", robust_median,
      "清洗后均值:", clean["volume"].mean())

这段代码的要点不在语法,而在两个设计选择:一是用1.4826×MAD作为正态一致尺度估计,使阈值可与标准差类比;二是清洗结果以布尔标记列保存,而不是直接drop掉行,这样任何统计都能在"原始/清洗后"两套数据上复算对比。示例中的参数(5倍MAD、99.9分位)是合理假设,实际使用时应根据标的流动性和数据质量调整,并通过清洗前后统计量的偏差来验证阈值是否合适。

在交易分析中的定位

逐笔成交清洗与稳健统计处在分析链路的最前端:它向下游的订单流指标(主动买卖净额、大单占比、成交笔数分布)、微观结构研究(价差、冲击成本)和策略回测提供可信输入。它的价值不是"让数据变好看",而是让后续每一个统计结论都建立在可解释、可复现的数据口径上。理解字段含义、识别来源差异、建立"校验—识别—标记—稳健统计"的处理习惯,是后续所有章节的方法基础。