第二章:数据源接入——交易所API对接、WebSocket实时行情、RESTful历史数据、数据缓存策略

做市系统里,数据源就是你的眼睛和耳朵。没有数据,策略就是瞎猜。我见过不少团队,策略模型写得漂漂亮亮,结果一上线,数据延迟了200毫秒,直接亏穿。所以这一章,咱们把数据接入这件事彻底聊透。

2.1 交易所API对接——先搞定“门”在哪

每个交易所都像一栋楼,API就是它的门。有的门是旋转门(RESTful),有的门是自动滑门(WebSocket)。你得先搞清楚门朝哪开,才能进去拿数据。

我个人习惯,对接一个新交易所时,先看三样东西:

  • API文档的版本号——别用v1接口,很多交易所v1已经废弃了,我踩过这个坑。
  • 频率限制(Rate Limit)——比如每秒最多10次请求,超了直接封IP。
  • 签名方式——HMAC-SHA256还是RSA?这个搞错了,请求永远过不去。

举个例子,对接币安的现货行情API,代码大概长这样:

import requests
import hashlib
import hmac
import time

API_KEY = 'your_api_key'
SECRET_KEY = 'your_secret_key'

def get_klines(symbol='BTCUSDT', interval='1m'):
    url = 'https://api.binance.com/api/v3/klines'
    params = {
        'symbol': symbol,
        'interval': interval,
        'limit': 100
    }
    resp = requests.get(url, params=params)
    return resp.json()

嗯,这里要注意:RESTful接口适合拿历史数据,但别用它做实时行情。为什么?因为每次请求都有网络延迟,你请求一次,行情已经变了三次。

⚠️ 我曾经犯过的错: 用RESTful轮询做实时行情,每200ms请求一次。结果交易所把我IP封了,理由是“异常高频访问”。后来我才知道,人家WebSocket就是干这个活的。

2.2 WebSocket实时行情——真正的“实时”

WebSocket就像一根水管,交易所那边一有数据,就顺着管子流到你这边。你不用去“拉”,它自己“推”过来。

做市系统里,实时行情通常包括:

  • 深度数据(Order Book)——买一卖一、买五卖五,甚至全量深度
  • 成交数据(Trade)——每一笔成交的价格和数量
  • Ticker数据——24小时涨跌幅、成交量等

我个人建议,订阅深度数据时,别订阅全量深度。全量深度每次更新都推送几千条数据,带宽扛不住。我见过一个团队,同时订阅了5个交易所的全量深度,结果服务器带宽被打满,连策略都跑不动了。

正确的做法是订阅增量深度(Incremental Depth),只推送变化的部分。代码示例:

import websocket
import json

def on_message(ws, message):
    data = json.loads(message)
    # 处理增量深度数据
    if data['e'] == 'depthUpdate':
        # 更新本地order book
        update_local_book(data['b'], data['a'])

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

def on_close(ws, close_status_code, close_msg):
    print("WebSocket closed, reconnecting...")
    # 自动重连逻辑
    connect_websocket()

def on_open(ws):
    # 订阅BTCUSDT的增量深度
    sub_msg = {
        "method": "SUBSCRIBE",
        "params": ["btcusdt@depth@100ms"],
        "id": 1
    }
    ws.send(json.dumps(sub_msg))

def connect_websocket():
    ws = websocket.WebSocketApp(
        "wss://stream.binance.com:9443/ws",
        on_open=on_open,
        on_message=on_message,
        on_error=on_error,
        on_close=on_close
    )
    ws.run_forever()
💡 小技巧: WebSocket一定要加心跳检测。交易所一般60秒不发数据就会断开连接。我习惯每30秒发一个ping,确保连接活着。

2.3 RESTful历史数据——补数据用的“时光机”

做回测或者策略初始化时,你需要历史数据。WebSocket只能拿实时数据,拿不到昨天的K线。这时候就得靠RESTful接口。

