4、数据获取与清洗:金融数据源选择
做套利交易,数据就是你的眼睛。眼睛瞎了,再好的策略也白搭。
我刚开始做量化那会儿,踩过最大的坑就是数据问题。明明策略回测跑得飞起,一上实盘就亏成狗。后来才发现,是数据源本身就有问题。嗯,这节课咱们就把数据这块彻底聊透。
4.1 金融数据源怎么选?
说白了,数据源就三种:API、数据库、文件。各有各的脾气。
4.1.1 API 接口
实时性最强,适合做高频套利。我个人习惯用 WebSocket 拿 tick 级数据,RESTful 接口用来补历史数据。
常见的 API 有:
- 交易所直连:比如 Binance、OKX 的官方 API,延迟最低
- 数据聚合商:比如 Polygon、Quandl,数据全但贵
- 开源方案:比如 CCXT,封装了上百家交易所,省事
4.1.2 数据库
适合存历史数据,做回测用。我推荐用时序数据库,比如 InfluxDB 或 ClickHouse。普通的关系型数据库存 tick 数据,查询慢得你想哭。
-- 创建 tick 数据表(PostgreSQL + TimescaleDB 示例)
CREATE TABLE tick_data (
time TIMESTAMPTZ NOT NULL,
symbol VARCHAR(20) NOT NULL,
bid DOUBLE PRECISION,
ask DOUBLE PRECISION,
volume DOUBLE PRECISION
);
-- 转为超表,自动按时间分区
SELECT create_hypertable('tick_data', 'time');
4.1.3 文件
最原始,但也最灵活。CSV、Parquet、HDF5 我都用过。Parquet 格式压缩率高,读取快,我现在的历史数据全用这个格式存。
4.2 数据清洗与对齐
数据拿到手,第一件事不是分析,是洗数据。你想想看,交易所的数据经常有跳变、重复、缺失,不洗干净就敢用?
4.2.1 常见的数据脏问题
- 重复数据:同一笔成交被推送两次
- 异常值:价格突然变成 0 或者天文数字
- 时间错乱:时间戳比实际时间早或晚几秒
- 数据缺失:某段时间内完全没有数据
我一般这样处理:
import pandas as pd
import numpy as np
def clean_tick_data(df):
# 1. 去重
df = df.drop_duplicates(subset=['time', 'symbol'])
# 2. 过滤异常价格(比如价格小于0或大于100万)
df = df[(df['bid'] > 0) & (df['bid'] < 1e6)]
df = df[(df['ask'] > 0) & (df['ask'] < 1e6)]
# 3. 检查买卖价差是否合理
df = df[df['ask'] > df['bid']]
# 4. 按时间排序
df = df.sort_values('time')
return df
4.2.2 多数据源对齐
套利交易最头疼的就是对齐。比如你要做 BTC/USDT 的期现套利,现货数据来自交易所 A,期货数据来自交易所 B。两个数据源的时间戳可能差了好几秒。
我的做法是:
- 先把所有数据的时间戳统一成 UTC
- 然后按 1 秒或 100 毫秒的窗口做重采样
- 最后用前向填充或插值法补齐缺失值
4.3 缺失值处理
金融数据缺失太常见了。交易所维护、网络波动、节假日...原因一大堆。
4.3.1 缺失类型
| 缺失类型 | 表现 | 处理方法 |
|---|---|---|
| 随机缺失 | 零星几个 tick 没有 | 前向填充或插值 |
| 连续缺失 | 几分钟甚至几小时没数据 | 标记为停盘,不填充 |
| 开盘缺失 | 每天开盘第一笔数据缺失 | 用前一日收盘价填充 |
# 前向填充示例
df['bid'].fillna(method='ffill', inplace=True)
# 线性插值
df['ask'].interpolate(method='linear', inplace=True)
# 连续缺失超过5个,直接标记为NaN
df['bid'] = df['bid'].mask(df['bid'].isna().rolling(5).sum() >= 5)
4.4 时间戳标准化
不同数据源的时间戳格式五花八门。有的用 Unix 毫秒,有的用 ISO 8601 字符串,有的甚至用本地时间不带时区。不统一的话,对齐就是噩梦。
4.4.1 统一成 UTC 时间
我不管数据源来自哪个时区,一律转成 UTC。这样跨交易所、跨品种对比才不会乱。
import pandas as pd
# 假设原始时间戳是 Unix 毫秒
df['time'] = pd.to_datetime(df['time'], unit='ms')
# 如果是字符串格式
df['time'] = pd.to_datetime(df['time'], utc=True)
# 设置时区
df['time'] = df['time'].dt.tz_convert('UTC')
4.4.2 处理时间戳精度问题
有的交易所给到微秒级,有的只到秒级。做套利时,精度不匹配会导致误判。
我的习惯是:
- 高频套利:统一到毫秒级
- 中低频套利:统一到秒级
- 永远不要用字符串比较时间,用数值或 datetime 对象
4.5 数据质量检查清单
每次拿到新数据,我都会跑一遍这个检查清单:
- 数据量是否合理?(比如一天应该有 86400 秒的秒级数据)
- 时间戳是否连续?有没有跳变?
- 价格是否在合理范围内?
- 买卖价差是否为正?
- 成交量是否异常?
- 不同数据源之间的相关性是否正常?
嗯,数据这块就聊到这儿。记住一句话:数据质量决定了策略的天花板。你策略再牛,数据是垃圾,结果就是垃圾。
下一章咱们聊聊套利策略的核心——价差计算与统计套利模型。