2. 市场数据获取:对接交易所API、WebSocket实时行情、RESTful历史数据

做套利交易,第一步就是搞定数据。没有数据,策略就是空中楼阁。

我个人习惯把数据获取分成三个层次:实时行情、历史数据、账户信息。每个层次都有不同的技术选型。今天咱们就聊聊怎么把这些数据从交易所里「掏」出来。

2.1 交易所API选型:选对接口少踩坑

市面上交易所很多,但API设计思路大同小异。我建议你重点关注三个维度:

  • 文档质量:接口文档写得清不清楚?有没有示例代码?
  • 限频策略:每秒能请求多少次?超过限制会怎样?
  • 数据精度:价格、数量的小数位数是否满足套利需求?

我在项目中遇到过最坑的事:某交易所文档写着「支持WebSocket」,结果连上后每30秒断一次。后来发现是他们负载均衡配置有问题。所以,选接口前一定要做压力测试。

避坑指南:我曾经因为没仔细看限频规则,写了个死循环请求K线数据,结果IP被交易所封了24小时。后来学乖了,所有请求都加上了重试机制和退避策略。

2.2 RESTful API:拉取历史数据

RESTful API适合获取历史K线、订单簿快照这类「一次性」数据。说白了就是:你问一次,它答一次。

以币安为例,获取BTC/USDT的1小时K线:

import requests
import time

def fetch_klines(symbol, interval, limit=500):
    url = "https://api.binance.com/api/v3/klines"
    params = {
        "symbol": symbol,
        "interval": interval,
        "limit": limit
    }
    response = requests.get(url, params=params)
    data = response.json()
    
    # 解析数据:时间戳、开、高、低、收、成交量...
    klines = []
    for item in data:
        kline = {
            "timestamp": item[0],
            "open": float(item[1]),
            "high": float(item[2]),
            "low": float(item[3]),
            "close": float(item[4]),
            "volume": float(item[5])
        }
        klines.append(kline)
    return klines

# 调用示例
klines = fetch_klines("BTCUSDT", "1h", 100)
print(f"获取到 {len(klines)} 条K线数据")

你想想看,这里有个细节:limit参数最大能设多少?币安是1000条。如果你需要更多历史数据,就得用startTimeendTime参数分批拉取。

小技巧:我习惯把历史数据缓存到本地SQLite数据库里。这样下次启动程序时,只需要增量更新,不用重新拉取全部数据。省时又省流量。

2.3 WebSocket:实时行情推送

套利交易对时效性要求极高。RESTful轮询太慢了,WebSocket才是王道。

WebSocket建立连接后,交易所会主动推送数据给你。你只需要「躺平」接收就行。

import websocket
import json

def on_message(ws, message):
    data = json.loads(message)
    # 解析深度数据
    if 'b' in data:  # 买单
        bids = data['b']
        print(f"最新买单:{bids[0]}")
    if 'a' in data:  # 卖单
        asks = data['a']
        print(f"最新卖单:{asks[0]}")

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

def on_close(ws, close_status_code, close_msg):
    print("WebSocket连接关闭")

def on_open(ws):
    # 订阅BTC/USDT的深度数据
    subscribe_msg = {
        "method": "SUBSCRIBE",
        "params": ["btcusdt@depth20@100ms"],
        "id": 1
    }
    ws.send(json.dumps(subscribe_msg))
    print("订阅成功")

# 启动WebSocket
ws_url = "wss://stream.binance.com:9443/ws"
ws = websocket.WebSocketApp(ws_url,
                            on_open=on_open,
                            on_message=on_message,
                            on_error=on_error,
                            on_close=on_close)
ws.run_forever()

嗯,这里要注意:WebSocket断线重连是必须处理的。我见过太多人因为网络抖动导致数据中断,策略还在傻傻地跑。结果呢?套利机会没抓到,反而亏了手续费。

核心要点:WebSocket的心跳机制一定要实现。交易所通常每3-5分钟发一次ping帧,如果你没回复pong,连接就会被断开。我建议你写个定时器,每60秒主动发一次ping,确保连接存活。

2.4 数据清洗与对齐

数据拿到手了,但能用吗?不一定。

