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数据,想对比一下价差。如果时间戳不对齐,比出来的结果就是错的。
我常用的对齐方法是:
- 统一时间精度:把所有时间戳都转成毫秒级。微秒级的精度太高,反而容易出问题。
- 按时间排序:确保数据是按时间顺序排列的。乱序的数据,对齐起来很麻烦。
- 填充缺失值:有些时间点没有数据,需要决定怎么处理。
缺失值处理,这个坑我踩过。有一次做回测,数据里有一段是空的,我没注意。结果回测结果特别好,我还以为找到了圣杯。后来才发现,那段空数据导致策略没有交易,回测曲线是平的。嗯,从那以后,我每次都会检查数据完整性。
小技巧:处理缺失值时,不要直接用前值填充。做市策略对价格变化很敏感,用前值填充会引入偏差。我建议用插值法,或者干脆把缺失时间段标记出来,回测时跳过。
下面是一个数据对齐和缺失值处理的代码示例:
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 本章知识体系
说了这么多,我画了一张图,帮你把本章的知识点串起来。这样你心里有个谱,知道每一步该做什么。
这张图把整个流程串起来了。从数据获取开始,到对齐、处理缺失值,最后存起来。每一步都有坑,但只要你按这个流程走,基本不会出大问题。
我记得刚开始做做市策略的时候,觉得数据这步最无聊。后来吃了亏才明白,数据才是整个回测的根基。地基没打好,上面盖的房子再漂亮也没用。
一句话总结:数据获取与清洗,花再多时间都值得。你在这步省的时间,后面会加倍还回来。