1. 逐笔成交数据字段解析与基础清洗
原始逐笔成交表长什么样
逐笔成交(Trade Tick)是交易所撮合引擎每发生一次成交就推送的一条记录。不同数据源字段名不同,但语义基本固定为六类:成交时间(时间戳,可能带毫秒/微秒)、成交编号(唯一序号)、证券代码、成交价格、成交数量、买卖方向(主动买/主动卖,或 BS 标志)。本案例用一份教学示例 CSV 演示,字段为 trade_time, trade_id, symbol, price, volume, side,其中故意埋入三类脏数据:时间戳缺失、时间戳格式不统一(部分为 HH:MM:SS 纯时间)、方向字段为空或写成 B/S/买/卖 混用。
清洗规则与字段映射
清洗目标不是"删掉脏行",而是把每条记录变成可被后续特征组合直接消费的规整行。规则如下:
- 时间戳:统一解析为
datetime64[ns]。纯时间(无日期)按当日交易日补齐;无法解析或为空的记录标记为NaT并剔除。 - 价格/数量:强制转数值,
price <= 0或volume <= 0视为异常剔除(交易所不会推送零价零量成交)。 - 方向:映射为
+1(主动买)/-1(主动卖);空值或无法识别记为0并单独计数,不直接丢弃,便于后续判断数据源质量。 - 排序与去重:按
trade_time升序,trade_id去重,保证时序单调。
用 pandas 完成清洗
下面代码可直接运行,输入为教学示例 CSV 字符串,输出规整 DataFrame。关键点:pd.to_datetime 的 errors='coerce' 把无法解析的时间变成 NaT;纯时间用 format='%H:%M:%S' 解析后补日期;方向映射用字典 + map,未命中填 0。
import pandas as pd
import io
raw = """trade_time,trade_id,symbol,price,volume,side
09:30:01.250,1001,600000,12.35,300,B
09:30:01.400,1002,600000,12.36,200,
,1003,600000,12.34,500,S
09:30:02,1004,600000,0.00,100,买
09:30:02.100,1005,600000,12.37,400,卖
09:30:02.100,1005,600000,12.37,400,卖
"""
df = pd.read_csv(io.StringIO(raw), dtype=str)
# 1) 时间解析:先尝试完整时间,再尝试纯时间补当日
t_full = pd.to_datetime(df["trade_time"], errors="coerce")
t_only = pd.to_datetime(df["trade_time"], format="%H:%M:%S", errors="coerce")
base = pd.Timestamp("2024-06-03") # 教学示例交易日
t_only = t_only.apply(lambda x: base + (x - x.normalize()) if pd.notna(x) else pd.NaT)
df["trade_time"] = t_full.fillna(t_only)
# 2) 数值转换
df["price"] = pd.to_numeric(df["price"], errors="coerce")
df["volume"] = pd.to_numeric(df["volume"], errors="coerce")
# 3) 方向映射
side_map = {"B": 1, "买": 1, "S": -1, "卖": -1}
df["side"] = df["side"].fillna("").str.strip().map(side_map).fillna(0).astype(int)
# 4) 剔除异常:时间缺失、非正价量
before = len(df)
df = df.dropna(subset=["trade_time", "price", "volume"])
df = df[(df["price"] > 0) & (df["volume"] > 0)]
# 5) 排序 + 去重
df = df.sort_values("trade_time").drop_duplicates("trade_id").reset_index(drop=True)
print(f"原始 {before} 行,清洗后 {len(df)} 行")
print(df[["trade_time", "trade_id", "price", "volume", "side"]])
预期输出(教学示例,非实测):原始 6 行,清洗后 4 行。1003 因时间缺失被剔除,1004 因 price=0 被剔除,1005 重复行被去重,1002 的 side 为空映射为 0 但保留。
清洗结果的自检清单
| 检查项 | 通过条件 | 本例结果 |
|---|---|---|
| 时间列类型 | datetime64[ns],无 NaT | 通过 |
| 时间单调性 | trade_time 严格非降 | 通过 |
| 价格/数量 | 全部 > 0 | 通过 |
| 方向取值 | 仅含 {-1, 0, 1} | 通过(含 1 条 0) |
| 主键唯一 | trade_id 无重复 | 通过 |
若数据源时间戳为微秒级整数(如
1717380601250000),用 pd.to_datetime(col, unit="us") 解析,不要先转字符串再解析,避免精度丢失。方向字段若为 0/1 或 B/S 混用,先统一 str.upper().str.strip() 再映射。不要用
dropna() 无差别删除整行:本例中 side 为空的行仍有价格和数量信息,直接删除会损失有效成交。正确做法是分列处理——时间/价量缺失才剔除,方向缺失降级为 0 并记录。