量化喵是独立第三方信息站,不是欧易(OKX)官方。站内含推广链接。 完整披露 →

欧易 · API 进阶

欧易 WebSocket 怎么接:ccxt.pro 订阅行情和订单,模拟盘地址与连接限制

REST 是你发一次请求、交易所回一次;WebSocket 是建一条长连接,订阅之后由交易所把更新推过来。欧易文档在 WebSocket 概述里写的是「强烈建议开发者使用WebSocket API获取市场行情和买卖深度等信息」。

两种方式差在四处。REST 轮询(fetch_)拿到的数据最多落后一个轮询间隔,WebSocket 推送(watch_)是服务端有更新就推。REST 每轮询一次算一次请求、计入接口限速,WebSocket 订阅一次之后不再为取数发请求。自己的订单,REST 要反复查才知道成交没有,订单频道在状态变化时推送。代价在代码上:REST 是同步的,好调试;WebSocket 是异步的,要处理保活和断线重连。

几分钟甚至几小时才检查一次的策略,REST 就够,少了异步和重连这层复杂度。值得换成 WebSocket 的情况是:要同时盯很多交易对、轮询开始撞限速;或者要在订单成交后马上接着挂下一单。

欧易 WebSocket 的三组地址和常用频道

欧易 API v5 的 WebSocket 有三个地址,实盘和模拟盘各一套(实盘、模拟盘):

地址实盘模拟盘
公共频道wss://ws.okx.com/ws/v5/publicwss://wspap.okx.com/ws/v5/public
私有频道wss://ws.okx.com/ws/v5/privatewss://wspap.okx.com/ws/v5/private
业务频道wss://ws.okx.com/ws/v5/businesswss://wspap.okx.com/ws/v5/business

文档的规则是:订阅公有频道用公有服务的地址,订阅私有频道用私有服务的地址。常用的几个频道:

  • 行情(tickers)、盘口深度(books)、逐笔成交(trades)、K 线(candle):公开的市场数据,不需要登录。
  • 订单(orders)、持仓(positions)、账户(account):你自己账户的数据,订阅前要先登录,登录用的还是 apiKey、Passphrase 和用 SecretKey 算出的签名。

每个频道在文档里都标了它走哪个地址,自己直连时按那一栏填。用 ccxt.pro 则由库来选:它的 okx 实现里,频道名带 candle 的订阅发往 business 地址,其余公共行情走 public,私有频道走 private。

用 ccxt.pro 订阅行情(watch_ticker)

ccxt 的 WebSocket 部分叫 ccxt.pro,已并入 ccxt 主包。普通 ccxt 走 REST,用 fetch_ 系列;ccxt.pro 走 WebSocket,用 watch_ 系列。底层是长连接,代码要写成异步(async)的循环。

ccxt 的版本要在 4.5.87 及以上(2026 年 10 月 13 日发布):从这一版起,ccxt.pro 的 okx 连的是 443 端口;更早的版本还连 :8443,欧易公网从 2026 年 10 月 31 日起就停用这个端口了。本机是哪一版、怎么升级,见欧易 API 地址现在填哪个里 ccxt 那一节。

下面这段订阅 BTC/USDT 的行情,价格一更新就打印:

import asyncio
import ccxt.pro as ccxtpro     # WS 版,watch_ 系列

async def main():
    okx = ccxtpro.okx()        # 公有行情不需要鉴权
    symbol = 'BTC/USDT'
    try:
        while True:
            ticker = await okx.watch_ticker(symbol)   # 有新行情才返回
            print(ticker['symbol'], ticker['last'])
    finally:
        await okx.close()       # 退出时关掉长连接

asyncio.run(main())

和 REST 的区别在 await okx.watch_ticker(...):它挂在那儿等推送,有新行情才返回。放进 while True 循环,就是一个持续接收行情的流。结尾的 await okx.close() 用来主动关掉长连接。

订阅自己的订单(watch_orders):先连模拟盘

watch_orders 在你的委托单状态变化(部分成交、完全成交、撤单)时返回。它属于私有频道,要带凭证登录。下面的例子连的是模拟盘:凭证填模拟盘那把 Key,再调 set_sandbox_mode(True)。模拟盘的 Key 要在模拟环境里单独建,步骤见 欧易模拟盘怎么用。

import asyncio
import ccxt.pro as ccxtpro

async def main():
    okx = ccxtpro.okx({
        'apiKey':   '模拟盘的_apiKey',
        'secret':   '模拟盘的_secret',
        'password': '模拟盘的_Passphrase',   # 私有频道要登录
    })
    okx.set_sandbox_mode(True)               # 切到模拟盘

    # 自检:地址里应当是 wspap,第二行应当打印 1;有一项不对就别往下跑
    print(okx.urls['api']['ws'])
    print(okx.headers.get('x-simulated-trading'))

    try:
        while True:
            orders = await okx.watch_orders()    # 订单状态有变化才返回
            for o in orders:
                print(o['symbol'], o['side'], o['status'], o.get('filled'))
    finally:
        await okx.close()

asyncio.run(main())

跑起来之后,在模拟交易界面里手动挂一笔单再撤掉,终端里就会打印出这笔订单的状态变化。set_sandbox_mode(True) 在这里把接口地址切到测试那一组(WebSocket 因此换成模拟盘的 wspap.okx.com),并给请求头加上 x-simulated-trading: 1。自检那两行分别核对地址和请求头。

