4. 高频数据预处理:去重、去噪、时间对齐、缺失值处理、异常值检测
高频行情数据在采集、传输和存储过程中,不可避免地会引入各种质量问题。原始数据通常包含重复记录、噪声、时间戳偏差、缺失值和异常跳变。本章将系统讲解高频数据预处理的五大核心步骤,确保标签校准建立在干净、可靠的数据基础之上。
核心原则:高频数据预处理的目标是“保留真实微观结构,剔除系统错误与偶然异常”。过度清洗会丢失有效信息,清洗不足则导致标签偏差。每一步都需要结合市场微观结构知识进行判断。
4.1 数据去重
重复数据通常由交易所冗余推送、网关重传或存储系统写入异常导致。重复记录会扭曲统计指标(如成交量、笔数)并导致标签错误。
| 重复类型 | 特征 | 处理方法 |
|---|---|---|
| 完全重复 | 所有字段(时间戳、价格、成交量、买卖方向)完全相同 | 保留第一条,删除后续完全相同的记录 |
| 时间戳重复 | 时间戳相同,但价格或成交量不同 | 按业务规则合并或保留最后一条(需确认撮合顺序) |
| 跨日重复 | 同一笔交易在收盘后再次推送 | 按交易日+唯一标识(如 TradeID)去重 |
# Python 示例:基于时间戳和价格去重
import pandas as pd
def deduplicate_tick_data(df):
# 完全重复去除
df = df.drop_duplicates(subset=['timestamp', 'price', 'volume', 'side'])
# 按时间戳排序后,保留同一毫秒内的最后一条(假设为最终状态)
df = df.sort_values('timestamp').drop_duplicates(subset='timestamp', keep='last')
return df.reset_index(drop=True)
4.2 数据去噪
高频行情中的噪声主要来源于微观结构效应(买卖价差、离散价格档位)和传输抖动。去噪的目标是平滑随机波动,同时保留趋势和突变点。
- 价格噪声:价格在买卖价差之间来回跳动,形成“锯齿”形态。使用中值滤波或移动平均可有效抑制。
- 成交量噪声:单笔成交量异常小(如1股)或异常大(如错误合并),需结合阈值过滤。
- 时间戳噪声:网络延迟导致时间戳出现毫秒级抖动,需进行时间对齐。
去噪策略对比:
- 中值滤波:对价格序列取滑动窗口(如5笔)中位数,抗脉冲噪声能力强,适合去除报价跳动。
- 卡尔曼滤波:基于状态空间模型,适合实时处理,但需设定过程噪声和观测噪声参数。
- 小波去噪:将信号分解到不同频率尺度,适合分离高频噪声与低频趋势。
4.3 时间对齐
不同数据源(如逐笔成交、逐笔委托、Level-2快照)的时间戳精度和基准可能不同。时间对齐是将所有数据统一到同一时间坐标系下的关键步骤。
| 对齐方式 | 适用场景 | 实现方法 |
|---|---|---|
| 精确对齐 | 同一交易所、同一精度(如微秒级) | 直接按时间戳匹配,取最近邻记录 |
| 插值对齐 | 不同精度(如毫秒 vs 微秒) | 线性插值或前向填充(forward fill) |
| 聚合对齐 | 需要降频(如从逐笔到1秒K线) | 按固定时间窗口聚合(OHLCV) |
# Python 示例:前向填充对齐
def align_timestamps(df, target_freq='1s'):
# 设置时间戳为索引
df = df.set_index('timestamp')
# 重采样到目标频率,使用前向填充
aligned = df.resample(target_freq).ffill()
return aligned.reset_index()
4.4 缺失值处理
高频数据中的缺失值可能由网络丢包、交易所暂停交易或数据采集故障引起。处理方式取决于缺失模式和数据用途。
- 连续缺失(如停牌期间):不填充,直接标记为“无交易区间”。
- 零星缺失(单笔丢失):使用前向填充(last observation carried forward, LOCF)或线性插值。
- 开盘/收盘缺失:参考集合竞价数据或相邻交易日同一时段数据。
注意:高频数据中,缺失值填充需谨慎。使用前向填充时,若缺失时间过长(如超过10秒),填充值可能严重偏离真实市场状态。建议设置最大填充间隔阈值。
4.5 异常值检测
异常值检测是预处理中最关键的环节,直接影响标签校准的准确性。高频行情中常见的异常包括价格跳变和成交量异常。
4.5.1 价格跳变检测
价格跳变指相邻两笔交易价格出现不符合市场微观结构的剧烈变动。检测方法包括固定阈值法和统计法。
| 检测方法 | 原理 | 阈值设定 |
|---|---|---|
| 固定阈值法 | 价格变动超过预设百分比(如0.5%) | 根据品种波动率设定,如股指期货0.2%,个股0.5% |
| Z-score法 | 计算价格变动的Z分数,超过3视为异常 | 动态计算,需剔除极端值后重新计算 |
| 中位数绝对偏差法 | 使用MAD(Median Absolute Deviation)替代标准差,更稳健 | MAD超过3倍中位数绝对偏差 |
| 市场微观结构法 | 结合买卖盘口深度判断价格跳变是否合理 | 跳变幅度超过当前买卖价差+深度加权 |
# Python 示例:基于MAD的价格跳变检测
import numpy as np
def detect_price_jumps(prices, threshold=3.0):
# 计算价格变动率
returns = np.diff(prices) / prices[:-1]
# 计算中位数和MAD
median = np.median(returns)
mad = np.median(np.abs(returns - median))
# 计算修正Z-score
modified_z_scores = 0.6745 * (returns - median) / mad
# 标记异常
jump_indices = np.where(np.abs(modified_z_scores) > threshold)[0] + 1
return jump_indices, modified_z_scores
4.5.2 成交量异常检测
成交量异常通常表现为单笔成交量远超正常水平(如错误合并)或远低于正常水平(如碎片化成交)。
- 单笔成交量过大:超过该品种日均单笔成交量的10倍,或超过当日累计成交量的5%。
- 单笔成交量过小:低于最小交易单位(如1手),或连续多笔成交量极低。
- 成交量与价格不匹配:价格大幅变动但成交量极小,或价格不变但成交量激增。
4.6 综合预处理流程
在实际工程中,上述五个步骤需要按顺序执行,形成完整的预处理流水线。
- 步骤1:数据加载与格式统一 — 将不同数据源转换为统一格式(如DataFrame),检查时间戳精度和字段完整性。
- 步骤2:去重 — 删除完全重复和时间戳重复的记录,保留唯一标识。
- 步骤3:时间对齐 — 将所有数据对齐到统一时间基准,处理不同精度问题。
- 步骤4:去噪 — 应用中值滤波或卡尔曼滤波,平滑微观结构噪声。
- 步骤5:缺失值处理 — 根据缺失模式选择填充或标记。
- 步骤6:异常值检测与处理 — 检测价格跳变和成交量异常,标记或修正异常记录。
- 步骤7:质量验证 — 检查预处理后的数据统计特征(如最大回撤、成交量分布),确保未引入新偏差。
工程实践建议:
- 预处理流水线应支持增量处理(incremental processing),避免每次全量重算。
- 所有异常标记应保留原始字段和标记原因,便于后续审计和标签校准回溯。
- 建议对每个品种单独设定预处理参数(如跳变阈值、滤波窗口),因为不同品种的微观结构差异显著。
# 完整预处理流水线示例
def preprocess_tick_pipeline(df, config):
# 1. 去重
df = deduplicate_tick_data(df)
# 2. 时间对齐(重采样到1ms)
df = align_timestamps(df, target_freq='1ms')
# 3. 去噪(中值滤波,窗口=5)
df['price_smooth'] = df['price'].rolling(window=5, center=True).median()
# 4. 缺失值填充(前向填充,最大间隔3笔)
df['price_smooth'] = df['price_smooth'].ffill(limit=3)
# 5. 异常检测
jump_idx, _ = detect_price_jumps(df['price_smooth'].values, threshold=config['jump_threshold'])
df.loc[jump_idx, 'is_anomaly'] = True
return df
经过上述预处理后,高频行情数据将具备一致性、连续性和可靠性,为后续的标签校准(如买卖方向识别、订单簿重建、微观结构特征提取)奠定坚实基础。