第三章 交易所API基础:REST与WebSocket的抉择

做市引擎要跟交易所打交道,第一步就是搞懂API。说实话,我见过不少新手一上来就闷头写代码,结果被限流、断连搞得焦头烂额。这一章,咱们把REST和WebSocket这两个基础概念掰扯清楚。

3.1 REST API vs WebSocket API

先问个问题:你下单的时候,是希望「发个请求等结果」,还是「跟交易所保持一条专线随时收发」?

这就是REST和WebSocket最本质的区别。

对比维度 REST API WebSocket API
通信模式 请求-响应(一问一答) 全双工(随时推送)
连接方式 短连接,每次请求新建TCP 长连接,建立后保持
适用场景 下单、查询账户、获取历史数据 实时行情、订单簿更新、成交推送
延迟 较高(50-200ms) 极低(1-10ms)
频率限制 严格,通常每秒几次到几十次 宽松,数据主动推送

我个人习惯:做市引擎的核心逻辑——报价、撤单、再报价——全部走WebSocket。但查询账户余额、获取历史K线这些低频操作,用REST就够了。你想想看,如果每次查个余额都要开一条WebSocket长连接,那资源浪费太严重了。

核心原则:高频、实时 → WebSocket;低频、一次性 → REST。

3.2 获取交易所API Key

要调用交易所的API,你得先有一把「钥匙」——API Key。我在项目中遇到过有人把Key硬编码在代码里,结果代码传到GitHub上,几分钟内账户就被盗了。嗯,这里要注意。

获取流程大致如下:

  1. 登录交易所官网,进入API管理页面
  2. 创建API Key,通常需要设置权限(交易、查询、提币等)
  3. 绑定IP白名单(强烈建议!)
  4. 保存API Key和Secret Key

警告:Secret Key只在创建时显示一次!我曾经见过有人截图保存,结果截图被同步到云盘,泄露了。建议用密码管理器或离线存储。

拿到Key之后,怎么用?以币安为例,每次请求需要签名:

import hashlib
import hmac
import time
import requests

API_KEY = 'your_api_key'
SECRET_KEY = 'your_secret_key'

def sign_request(params):
    query_string = '&'.join([f'{k}={v}' for k, v in sorted(params.items())])
    signature = hmac.new(
        SECRET_KEY.encode(),
        query_string.encode(),
        hashlib.sha256
    ).hexdigest()
    params['signature'] = signature
    return params

# 查询账户余额
params = {
    'timestamp': int(time.time() * 1000),
    'recvWindow': 5000
}
signed_params = sign_request(params)
headers = {'X-MBX-APIKEY': API_KEY}
resp = requests.get('https://api.binance.com/api/v3/account', 
                    headers=headers, params=signed_params)
print(resp.json())

这段代码里,timestamprecvWindow是防重放攻击的关键。我建议recvWindow设成5000毫秒,太短容易超时,太长不安全。

3.3 构建基础的HTTP请求客户端

直接写requests.get()当然可以,但做市引擎里要处理成百上千次请求,必须封装一个健壮的客户端。

先看看我常用的基础框架:

import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

class ExchangeClient:
    def __init__(self, api_key, secret_key, base_url):
        self.api_key = api_key
        self.secret_key = secret_key
        self.base_url = base_url
        
        # 配置重试策略
        retry_strategy = Retry(
            total=3,
            backoff_factor=1,
            status_forcelist=[429, 500, 502, 503, 504]
        )
        adapter = HTTPAdapter(max_retries=retry_strategy)
        self.session = requests.Session()
        self.session.mount('https://', adapter)
        self.session.mount('http://', adapter)
    
    def _sign(self, params):
        # 签名逻辑同上
        pass
    
    def request(self, method, endpoint, params=None):
        url = self.base_url + endpoint
        headers = {'X-MBX-APIKEY': self.api_key}
        
        if params is None:
            params = {}
        params['timestamp'] = int(time.time() * 1000)
        params = self._sign(params)
        
        try:
            resp = self.session.request(method, url, 
                                       headers=headers, 
                                       params=params,
                                       timeout=5)
            resp.raise_for_status()
            return resp.json()
        except requests.exceptions.RequestException as e:
            # 我曾经踩过坑:异常只打印不处理,导致订单状态丢失
            # 这里应该记录日志并触发告警
            print(f'请求失败: {e}')
            return None

# 使用示例
client = ExchangeClient(API_KEY, SECRET_KEY, 'https://api.binance.com')
balance = client.request('GET', '/api/v3/account')
print(balance)

避坑指南:我曾经把timeout设成30秒,结果某个交易所响应慢,线程池被占满,整个引擎卡死。后来改成5秒+重试,问题解决。记住:做市引擎里,超时比失败更可怕。

3.4 核心知识体系

下面这张图,是我做培训时必画的。它把REST和WebSocket在整个做市引擎中的位置说清楚了:

做市引擎API通信架构 做市引擎核心 通信方式决策层 REST API 下单 / 查询 / 历史数据 WebSocket API 实时行情 / 订单推送 交易所A / B / C 交易所A / B / C 高频策略走WebSocket,低频操作走REST,各司其职

说白了,这张图就一个意思:做市引擎核心根据业务需求,把请求分发给REST或WebSocket,再转发到不同的交易所。我刚开始做的时候,把所有请求都塞进WebSocket,结果连接数爆炸,被交易所封了IP。后来学乖了,该用REST就用REST。

3.5 实战建议

最后给几个实在的建议:

  • 先测试再上线:所有交易所都提供测试网(Testnet),用测试Key先跑通流程。我曾经在正式环境调试,不小心下了个市价单,亏了500U。
  • 频率控制:REST请求一定要加限流。我习惯用令牌桶算法,每秒最多5次请求,超过就排队。
  • 日志记录:每次请求的URL、参数、响应、耗时,全部记下来。排查问题的时候,日志就是你的救命稻草。
  • 异常处理:网络超时、签名错误、余额不足……每种异常都要有对应的处理逻辑。别问我怎么知道的,都是血泪教训。

记住:API是连接你和交易所的桥梁。桥塌了,再好的策略也是白搭。把基础打牢,后面的路才走得稳。

专注资料整理