数据模块构建:历史数据获取、数据清洗与对齐、Tick与K线数据、数据存储方案
做市商策略的回测,说白了就是一场「数据游戏」。
我见过太多人,策略逻辑写得天花乱坠,结果一跑回测,数据源就有问题。嗯,那基本等于白干。今天这一章,咱们就把数据模块的底裤扒干净。
1. 历史数据获取:从哪里来,怎么拿?
做市商策略对数据的要求,比普通趋势策略高得多。你想想看,我们赚的是买卖价差和返佣,每一笔订单的成交细节都至关重要。
数据源的选择,我个人习惯分三类:
- 交易所官方API:最可靠,但有限流。适合小规模回测。
- 第三方数据服务商:比如彭博、路透、或者一些加密货币数据平台。数据干净,但贵。
- 自建数据节点:自己搭节点,全量同步链上数据。最费劲,但最灵活。
获取数据时,有几个坑你得避开:
- 时间戳问题:交易所返回的时间戳,是毫秒还是微秒?是UTC还是本地时间?我曾经因为没注意这个,回测结果差了十万八千里。
- 数据完整性:网络波动可能导致数据缺失。我习惯在获取时加个重试机制,最多重试3次。
- 限流策略:别一股脑儿全请求,会被封IP。加个sleep,或者用异步请求。
# 一个简单的数据获取示例(伪代码)
def fetch_trades(symbol, start_time, end_time):
trades = []
cursor = start_time
while cursor < end_time:
try:
resp = api.get_trades(symbol, since=cursor, limit=1000)
trades.extend(resp['data'])
cursor = resp['next_cursor']
time.sleep(0.1) # 限流
except Exception as e:
print(f"获取失败,重试中... {e}")
time.sleep(1)
return trades
2. 数据清洗与对齐:脏数据是回测的毒药
数据拿到手,别急着用。先洗一洗。
常见的数据脏问题:
- 重复数据:同一个tick出现了两次。直接去重,保留第一条。
- 异常值:价格突然跳了100倍,明显是数据错误。我一般用3倍标准差法剔除。
- 缺失值:某几秒没有数据。做市商策略对连续性要求高,缺失会导致订单簿重建失败。我习惯用前值填充。
数据对齐,这是个技术活。
不同交易所的数据,时间戳可能不同步。比如A交易所用毫秒,B交易所用微秒。对齐时,我习惯统一转成毫秒时间戳,然后按时间排序。
还有一个更隐蔽的问题:订单簿的快照和增量数据对齐。快照是某一时刻的完整订单簿,增量是后续的变动。对齐时,得先应用快照,再按时间顺序应用增量。顺序错了,订单簿就乱了。
# 数据对齐示例
def align_orderbook(snapshot, deltas):
# 先应用快照
ob = snapshot.copy()
# 按时间排序增量
deltas_sorted = sorted(deltas, key=lambda x: x['timestamp'])
for delta in deltas_sorted:
# 应用增量更新
ob.update(delta)
return ob
3. Tick与K线数据:两种视角,两种用途
Tick数据,就是每一笔成交的原始记录。谁买了,谁卖了,价格多少,数量多少。
做市商策略回测,必须用Tick数据。为什么?因为K线数据是聚合后的,丢失了微观结构信息。你想想看,如果只用1分钟K线,你根本不知道这一分钟里价格是怎么波动的,是平滑上升还是剧烈震荡。这对做市商策略来说,信息量完全不够。
K线数据,适合做宏观分析,比如判断市场趋势、波动率等。但在回测中,K线只能作为辅助。
| 数据类型 | 粒度 | 用途 | 数据量 |
|---|---|---|---|
| Tick数据 | 每笔成交 | 做市商策略回测 | 极大 |
| K线数据 | 1分钟/5分钟等 | 趋势分析、波动率计算 | 中等 |
我个人习惯,在回测时同时加载两种数据。Tick数据用于模拟订单簿和成交,K线数据用于计算一些辅助指标,比如短期波动率、买卖压力等。
4. 数据存储方案:别让数据成为瓶颈
数据量大了,存储就成了问题。一天几千万条Tick数据,普通数据库根本扛不住。
我推荐几种方案:
- Parquet格式:列式存储,压缩率高,读取快。适合做离线回测。
- InfluxDB:时序数据库,专门处理时间序列数据。查询效率高,但部署稍复杂。
- HDF5:老牌科学计算格式,支持多维数据。适合存储订单簿快照。
我自己的项目里,用的是Parquet + 内存映射。回测时,把数据映射到内存里,读取速度接近内存访问,比数据库快一个数量级。
# 使用Parquet存储Tick数据
import pandas as pd
# 假设trades是一个DataFrame
trades.to_parquet('trades_2024.parquet', compression='snappy')
# 读取时
trades = pd.read_parquet('trades_2024.parquet')
还有一个容易被忽略的点:数据版本管理。数据源可能会更新,比如交易所修正了历史数据。我习惯在文件名里加上数据获取日期,比如 trades_2024_20250101.parquet,这样能追溯数据来源。
知识体系总览
下面这张图,是我对数据模块构建的整体理解。你可以把它当作一个检查清单,看看自己有没有遗漏。
数据模块构建,说白了就是四个步骤:获取、清洗、理解、存储。每一步都有坑,但踩过了,后面就顺了。
我个人习惯,在开始写策略代码之前,先花一周时间把数据模块搭好。磨刀不误砍柴工,这话一点不假。
公众号:蓝海资料掘金营,微信deep3321