RESTful历史数据有几个坑:

  • 数据粒度——1分钟K线、5分钟K线、日线,粒度不同,请求参数不同
  • 数据完整性——有些交易所的历史数据有缺失,比如某一天突然少了几根K线
  • 请求限制——一次最多拿1000根K线,想拿一年的数据,得循环请求

我遇到过最头疼的问题:回测时发现某天的数据对不上,查了半天,原来是交易所那天做了系统升级,数据丢了两个小时。所以,我建议从至少两个数据源交叉验证历史数据。

批量拉取历史数据的代码模式:

def fetch_historical_klines(symbol, interval, start_time, end_time):
    all_klines = []
    current_start = start_time
    
    while current_start < end_time:
        url = f'https://api.binance.com/api/v3/klines'
        params = {
            'symbol': symbol,
            'interval': interval,
            'startTime': current_start,
            'limit': 1000
        }
        resp = requests.get(url, params=params)
        data = resp.json()
        
        if not data:
            break
            
        all_klines.extend(data)
        # 更新起始时间为最后一根K线的结束时间
        current_start = data[-1][0] + 1
        
        # 控制请求频率,别被封
        time.sleep(0.1)
    
    return all_klines

2.4 数据缓存策略——别让CPU闲着等网络

数据到了本地,不能直接用完就扔。做市系统里,数据缓存是性能的关键。你想想看,每次策略需要深度数据时都去请求交易所,那延迟得多高?

我常用的缓存策略分三层:

缓存层级 存储介质 用途 过期时间
L1:内存缓存 Python dict / Redis 实时深度、最新成交价 毫秒级
L2:本地文件缓存 SQLite / Parquet 分钟级K线、日内数据 小时级
L3:数据库持久化 PostgreSQL / InfluxDB 历史数据、回测数据 永久

说白了,L1缓存是为了让策略能“零延迟”拿到最新数据。L2缓存是为了防止程序重启后重新拉取大量数据。L3缓存则是为了回测和分析。

我习惯用Redis做L1缓存,因为它快,而且支持发布订阅模式。举个例子:

import redis

r = redis.Redis(host='localhost', port=6379, db=0)

# 存储最新深度数据
def update_order_book(symbol, bids, asks):
    key = f'orderbook:{symbol}'
    data = {
        'bids': bids[:5],  # 只存前5档
        'asks': asks[:5],
        'timestamp': int(time.time() * 1000)
    }
    r.setex(key, 10, json.dumps(data))  # 10秒过期

# 策略读取深度数据
def get_order_book(symbol):
    key = f'orderbook:{symbol}'
    data = r.get(key)
    if data:
        return json.loads(data)
    else:
        # 缓存失效,从WebSocket重新获取
        return fetch_from_websocket(symbol)
🔑 核心原则: 缓存不是存得越多越好。只缓存策略真正需要的数据。我见过有人把全量深度(几千条)都塞进Redis,结果内存爆了,策略反而变慢。记住,做市系统里,延迟就是金钱。

2.5 本章知识体系总览

下面这张图,是我自己梳理的数据源接入整体架构。你看一眼,心里就有数了:

数据源接入架构图 交易所数据源 币安 OKX Bybit 接入方式 WebSocket 实时行情 RESTful 历史数据 API 签名认证 数据缓存策略 L1: 内存缓存 (Redis) L2: 本地文件 (SQLite) L3: 数据库 (PostgreSQL) → 策略层消费数据 ←

这张图把整个流程串起来了:交易所数据通过WebSocket和RESTful两种方式接入,经过API认证后,进入三层缓存体系,最终供策略层使用。说白了,这就是做市系统的数据高速公路。

📌 我的经验总结: 数据接入这块,80%的问题出在“连接不稳定”和“数据对不上”。我的建议是:
1. 所有WebSocket连接都要有自动重连机制
2. 所有数据都要打时间戳,方便排查延迟
3. 缓存一定要设置过期时间,别让脏数据一直留在内存里

公众号:蓝海资料掘金营,微信deep3321