不同交易所的数据格式、时间戳、精度都不一样。比如币安的时间戳是毫秒级,而OKX是微秒级。你得把它们统一成标准格式。

交易所 时间戳格式 价格精度 数量精度
币安 毫秒 2位小数 6位小数
OKX 微秒 4位小数 8位小数
Bybit 毫秒 2位小数 4位小数

我建议你写一个统一的数据适配层。不管从哪个交易所拿数据,都转成统一的内部格式。这样策略代码就不用关心底层数据源了。

class UnifiedKline:
    def __init__(self, exchange, symbol):
        self.exchange = exchange
        self.symbol = symbol
        self.timestamp = None
        self.open = 0.0
        self.high = 0.0
        self.low = 0.0
        self.close = 0.0
        self.volume = 0.0
    
    @staticmethod
    def from_binance(data):
        kline = UnifiedKline("binance", data["symbol"])
        kline.timestamp = data[0]  # 毫秒
        kline.open = float(data[1])
        kline.high = float(data[2])
        kline.low = float(data[3])
        kline.close = float(data[4])
        kline.volume = float(data[5])
        return kline
    
    @staticmethod
    def from_okx(data):
        kline = UnifiedKline("okx", data["symbol"])
        kline.timestamp = data[0] // 1000  # 微秒转毫秒
        kline.open = float(data[1])
        kline.high = float(data[2])
        kline.low = float(data[3])
        kline.close = float(data[4])
        kline.volume = float(data[5])
        return kline

2.5 数据存储方案

数据存哪里?这是个好问题。

我个人习惯分层存储:

  • 热数据(最近1小时):存内存里,用Redis或者Python的deque。读写快,适合实时计算。
  • 温数据(最近7天):存SQLite或者PostgreSQL。方便回测和分析。
  • 冷数据(历史数据):存CSV文件或者Parquet格式。压缩率高,适合长期归档。

你想想看,如果所有数据都放内存,程序跑几天就爆了。如果都放数据库,实时查询又太慢。分层存储是最优解。

我的习惯:每天凌晨3点,我会把昨天的热数据转存到温数据层,同时清理过期数据。这个定时任务用crontab或者APScheduler都能实现。

2.6 数据质量监控

数据出问题,策略必死。我吃过这个亏。

有一次,交易所的WebSocket推送了重复数据,我的策略以为出现了套利机会,连续开了10个仓位。结果呢?全是假信号,亏了一笔。

从那以后,我加了三道防线:

  1. 时间戳校验:如果新数据的时间戳比上一条还早,直接丢弃。
  2. 价格合理性检查:如果价格波动超过5%,触发告警,人工确认。
  3. 数据完整性统计:每分钟统计一次接收到的数据条数,如果低于阈值,说明连接可能断了。
def validate_tick(tick, last_tick):
    # 时间戳校验
    if tick['timestamp'] <= last_tick['timestamp']:
        return False
    
    # 价格合理性检查
    price_change = abs(tick['price'] - last_tick['price']) / last_tick['price']
    if price_change > 0.05:  # 超过5%
        print(f"警告:价格异常波动 {price_change*100:.2f}%")
        return False
    
    return True
重要提醒:数据监控不是可选项,是必选项。我曾经因为没做监控,数据断流了整整2小时才发现。那2小时里,策略一直在用旧数据计算,结果可想而知。

2.7 本章知识体系

下面这张图展示了市场数据获取的整体架构。你可以看到数据从交易所到策略的完整流转路径。

市场数据获取架构图 交易所数据源 币安 OKX Bybit 其他交易所 数据获取方式 RESTful API(历史数据) WebSocket(实时行情) 账户API(交易信息) 数据处理与清洗 时间戳对齐 精度统一 数据校验 去重处理 内存(热数据) 数据库(温数据) 文件归档(冷数据)

从这张图你能看到,数据从交易所出发,经过不同的获取方式,再到清洗对齐,最后分层存储。每一步都有坑,每一步都需要精心设计。

好了,关于市场数据获取就聊这么多。记住一句话:数据质量决定策略上限。把数据搞扎实了,后面的套利逻辑才能跑得稳。


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