4. 数据清洗与预处理:缺失值处理、异常值检测、时间对齐与重采样、数据标准化
Level2 行情数据在采集、传输与存储过程中,极易受到网络抖动、交易所撮合延迟、硬件故障等因素影响,产生缺失、异常、时间戳错位等问题。若直接使用未经清洗的原始数据进行收益模拟,会导致策略回测结果失真、信号延迟误判甚至模型失效。本章系统讲解数据清洗与预处理的四大核心环节,确保后续量化分析建立在高质量、高一致性的数据基础之上。
4.1 缺失值处理
Level2 数据缺失通常表现为某只股票在某个 tick 或某一秒的快照数据完全丢失,或部分字段(如买卖十档挂单量、逐笔成交笔数)为空。常见的缺失值处理策略如下:
| 策略 | 适用场景 | 实现方式 | 注意事项 |
|---|---|---|---|
| 前向填充(ffill) | 连续高频 tick 数据,缺失时间短(< 3 个 tick) | 用上一个有效值填充当前缺失 | 若缺失时间过长,会引入滞后偏差 |
| 后向填充(bfill) | 数据段起始位置缺失 | 用下一个有效值填充当前缺失 | 可能引入未来信息,回测中需谨慎 |
| 线性插值 | 价格、成交量等连续数值型字段 | 按时间间隔线性估算缺失值 | 不适用于离散字段(如买卖方向) |
| 删除缺失行 | 缺失比例极低(< 0.1%)且随机分布 | 直接 dropna() | 避免连续删除导致时间序列断裂 |
| 标记缺失 | 缺失本身可能包含信息(如停牌、熔断) | 填充特殊值(-1 或 NaN)并增加缺失标记列 | 需在模型训练时处理该标记特征 |
实践建议:对于 Level2 逐笔成交数据,优先使用前向填充(ffill)处理毫秒级缺失;对于 1 秒切片快照数据,若连续缺失超过 5 秒,建议删除该段数据或标记为“交易中断”,避免插值引入虚假行情。
4.2 异常值检测
异常值在 Level2 行情中主要表现为价格跳空和成交量异常。这两类异常会严重干扰收益计算与信号生成,必须进行识别与处理。
4.2.1 价格跳空检测
价格跳空指相邻两个 tick 或相邻秒之间的价格变化幅度显著超出正常波动范围。常见检测方法:
- 固定阈值法:设定价格变化百分比阈值(如 0.5%),超过即视为跳空。
- 移动标准差法:计算过去 N 个 tick 的价格变化标准差,若当前变化超过 3 倍标准差,判定为跳空。
- 结合涨跌停规则:若价格变化超过当日涨跌停限制,直接标记为异常。
4.2.2 成交量异常检测
成交量异常通常由大单集中成交、数据推送合并错误或交易所异常引起。检测方法包括:
- Z-Score 方法:计算过去一段时间成交量的均值和标准差,若当前成交量 Z-Score > 3,视为异常。
- IQR 四分位距法:成交量超出 Q3 + 1.5×IQR 或低于 Q1 - 1.5×IQR 时标记为异常。
- 成交量与价格联动检测:若成交量突增但价格未变,或价格大幅波动但成交量极低,均需重点核查。
重要提醒:异常值不一定是错误数据。例如,突发重大利好导致的瞬间放量跳空是真实市场行为,不应直接删除。建议对检测到的异常值进行人工复核或结合市场事件日历判断,再决定是修正、保留还是剔除。
4.3 时间对齐与重采样
Level2 行情数据包含多种时间粒度:逐笔成交(毫秒级)、快照切片(通常 3 秒或 1 秒)、订单簿变更(微秒级)。在进行收益模拟时,必须将所有数据统一到相同的时间基准上,否则会出现信号与成交时间错位的问题。
4.3.1 时间对齐策略
- 以交易所时间戳为基准:所有数据统一使用交易所推送的时间戳,避免使用本地接收时间。
- 对齐到整秒边界:将毫秒/微秒级时间戳向下取整到最近的整秒,作为公共时间键。
- 多源数据合并:使用 pandas 的 merge_asof 进行非精确时间匹配,允许前后 1 个 tick 的偏差。
4.3.2 重采样方法
重采样是将高频数据聚合到较低频率(如从 tick 级聚合到 1 分钟、5 分钟 K 线)的过程。常用规则:
| 字段 | 重采样聚合函数 | 说明 |
|---|---|---|
| 最新价 | last() | 取该时间窗口最后一笔成交价 |
| 成交量 | sum() | 窗口内所有成交量累加 |
| 最高价 / 最低价 | max() / min() | 窗口内价格极值 |
| 买卖挂单量 | mean() 或 last() | 取均值或最后一笔快照数据 |
| 逐笔成交笔数 | count() | 统计窗口内成交笔数 |
代码示例:使用 pandas 进行 1 秒到 1 分钟的重采样
import pandas as pd
# 假设 df 包含 'timestamp'(datetime 类型)和 'price'、'volume' 列
df.set_index('timestamp', inplace=True)
# 1 分钟重采样
resampled = df.resample('1T').agg({
'price': 'last',
'volume': 'sum',
'high': 'max',
'low': 'min'
}).dropna()
print(resampled.head())
4.4 数据标准化方法
Level2 行情中不同字段的量纲差异巨大(如价格在 1-100 元,成交量在 0-10^6 手,委比在 -100% 到 100%)。标准化可以消除量纲影响,使模型训练更稳定、收敛更快。
4.4.1 常用标准化方法对比
| 方法 | 公式 | 输出范围 | 适用场景 |
|---|---|---|---|
| Z-Score 标准化 | (x - μ) / σ | 无固定范围,均值为 0,标准差为 1 | 数据近似正态分布,存在极端值 |
| Min-Max 归一化 | (x - min) / (max - min) | [0, 1] | 数据有明确上下界,无极端离群点 |
| Robust 标准化 | (x - median) / IQR | 无固定范围,受异常值影响小 | 数据包含较多异常值或长尾分布 |
| 分位数标准化 | 映射到标准正态分布的分位数 | 近似 [-3, 3] | 需要将不同分布的数据对齐到同一分布 |
4.4.2 标准化在 Level2 数据中的实践
- 价格类字段:建议使用 Z-Score 标准化,因为不同股票价格区间差异大,且存在少量极端价格。
- 成交量类字段:建议使用 Robust 标准化,因为成交量常出现突发巨量(异常值),IQR 能有效抑制其影响。
- 比率类字段:如委比、涨跌幅,本身已在 [-1, 1] 或 [-100%, 100%] 范围内,可直接使用 Min-Max 归一化或不做处理。
- 注意:标准化参数(μ、σ、min、max 等)必须使用训练集计算,并应用到验证集和测试集,避免数据泄露。
4.5 数据清洗流水线示例
在实际量化项目中,建议将上述步骤封装为可复用的数据清洗流水线(Pipeline),按顺序执行:
- 加载原始数据:读取 Level2 逐笔成交、快照、订单簿数据。
- 时间戳标准化:统一转换为 datetime 类型,对齐到整秒。
- 缺失值处理:按字段类型选择 ffill 或线性插值。
- 异常值检测与标记:使用 Z-Score 或 IQR 检测价格和成交量异常,生成异常标记列。
- 重采样:根据策略需求聚合到 1 分钟、5 分钟或更高粒度。
- 标准化:对数值型字段应用 Z-Score 或 Robust 标准化。
- 输出清洗后数据:保存为 Parquet 或 HDF5 格式,供后续收益模拟使用。
代码示例:完整的数据清洗流水线框架
def clean_level2_pipeline(df):
# 1. 时间戳标准化
df['timestamp'] = pd.to_datetime(df['timestamp'])
df.set_index('timestamp', inplace=True)
# 2. 缺失值处理(前向填充)
df.fillna(method='ffill', inplace=True)
# 3. 异常值检测(价格 Z-Score)
price_mean = df['price'].rolling(20).mean()
price_std = df['price'].rolling(20).std()
df['price_anomaly'] = (abs(df['price'] - price_mean) > 3 * price_std).astype(int)
# 4. 重采样到 1 分钟
resampled = df.resample('1T').agg({
'price': 'last',
'volume': 'sum',
'price_anomaly': 'max'
}).dropna()
# 5. 标准化(Z-Score)
resampled['price_z'] = (resampled['price'] - resampled['price'].mean()) / resampled['price'].std()
resampled['volume_z'] = (resampled['volume'] - resampled['volume'].mean()) / resampled['volume'].std()
return resampled
效率提示:Level2 数据量巨大(单只股票一天可产生数百万条逐笔数据),建议使用 Dask 或 Modin 进行分布式清洗,或使用 PyArrow 进行列式内存管理,避免单机 OOM。