第三章:数据源接入——交易所API对接(REST与WebSocket),数据清洗与对齐

做跨市场套利,第一步不是写策略,而是搞定数据。

我见过太多团队,策略逻辑写得天花乱坠,结果一上线就亏钱。为什么?数据源没接好。要么延迟高,要么数据对不齐,要么清洗环节漏了脏数据。说白了,数据是套利交易的「米」,没有米,再好的厨子也做不出饭。

这一章,我们就来聊聊怎么把交易所的数据「搬」到自己的系统里,并且搬得干净、搬得及时。

3.1 REST API:稳,但慢

REST API 就像你去银行柜台办业务。你提交请求,等柜员处理,然后拿到结果。这个过程是同步的,你发一个请求,等一个响应。

在量化交易里,REST 通常用来做两件事:

  • 获取历史数据:比如拉取过去1000根K线,用来回测或初始化策略状态。
  • 执行交易指令:下单、撤单、查询持仓。

我个人习惯,把 REST 当作「慢通道」。它不追求极致的速度,但要求绝对的可靠。

核心要点:REST 请求一定要做「重试机制」和「限速控制」。交易所都有频率限制,你打得太快会被封IP。

来看一段伪代码,展示如何用 REST 拉取币安的最新价格:

import requests
import time

def fetch_price(symbol):
    url = f"https://api.binance.com/api/v3/ticker/price?symbol={symbol}"
    for retry in range(3):
        try:
            resp = requests.get(url, timeout=2)
            if resp.status_code == 200:
                return float(resp.json()['price'])
            else:
                time.sleep(0.5 * (retry + 1))
        except Exception as e:
            print(f"请求失败,重试第{retry+1}次: {e}")
    return None

嗯,这里要注意。重试间隔不要固定,最好用指数退避。我第一次写的时候没注意,结果交易所一波动,我的重试请求直接把API打挂了。后来被交易所发邮件警告,才老老实实加了退避逻辑。

3.2 WebSocket:快,但容易断

WebSocket 就不一样了。它像你打开水龙头,水一直流。你不需要每次去拧开关,只要接住流过来的数据就行。

对于套利交易,WebSocket 是主力。因为我们需要实时行情,毫秒级的延迟差异可能就是盈亏的分水岭。

WebSocket 有几个坑,我一个个说:

  • 断线重连:网络波动、交易所维护、连接超时,都会导致WebSocket断开。你必须实现自动重连逻辑。
  • 心跳维持:很多交易所要求客户端定期发送ping帧,否则会主动断开连接。
  • 数据积压:如果行情太快,你的处理速度跟不上,数据会在缓冲区堆积。这时候要么丢数据,要么内存爆掉。

避坑指南:我曾经在对接某二线交易所时,它的WebSocket每隔45分钟就会自动断开一次,而且不发送任何断开通知。我的程序傻傻地等着,直到半小时后才发现数据停了。从那以后,我所有WebSocket连接都加了一个「静默超时检测」——如果超过60秒没有收到任何数据,就主动重连。

下面是一个带自动重连的WebSocket示例:

import websocket
import json
import threading

class MarketDataStream:
    def __init__(self, url, callback):
        self.url = url
        self.callback = callback
        self.ws = None
        self.keep_running = True

    def on_message(self, ws, message):
        data = json.loads(message)
        self.callback(data)

    def on_error(self, ws, error):
        print(f"WebSocket错误: {error}")

    def on_close(self, ws, close_status_code, close_msg):
        print(f"连接关闭,状态码: {close_status_code}")
        if self.keep_running:
            self.reconnect()

    def on_open(self, ws):
        print("连接已建立")
        # 订阅行情
        subscribe_msg = {
            "method": "SUBSCRIBE",
            "params": ["btcusdt@ticker"],
            "id": 1
        }
        ws.send(json.dumps(subscribe_msg))

    def reconnect(self):
        print("尝试重连...")
        threading.Timer(3.0, self.connect).start()

    def connect(self):
        self.ws = websocket.WebSocketApp(
            self.url,
            on_message=self.on_message,
            on_error=self.on_error,
            on_close=self.on_close,
            on_open=self.on_open
        )
        self.ws.run_forever(ping_interval=30, ping_timeout=10)

    def stop(self):
        self.keep_running = False
        if self.ws:
            self.ws.close()

3.3 数据清洗:脏数据是策略的毒药

数据到了,但别急着用。交易所返回的数据,远比你想象的脏。

我遇到过的情况包括:

  • 价格字段出现负数(交易所内部测试数据误推送)
  • 时间戳是字符串格式,而且不同交易所格式还不一样
  • 成交量突然变成0,但价格还在跳动(明显是数据缺失)
  • 同一笔订单的成交记录重复推送了两次

清洗规则,我总结了三步:

  1. 格式校验:检查字段类型是否正确。价格必须是正数,时间戳必须是整数或标准时间格式。
  2. 逻辑校验:检查数据是否合理。比如买一价必须小于卖一价,成交量不能为负。
  3. 去重:根据交易ID或时间戳+价格+数量的组合,去除重复数据。

小技巧:清洗后的数据,我习惯打一个「清洗标记」字段。这样在后续分析时,可以快速区分原始数据和清洗后的数据,方便排查问题。

3.4 数据对齐:套利的核心难点

数据清洗完了,接下来是最关键的一步——对齐。

为什么需要对齐?因为不同交易所的行情到达时间不一样。假设币安在T1时刻推送了BTC价格,OKX在T2时刻推送了同一时刻的价格。如果你直接用这两个价格计算价差,结果可能是错的——因为这两个价格对应的「市场时刻」不同。

对齐的方法,我常用两种:

对齐方式 原理 适用场景 缺点
时间戳对齐 以交易所返回的时间戳为准,将同一秒内的数据归为一组 低频套利(秒级) 交易所时间戳可能不同步
事件驱动对齐 以本地接收到数据的时间为准,设定一个时间窗口(如100ms) 高频套利(毫秒级) 网络延迟会影响对齐精度

我个人更倾向于「事件驱动对齐」。因为交易所的时间戳,说实话,我不太信得过。有些交易所的服务器时间会漂移,你拿它的时间戳对齐,反而对不准。

具体做法是这样的:

  • 本地维护一个「行情缓存」,按交易所+交易对分类
  • 每收到一条新数据,打上本地接收时间戳
  • 每隔一个固定时间窗口(比如50ms),扫描所有缓存,取出最新的一条数据
  • 用这些「对齐后」的数据计算价差

注意:时间窗口不能设得太小,否则可能等不到所有交易所的数据;也不能设得太大,否则套利机会就溜走了。我一般根据交易所的网络延迟来动态调整,通常在50ms到200ms之间。

3.5 知识体系总览

说了这么多,我们来画一张图,把整个数据接入的流程串起来。

数据源接入全链路流程图 数据源层 交易所A REST API 交易所A WebSocket 交易所B REST API 交易所B WS 数据接入层 REST请求管理(限速+重试) WebSocket连接池(心跳+重连) 数据接收缓冲队列 数据清洗层 格式校验 逻辑校验 去重处理 打标记 数据对齐层 时间窗口对齐(50-200ms) 多交易所行情缓存管理

这张图把整个流程分成了四层:数据源层、接入层、清洗层、对齐层。每一层都有自己需要解决的问题。你想想看,如果其中任何一层出了问题,后面的策略逻辑再漂亮也没用。

好了,数据源接入这块,核心内容就是这些。从REST到WebSocket,从清洗到对齐,每一步都有坑,但也都有解法。做套利交易,数据是地基,地基稳了,楼才能盖得高。