3. 数据准备与清洗:数据源选择、质量检查与异常处理
异常波动复盘的第一步,也是最容易被低估的一步,是数据准备。原始市场数据通常包含大量噪声、缺失和错误,直接用于回测或归因分析会导致结论完全失真。本节将系统讲解从数据源选择到清洗完成的完整流程,确保后续风险暴露计算建立在可靠的数据基础上。
3.1 数据源选择:Tick级、分钟级与日线
不同分析场景对数据粒度的要求差异巨大。下表对比了三种常见数据源的核心特征与适用场景:
| 数据粒度 | 典型频率 | 数据量(单品种/日) | 主要用途 | 常见问题 |
|---|---|---|---|---|
| Tick级 | 逐笔成交/逐笔报价 | 数万~数十万条 | 高频策略回测、微观结构分析、闪崩归因 | 数据量大、传输延迟、撮合异常 |
| 分钟级 | 1分钟 / 5分钟 / 15分钟 | 240~1440条 | 日内策略开发、波动率建模、事件驱动分析 | 聚合偏差、时间戳对齐、复权处理 |
| 日线 | 每日一根K线 | 1条 | 中长线策略、风险因子暴露、组合归因 | 除权除息、停牌、涨跌停限制 |
选型建议:异常波动复盘通常需要分钟级数据作为底线。Tick级数据用于分析秒级闪崩或流动性枯竭;日线数据则用于评估波动对组合净值的长期影响。建议同时准备至少两个粒度的数据,以便交叉验证。
3.2 数据质量检查:六维评估框架
在开始清洗之前,必须对原始数据进行系统性质量评估。以下六个维度是检查的核心:
- 完整性:检查时间序列是否连续,是否存在缺失时间段。
- 一致性:不同数据源之间、不同品种之间的时间戳和价格是否对齐。
- 准确性:价格、成交量是否在合理范围内,是否存在明显录入错误。
- 时效性:数据是否包含最新的除权除息、拆并股等公司行为调整。
- 唯一性:是否存在重复记录,同一时间戳是否出现多条相同数据。
- 规范性:字段格式、单位、编码是否统一。
实战检查清单:对分钟级数据,建议逐日统计记录条数是否等于理论值(如A股240分钟/日)。若某日条数偏离超过±5%,标记为可疑区间,优先排查。
3.3 缺失值处理:策略与边界条件
缺失值在金融时间序列中极为常见,原因包括停牌、数据源中断、节假日差异等。处理策略需根据缺失比例和业务场景选择:
| 缺失比例 | 推荐策略 | 适用场景 | 注意事项 |
|---|---|---|---|
| < 1% | 前向填充(ffill) | 连续交易品种,短时缺失 | 需确认缺失期间无重大事件 |
| 1%~5% | 线性插值 | 流动性较好的股票、期货 | 避免在涨跌停附近使用 |
| 5%~20% | 模型插补(如KNN、ARIMA) | 低频数据、因子计算 | 需验证插补值不引入偏差 |
| > 20% | 剔除该时间段或品种 | 停牌期、数据源故障期 | 需在报告中明确说明剔除原因 |
关键警告:在异常波动复盘场景中,缺失值往往不是随机出现的。例如,闪崩期间交易所撮合引擎可能短暂中断,导致Tick数据大面积缺失。此时使用前向填充会严重低估波动幅度,必须优先从备用数据源补全。
3.4 异常值识别:统计方法与业务规则
异常值识别需要结合统计方法和业务规则,两者缺一不可。以下是常用的识别流程:
- 统计方法:
- Z-Score法:|Z| > 3 视为异常,适用于正态分布数据。
- IQR法:超出 Q1-1.5×IQR 或 Q3+1.5×IQR 视为异常,对偏态分布更鲁棒。
- 移动平均偏离法:价格偏离N期移动平均超过M个标准差时标记。
- 业务规则:
- 价格不能为负,不能超过当日涨跌停板限制。
- 成交量不能为负,单笔成交量不能超过流通盘的一定比例。
- 买卖价差不能为负,且不能超过合理阈值(如A股通常不超过1%)。
以下SVG图展示了异常值识别的完整决策流程:
3.5 清洗流程代码示例
以下Python代码展示了分钟级数据的清洗流程,包含缺失值处理和异常值识别:
import pandas as pd
import numpy as np
def clean_minute_data(df):
"""
分钟级数据清洗函数
df: 包含 'datetime', 'open', 'high', 'low', 'close', 'volume' 的DataFrame
"""
# 1. 时间索引排序与去重
df = df.sort_values('datetime').drop_duplicates(subset='datetime')
df.set_index('datetime', inplace=True)
# 2. 生成完整时间序列(假设交易时间为9:30-11:30, 13:00-15:00)
full_index = pd.date_range(
start=df.index.min().replace(hour=9, minute=30),
end=df.index.max().replace(hour=15, minute=0),
freq='1min'
)
# 过滤非交易时段
trading_mask = ((full_index.hour < 12) | (full_index.hour >= 13))
full_index = full_index[trading_mask]
df = df.reindex(full_index)
# 3. 缺失值处理:前向填充(连续缺失不超过3分钟)
df['close'] = df['close'].fillna(method='ffill', limit=3)
df['volume'] = df['volume'].fillna(0)
# 4. 异常值识别:IQR法
Q1 = df['close'].quantile(0.25)
Q3 = df['close'].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
df['price_anomaly'] = (df['close'] < lower_bound) | (df['close'] > upper_bound)
# 5. 业务规则:涨跌停限制(假设±10%)
df['pct_change'] = df['close'].pct_change()
df['limit_anomaly'] = df['pct_change'].abs() > 0.10
# 6. 综合标记
df['is_anomaly'] = df['price_anomaly'] & df['limit_anomaly']
return df
代码要点:上述代码将统计异常(IQR)与业务异常(涨跌停)进行逻辑与运算,只有两者同时触发才标记为异常值。这有效降低了因市场正常波动(如开盘跳空)导致的误报率。
3.6 数据清洗后的质量验证
清洗完成后,必须进行质量验证,确保清洗过程没有引入新的偏差。验证指标包括:
- 统计特征对比:清洗前后的均值、标准差、偏度、峰度变化是否在可接受范围内(通常<5%)。
- 相关性保持:清洗后的数据与参考数据源(如Wind、Bloomberg)的相关性应保持在0.99以上。
- 回测稳定性:使用清洗前后的数据运行同一策略,收益曲线形态应基本一致,仅波动率可能略有下降。
常见陷阱:过度清洗会抹去真实的异常波动信号。例如,闪崩期间的价格剧烈波动在统计上可能是异常值,但正是复盘需要分析的核心事件。建议在清洗流程中保留原始数据副本,并记录所有被标记为异常的时间点,供后续归因分析使用。