第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毫秒足以让你做出完全相反的买卖决策。所以,对齐之前,一定要先检查时间戳的精度是否一致。
四、知识体系总览
下面这张图,是我自己总结的数据清洗与对齐的完整流程。你可以把它当作一个检查清单,每次处理数据时,按这个顺序走一遍,基本不会出大问题。
这张图里,我把整个流程分成了四个阶段:原始数据 → 缺失值处理 → 异常值检测 → 时间轴对齐。每一步都有对应的技术方案,你可以根据实际数据情况灵活选择。
五、总结
数据清洗与对齐,听起来很基础,但真正做起来,坑特别多。我见过太多人,数据没洗干净就开始建模,结果模型在实盘时一塌糊涂。
记住三个要点:
- 缺失值:先看缺失率,再选方法。前向填充适合短期缺失,插值适合连续缺失。
- 异常值:Z-Score适合正态分布数据,箱线图更稳健。两个方法可以结合使用。
- 时间轴对齐:先清洗,再对齐。确定主时间频率,用重采样统一。
好了,这一章的内容就到这里。下一章我们会深入讲数据标准化与特征工程,到时候见。