4. 行情数据接入:交易所API对接与数据标准化

行情数据,说白了就是做市商的「眼睛」。没有它,你就是在黑箱里做交易。

我个人习惯把行情接入分成三层:链路层(怎么连)、解析层(怎么读)、聚合层(怎么用)。今天咱们就一层层拆开来讲。

4.1 交易所API对接:REST vs WebSocket

大部分交易所都提供两种接口:REST和WebSocket。你可能会问,为什么要有两套?

REST接口,适合做「一次性查询」。比如开盘前拉一下历史K线,或者查账户余额。它的优点是简单,缺点嘛——太慢了。一次请求几十毫秒,高频场景根本扛不住。

WebSocket,才是做市商的主力。它建立一条长连接,交易所主动往你这边推数据。延迟能压到1-5毫秒。我在项目中遇到过,用REST去轮询深度数据,结果还没轮完一轮,盘口已经变了三次。嗯,从那以后,实时行情我只用WebSocket。

核心原则:REST做「查」,WebSocket做「收」。两者配合,不要混用。

下面是一个简单的WebSocket连接示例(Python):

import websocket
import json

def on_message(ws, message):
    data = json.loads(message)
    # 这里处理行情数据
    process_tick(data)

def on_error(ws, error):
    print(f"连接异常: {error}")
    # 自动重连逻辑
    reconnect()

def on_close(ws, close_status_code, close_msg):
    print("连接关闭,准备重连...")
    reconnect()

def on_open(ws):
    print("连接成功")
    # 订阅BTC/USDT深度
    sub_msg = {
        "op": "subscribe",
        "args": ["depth.BTC-USDT"]
    }
    ws.send(json.dumps(sub_msg))

websocket.enableTrace(False)
ws = websocket.WebSocketApp(
    "wss://api.exchange.com/ws",
    on_open=on_open,
    on_message=on_message,
    on_error=on_error,
    on_close=on_close
)
ws.run_forever()
避坑指南:我曾经因为没处理WebSocket断线重连,导致行情停了整整3分钟。那3分钟里,系统还在按旧数据报价,结果被套利机器人狠狠咬了一口。记住:重连逻辑不是可选项,是必选项

4.2 行情数据标准化处理

不同交易所,数据格式五花八门。有的用数组,有的用字典;有的时间戳是秒级,有的是毫秒级。你想想看,如果每个交易所都单独写一套处理逻辑,那代码得多乱?

我的做法是:统一内部数据模型。不管外面来的是什么,进到系统里,必须转成标准格式。

举个例子,深度数据的标准化:

class StandardDepth:
    def __init__(self):
        self.exchange = ""      # 交易所名称
        self.symbol = ""        # 交易对
        self.bids = []          # 买盘 [[price, qty], ...]
        self.asks = []          # 卖盘 [[price, qty], ...]
        self.timestamp = 0      # 毫秒时间戳
        self.seq_id = 0         # 序列号,用于去重

def normalize_depth(raw_data, exchange_name):
    """将不同交易所的深度数据转为标准格式"""
    std = StandardDepth()
    std.exchange = exchange_name
    
    if exchange_name == "Binance":
        std.symbol = raw_data['s']
        std.bids = [[float(p), float(q)] for p, q in raw_data['b']]
        std.asks = [[float(p), float(q)] for p, q in raw_data['a']]
        std.timestamp = raw_data['E']
        std.seq_id = raw_data['u']
    elif exchange_name == "OKX":
        # OKX的格式略有不同
        data = raw_data['data'][0]
        std.symbol = data['instId']
        std.bids = [[float(item[0]), float(item[1])] for item in data['bids']]
        std.asks = [[float(item[0]), float(item[1])] for item in data['asks']]
        std.timestamp = data['ts']
        std.seq_id = int(data['seqId'])
    
    return std
小技巧:我习惯在标准化阶段就把所有数值转成float。别等到计算时才转,那样容易出精度问题。另外,时间戳统一用毫秒,避免后面做时间比较时还要换算。

4.3 多源数据聚合与去重

做市商通常要接入3-5家交易所。为什么?因为单一交易所的深度不够,容易被操纵。多源聚合后,你能看到更真实的盘口。

但多源也带来了新问题:重复数据。同一笔成交,可能从两个交易所同时推送过来。如果不做去重,你的统计就会翻倍。

去重的核心是序列号。每个交易所的每一条行情,都有一个唯一的序列号。我们只需要记住每个交易所、每个交易对「最后处理过的序列号」,新来的数据如果序列号小于等于它,直接丢弃。

class Deduplicator:
    def __init__(self):
        # key: "exchange:symbol", value: last_seq_id
        self.last_seq_map = {}
    
    def is_duplicate(self, exchange, symbol, seq_id):
        key = f"{exchange}:{symbol}"
        last_seq = self.last_seq_map.get(key, 0)
        if seq_id <= last_seq:
            return True
        self.last_seq_map[key] = seq_id
        return False

# 使用示例
dedup = Deduplicator()
for raw_data in stream:
    std_data = normalize_depth(raw_data, "Binance")
    if dedup.is_duplicate(std_data.exchange, std_data.symbol, std_data.seq_id):
        continue  # 重复数据,跳过
    process_depth(std_data)

聚合的逻辑更直接:把多个交易所的深度,按价格合并到一起。比如买盘,把所有交易所的买单按价格从高到低排序,取前N档。这样你看到的深度,就是整个市场的「合成深度」。

聚合要点:
  • 不同交易所的深度,时间戳要尽量对齐(误差不超过50ms)
  • 价格精度要统一,比如都保留到小数点后2位
  • 数量单位要一致,别一个用BTC,一个用SAT

4.4 整体架构图

下面这张图,是我在实际项目中用过的行情接入架构。你可以看到数据从交易所到最终使用的完整链路:

行情数据接入架构图 交易所A (Binance) 交易所B (OKX) 交易所C (Bybit) WebSocket连接管理(心跳检测、自动重连、连接池) 数据标准化(统一格式、统一精度、统一时间戳) 去重与排序(序列号去重、时间戳对齐、价格排序) 合成深度数据 → 策略模块

这张图里,从上到下依次是:交易所源、连接管理、标准化、去重排序、最终输出。每一层都有明确的职责,互不干扰。我在项目里就是这么搭的,维护起来特别省心。

4.5 性能优化要点

行情接入对性能要求极高。我总结了几条经验:

优化项 具体做法 效果
内存复用 预分配对象池,避免频繁GC 延迟降低30%
零拷贝 直接操作字节数组,减少数据复制 吞吐量提升2倍
批量处理 攒够100条再一起处理,减少锁竞争 CPU使用率下降40%
无锁队列 使用Disruptor或类似机制 延迟稳定在微秒级
特别注意:我曾经在行情处理链路里用了Python的list.append,结果GC一触发,延迟直接飙到200ms。后来换成预分配数组,问题就解决了。高频场景下,内存分配是最大的敌人

好了,行情接入这块就聊到这儿。核心就三件事:连得上、读得懂、合得拢。把这三点做好,你的做市商系统就成功了一半。


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