4、数据获取与清洗:金融数据源选择

做套利交易,数据就是你的眼睛。眼睛瞎了,再好的策略也白搭。

我刚开始做量化那会儿,踩过最大的坑就是数据问题。明明策略回测跑得飞起,一上实盘就亏成狗。后来才发现,是数据源本身就有问题。嗯,这节课咱们就把数据这块彻底聊透。

4.1 金融数据源怎么选?

说白了,数据源就三种:API、数据库、文件。各有各的脾气。

4.1.1 API 接口

实时性最强,适合做高频套利。我个人习惯用 WebSocket 拿 tick 级数据,RESTful 接口用来补历史数据。

常见的 API 有:

  • 交易所直连:比如 Binance、OKX 的官方 API,延迟最低
  • 数据聚合商:比如 Polygon、Quandl,数据全但贵
  • 开源方案:比如 CCXT,封装了上百家交易所,省事
我的经验:别迷信单一数据源。我在项目中遇到过某交易所 API 突然返回错误数据,导致套利信号全乱。后来我改成同时拉两个数据源做交叉验证,才稳下来。

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 格式压缩率高,读取快,我现在的历史数据全用这个格式存。

注意:千万别用 Excel 存金融数据!我曾经见过有人用 Excel 存了 3 年的分钟线,文件打开就要 10 分钟,还经常崩溃。

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)
避坑指南:我曾经在回测中用了线性插值填充了 30 分钟的缺失数据,结果回测收益高得离谱。后来才发现,那段时间交易所真的停盘了,我硬生生"造"出了交易机会。所以,长时间缺失一定要标记,别瞎填。

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 数据质量检查清单

每次拿到新数据,我都会跑一遍这个检查清单:

  1. 数据量是否合理?(比如一天应该有 86400 秒的秒级数据)
  2. 时间戳是否连续?有没有跳变?
  3. 价格是否在合理范围内?
  4. 买卖价差是否为正?
  5. 成交量是否异常?
  6. 不同数据源之间的相关性是否正常?

嗯,数据这块就聊到这儿。记住一句话:数据质量决定了策略的天花板。你策略再牛,数据是垃圾,结果就是垃圾。

下一章咱们聊聊套利策略的核心——价差计算与统计套利模型。