3. 数据准备与清洗:数据源选择、质量检查与异常处理

异常波动复盘的第一步,也是最容易被低估的一步,是数据准备。原始市场数据通常包含大量噪声、缺失和错误,直接用于回测或归因分析会导致结论完全失真。本节将系统讲解从数据源选择到清洗完成的完整流程,确保后续风险暴露计算建立在可靠的数据基础上。

3.1 数据源选择:Tick级、分钟级与日线

不同分析场景对数据粒度的要求差异巨大。下表对比了三种常见数据源的核心特征与适用场景:

数据粒度 典型频率 数据量(单品种/日) 主要用途 常见问题
Tick级 逐笔成交/逐笔报价 数万~数十万条 高频策略回测、微观结构分析、闪崩归因 数据量大、传输延迟、撮合异常
分钟级 1分钟 / 5分钟 / 15分钟 240~1440条 日内策略开发、波动率建模、事件驱动分析 聚合偏差、时间戳对齐、复权处理
日线 每日一根K线 1条 中长线策略、风险因子暴露、组合归因 除权除息、停牌、涨跌停限制
选型建议:异常波动复盘通常需要分钟级数据作为底线。Tick级数据用于分析秒级闪崩或流动性枯竭;日线数据则用于评估波动对组合净值的长期影响。建议同时准备至少两个粒度的数据,以便交叉验证。

3.2 数据质量检查:六维评估框架

在开始清洗之前,必须对原始数据进行系统性质量评估。以下六个维度是检查的核心:

  1. 完整性:检查时间序列是否连续,是否存在缺失时间段。
  2. 一致性:不同数据源之间、不同品种之间的时间戳和价格是否对齐。
  3. 准确性:价格、成交量是否在合理范围内,是否存在明显录入错误。
  4. 时效性:数据是否包含最新的除权除息、拆并股等公司行为调整。
  5. 唯一性:是否存在重复记录,同一时间戳是否出现多条相同数据。
  6. 规范性:字段格式、单位、编码是否统一。
实战检查清单:对分钟级数据,建议逐日统计记录条数是否等于理论值(如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图展示了异常值识别的完整决策流程:

异常值识别决策流程 原始数据输入 统计方法检测 业务规则检测 Z-Score > 3 IQR 1.5倍法则 涨跌停限制 价差非负 综合判断:是否同时被标记? 是 → 确认为异常值 否 → 人工复核 标记 → 修正或剔除 人工确认 → 更新规则库 说明:统计方法与业务规则需同时通过才确认为异常值,否则进入人工复核流程

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以上。
  • 回测稳定性:使用清洗前后的数据运行同一策略,收益曲线形态应基本一致,仅波动率可能略有下降。
常见陷阱:过度清洗会抹去真实的异常波动信号。例如,闪崩期间的价格剧烈波动在统计上可能是异常值,但正是复盘需要分析的核心事件。建议在清洗流程中保留原始数据副本,并记录所有被标记为异常的时间点,供后续归因分析使用。