OKX交易所API Python调用实战:签名鉴权、现货杠杆与历史数据

发布时间:2026/10/8 3:40:34
OKX交易所API Python调用实战:签名鉴权、现货杠杆与历史数据 简介面向需要调用OKEx Web API的Python开发者提供一套聚焦杠杆交易、现货交易、历史记录与历史数据获取的轻量代码包适合量化交易、自动化下单或行情分析场景。压缩包共12个文件全部为py脚本整体仅11KB按现货、合约、杠杆、账户、ETT等业务拆分模块并包含WebSocket客户端封装便于快速集成。目前已有188人学习/下载。通过阅读源码可掌握OKEx签名鉴权、请求构造、数据解析等关键流程也能直接复用函数对接杠杆下单、K线历史查询等操作结合MVC标签可在此基础上构建结构清晰、界面友好的管理工具。对于熟悉Python但初次接触加密交易所API的开发者尤其具有参考价值。1. ok交易所的 web api 调用应用现货、杠杆与历史数据一套 Python 方案能解决什么“ok交易所的 web api 调用应用”这类 python 源码包打开之后通常是一堆 py 文件有下单的、有拉行情的、有查订单历史的。别急着跑先分清它调的是哪套接口、用什么签名方式、下单参数是不是字符串——这三个问题没搞明白代码跑通概率很低。这套方案典型价值在于把现货交易、币币杠杆、历史订单和 K 线数据统一封装成函数让策略脚本和复盘脚本不用重复造轮子。适合两类人一类是想照着源码理解交易所 web api 调用链的 python 入门者另一类是已经跑着策略、需要补历史数据或对账的。以下按 REST 接口主线把封装、现货、杠杆、历史数据依次讲透高频坑集中放在第 5 章。2. 先跑通 REST 客户端签名鉴权与最小可复用的 Python 封装看再多免费 python 源码交易所 API 真正卡人的永远是签名。OK交易所的 web api 签名像黑匣子四个请求头、一个 Base64 的 HMAC SHA256 串任何字符不对就报错。这一章先把签名规则拆开再给一个能复用的客户端封装最后用公开接口验证连通性。2.1 鉴权签名规则三个必填请求头和一个 HMAC SHA256 签名串OK交易所 V5 接口的私有请求需要四个请求头OK-ACCESS-KEYAPI Key、OK-ACCESS-SIGN签名、OK-ACCESS-TIMESTAMP请求时间戳、OK-ACCESS-PASSPHRASEAPI 口令。签名串的拼接顺序是固定的import base64 import hashlib import hmac from datetime import datetime, timezone def iso_timestamp() - str: # V5 要求 UTC 时间精确到毫秒格式2025-01-01T08:00:00.123Z now datetime.now(timezone.utc) return now.strftime(%Y-%m-%dT%H:%M:%S.%f)[:-3] Z def build_sign(timestamp: str, method: str, request_path: str, body: str, secret_key: str) - str: # message 时间戳 请求方法 请求路径(含query string) 请求体 message timestamp method.upper() request_path body mac hmac.new( secret_key.encode(utf-8), message.encode(utf-8), hashlib.sha256, ) return base64.b64encode(mac.digest()).decode(utf-8)这段代码有三个容易翻车的地方。第一时间戳必须是 UTC不能用datetime.now()直接格式化否则服务器和本地时间差超过 30 秒就会返回50118。第二request_path要包含查询参数原样拼接比如GET /api/v5/account/balance?ccyBTC这里的?ccyBTC必须进入签名串少一个字符签名都无效。第三body是 POST 请求体 JSON 字符串不能带多余空格{}和{a:1}这类序列化结果要固定。2.2 最小客户端封装requests 实现、限频与重试签名只是第一步真正干活的是一个统一的请求封装。用requests.Session()复用连接把签名、请求头、超时、重试、模拟盘开关都收进去import json import time import requests class OkxV5Client: def __init__(self, api_key: str, secret_key: str, passphrase: str, demo: bool False, base_url: str https://www.okx.com): self.api_key api_key self.secret_key secret_key self.passphrase passphrase self.demo demo self.session requests.Session() self.base_url base_url def request(self, method: str, path: str, params: dict None, body: dict None, retries: int 3): method method.upper() body_str if body is None else json.dumps(body) query if params: query ? .join(f{k}{v} for k, v in params.items()) request_path path query timestamp iso_timestamp() sign build_sign(timestamp, method, request_path, body_str, self.secret_key) headers { OK-ACCESS-KEY: self.api_key, OK-ACCESS-SIGN: sign, OK-ACCESS-TIMESTAMP: timestamp, OK-ACCESS-PASSPHRASE: self.passphrase, Content-Type: application/json, } if self.demo: headers[x-simulated-trading] 1 # 模拟盘开关 url self.base_url request_path for attempt in range(retries): try: if method GET: resp self.session.get(url, headersheaders, timeout10) else: resp self.session.post(url, headersheaders, databody_str, timeout10) data resp.json() if data.get(code) 0: return data # 50011/429 限频500/501/503 服务端抖动退避重试 if data.get(code) in (50011, 50013) or resp.status_code in (429, 500, 501, 503): time.sleep(0.5 * (attempt 1)) continue return data except requests.RequestException: time.sleep(0.5 * (attempt 1)) return {code: -1, msg: request failed after retries, data: []}封装里我加了两个实用设计。一是模拟盘开关通过x-simulated-trading: 1请求头切换新写的下单逻辑先在这边跑避免真金白银试错二是针对50011和429的退避重试注意重试只对幂等查询安全下单接口重试前要确认上一笔是否已经提交成功。2.3 连通性自检先用公开行情接口验证封装客户端写完先别急着下单用不需要签名的公开接口验证网络和封装再用一个私有接口验证签名链路curl https://www.okx.com/api/v5/public/time curl https://www.okx.com/api/v5/market/ticker?instIdBTC-USDT第一次接入时我习惯先跑一段 python 脚本顺序是调public/time看本地时间与服务端时间差调market/ticker看行情返回结构最后调一次account/balance私有接口确认签名没毛病。如果前面公开接口都通、私有接口报签名错误问题基本锁定在时间戳或request_path拼法上。3. 现货交易接口下单、撤单与持仓查询的落地写法现货是整个方案的基础业务杠杆、历史数据都围绕它展开。现货下单走/api/v5/trade/order关键参数是tdModecash。这一章讲清楚下单请求体的每个字段怎么填撤单怎么做到幂等以及查询余额、持仓、订单状态三个接口的分工。3.1 下单请求体订单类型、价格、数量与 tgtCcy 的边界现货下单的请求体核心字段有六个instId交易对如BTC-USDT、tdMode现货固定cash、sidebuy/sell、ordTypemarket/limit、sz数量、px价格。注意一个反直觉的点所有数值字段都必须是字符串传浮点数会在精度校验上翻车。def place_order(self, inst_id: str, side: str, ord_type: str, sz, pxNone, tgt_ccyNone, cl_ord_idNone): body { instId: inst_id, tdMode: cash, side: side, ordType: ord_type, sz: str(sz), } if px is not None: body[px] str(px) # 限价单必填 if tgt_ccy is not None: body[tgtCcy] tgt_ccy # 市价单按金额买时传 quote_ccy if cl_ord_id is not None: body[clOrdId] cl_ord_id # 客户端订单号用于幂等 return self.request(POST, /api/v5/trade/order, bodybody)参数说明限价单必须同时给px和sz价格精度要符合交易对规则比如BTC-USDT的价格最小变动是 0.1传67000.05会被拒。市价买单有两种下法按数量买sz传币数量按金额买tgtCcyquote_ccy且sz传 USDT 金额。市价卖单只能按数量。clOrdId是客户端生成的唯一单号下单成功后用它查单、撤单比ordId更可控。3.2 撤单与幂等用 clOrdId 防止重复动作撤单是高频动作最容易出问题的是重复撤单和撤单时订单已成交。cancel-order接口支持用ordId或clOrdId定位订单两个至少要传一个def cancel_order(self, inst_id: str, ord_id: str None, cl_ord_id: str None): body {instId: inst_id} if ord_id: body[ordId] ord_id if cl_ord_id: body[clOrdId] cl_ord_id return self.request(POST, /api/v5/trade/cancel-order, bodybody)逻辑说明撤单前最好先查一次订单状态只有live和partially_filled状态的单能撤。filled和canceled的单再撤会返回错误码如果不做状态检查就要接受这个错误码当作“已终结”处理。批量撤单用/api/v5/trade/cancel-batch-orders数组格式传多笔订单我一般把批量撤单放在异常清理场景里比如策略停止时把所有活单一次性撤掉。3.3 余额、持仓与订单状态三个查询接口的分工查询类接口有三个容易混淆。/api/v5/account/balance查账户资产返回每个币种的availBal可用和frozenBal冻结/api/v5/account/positions查持仓现货模式下基本为空杠杆和合约才有实际内容/api/v5/trade/order查单笔订单详情状态字段state取值live、partially_filled、filled、canceled。def get_order(self, inst_id: str, ord_id: str None, cl_ord_id: str None): params {instId: inst_id} if ord_id: params[ordId] ord_id if cl_ord_id: params[clOrdId] cl_ord_id return self.request(GET, /api/v5/trade/order, paramsparams) def get_balance(self): return self.request(GET, /api/v5/account/balance)下单后轮询订单状态是个实用的模式每 1~2 秒查一次连续查到filled或canceled就结束超过 30 秒没终态就告警人工介入。注意balance返回的是各币种原始数量BTC 就是 BTC 的数量要折合成 USDT 得另取价格别把数量当金额。frozenBal是挂单冻结的部分计算可用资金时两个都要看。4. 杠杆交易接口逐仓、全仓与倍数调整的落地方案杠杆交易在 OK交易所 V5 里和现货共用/api/v5/trade/order下单接口差别只在tdMode。但这一个字段的差别背后是一整套不同的账户和风控逻辑。这一章讲杠杆倍数怎么设置、逐仓全仓有哪些接口差异、下单前怎么做保证金校验。4.1 杠杆倍数设置set-leverage 与全仓/逐仓的差异杠杆倍数通过/api/v5/account/set-leverage设置下单前必须先把倍数设好否则接口可能按默认倍数执行或者直接报错。参数是instId、lever、mgnMode其中mgnMode传cross全仓或isolated逐仓def set_leverage(self, inst_id: str, lever: int, mgn_mode: str, pos_side: str None): body { instId: inst_id, lever: str(lever), mgnMode: mgn_mode, } if pos_side: body[posSide] pos_side # 双向持仓模式下传 long/short return self.request(POST, /api/v5/account/set-leverage, bodybody)参数说明lever是整数比如 3 表示 3 倍杠杆mgnMode决定保证金模式。全仓模式下整个账户的资产都作为保证金风险共担逐仓模式下每笔仓位的保证金独立亏到该仓位的保证金就强平。如果你的账户开了双向持仓模式posSide必须传long或short单向持仓模式net可以不传。设置杠杆的返回值里一般会包含当前杠杆倍数注意回读确认。4.2 杠杆下单与现货下单的差别tdMode、posSide 与借币杠杆下单走同一个下单接口tdMode从cash换成cross或isolated。下单前先设置杠杆再按开仓方向下单def place_margin_order(self, inst_id: str, side: str, ord_type: str, sz, pxNone, mgn_modecross, pos_sideNone): body { instId: inst_id, tdMode: mgn_mode, # cross全仓杠杆, isolated逐仓杠杆 side: side, ordType: ord_type, sz: str(sz), } if px is not None: body[px] str(px) if pos_side: body[posSide] pos_side return self.request(POST, /api/v5/trade/order, bodybody)逻辑说明杠杆买入和现货买入的表面差别是tdMode但底层逻辑完全不同。杠杆做多是买入资产的同时借入计价币杠杆做空是卖出自有资产的同时借入标的币。所以下单前要确认账户里有足够本金系统才会按杠杆借币本金不足但可借额度够时也能成交。双向持仓模式下开多传posSidelong平多传sidesell, posSidelong这个方向很容易写反。4.3 保证金与风控可借数量、强平价与模拟盘验证杠杆单的保证金校验比现货严格得多。我下单前固定做三步检查第一步查/api/v5/account/balance确认可用余额第二步查/api/v5/account/positions看现有仓位方向和已占用保证金第三步用instId的元数据估算下单量是否在可借范围内。positions返回里有一个我一直当成强平预警用的字段liqPx预估强平价。当行情接近这个价格时仓位会非常危险。杠杆是把双刃剑做多时币价跌 1/杠杆倍数的幅度就爆仓。第一次跑杠杆逻辑我的建议是坚持用模拟盘验证OkxV5Client(..., demoTrue)下几笔 1 倍和 2 倍的单观察仓位变化和强平价计算是否符合预期确认没有问题再切实盘。5. OK交易所 Web API 高频排查点现象、原因与解决路径以下五条是调用这套接口里最常见的踩坑现场按“现象 → 原因 → 解决”写全是血泪经验。5.1 签名一直报 50118时间戳格式与 query string 没参与签名现象私钥、Key、Passphrase 填得都对但接口稳定返回50118看代码逻辑怎么都对。原因两个高频点。一是时间戳不是 UTC 或毫秒格式不对datetime.now()拿到的是本地时区服务器比对的却是 UTC时差加签名串错位一起导致校验失败二是 GET 请求的query string没有拼进request_path比如查余额带了?ccyBTC签名时却只签了/api/v5/account/balance。解决统一用第 2 章的iso_timestamp()生成时间戳先调public/time接口对时把本地时间与服务端时间的偏移记下来。签名用的request_path必须是除域名外的完整路径含问号和参数原样。5.2 下单报 5xxxx数字必须是字符串精度要对齐 tickSz/lotSz现象下单接口返回5xxxx系列错误码提示参数无效或下单失败但看参数感觉都对。原因最常见的两个原因。一是sz、px用了浮点数而非字符串JSON 序列化时0.1可能变成0.1000000000000000055之类二是数量或价格精度不满足交易对规则比如最小下单量是 0.01 BTC传了 0.001 就会被拒。解决所有数值一律先str()再放进请求体。交易对精度用/api/v5/public/instruments?instTypeSPOT查询返回里有lotSz下单数量步长、minSz最小下单量、tickSz价格步长下单前对参数做一次取整和范围校验。这一条几乎能解决 80% 的下单失败问题。5.3 历史K线总少最后一根confirm 未确认与游标翻页边界现象拉历史K线时每次拉完 100 根最后一根的时间戳和上一批对不上中间有缺口或重复。原因K线接口返回的每根K线带一个confirm字段0表示这根K线还没走完数据会变化1表示已确认。拉最新K线时会把未确认的这根也返回直接入库就会造成最后一根数据不准。另外用after/before游标翻页时容易搞混方向after是往更早翻before是往更近翻很多人正好用反。解决入库时对confirm 0的K线做特殊处理要么跳过等下一根要么单独标记为“未确认”并在下次拉取时覆盖更新。翻页时以上一页最早一条的ts作为after参数继续拉更早的数据形成一个单调向前的爬取序列。5.4 请求一快就被限频429/50011 与退避重试现象脚本跑到一半报429或50011加日志一看是高频请求触发了接口限频。原因OK交易所的 web api 按 IP 维度限频不同接口的限频档位不同行情类接口和交易类接口是分开算的。单线程循环里不控制请求间隔很容易在批量拉历史数据或批量下单时触发。解决客户端里加一个请求间隔控制器我一般用threading.SLock配合最小间隔time.sleep(0.1)做全局节流批量任务再把间隔放宽到 0.2~0.3 秒。遇到限频错误码就退避重试退避时间按0.5s / 1s / 2s递增连续重试超过 3 次就停下来人工看。5.5 杠杆单被当成现货单拒绝tdMode 传错与账户资金隔离现象用杠杆下单接口传了tdModeisolated结果报错说账户没有开通杠杆或者资金不足但现货账户里明明有币。原因OK交易所的现货和杠杆共用“现货/杠杆”账户资金池但杠杆下单需要账户处于可杠杆状态且资金要在这个账户体系内。如果代码里tdMode没生效或者资金没有划转到对应账户就会报错。另外一个类似问题订单已经用cash模式成交了持仓查询里当然看不到杠杆仓位。解决下单前先调set-leverage确认倍数设置成功再查balance看可用资金杠杆仓位必须用positions查询而不是看现货余额变化。建议在下单函数里强制校验tdMode把cash和cross/isolated分流到不同函数避免复用一个下单函数时传错。6. 进阶技巧历史数据的增量同步与本地校验历史数据拉取最容易出现“全量重跑”的低效问题。我习惯把历史K线的同步拆成两步首次全量拉取 增量更新。全量拉取用after游标一路往前翻翻到data为空或到达目标时间为止增量更新则只拉最近 200~300 根与本地最后一根ts对比缺口小就只补最新几根缺口大就回补全量。def incremental_sync_candles(client: OkxV5Client, inst_id: str, bar: str 1m): latest client.request(GET, /api/v5/market/history-candles, params{instId: inst_id, bar: bar, limit: 100}) latest_ts int(latest[data][0][0]) # 服务器最新K线时间戳 local_ts get_local_latest_ts(inst_id, bar) # 本地最新K线时间戳 if local_ts is None or latest_ts - local_ts 120_000: full_sync(client, inst_id, bar) # 缺口大于2分钟回补全量 else: upsert_candles(latest[data]) # 否则直接覆盖更新最近100根本地校验有两个动作一个是以ts为主键入库用INSERT OR REPLACE天然去重重复拉取不会产生脏数据另一个是检查最新一根K线的confirm字段未确认的K线单独标记或直接跳过等下一轮增量同步时自然覆盖成确认数据。跑数据落库后抽查几个时间点的high/low/close与kline图表对照确认没有错位。我已经习惯了任何行情库都先跑一遍“最近 100 根与服务器对比”的脚本再投入使用这一步成本很低能挡掉大部分数据质量事故。杠杆单当年因为没看清tgtCcy参数市价按金额买入了超预期数量的币差点爆仓翻车之后所有下单函数都加了参数边界校验。希望这些细节能帮到你少交点学费。本文还有配套的精品资源点击获取