3、交易所API对接:REST API与WebSocket API的区别、API密钥管理、获取行情数据、下单与撤单

做市交易框架的核心,说白了就是跟交易所打交道。你想想看,没有数据来源,策略就是瞎子;没有下单通道,策略就是瘸子。这一章,我们就来聊聊怎么跟交易所「对话」。

我个人习惯把交易所API分成两类:REST APIWebSocket API。很多新手上来就混着用,结果踩了不少坑。嗯,我们先把这个搞清楚。

REST API vs WebSocket API:到底选哪个?

先问个问题:你平时刷网页,点一下等结果,这是什么模式?这就是REST。你打开微信聊天,消息实时推送过来,这又是什么模式?这就是WebSocket。

放到交易所场景里,区别就很明显了:

对比维度 REST API WebSocket API
通信方式 请求-响应(一问一答) 全双工(实时推送)
适用场景 下单、撤单、查询账户 订阅行情、深度、成交
延迟 较高(50-200ms) 极低(1-10ms)
连接方式 短连接,用完即断 长连接,保持心跳
资源消耗 较高(需维护连接)

我在项目中遇到过一件事:有个同事用REST API去拉取tick级别的行情,每秒请求200次。结果呢?交易所直接把他IP封了。为什么?REST API本质上是为低频操作设计的,你拿它做高频数据流,肯定不行。

我的建议是:

  • 行情数据、深度数据、成交数据 → 用WebSocket
  • 下单、撤单、查询余额 → 用REST API
  • 订单状态更新 → 用WebSocket监听(如果有的话)
核心原则:WebSocket负责「快」,REST负责「稳」。两者配合使用,才是做市系统的正确姿势。

API密钥管理:别让你的钱打水漂

说到密钥管理,我见过最离谱的事——有人把API密钥直接写在代码里,然后上传到GitHub公开仓库。结果呢?几分钟内账户被洗劫一空。嗯,这不是段子,是真事。

API密钥通常包含两部分:

  • API Key:相当于你的用户名,公开也没太大关系
  • Secret Key:相当于你的密码,绝对不能泄露

怎么管理?我分享几个实战经验:

  1. 环境变量:把密钥写在.env文件里,用python-dotenv加载。千万别硬编码。
  2. 权限最小化:交易所一般支持设置API权限。只开你需要的——比如只开交易权限,不开提币权限。
  3. IP白名单:如果你的服务器IP固定,一定要设置白名单。这样就算密钥泄露,别人也用不了。
  4. 定期轮换:我习惯每3个月换一次密钥。虽然麻烦,但安全第一。
警告:千万不要把API密钥提交到版本控制系统中。如果你不小心提交了,立即撤销并更换密钥。我曾经吃过这个亏,现在每次提交前都会检查一遍。

代码示例:用Python加载密钥

import os
from dotenv import load_dotenv

load_dotenv()

API_KEY = os.getenv("EXCHANGE_API_KEY")
SECRET_KEY = os.getenv("EXCHANGE_SECRET_KEY")

# 千万别这样写:
# API_KEY = "your-api-key-here"
# SECRET_KEY = "your-secret-key-here"

获取行情数据:从深度到K线

行情数据是做市策略的「眼睛」。没有它,你就是在闭眼开车。

常见的行情数据类型:

  • Ticker:最新价、24小时涨跌幅、成交量等概要信息
  • Order Book(深度):当前所有买单和卖单的挂单情况
  • Trade(成交):最近发生的每一笔交易
  • K线(Candlestick):按时间聚合的开高低收数据

我个人习惯用WebSocket订阅深度和成交数据,用REST API拉取历史K线做回测。为什么?因为深度数据变化太快,REST根本跟不上节奏。

举个例子,用WebSocket订阅深度数据:

import websocket
import json

def on_message(ws, message):
    data = json.loads(message)
    # 处理深度数据
    bids = data['bids']  # 买单
    asks = data['asks']  # 卖单
    print(f"最新深度: 买一 {bids[0][0]}, 卖一 {asks[0][0]}")

def on_error(ws, error):
    print(f"连接出错: {error}")

