1. 逐笔成交数据初识
逐笔成交(Tick Trade)是交易所撮合引擎在每一笔成交达成时推送的最细粒度行情记录。与分钟K线不同,它不经过任何时间聚合,保留了成交发生的先后顺序、成交价格、成交量和买卖方向等原始信息。做交易分析时,逐笔成交是微观结构研究、订单流分析、大单识别和高频策略回测的基础数据;而它的原始形态往往并不"干净",这正是异常值清洗与稳健统计要解决的问题。
一条逐笔成交记录长什么样
以国内A股常见的Level-2逐笔成交为例,一条记录通常包含以下核心字段:
| 字段 | 含义 | 示例值 | 说明 |
|---|---|---|---|
| 证券代码 | 标的唯一标识 | 600519 | 含市场后缀更稳妥,如 600519.SH |
| 成交时间 | 撮合时刻 | 09:31:02.350 | 毫秒级,同一毫秒可有多笔 |
| 成交序号 | 当日成交流水号 | 12873 | 用于排序与去重,通常单调递增 |
| 成交价格 | 本笔成交价 | 1682.50 | 单位元,需注意复权问题 |
| 成交数量 | 本笔成交股数 | 300 | A股以100股为一手,但零股成交存在 |
| 成交金额 | 价格×数量 | 504750 | 可校验,也可自行计算 |
| 买卖方向 | 主动方标识 | B / S / 中性 | 不同数据商标注口径不同 |
需要特别说明方向字段的口径:有的数据源用"B/S"表示主动买/主动卖,有的用"买/卖盘"表示成交落在买一价还是卖一价,还有的只给一个标志位。方向判断错误会直接导致后续资金流统计反向,因此拿到数据的第一件事应是抽样核对方向字段与价格、盘口的关系,而不是急着算指标。
常见来源与格式差异
逐笔成交数据的来源大致分三类:交易所官方Level-2行情(经券商或行情商转发)、第三方数据服务商(如Wind、聚源、Tushare等)、以及自建行情网关落地的原始二进制或CSV文件。格式上,常见的有CSV/Parquet批量文件、数据库表(MySQL、ClickHouse)、以及实时推送的二进制协议流。不同来源的差异主要体现在:时间精度(秒级还是毫秒级)、方向字段口径、是否含撤单记录、复权处理方式、以及是否已做集合竞价剔除。
为什么必须做异常值清洗与稳健统计
逐笔成交的"脏"体现在多个层面。第一类是重复记录:行情重连或落库重试会造成同一成交序号出现多次,直接累加会虚增成交量。第二类是价格异常:集合竞价阶段的虚拟成交、涨跌停板上的撮合、以及数据源偶发的价格错位(如小数点偏移),会产生远离当日价格中枢的离群点。第三类是数量异常:零股、超大单(如单笔百万股)在统计换手率或大单占比时若不加区分,会严重扭曲均值。第四类是时间乱序:多路行情合并时时间戳可能非单调,影响按时间窗口的聚合。
传统统计量(均值、标准差、相关系数)对离群值极其敏感——一笔错误的价格记录就能把当日均价拉偏,一个超大单就能让成交量标准差翻倍。稳健统计的思路是用中位数、分位数、截尾均值、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分位)是合理假设,实际使用时应根据标的流动性和数据质量调整,并通过清洗前后统计量的偏差来验证阈值是否合适。
在交易分析中的定位
逐笔成交清洗与稳健统计处在分析链路的最前端:它向下游的订单流指标(主动买卖净额、大单占比、成交笔数分布)、微观结构研究(价差、冲击成本)和策略回测提供可信输入。它的价值不是"让数据变好看",而是让后续每一个统计结论都建立在可解释、可复现的数据口径上。理解字段含义、识别来源差异、建立"校验—识别—标记—稳健统计"的处理习惯,是后续所有章节的方法基础。