3. Tick 数据清洗:缺失值处理、异常值检测与时间戳对齐
Tick 数据是高频交易的“原油”,但原始交易所数据往往包含大量噪声、缺失和异常。本章聚焦于清洗流程中的三个核心环节:缺失值处理、异常值检测(价格跳跃与涨跌停过滤)以及时间戳对齐与去重。清洗质量直接决定后续特征工程的可靠性。
3.1 缺失值处理
Tick 数据缺失通常由网络延迟、交易所撮合引擎快照间隔或数据供应商采样不均导致。缺失模式分为三类:
| 缺失类型 | 典型场景 | 推荐处理方式 |
|---|---|---|
| 随机缺失 | 单笔买卖报价缺失,相邻 Tick 正常 | 前向填充(ffill)或线性插值 |
| 连续缺失 | 盘中断线超过 500ms,连续 5 笔以上缺失 | 标记为“无报价区间”,不填充,策略中跳过 |
| 开盘/收盘缺失 | 集合竞价阶段无逐笔成交 | 使用集合竞价第一笔成交价填充开盘 Tick |
实践建议: 对于高频策略,优先使用前向填充(ffill)而非均值填充,因为均值会引入未来信息。连续缺失超过 3 笔时,建议直接丢弃该区间,避免插值引入虚假价格序列。
import pandas as pd
import numpy as np
# 示例:Tick 数据前向填充与连续缺失标记
def clean_missing_ticks(df, max_gap=3):
"""
df: 包含 'time', 'price', 'volume' 的 DataFrame
max_gap: 允许连续缺失的最大笔数
"""
# 标记缺失
df['price_missing'] = df['price'].isna()
# 计算连续缺失组
df['gap_group'] = (~df['price_missing']).cumsum()
gap_counts = df.groupby('gap_group')['price_missing'].sum()
large_gaps = gap_counts[gap_counts > max_gap].index
# 对连续缺失过多的区间,将价格设为 NaN(后续丢弃)
df.loc[df['gap_group'].isin(large_gaps) & df['price_missing'], 'price'] = np.nan
# 前向填充其余缺失
df['price'] = df['price'].ffill()
# 丢弃仍为 NaN 的行(开盘前或收盘后)
df = df.dropna(subset=['price'])
return df
3.2 异常值检测:价格跳跃与涨跌停过滤
异常 Tick 主要来自:错误报价(胖手指)、撮合异常(闪电崩盘中的极端单笔)以及涨跌停边界。检测策略需结合统计方法与市场规则。
3.2.1 价格跳跃检测
价格跳跃定义为相邻 Tick 价格变化超过阈值。阈值设定方法:
- 固定阈值法: 基于合约最小变动价位(Tick Size)的 N 倍,例如 N=10 倍 Tick Size。
- 动态阈值法: 使用滚动窗口的 Median Absolute Deviation (MAD),对波动率变化具有鲁棒性。
- Z-Score 法: 计算价格变化率的 Z-Score,|Z| > 3 视为异常。
def detect_price_jumps(df, tick_size=0.2, n_mult=10):
"""
基于固定阈值检测价格跳跃
"""
df['price_diff'] = df['price'].diff().abs()
threshold = tick_size * n_mult
df['is_jump'] = df['price_diff'] > threshold
return df[df['is_jump'] == False] # 过滤跳跃点
注意: 涨跌停附近的跳跃可能是真实市场行为(封板/开板),不应直接过滤。建议结合涨跌停状态标记,仅过滤非涨跌停状态下的极端跳跃。
3.2.2 涨跌停过滤
交易所涨跌停规则:
- 涨停/跌停价 基于前收盘价计算,例如 ±10%(A 股主板)。
- 涨停时 卖一价可能为空,买一价等于涨停价;跌停时相反。
- 过滤策略: 将价格等于涨跌停价的 Tick 标记为“涨跌停状态”,不删除,但策略中需特殊处理(如禁止开仓)。
def filter_limit_up_down(df, prev_close, limit_pct=0.10):
"""
标记涨跌停状态
"""
limit_up = prev_close * (1 + limit_pct)
limit_down = prev_close * (1 - limit_pct)
df['is_limit_up'] = df['price'] >= limit_up
df['is_limit_down'] = df['price'] <= limit_down
# 保留所有数据,仅添加标记
return df
3.3 时间戳对齐与去重
不同数据源(交易所直连、第三方供应商)的时间戳精度和时区可能不一致。对齐与去重是保证时序正确性的关键。
3.3.1 时间戳对齐步骤
- 统一时区: 将全部时间戳转换为交易所本地时区(如 Asia/Shanghai),避免夏令时问题。
- 精度归一化: 将毫秒/微秒时间戳统一为纳秒级(Python 的
pd.Timestamp默认支持)。 - 对齐到标准网格: 对于需要等间隔采样的策略,将 Tick 对齐到固定的 100ms/500ms 网格,使用前向填充。
3.3.2 去重策略
重复 Tick 通常由数据重传或多路订阅导致。去重规则:
| 去重维度 | 判断依据 | 保留策略 |
|---|---|---|
| 精确重复 | 时间戳 + 价格 + 成交量完全相同 | 仅保留第一条 |
| 时间戳重复但价格不同 | 同一毫秒内出现多笔不同价格 | 保留最后一条(最新撮合结果) |
| 跨日重复 | 收盘后数据与次日开盘数据混淆 | 按交易日分组后去重 |
def align_and_deduplicate(df, time_col='time', price_col='price', volume_col='volume'):
"""
时间戳对齐与去重
"""
# 1. 确保时间戳为 datetime 类型
df[time_col] = pd.to_datetime(df[time_col], utc=True)
# 2. 按时间戳排序
df = df.sort_values(time_col).reset_index(drop=True)
# 3. 精确去重
df = df.drop_duplicates(subset=[time_col, price_col, volume_col], keep='first')
# 4. 时间戳重复但价格不同:保留最后一条
df = df.drop_duplicates(subset=[time_col], keep='last')
return df
3.4 清洗效果评估指标
清洗完成后,建议使用以下指标量化清洗效果:
- 缺失率: 清洗前后缺失值占比变化,目标降至 < 0.1%。
- 异常点占比: 被标记为跳跃或涨跌停的 Tick 比例,通常 < 0.5%。
- 重复率: 去重前后数据量减少比例,正常应在 0.1% ~ 2% 之间。
- 时间戳连续性: 相邻 Tick 时间间隔的 MAD,越小说明对齐越好。
核心原则: 清洗不是“删除越多越好”,而是保留真实市场微观结构,同时剔除数据采集环节引入的噪声。每次清洗操作都应记录日志,便于回溯。