def on_close(ws, close_status_code, close_msg):
    print("连接关闭,尝试重连...")

def on_open(ws):
    # 订阅深度数据
    subscribe_msg = {
        "op": "subscribe",
        "args": ["depth.BTC-USDT"]
    }
    ws.send(json.dumps(subscribe_msg))

# 启动WebSocket连接
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连接一定要加心跳机制。我一般每30秒发一次ping,如果60秒没收到pong,就主动重连。不然连接断了你还不知道,策略就瞎了。

下单与撤单:REST API的拿手好戏

下单这事儿,用REST API最稳。为什么?因为下单需要确认——你到底有没有成交?成交了多少?这些信息需要明确的响应。

下单的基本流程:

  1. 构造订单参数(交易对、方向、价格、数量)
  2. 用API密钥签名请求
  3. 发送POST请求到交易所
  4. 解析响应,获取订单ID
  5. 用订单ID查询成交状态

代码示例:用REST API下单

import requests
import time
import hmac
import hashlib
import base64

def place_order(api_key, secret_key, symbol, side, order_type, price, quantity):
    timestamp = int(time.time() * 1000)
    
    # 构造请求参数
    params = {
        "symbol": symbol,
        "side": side,        # "buy" 或 "sell"
        "type": order_type,  # "limit" 或 "market"
        "price": price,
        "quantity": quantity,
        "timestamp": timestamp
    }
    
    # 签名(不同交易所签名方式不同,这里以常见方式为例)
    message = f"{timestamp}{symbol}{side}{order_type}{price}{quantity}"
    signature = hmac.new(
        secret_key.encode(),
        message.encode(),
        hashlib.sha256
    ).hexdigest()
    
    headers = {
        "X-API-KEY": api_key,
        "X-SIGNATURE": signature,
        "X-TIMESTAMP": str(timestamp)
    }
    
    response = requests.post(
        "https://api.exchange.com/v1/order",
        json=params,
        headers=headers
    )
    
    if response.status_code == 200:
        order_id = response.json()["order_id"]
        print(f"下单成功,订单ID: {order_id}")
        return order_id
    else:
        print(f"下单失败: {response.text}")
        return None

# 使用示例
order_id = place_order(
    api_key=API_KEY,
    secret_key=SECRET_KEY,
    symbol="BTC-USDT",
    side="buy",
    order_type="limit",
    price=50000.0,
    quantity=0.01
)

撤单就更简单了,只需要订单ID:

def cancel_order(api_key, secret_key, symbol, order_id):
    timestamp = int(time.time() * 1000)
    
    params = {
        "symbol": symbol,
        "order_id": order_id,
        "timestamp": timestamp
    }
    
    # 签名过程同上...
    
    response = requests.delete(
        "https://api.exchange.com/v1/order",
        json=params,
        headers=headers
    )
    
    if response.status_code == 200:
        print(f"撤单成功: {order_id}")
    else:
        print(f"撤单失败: {response.text}")
注意:撤单不一定100%成功。比如订单已经全部成交了,你再撤单就会返回错误。所以一定要处理异常情况,别让程序崩了。

知识体系总览

说了这么多,我们来画个图,把这一章的核心逻辑串起来:

交易所API对接核心逻辑 做市交易系统 REST API(稳) WebSocket API(快) 下单 撤单 查询账户 深度数据 成交数据 K线数据 密钥管理:环境变量 + 权限最小化 + IP白名单 + 定期轮换 WebSocket负责「快」,REST负责「稳」,两者配合使用

这张图把这一章的核心逻辑都串起来了。你想想看,左边是REST API负责的「稳」操作,右边是WebSocket负责的「快」数据,底部是贯穿始终的密钥管理。做市系统就是这么搭建起来的。

嗯,这一章的内容就到这里。API对接是基本功,但也是最容易出问题的地方。我见过太多人因为密钥泄露或者API选型错误而翻车。记住一句话:稳字当头,快字为辅

最后提醒:对接交易所API时,一定要先看文档。每个交易所的签名方式、限频规则、错误码都不一样。我每次对接新交易所,都会先花半天时间把文档通读一遍,再动手写代码。磨刀不误砍柴工。

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