第1章:数据清洗与对齐——缺失值处理、异常值检测与多源时间轴对齐

各位同学,欢迎来到《多源行情数据融合处理实战》的第一章。

说实话,做量化交易这些年,我踩过最大的坑,不是策略逻辑写错了,而是数据本身出了问题。你想想看,你辛辛苦苦写了个回测框架,跑出来收益曲线漂亮得不行,结果一查,原来是某天的行情数据缺失了,程序自动用前一天的收盘价填上了——这能不出问题吗?

所以,数据清洗与对齐,是所有量化工作的地基。地基不稳,楼盖得再高也得塌。

一、缺失值处理:别让“空”坑了你

行情数据里,缺失值太常见了。比如某只股票停牌了,或者交易所网络波动,那一分钟的数据就是空的。我个人习惯,拿到数据第一件事,先检查缺失率。

核心原则:缺失率超过30%的字段,直接扔掉。低于30%的,才考虑填充。

1. 前向填充(Forward Fill)

这是我最常用的方法。说白了,就是用上一个有效值,填掉后面的空位。比如股票停牌一天,复牌后,我们用停牌前的收盘价作为当天的参考价。

import pandas as pd

# 假设df是分钟级行情数据,'close'列有缺失
df['close'].fillna(method='ffill', inplace=True)

我在项目中遇到过一个问题:如果连续缺失太多,前向填充会把很久以前的数据一直往后推,导致价格曲线变成一条直线。嗯,这里要注意——连续缺失超过5个点,我建议改用插值。

2. 插值法(Interpolation)

插值比前向填充更“聪明”一点。它根据缺失点前后的数据,算出一个合理的中间值。线性插值是最简单的,但如果你处理的是高频数据,我个人更推荐使用时间加权插值。

# 线性插值
df['close'].interpolate(method='linear', inplace=True)

# 时间加权插值(适合不均匀时间轴)
df['close'].interpolate(method='time', inplace=True)

小技巧:如果你处理的是Tick级数据,时间加权插值比线性插值更贴近真实市场行为。我曾经用线性插值处理过一笔期货数据,结果回测时发现滑点被严重低估了。

二、异常值检测:揪出那些“离谱”的数据

行情数据里,偶尔会出现一些离谱的值。比如某只股票突然涨了1000%,或者价格变成负数。这些异常值如果不处理,你的模型会学出一些奇怪的东西。

1. Z-Score方法

Z-Score的原理很简单:计算每个数据点距离均值有多少个标准差。如果超过3个标准差,就认为是异常值。我个人习惯用3.5作为阈值,因为金融数据本身波动就大,3倍标准差太容易误杀了。

import numpy as np

def detect_outliers_zscore(data, threshold=3.5):
    z_scores = np.abs((data - data.mean()) / data.std())
    return z_scores > threshold

# 使用
outliers = detect_outliers_zscore(df['close'])
df.loc[outliers, 'close'] = np.nan  # 先置空,再填充

注意:Z-Score对数据分布有要求。如果数据本身不是正态分布(比如成交量数据),Z-Score的效果会大打折扣。我曾经在成交量数据上用过Z-Score,结果把正常的放量日全标记成了异常——那叫一个惨。

2. 箱线图方法(IQR)

箱线图不依赖正态分布假设,它用四分位数来定义异常。具体来说,低于Q1-1.5*IQR或高于Q3+1.5*IQR的点,就是异常值。

def detect_outliers_iqr(data):
    Q1 = data.quantile(0.25)
    Q3 = data.quantile(0.75)
    IQR = Q3 - Q1
    lower_bound = Q1 - 1.5 * IQR
    upper_bound = Q3 + 1.5 * IQR
    return (data < lower_bound) | (data > upper_bound)

outliers = detect_outliers_iqr(df['close'])

你想想看,箱线图的好处是什么?它不受极端值的影响。因为Q1和Q3本身是稳健统计量。所以,如果你的数据里已经有一些异常值了,用箱线图反而更安全。

三、多源时间轴对齐:让不同数据“说同一种语言”

做多源数据融合,最头疼的问题就是时间轴不一致。比如,A交易所的数据是每秒一个Tick,B交易所的数据是每5秒一个快照,C数据源是分钟级K线。怎么把它们对齐到同一个时间轴上?

答案就是:重采样(Resampling)。

1. 重采样的核心逻辑

重采样说白了,就是把不同频率的数据,统一到同一个时间频率上。比如,把所有数据都变成1分钟K线。

# 假设df的索引是datetime类型
# 将Tick数据重采样为1分钟K线
df_resampled = df.resample('1T').agg({
    'open': 'first',
    'high': 'max',
    'low': 'min',
    'close': 'last',
    'volume': 'sum'
})

关键点:重采样时,一定要先处理缺失值和异常值,再对齐时间轴。顺序搞反了,你会把异常值也“对齐”到新数据里。

2. 多源对齐的实战策略

我个人习惯,先确定一个“主时间轴”。比如,我的策略是1分钟级别,那就把所有数据都重采样到1分钟。然后,用前向填充把缺失的时间点补上。

# 创建统一的时间索引
time_index = pd.date_range(start='2024-01-01', end='2024-01-02', freq='1T')

# 对齐多个数据源
df_source1 = df_source1.reindex(time_index, method='ffill')
df_source2 = df_source2.reindex(time_index, method='ffill')

# 合并
df_merged = pd.concat([df_source1, df_source2], axis=1)

避坑指南:我曾经在合并两个交易所的订单簿数据时,发现时间戳差了2毫秒。你以为2毫秒没什么?但在高频交易里,这2毫秒足以让你做出完全相反的买卖决策。所以,对齐之前,一定要先检查时间戳的精度是否一致。

四、知识体系总览

下面这张图,是我自己总结的数据清洗与对齐的完整流程。你可以把它当作一个检查清单,每次处理数据时,按这个顺序走一遍,基本不会出大问题。

多源行情数据清洗与对齐流程 原始多源数据 缺失值处理 前向填充(ffill) 插值法(interpolate) 删除缺失行(dropna) 异常值检测 Z-Score(3.5σ) 箱线图(IQR) 时间轴对齐(重采样)

这张图里,我把整个流程分成了四个阶段:原始数据 → 缺失值处理 → 异常值检测 → 时间轴对齐。每一步都有对应的技术方案,你可以根据实际数据情况灵活选择。

五、总结

数据清洗与对齐,听起来很基础,但真正做起来,坑特别多。我见过太多人,数据没洗干净就开始建模,结果模型在实盘时一塌糊涂。

记住三个要点:

  • 缺失值:先看缺失率,再选方法。前向填充适合短期缺失,插值适合连续缺失。
  • 异常值:Z-Score适合正态分布数据,箱线图更稳健。两个方法可以结合使用。
  • 时间轴对齐:先清洗,再对齐。确定主时间频率,用重采样统一。

好了,这一章的内容就到这里。下一章我们会深入讲数据标准化与特征工程,到时候见。


蓝海数据掘金营,专注资料整理