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 时标记为异常。
  • 成交量与价格联动检测:若成交量突增但价格未变,或价格大幅波动但成交量极低,均需重点核查。
重要提醒:异常值不一定是错误数据。例如,突发重大利好导致的瞬间放量跳空是真实市场行为,不应直接删除。建议对检测到的异常值进行人工复核或结合市场事件日历判断,再决定是修正、保留还是剔除。
异常值检测与处理流程 原始 Level2 数据 价格跳空检测 成交量异常检测 合并异常判定 修正 / 保留 / 剔除 说明:价格跳空与成交量异常需分别检测,合并判定后根据市场背景选择处理策略

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 等)必须使用训练集计算,并应用到验证集和测试集,避免数据泄露。
Level2 字段标准化方法选择 Level2 字段 价格类字段 成交量类字段 比率类字段 Z-Score 标准化 Robust 标准化 Min-Max 归一化 提示:标准化参数必须从训练集计算,避免数据泄露;测试集使用相同的参数进行变换

4.5 数据清洗流水线示例

在实际量化项目中,建议将上述步骤封装为可复用的数据清洗流水线(Pipeline),按顺序执行:

  1. 加载原始数据:读取 Level2 逐笔成交、快照、订单簿数据。
  2. 时间戳标准化:统一转换为 datetime 类型,对齐到整秒。
  3. 缺失值处理:按字段类型选择 ffill 或线性插值。
  4. 异常值检测与标记:使用 Z-Score 或 IQR 检测价格和成交量异常,生成异常标记列。
  5. 重采样:根据策略需求聚合到 1 分钟、5 分钟或更高粒度。
  6. 标准化:对数值型字段应用 Z-Score 或 Robust 标准化。
  7. 输出清洗后数据:保存为 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。