确认逻辑没问题、要切实盘时,去掉 set_sandbox_mode(True),凭证换成实盘的 Key。实盘 Key 的权限只勾「读取」和「交易」,不勾「提现」,并绑定 IP 白名单,见 API 密钥最小权限。

连接限制、保活和断线重连

欧易文档的「连接」和「连接限制」两节给了这几条数字,写重连逻辑时要守住:

  • 建连:3 次/秒,按 IP 计。
  • 订阅次数:每个连接上,订阅、取消订阅、登录三类请求合计 480 次/小时。
  • 30 秒规则:连接成功后 30 秒未订阅,或订阅后 30 秒内服务器没有向你推送数据,系统会自动断开连接。
  • 同一频道的连接数:订单、账户、持仓等私有频道,以子账户为维度,每个频道最多 30 个连接。

保活靠的是一条内容为 ping 的字符串:N 秒(N 小于 30)没收到新消息就发 ping,等服务器回 pong。ccxt.pro 的 okx 实现替你做了这件事。自己用 websockets 之类的库直连时要另外写,写法见 欧易 WebSocket 为什么连上 30 秒就断开。

连接断了要能自动重连,重连后要重新订阅,并补查一次当前状态(重新拉一遍挂单和持仓),因为断线期间的推送收不到。写法是把 watch_ 调用包在 try/except 里,只接网络类异常,等待时间逐次加长,连续失败超过上限就退出。下面的例子连的是模拟盘:

import asyncio
import ccxt.pro as ccxtpro

MAX_FAILS = 5        # 连续失败超过 5 次就退出,不无限重试

async def resync(okx, symbol):
    # 断线期间的推送收不到,用 REST 重新拉一遍挂单和持仓
    open_orders = await okx.fetch_open_orders(symbol)
    positions = await okx.fetch_positions()
    print('补查:挂单', len(open_orders), '笔,持仓', len(positions), '条')

async def main():
    okx = ccxtpro.okx({
        'apiKey':   '模拟盘的_apiKey',
        'secret':   '模拟盘的_secret',
        'password': '模拟盘的_Passphrase',
    })
    okx.set_sandbox_mode(True)
    symbol = 'BTC/USDT'
    fails = 0
    try:
        while True:
            try:
                # 出错之后再次调用,由 ccxt.pro 重新建连并订阅
                orders = await okx.watch_orders(symbol)
                fails = 0                             # 收到推送,计数清零
                for o in orders:
                    print(o['symbol'], o['side'], o['status'])
            except ccxtpro.NetworkError as e:
                fails += 1
                if fails > MAX_FAILS:
                    print('连续失败,退出:', e)
                    break
                wait = min(2 ** fails, 30)            # 2、4、8、16、30 秒
                print('连接异常,', wait, '秒后重连:', e)
                await asyncio.sleep(wait)
                try:
                    await resync(okx, symbol)         # 继续订阅之前先补查当前状态
                except ccxtpro.NetworkError as e2:
                    print('补查失败,下一轮再试:', e2)
    finally:
        await okx.close()

asyncio.run(main())

这段代码只接 NetworkError。鉴权失败、参数错误这类异常没有接,会直接抛出来终止脚本,重试对它们没用。

断线之后别假设状态没变,先补查再继续:失联期间订单可能已经成交,价格也可能已经走远。这一步和 API 自动交易风控 里「重连后先查状态、再动作」是同一件事。

风险提示:加密资产价格波动剧烈,本金可能大幅缩水甚至归零。自动化脚本出错时会连续执行错误的逻辑,亏损会被放大;合约与杠杆交易可能导致 100% 本金亏损。本文是对接口用法的说明,不构成投资建议,请只用你能承受全部损失的资金。

常见问题

欧易的订单频道最多能同时开多少个 WebSocket 连接?

按欧易 API 文档,以子账户为维度,订阅每个 WebSocket 频道的最大连接数是 30 个,受这条限制的有订单、账户、持仓、账户余额和持仓、爆仓风险预警、账户 greeks 这几个频道,不同频道分开计数。超出时,最新订阅的那条连接会先收到订阅成功,随后收到 channel-conn-count-error,表示平台终止了它的订阅。

用 ccxt.pro 订阅欧易 K 线,连的是哪个地址?

不用自己选。ccxt.pro 的 okx 实现里,频道名带 candle 的订阅发往 business 地址,条件单的订单频道也走 business;其余公共行情走 public,私有频道走 private。调用 set_sandbox_mode(True) 之后,这三条路径都换到模拟盘的 wspap 主机下。自己直连时,以文档里该频道标注的地址为准。

ccxt.pro 和普通 ccxt 是一回事吗?

ccxt.pro 是 ccxt 的 WebSocket 扩展,把各交易所的 WS 也封装成了统一接口。普通 ccxt 走 REST(fetch_ 系列),ccxt.pro 走 WS(watch_ 系列,比如 watch_ticker、watch_orders)。如今 ccxt.pro 已并入 ccxt 主包,装好 ccxt 后用异步的 watch_ 方法即可。底层是长连接,所以代码要写成异步循环。