2、数据源与采集:主流Tick数据提供商对比(交易所、第三方)、数据采集架构设计、实时采集与历史数据回放

做Tick级回测,第一步就是搞定数据。没有高质量的数据,后面所有策略都是空中楼阁。我这些年踩过的坑,有一半都跟数据源有关。今天咱们就把这块掰开揉碎了讲清楚。

2.1 主流Tick数据提供商对比

先说说数据从哪来。市面上主要有两类:交易所直连和第三方聚合。我个人的经验是,没有绝对的好坏,关键看你的场景。

提供商 数据类型 延迟 成本 适用场景
交易所直连(如CTP、XTP) 原始Tick 微秒级 高(需席位费) 高频交易、做市
第三方(如Wind、聚宽、Tushare) 清洗后Tick 秒级 中低 策略回测、中低频
开源数据(如Quandl、Binance API) 部分Tick 分钟级 免费 学习、原型验证

核心观点:如果你做的是高频策略,别犹豫,直接上交易所直连。如果是普通回测,第三方数据完全够用。我曾经为了省成本用免费数据做回测,结果策略在实盘上完全失效——数据精度差太多了。

2.2 数据采集架构设计

数据采集架构,说白了就是怎么把数据从源头搬到你的存储里。我建议分三层设计:采集层、缓冲层、存储层。

采集层 交易所API 第三方API WebSocket 文件导入 缓冲层 消息队列(Kafka) 内存缓存(Redis) 数据校验与清洗 存储层 时序数据库(InfluxDB) 列式存储(Parquet) 关系数据库(PostgreSQL) 文件系统

我的建议:缓冲层千万别省。我早期做采集时直接往数据库写,结果网络抖动导致数据丢失,回测结果全偏了。加上Kafka之后,数据可靠性提升了一个量级。

2.3 实时采集与历史数据回放

实时采集和历史回放,其实是同一套架构的两个模式。你想想看,回测本质上就是「把历史数据当成实时数据来喂」。我习惯用同一个接口处理这两种场景。

实时采集核心逻辑

实时采集的关键是低延迟和可靠性。我一般用WebSocket连接交易所,然后异步写入缓冲层。下面是一个简化版的采集代码:

import asyncio
import websockets
import json
from kafka import KafkaProducer

class TickCollector:
    def __init__(self, symbol, exchange_url):
        self.symbol = symbol
        self.url = exchange_url
        self.producer = KafkaProducer(
            bootstrap_servers=['localhost:9092'],
            value_serializer=lambda v: json.dumps(v).encode('utf-8')
        )
    
    async def collect(self):
        async with websockets.connect(self.url) as ws:
            while True:
                data = await ws.recv()
                tick = json.loads(data)
                # 数据校验
                if self.validate(tick):
                    self.producer.send('ticks', tick)
    
    def validate(self, tick):
        # 检查价格、数量是否合理
        if tick['price'] <= 0 or tick['volume'] <= 0:
            return False
        return True

注意:实时采集最怕的就是断连。我曾经在生产环境遇到过交易所WebSocket断开后自动重连,但重连期间的数据全丢了。一定要实现断点续传机制。

历史数据回放设计

历史回放的核心是「时间控制」。你不能一股脑把数据全塞进去,得按真实的时间间隔来。我常用的做法是:

  1. 数据加载:从存储层按时间顺序读取Tick数据
  2. 时间戳对齐:用系统时间或模拟时间控制回放速度
  3. 事件驱动:每个Tick触发一次策略计算
import time
from datetime import datetime

class TickReplayer:
    def __init__(self, data_path, speed=1.0):
        self.data = self.load_data(data_path)
        self.speed = speed  # 回放速度倍率
    
    def replay(self):
        prev_time = None
        for tick in self.data:
            current_time = datetime.fromtimestamp(tick['timestamp'])
            if prev_time:
                # 计算真实时间间隔
                interval = (current_time - prev_time).total_seconds()
                # 按速度倍率等待
                time.sleep(interval / self.speed)
            # 触发策略
            self.on_tick(tick)
            prev_time = current_time
    
    def on_tick(self, tick):
        # 策略逻辑
        pass

关键点:回放速度倍率很重要。调试时用10倍速快速验证,正式回测用1倍速保证精度。我一般会加一个「暂停/继续」功能,方便排查问题。

2.4 数据质量保障

数据质量是回测的生命线。我总结了几条必须做的检查:

  • 缺失值检测:检查时间戳是否连续,有没有跳秒
  • 异常值过滤:价格突变超过阈值要标记
  • 重复数据去重:同一个Tick出现两次要剔除
  • 时间戳对齐:不同数据源的时间戳要统一到同一时区

避坑指南:我曾经遇到过第三方数据的时间戳是本地时间,而交易所用的是UTC。回测时没注意,结果策略在开盘前就触发了交易。从那以后,我所有数据都强制转成UTC+0存储。

嗯,数据源和采集这块就讲这么多。记住一句话:数据质量决定回测可信度。下一节咱们聊聊数据存储的具体方案,包括怎么用Parquet和InfluxDB高效存储Tick数据。

专注资料整理