第4章:数据清洗与预处理
数据清洗,说白了就是给原始数据「洗澡」。
我做了这么多年量化交易,见过太多人模型跑得飞起,结果一查数据全是脏的。嗯,这章咱们就把这些坑一个个填上。
4.1 缺失值处理
加密货币数据有个特点——它不像股票那样规整。交易所宕机、网络延迟、API限流,都会导致数据缺失。
我个人习惯,先看看缺失率有多高:
import pandas as pd
import numpy as np
# 加载数据
df = pd.read_csv('btc_1h.csv', parse_dates=['timestamp'])
df.set_index('timestamp', inplace=True)
# 检查缺失情况
missing_ratio = df.isnull().sum() / len(df)
print(missing_ratio)
如果缺失率低于5%,我一般直接填充。高于20%?那这条特征我可能就不要了。
常用的填充方法:
- 前向填充:用上一个有效值填充。适合价格数据,因为价格不会突变太多。
- 线性插值:用前后两个值的平均值。适合成交量这类连续数据。
- 中位数填充:适合异常值较多的场景。
避坑指南
我曾经在回测时用了前向填充,结果发现缺失的那段行情正好是暴涨。前向填充把旧价格带到了新行情里,回测收益直接翻倍。嗯,后来我学乖了——缺失超过3根K线,直接标记为「不可交易时段」。
# 我的常用策略
df['close'].fillna(method='ffill', inplace=True) # 价格用前向填充
df['volume'].interpolate(method='linear', inplace=True) # 成交量用插值
4.2 异常值检测
加密货币市场什么妖蛾子都有。闪崩、插针、数据录入错误……这些异常值会严重干扰模型训练。
你想想看,一个模型如果见过「比特币瞬间跌到1美元」这种数据,它学出来的东西能靠谱吗?
我常用的检测方法:
| 方法 | 适用场景 | 缺点 |
|---|---|---|
| Z-score | 正态分布的数据 | 对极端值敏感 |
| IQR(四分位距) | 非正态分布 | 可能漏掉部分异常 |
| 滚动窗口标准差 | 时间序列数据 | 窗口大小需要调参 |
我个人最常用的是滚动窗口法。为什么?因为加密货币的波动率本身就在变,用固定阈值不科学。
# 滚动窗口异常检测
window = 20
mean = df['close'].rolling(window).mean()
std = df['close'].rolling(window).std()
# 超过3个标准差视为异常
df['is_anomaly'] = (np.abs(df['close'] - mean) > 3 * std)
注意:别把「插针」直接删掉。有些插针是真实的流动性枯竭,删了反而会丢失市场信息。我一般会标记异常值,让模型自己决定要不要学。
4.3 数据对齐与重采样
做多币种策略时,最头疼的就是数据对齐。
比特币1分钟K线有100万条,以太坊可能只有80万条。直接合并?时间戳对不上,模型会疯掉。
对齐的核心思路:
- 确定一个基准时间频率(比如1小时)
- 把所有币种的数据都重采样到这个频率
- 缺失的时间段用NaN填充,或者用前一个有效值
# 重采样到1小时
df_resampled = df.resample('1H').agg({
'open': 'first',
'high': 'max',
'low': 'min',
'close': 'last',
'volume': 'sum'
})
我记得有一次做跨币种套利,比特币和莱特币的时间戳差了3秒。3秒在别的市场不算什么,但在加密货币市场,足够让套利机会消失。后来我强制把所有时间戳对齐到整秒。
小技巧:对齐时用「左闭右开」原则。比如1:00到2:00的数据,包含1:00但不包含2:00。这样能避免数据重复。
4.4 时间戳处理
加密货币的时间戳格式五花八门。有的用Unix时间戳(秒级),有的用毫秒级,还有的用字符串。
我见过最离谱的——某个交易所返回的时间戳居然是「2024-01-15 08:23:45 +0800」这种带时区的。你想想看,如果没处理时区,回测结果能对吗?
标准处理流程:
- 统一转为UTC时间
- 设置成pandas的DatetimeIndex
- 检查时间间隔是否均匀
# 时间戳标准化
df['timestamp'] = pd.to_datetime(df['timestamp'], unit='s', utc=True)
df.set_index('timestamp', inplace=True)
# 检查时间间隔
time_diff = df.index.to_series().diff().value_counts()
print(time_diff.head())
避坑指南
我曾经从某个API拉数据,它返回的时间戳是「本地时间」。我直接用了,结果回测时发现凌晨的数据全对不上。后来查了半天,原来是夏令时搞的鬼。从那以后,我所有时间戳都强制转成UTC。
4.5 知识体系总览
下面这张图,是我整理的数据清洗全流程。建议你保存下来,每次做数据预处理时对照着看。
数据清洗这件事,看起来琐碎,但决定了模型的上限。我见过太多人花90%的时间调参,却只花10%的时间处理数据。结果呢?模型在训练集上跑得漂亮,一到实盘就崩。
记住一句话:垃圾进,垃圾出。数据干净了,模型自然就稳了。
公众号:蓝海数据掘金营,微信deep3321