3、数据获取与清洗:获取历史Tick数据、数据对齐与缺失值处理、数据存储

做市策略这东西,说白了就是跟市场做对手盘。你报个买价,再报个卖价,赚中间的差价。但有个前提——你得有数据。没有Tick数据,你连市场长什么样都不知道,还做什么市?

我个人习惯,做回测之前先把数据这关过了。数据不干净,后面所有分析都是白搭。你想想看,拿一堆错位的数据去调参数,调出来的结果你敢信吗?

3.1 获取历史Tick数据

Tick数据是什么?就是每一笔成交的原始记录。包括成交时间、价格、数量、买卖方向这些。做市策略对Tick数据的需求,比普通策略要高得多。

为什么?因为做市策略盯的是订单簿的微观结构。你需要在毫秒级别感知市场的变化。普通日线级别的数据,根本不够用。

获取Tick数据,常见的有几个渠道:

  • 交易所直连:最靠谱,但门槛高。你得有交易所的会员资格,还得自己搭服务器。
  • 数据供应商:像Bloomberg、Reuters这些,数据质量好,但价格不便宜。
  • 开源数据源:比如一些加密货币交易所的API,或者学术机构公开的数据集。免费,但质量参差不齐。

我在项目中遇到过最头疼的事,就是数据源突然断了。有一次做比特币的做市回测,数据跑到一半,API限流了。嗯,从那以后我养成了一个习惯——永远准备一个备用数据源。

重要提醒:获取Tick数据时,一定要注意时间戳的时区问题。很多交易所用的是UTC时间,而你的回测系统可能用的是本地时间。不对齐的话,后面全乱套。

下面是一个简单的Python代码示例,用来从交易所API获取Tick数据:

import requests
import pandas as pd
from datetime import datetime

def fetch_tick_data(symbol, start_time, end_time):
    """
    获取历史Tick数据
    """
    url = f"https://api.exchange.com/v1/tick/{symbol}"
    params = {
        'start': start_time,
        'end': end_time,
        'limit': 1000
    }
    
    response = requests.get(url, params=params)
    data = response.json()
    
    # 转换成DataFrame
    df = pd.DataFrame(data['ticks'])
    df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')
    
    return df

# 使用示例
tick_data = fetch_tick_data('BTCUSDT', '2024-01-01', '2024-01-02')
print(tick_data.head())

3.2 数据对齐与缺失值处理

数据拿回来了,但问题才刚刚开始。不同交易所的数据,时间戳可能不一样。有的用毫秒,有的用微秒。有的有数据,有的没有。这就涉及到数据对齐的问题。

数据对齐,说白了就是把不同来源的数据,放到同一个时间轴上。比如你同时拿了币安和OKX的Tick数据,想对比一下价差。如果时间戳不对齐,比出来的结果就是错的。

我常用的对齐方法是:

  1. 统一时间精度:把所有时间戳都转成毫秒级。微秒级的精度太高,反而容易出问题。
  2. 按时间排序:确保数据是按时间顺序排列的。乱序的数据,对齐起来很麻烦。
  3. 填充缺失值:有些时间点没有数据,需要决定怎么处理。

缺失值处理,这个坑我踩过。有一次做回测,数据里有一段是空的,我没注意。结果回测结果特别好,我还以为找到了圣杯。后来才发现,那段空数据导致策略没有交易,回测曲线是平的。嗯,从那以后,我每次都会检查数据完整性。

小技巧:处理缺失值时,不要直接用前值填充。做市策略对价格变化很敏感,用前值填充会引入偏差。我建议用插值法,或者干脆把缺失时间段标记出来,回测时跳过。

下面是一个数据对齐和缺失值处理的代码示例:

def align_and_fill(tick_data, freq='1s'):
    """
    数据对齐与缺失值处理
    """
    # 设置时间索引
    tick_data.set_index('timestamp', inplace=True)
    
    # 重采样到指定频率
    aligned = tick_data.resample(freq).agg({
        'price': 'last',
        'volume': 'sum'
    })
    
    # 处理缺失值
    # 方法1:前向填充
    # aligned.fillna(method='ffill', inplace=True)
    
    # 方法2:线性插值(推荐)
    aligned.interpolate(method='linear', inplace=True)
    
    # 标记缺失时间段
    aligned['is_filled'] = aligned['price'].isna()
    
    return aligned

# 使用示例
aligned_data = align_and_fill(tick_data, freq='100ms')
print(f"对齐后数据量: {len(aligned_data)}")

3.3 数据存储

数据清洗完了,得存起来。存哪里?怎么存?这也有讲究。

我见过有人把Tick数据直接存CSV文件。小规模还行,数据量一大,读写速度就成问题。做市策略的Tick数据,一天可能就有几百万条。CSV文件打开都费劲,更别说分析了。

我个人推荐用以下几种方式:

存储方式 优点 缺点 适用场景
CSV/Parquet 简单、通用 读写慢、不支持随机访问 小规模数据、一次性分析
SQLite 轻量、支持SQL查询 并发写入性能差 单机回测、个人项目
ClickHouse 列式存储、查询快 部署复杂 大规模数据、生产环境
InfluxDB 时序数据库、自动压缩 学习成本高 实时数据、监控系统

我自己的习惯是,回测阶段用Parquet格式。Parquet是列式存储,压缩率高,读取速度快。而且Pandas直接支持,用起来很方便。

注意:数据存储时,一定要做好版本管理。我建议每次清洗完的数据,都打上时间戳。这样万一发现数据有问题,可以回退到之前的版本。我曾经因为覆盖了原始数据,不得不重新下载,浪费了两天时间。

下面是一个数据存储的代码示例:

def save_tick_data(df, file_path, format='parquet'):
    """
    存储Tick数据
    """
    if format == 'parquet':
        df.to_parquet(file_path, compression='snappy')
    elif format == 'csv':
        df.to_csv(file_path, index=False)
    elif format == 'sqlite':
        import sqlite3
        conn = sqlite3.connect(file_path)
        df.to_sql('tick_data', conn, if_exists='replace', index=False)
        conn.close()
    else:
        raise ValueError(f"不支持的格式: {format}")
    
    print(f"数据已保存到: {file_path}")

# 使用示例
save_tick_data(aligned_data, 'btc_tick_20240101.parquet')

3.4 本章知识体系

说了这么多,我画了一张图,帮你把本章的知识点串起来。这样你心里有个谱,知道每一步该做什么。

数据获取与清洗流程 数据获取 交易所直连 / 供应商 / 开源 数据对齐 统一时间精度 / 排序 缺失值处理 插值 / 标记 数据存储 Parquet / SQLite / ClickHouse 常见问题与避坑 • 时间戳时区不一致 • API限流导致数据中断 • 缺失值用前值填充引入偏差 • 覆盖原始数据无法回退 核心原则 数据质量决定回测可信度 | 宁可少数据,不要坏数据 每次清洗都保留原始数据备份

这张图把整个流程串起来了。从数据获取开始,到对齐、处理缺失值,最后存起来。每一步都有坑,但只要你按这个流程走,基本不会出大问题。

我记得刚开始做做市策略的时候,觉得数据这步最无聊。后来吃了亏才明白,数据才是整个回测的根基。地基没打好,上面盖的房子再漂亮也没用。

一句话总结:数据获取与清洗,花再多时间都值得。你在这步省的时间,后面会加倍还回来。

专注资料整理