第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」这种带时区的。你想想看,如果没处理时区,回测结果能对吗?

标准处理流程:

  1. 统一转为UTC时间
  2. 设置成pandas的DatetimeIndex
  3. 检查时间间隔是否均匀
# 时间戳标准化
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 知识体系总览

下面这张图,是我整理的数据清洗全流程。建议你保存下来,每次做数据预处理时对照着看。

数据清洗与预处理流程 原始数据 缺失值处理(填充/删除/标记) 异常值检测(Z-score/IQR/滚动窗口) 数据对齐与重采样 时间戳标准化(UTC/时区处理) 干净数据 → 模型训练

数据清洗这件事,看起来琐碎,但决定了模型的上限。我见过太多人花90%的时间调参,却只花10%的时间处理数据。结果呢?模型在训练集上跑得漂亮,一到实盘就崩。

记住一句话:垃圾进,垃圾出。数据干净了,模型自然就稳了。


公众号:蓝海数据掘金营,微信deep3321