3. 成交分布统计的数据准备
开盘集合竞价的成交分布统计,本质上是对“9:15—9:25 这十分钟里,每一笔委托如何进入、如何被撮合、最终以什么价格成交多少”的一次结构化还原。统计结果是否可信,八成取决于数据准备阶段。本节围绕三个环节展开:获取(拿到逐笔委托与逐笔成交原始数据)、清洗(剔除无效、重复、错位记录)、标准化(统一字段、单位、时间口径与代码映射),最终形成一张可直接用于分布统计的“干净明细表”。
一、需要获取哪些数据:两类明细 + 一份参考
集合竞价统计至少需要三类输入。第一类是逐笔委托明细,核心字段包括:证券代码、委托时间(精确到毫秒或秒)、委托方向(买/卖)、委托价格、委托数量、订单编号、委托类型(限价/市价/本方最优等)。第二类是逐笔成交明细,核心字段包括:成交时间、成交价格、成交数量、买方订单号、卖方订单号、成交编号。第三类是参考数据,包括当日开盘参考价、涨跌停价格、证券代码与简称对照、交易日历,用于后续校验和分组。
获取渠道通常有两类:交易所行情文件(如 Level-2 逐笔委托与逐笔成交的二进制或 CSV 落地文件)、以及行情商/数据商的接口回放。无论哪种渠道,都要在获取时记录数据版本、采集时间、文件校验值,否则后续出现“同一只股票两次统计结果不一致”时无法追溯。示例场景中,我们假设某交易日全市场逐笔委托约 2000 万条、逐笔成交约 800 万条,单日原始数据量在数 GB 级别,这决定了清洗必须用批处理脚本而非手工表格。
二、清洗:先解决“脏数据”的四种典型形态
原始逐笔数据常见的质量问题有四类,处理顺序建议按“去重 → 时间对齐 → 异常值处理 → 一致性校验”推进。
- 重复记录:同一订单编号在委托表中出现多次,多因文件分片重叠或断点续传导致。处理方式是按“订单编号 + 委托时间”去重,保留首条。
- 时间越界:集合竞价统计只关心 9:15:00—9:25:00 的委托,但原始文件常包含 9:25 之后的连续竞价数据,甚至包含盘后固定价格交易。需按交易所时区(北京时间)截取窗口,并注意 9:25 的撮合成交时间戳可能落在 9:25:00.000 附近,边界应取闭区间。
- 字段缺失与类型错误:价格为空、数量为 0 或负、方向字段为未知编码。此类记录不能简单删除,应先标记为“待核”,再判断是否影响统计口径——例如数量为 0 的撤单记录应保留用于计算撤单率,但不计入成交分布。
- 成交与委托对不上:成交明细中的买方/卖方订单号在委托表中找不到对应记录。这通常意味着委托表被截断或代码映射错误,必须做双向关联校验,统计“孤儿成交”比例,超过阈值(如 0.1%)就应回退检查数据源。
三、标准化:让字段“可计算”
清洗解决“对不对”,标准化解决“能不能算”。关键动作包括:
- 统一时间口径:全部转换为“当日 00:00:00 起的毫秒偏移”,避免跨文件时区与格式(HH:MM:SS vs 毫秒时间戳)混用。
- 统一价格与数量单位:价格统一为“元”,保留两位或三位小数(视品种最小价位而定);数量统一为“股”,注意部分数据源以“手”为单位,需乘以 100。
- 统一方向编码:将 B/S、1/2、买/卖等不同编码映射为统一的 buy/sell 枚举。
- 代码映射:把带市场前缀的代码(如 SH600000、600000.SH)统一为六位数字代码,并关联板块(主板/创业板/科创板)与涨跌停幅度,供后续分组统计。
- 派生字段:计算委托金额 = 价格 × 数量,标记“是否在 9:20 前可撤单窗口”“是否参与开盘撮合”等布尔字段,为分布统计预置维度。
下面这张图展示了从原始文件到可统计明细表的完整数据流,以及每一步的关键校验点:
四、一个可执行的处理示例
假设我们拿到某日委托文件 order_20240612.csv,字段为 code,time,side,price,qty,order_id,type。用 Python 的 pandas 做最小闭环处理,关键代码如下(示例参数,非实测结果):
import pandas as pd
df = pd.read_csv("order_20240612.csv", dtype={"code": str})
# 1) 时间标准化:HH:MM:SS.mmm → 当日毫秒偏移
df["t_ms"] = pd.to_timedelta(df["time"]).dt.total_seconds().mul(1000).astype("int64")
# 2) 截取集合竞价窗口(闭区间)
df = df[(df["t_ms"] >= 9*3600*1000 + 15*60*1000) &
(df["t_ms"] <= 9*3600*1000 + 25*60*1000)]
# 3) 去重:订单号 + 时间保留首条
df = df.drop_duplicates(subset=["order_id", "t_ms"], keep="first")
# 4) 方向与单位标准化
df["side"] = df["side"].map({"B": "buy", "S": "sell", "1": "buy", "2": "sell"})
df["qty"] = df["qty"].astype("int64") # 统一为股
df["amount"] = df["price"] * df["qty"] # 派生委托金额
# 5) 标记撤单(假设 type=C 表示撤单)
df["is_cancel"] = (df["type"] == "C").astype("int8")
print(df.shape, df["is_cancel"].mean())
运行后应重点观察三个数:清洗后记录数相对原始记录数的保留率(正常应在 95% 以上,过低说明窗口或去重逻辑有误)、撤单标记占比(集合竞价阶段通常不低,需与交易所公开统计口径交叉验证)、以及 amount 是否存在负值或异常大值。若保留率骤降,优先检查时间字段是否被误解析为 UTC。
五、数据质量检查清单
在进入分布统计前,建议逐项确认以下要点,任一项不通过都应先修复数据而非继续计算:
| 检查项 | 判断标准(示例) | 不通过时的处理 |
|---|---|---|
| 时间窗口完整性 | 9:15—9:25 每分钟均有记录 | 检查文件分片是否缺失 |
| 订单号唯一性 | 委托表内无重复 order_id | 按订单号+时间去重 |
| 成交可回查率 | 孤儿成交占比 < 0.1% | 回退核对代码映射与文件版本 |
| 价格合法性 | 全部落在当日涨跌停区间内 | 标记异常并单独核查 |
| 单位一致性 | 数量均为 100 的整数倍(A 股) | 排查“手/股”混用 |
| 撤单标记覆盖 | 撤单记录均有 is_cancel 标记 | 补充委托类型映射表 |
完成本节的数据准备后,我们得到的是一张字段统一、时间对齐、可双向追溯的逐笔明细表。它是后续“按价格档位统计成交量分布”“按委托金额区间统计委托笔数”“计算撤单率与成交率”等所有指标的唯一数据底座。数据准备做得扎实,后面的分布统计才有讨论价值。