数据源接入:实时行情API(WebSocket vs REST)

做量化交易,第一关就是数据。没有数据,策略就是空中楼阁。

我个人习惯把数据接入比作「做饭的食材采购」。你想想看,食材不新鲜、买错了、或者买回来没处理好,再好的厨艺也白搭。今天我们就聊聊怎么搞定实时行情数据。

WebSocket vs REST:两个流派

先说结论:实盘交易,WebSocket 是标配,REST 是备胎

REST 接口,说白了就是「你问一句,它答一句」。你发个 HTTP 请求过去,服务器给你返回当前价格。简单吧?但问题来了——你要多快的数据?

我早期做的一个项目,用的就是 REST 轮询。每秒请求一次,看起来还行。但有一次行情剧烈波动,我眼睁睁看着价格从 100 跳到 105,中间那几秒的报价全丢了。嗯,那次回撤让我记忆犹新。

核心区别:
  • REST:请求-响应模式,适合历史数据查询、低频操作
  • WebSocket:全双工通信,服务器主动推送,适合实时行情

WebSocket 建立连接后,服务器会持续推送数据给你。你不需要反复问「现在多少钱」,服务器会主动告诉你「价格变了」。延迟通常在毫秒级,比 REST 的几百毫秒快了一个数量级。

为什么会这样?因为 WebSocket 省去了 HTTP 的握手开销。每次 REST 请求都要建立 TCP 连接、发送 HTTP 头、等待响应。而 WebSocket 一次握手,长期保持连接。

我的建议:
  • 回测、分析用 REST 拉历史数据
  • 实盘监控、交易执行用 WebSocket
  • 如果交易所只提供 REST,那就用多线程并发轮询,但要做好限流

数据清洗与对齐:脏活累活

拿到原始数据,别急着用。我见过太多人直接拿交易所的原始 tick 数据跑策略,结果回测漂亮,实盘一塌糊涂。

为什么?因为原始数据有「脏」的地方。

常见的坑:

  • 重复数据:网络重传导致同一笔成交收到两次
  • 乱序数据:不同交易所的行情到达顺序不一致
  • 缺失数据:网络抖动丢包,某个 tick 没收到
  • 异常值:价格突然跳到一个离谱的数字(比如 0.01 或 999999)

我曾经在实盘中遇到过一个问题:某次行情剧烈波动,WebSocket 推送的数据包顺序乱了。我的策略收到一个「最新价 100.5」,然后收到「最新价 100.2」,结果策略以为价格在下跌,触发了止损。实际上价格是在上涨,只是数据包到达顺序反了。

那次之后,我加了一个数据对齐模块。核心逻辑很简单:

# 伪代码:数据对齐逻辑
class DataAligner:
    def __init__(self):
        self.buffer = []  # 缓存未对齐的数据
        self.last_seq = 0  # 上一笔的序列号
    
    def process(self, tick):
        # 检查序列号是否连续
        if tick.seq != self.last_seq + 1:
            # 有缺失或乱序,先缓存
            self.buffer.append(tick)
            return None
        
        # 正常数据,直接返回
        self.last_seq = tick.seq
        return tick
    
    def flush(self):
        # 按序列号排序后输出
        self.buffer.sort(key=lambda x: x.seq)
        for tick in self.buffer:
            yield tick
        self.buffer.clear()
注意:
  • 不同交易所的序列号机制不同,有的用时间戳,有的用递增整数
  • 对齐时要注意时间戳精度,毫秒级和微秒级要统一
  • 如果数据延迟超过阈值(比如 5 秒),建议直接丢弃,避免影响实时性

Tick 级与 K 线级数据存储

数据存哪里?存什么格式?存多久?这些问题在系统设计阶段就要想清楚。

先看一个对比表格:

维度 Tick 级数据 K 线级数据
数据量 极大(每天几百万条) 较小(每天几千条)
存储成本
查询速度 慢(需要索引优化)
用途 高频策略、回测、分析 中低频策略、监控、展示
保留周期 短期(1-3个月) 长期(数年)

我个人习惯的做法是:

  • Tick 数据:用时序数据库(比如 InfluxDB、ClickHouse)存储,按天分区
  • K 线数据:用关系型数据库(比如 PostgreSQL)或者直接存 CSV 文件
  • 实时数据:内存缓存(Redis)保留最近 1 小时的 tick,用于策略计算

这里有个经验:不要把所有 tick 数据都存到同一个表里。我早期犯过这个错,一张表里塞了上亿条记录,查询一次要几十秒。后来按日期分表,查询速度提升了 100 倍。

存储建议:
  • Tick 数据:按「交易所_品种_日期」命名表,比如 binance_btcusdt_20240101
  • K 线数据:按「交易所_品种_周期」命名表,比如 binance_btcusdt_1m
  • 定期归档:超过 3 个月的 tick 数据压缩后存到冷存储(比如 S3)

整体架构图

下面这张图展示了数据接入的完整流程,从交易所到策略计算:

实时行情数据接入架构 交易所 A WebSocket / REST 交易所 B WebSocket / REST 交易所 C WebSocket / REST 数据接入层 WebSocket 连接管理 | 数据清洗 | 去重 | 对齐 | 异常检测 多线程/异步处理 | 心跳检测 | 断线重连 数据存储层 时序数据库 (Tick) | 关系型数据库 (K线) | Redis 缓存 按天分区 | 冷热分离 | 定期归档 策略计算层 Tick 级策略 | K 线级策略 | 信号生成 | 风控检查

这张图展示了我常用的三层架构。数据从交易所进来,经过清洗对齐,存入不同层级的存储,最后供策略消费。每一层都有独立的职责,方便后期扩展和维护。

避坑指南:
  • 我曾经因为 WebSocket 断线重连逻辑没写好,导致实盘中断了 10 分钟。后来加了心跳检测和自动重连,才解决。
  • 数据对齐时,不要只依赖时间戳。不同交易所的时钟可能有偏差,最好用交易所自带的序列号。
  • Tick 数据存储要控制写入频率。批量写入比逐条写入效率高 10 倍以上。

好了,数据接入这块就聊这么多。记住一句话:数据质量决定策略上限。把数据搞干净了,后面的工作才能顺利推进。