
简介OKEx交易所Web API的Python调用示例覆盖杠杆交易、现货交易、历史记录与历史数据获取等核心场景适合具备Python基础、想对接加密货币交易所API的开发者与量化交易初学者。资源包共12个文件全部为.py脚本按现货、合约、杠杆、账户、WebSocket等模块拆分并附带通用工具类与异常处理包体仅11KB结构清晰、便于阅读。目前已有187人学习下载可用于快速理解OKEx签名流程、HTTP请求封装、下单撤单、杠杆参数设置及历史K线拉取等方法为搭建自动化交易或行情分析工具提供可直接复用的参考实现。1. 为什么Python调OK交易所API值得当成系统工程来做拿到一个写着“OK交易所Web API调用应用杠杆、现货、历史记录、历史数据”的Python源码压缩包多数人的第一反应是解压、装依赖、跑起来。但真正动手的人会发现OKX这套V5 API的坑不在接口数量而在认证签名、参数组合和限频节奏上。这个方向能做的东西其实很明确用Python把行情、现货下单、杠杆仓位和历史数据串成一条自动化链路省掉手动盯盘和复制粘贴同时也为后面的Python量化交易策略代码铺好数据底子。适合有Python基础、想把自己的交易逻辑落到代码上的开发者不管是做自动止损、定时定投还是行情监控这套API都能覆盖。下面按从地基到应用的顺序把这套方案的每个环节拆开讲。2. 先跑通API地基密钥权限、签名算法与统一请求封装2.1 密钥权限只读、交易、提币三档怎么选OKX后台创建API Key时有三个权限维度读取、交易、提币。对于这个RAR里的应用策略很简单——只勾“读取”和“交易”永远不勾“提币”。原因不是技术上的而是安全层面给账号留一条后悔药即使密钥泄露攻击者只能帮你买币卖币但转不走资产。很多第一次做API接入的人习惯把三个权限全勾上这等于把保险柜钥匙挂在门口属于典型的翻车前提。密钥的存放位置也要注意。不要写在代码里更不要把这个RAR里的代码直接推到公开仓库。常见做法是把API Key、Secret Key、Passphrase放到项目根目录的.env文件里然后让.gitignore把.env排除掉。RAR里如果已经有config.py或settings.py改造成读取环境变量也不难核心思路是密钥不跟着代码走、不和代码一起分发。# env.py import os from dotenv import load_dotenv load_dotenv() API_KEY os.getenv(OKX_API_KEY, ) SECRET_KEY os.getenv(OKX_SECRET_KEY, ) PASSPHRASE os.getenv(OKX_PASSPHRASE, ) DEMO os.getenv(OKX_DEMO, 1) 1这段代码做的事情是在启动时从环境变量文件加载三项密钥配置并在变量缺失时返回空字符串避免程序直接抛异常退出。参数说明API_KEY是你在OKX后台生成的Access KeySECRET_KEY是配套的Secret KeyPASSPHRASE是你创建密钥时单独设置的交易口令三者缺一不可。DEMO建议默认开1先用模拟盘验证代码逻辑后面第6章我会细说模拟盘的验证路径。提示创建API Key时IP白名单能填就填。固定IP环境下的限制会让签名校验更安全但要注意家用宽带IP会漂填了之后需要定期更新。2.2 签名算法从时间戳到请求头的完整链路OKX V5 API的认证方式和别的交易所不太一样它不传token而是每次请求都带上四个HeaderOK-ACCESS-KEY、OK-ACCESS-SIGN、OK-ACCESS-TIMESTAMP、OK-ACCESS-PASSPHRASE。其中签名串的拼接规则是时间戳 请求方法大写 请求路径含查询参数 请求体然后用HMAC-SHA256加密再做Base64编码。这里最容易踩坑的是“请求路径必须包含查询参数”。很多从别的交易所转过来的开发者习惯只签路径不签参数结果GET请求全部返回401。另一个坑是时间戳格式OKX接受秒级和毫秒级Unix时间戳但要求同一个时间戳同时用于签名串和Header不能签名时用一个时间、请求时又生成一个新的。import hmac import hashlib import base64 def sign_message(timestamp: str, method: str, request_path: str, body: str, secret: str) - str: 构建OKX V5 API签名 :param timestamp: 与请求头OK-ACCESS-TIMESTAMP保持一致 :param method: GET或POST必须大写 :param request_path: 请求路径GET时包含?后面的查询参数 :param body: POST请求的JSON字符串GET传空串 :param secret: Secret Key message timestamp method.upper() request_path body mac hmac.new(secret.encode(utf-8), message.encode(utf-8), hashlib.sha256) return base64.b64encode(mac.digest()).decode(utf-8)这段函数是OKX Python客户端的基础底座后面所有请求都靠它生成签名。逻辑说明先把时间戳、方法、路径、请求体按顺序拼成一个长字符串再用Secret Key做HMAC-SHA256散列最后Base64编码成Header里要传的签名值。参数说明timestamp必须是字符串且要和请求头里的一致request_path对GET请求来说是从“/api/v5/”开始到问号后面的完整内容例如/api/v5/market/ticker?instIdBTC-USDTbody是JSON序列化后的字符串json.dumps(body)时不要有多余空格否则签名不一致。顺带说一句如果你是在Windows上跟着Python安装教程搭的环境最好把系统时间同步打开。我就有一次在虚拟机里跑脚本宿主机时间快了3分钟所有请求全部401排查了半小时才发现是虚拟机的时钟漂移。2.3 统一请求封装限频、重试与日志一次到位RAR里的代码如果只有几个零散的requests.get调用那只能算Demo不能算应用。真正扛得住实盘的是在请求层做三件事会话复用、限频保护和错误日志。会话复用用requests.Session底层连接会被复用避免每个请求都重新TCP握手限频保护主要靠一个简单的节流窗口因为OKX按IP对接口做限频频繁请求返回429后如果继续蛮干封禁会更久错误日志则是把每次请求的URL、状态码、业务码和耗时记录下来出问题时不用抓着头看黑匣子。import json import time import logging import requests logger logging.getLogger(okx_client) class OKXClient: BASE_URL https://www.okx.com def __init__(self, api_key, secret_key, passphrase, demoFalse): self.api_key api_key self.secret_key secret_key self.passphrase passphrase self.demo demo self.session requests.Session() self._last_request_ts 0.0 self._min_interval 0.1 # 单线程下限制请求间隔 def _throttle(self): elapsed time.time() - self._last_request_ts if elapsed self._min_interval: time.sleep(self._min_interval - elapsed) def _sign(self, timestamp, method, path, body_str): return sign_message(timestamp, method, path, body_str, self.secret_key) def request(self, method, path, paramsNone, bodyNone, retries3): for attempt in range(retries): self._throttle() timestamp str(int(time.time())) body_str json.dumps(body) if body else if method GET and params: full_path path ? requests.compat.urlencode(params) else: full_path path sign self._sign(timestamp, method, full_path, body_str) 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 full_path try: resp self.session.request(method, url, headersheaders, databody_str or None) if resp.status_code 429 and attempt retries - 1: time.sleep(2 ** attempt) # 指数退避 continue return resp.json() except requests.RequestException: logger.error(请求异常: %s %s, url, exc_infoTrue) if attempt retries - 1: time.sleep(2 ** attempt) continue raise这段封装把前面两小节的东西合并成了一个可复用的客户端。逻辑说明每次请求前先做节流然后拼签名、组Header、发请求遇到HTTP 429时按指数退避重试最终返回统一JSON结构。参数说明_min_interval是两次请求的最小间隔秒数0.1秒对应单线程下每秒最多10次请求如果哪个接口限频更严把值调大到0.2或0.5demo为True时请求头会带上OKX模拟盘标识让所有请求打在模拟环境里这是验证代码最安全的方式。注意这个模拟盘Header字段在不同时期可能微调接入前查一下当前官方文档。有了这个客户端下面几章的所有接口调用都基于它展开。后面要加WebSocket推送也只是在这层扩一套长连接管理基础不用动。3. 现货与杠杆交易模块下单参数与仓位控制的完整链路3.1 行情接口先说话ticker、K线与深度现货交易的第一步不是下单是拉行情。OKX的行情接口大部分不需要认证甚至可以不通过上面的OKXClient直接requests.get就能跑。最常用的三个公开接口是ticker最新行情、books订单深度和candlesK线。import requests def get_ticker(inst_id: str) - dict: 拉取现货最新行情 resp requests.get( https://www.okx.com/api/v5/market/ticker, params{instId: inst_id}, timeout5, ).json() return resp[data][0]逻辑说明这个接口返回的是一个数组取第一个元素就是当前交易对的最新行情数据。参数说明instId是OKX的合约ID格式币对之间用连字符比如BTC-USDT、ETH-USDT大小写敏感写成btc_usdt或BTCUSDT都会返回错误。返回的data里有last最新价、open24h、high24h、low24h等字段之后做Python数据分析与可视化时这些字段正好是清洗后的基础素材。行情接口的限频通常比交易接口宽松但也别在循环里不加sleep地猛拉。写Python爬虫和写交易程序最大的区别是爬虫被抓了顶多重试交易程序被限频了可能错过止损点。所以即便不涉及资金安全的公开接口我也习惯在循环里加个0.1秒的间隔。3.2 现货下单与订单状态机参数和返回结构现货下单用POST /api/v5/trade/order核心参数有六个instId、tdMode、side、ordType、sz、px。其中tdMode在现货里固定填cash意思是现金交易side填buy或sellordType填market或limitmarket单不需要pxlimit单必须给pxsz是委托数量币对的数量精度由交易所的tickSize约束不是你想填几位就填几位。参数取值说明tdModecash / cross / isolated现货用cash杠杆用cross或isolatedordTypemarket / limit市价单不需要px限价单必须给pxsidebuy / sell买入或卖出参数说明instId交易对ID格式如BTC-USDTsz委托数量精度受交易所约束px委托价格限价单必填client OKXClient(API_KEY, SECRET_KEY, PASSPHRASE, demoTrue) def place_spot_limit_order(inst_id: str, side: str, price: str, size: str) - dict: 现货限价单示例 body { instId: inst_id, tdMode: cash, side: side, ordType: limit, px: price, sz: size, } result client.request(POST, /api/v5/trade/order, bodybody) if result[code] 0: return result[data][0] # 包含ordId raise RuntimeError(f下单失败: {result})逻辑说明通过统一封装的OKXClient下单自然带有签名和限频处理返回结果里data[0]包含ordId这是后续撤单和查订单状态的凭证。参数说明price和size都建议用字符串而不是浮点数OKX对精度要求严格浮点数可能触发精度错误或四舍五入后价格不合法size的最小值要看合约信息接口返回的minSz字段不同币对不一样我当年就因为少看这个字段在某个小币种上面下了个被秒拒的单。订单状态流转是另一个容易忽略的点。下单后返回的ordId对应的订单状态可能是live进行中、partially_filled部分成交、filled全部成交或canceled已撤销。查询订单详情用GET /api/v5/trade/order传instId和ordId两个参数返回的state字段就是状态码。千万不要根据下单返回码判断“一定成交了”下单成功只代表订单进到了订单簿能不能成交要看后面的状态查询。3.3 杠杆交易前置模式设置、借贷划转与下单差异杠杆交易和现货最大的区别是资金从哪里来。现货用的是自己账户里的可用余额杠杆交易在OKX里除了自有资金还可以通过系统借贷放大仓位。所以要跑通杠杆模块至少要过三关设置杠杆倍数、设置持仓模式、搞清楚借贷划转。首先要设置杠杆倍数。OKX的杠杆设置接口是POST /api/v5/account/set-leverage传入instId、lever和mgnMode三个参数。mgnMode是保证金模式isolated是逐仓每笔仓位单独算保证金、cross是全仓整个账户的资金共享保证金池。def set_leverage(inst_id: str, lever: str, margin_mode: str) - dict: 设置杠杆倍数margin_mode可选isolated或cross body { instId: inst_id, lever: lever, mgnMode: margin_mode, } return client.request(POST, /api/v5/account/set-leverage, bodybody)逻辑说明这个接口必须在开仓之前调用否则下单时会报错因为新仓位的杠杆倍数默认是1或者沿用上一次的旧值。参数说明lever传字符串“3”代表3倍杠杆mgnMode一旦设置这笔仓位的保证金模式就定了后面改杠杆只会影响新仓位已经开着的仓位不受影响。还有个细节逐仓模式下的杠杆可以每个交易对不同倍数全仓则是整个账户统一倍数做参数校验时不要把它俩混成一个全局变量。其次是持仓模式。OKX的持仓分为单向持仓net和双向持仓long_short。双向持仓下同一币对可以同时持有买单和卖单两笔仓位适合做市策略单向持仓则一个方向只允许一笔仓位。切换持仓模式用POST /api/v5/account/set-position-mode参数是posMode这个设置对同一资金账户下的所有交易对生效会影响杠杆和合约的持仓结构。最后是借贷和划转。杠杆账户里如果自有资金不够系统会在开仓时自动借币借款要付利息如果你想主动把资金从现货账户划转到杠杆账户用POST /api/v5/asset/transfer接口参数包括ccy币种、amt数量、from转出账户类型、to转入账户类型。这里的from和to不是字符串而是数字编码比如6代表资金账户、18代表交易账户具体对应关系要以官方文档为准账户体系编号在不同时期会调整。杠杆下单和现货下单在接口层面几乎一样仍然走POST /api/v5/trade/order唯一的区别是把tdMode从cash改成isolated或cross。这个参数一旦传错订单要么被拒要么按错误的资金模式进入订单簿属于最容易被忽略的翻车点我在第5章会专门展开。4. 历史记录与历史数据落地订单分页、K线整理与本地存储4.1 历史订单接口分页参数与限频下的拉取策略历史记录是这个方案里最容易被低估的一块。很多人以为拉历史订单就是调一次接口拿全部数据但实际上OKX的限制非常明确普通历史订单接口只能查最近7天要查更久得走orders-history-archive这类归档接口而且单次最多返回100条。所谓“历史记录”在实盘里是几十万条级别必须靠分页。分页参数是after和before配合limit一起用。OKX的after/before方向和直觉正好相反after是取该时间戳之后的数据before是取之前的数据。而且分页不能像MySQL那样随便offset它基于游标用上一条返回记录的时间戳作为下一次请求的after值。def fetch_order_history(inst_id: str, end_ts: int): 分批拉取历史订单 :param end_ts: 从最新的时间戳开始往回翻单位毫秒 all_orders [] cursor_ts end_ts while True: params { instType: SPOT, instId: inst_id, limit: 100, after: str(cursor_ts), } result client.request(GET, /api/v5/trade/orders-history-archive, paramsparams) if result[code] ! 0: raise RuntimeError(f拉取历史订单失败: {result}) data result.get(data, []) if not data: break all_orders.extend(data) # 取本页最后一条的时间戳往前推1毫秒作为下一页游标 cursor_ts int(data[-1][ts]) - 1 if len(data) 100: break time.sleep(0.2) # 限频保护 return all_orders逻辑说明这段代码的核心思路是用游标代替页码每次取100条用返回的最后一条订单时间戳往前推1毫秒作为下一页的after值。参数说明instTypeSPOT表示只拉现货订单如果你同时跑杠杆仓位这里要改成MARGIN或在参数里加上相关字段after和before的方向在OKX里容易搞反建议第一次跑的时候先打印前两页的ts值确认它是往前翻还是往后翻。这里有个值得说的细节为什么用data[-1][ts] - 1而不是直接用最后一条的时间戳因为OKX的分页边界是包含式的如果直接用最后一笔订单的ts作为下一页的after值那条订单会被重复拉一次。减掉1毫秒就从源头上避免重复数据这是我在一次对账数据对不平之后学到的血泪经验。4.2 K线等历史数据周期、窗口、缺失值处理历史数据指的是K线这类行情数据。OKX提供两个K线接口市场行情接口和市场历史行情接口。两者的区别是前者虽然支持分页但覆盖的时间窗口比较短主要用于实时画图后者才是拉长时间序列的正确入口单次最多返回300条支持的时间周期从1分钟到1月不等。拉K线的参数主要有四个instId、bar周期、limit数量、before/after分页游标。bar的取值很固定1m、5m、15m、1H、4H、1D等大小写不能乱写写成1h会直接报参数错误。返回的每个数组元素格式是固定的时间戳、开盘价、最高价、最低价、收盘价、成交量、成交额等这个顺序和不少其他交易所正好相反做数据处理的时候别按老经验套字段。def fetch_klines(inst_id: str, bar: str 1H, limit: int 300) - list: 拉取历史K线返回按时间升序排列的列表 params { instId: inst_id, bar: bar, limit: str(limit), } result requests.get( https://www.okx.com/api/v5/market/history-candles, paramsparams, timeout10, ).json() if result[code] ! 0: return [] data result[data] # OKX返回的是时间降序这里反转成升序 data.reverse() return data代码逻辑先按参数请求返回原始数据然后通过reverse把时间序列调成从小到大这一步不处理的话后面做Python量化交易策略回测时会画反图。参数说明limit最大300超过300会被截断或报参数错误要拉一整年的数据正确做法是外层循环改时间窗口用after和before从最新时间往旧时间翻页每次只取一小段。缺失值处理是拉K线最容易被忽视的环节。OKX的K线接口不会帮你填补没有成交的时间段极端行情下某个交易对可能某根1分钟K线不存在返回序列里直接少了那个时间点。做技术指标计算时如果直接用连续序号处理均线会在缺失处出现毛刺正确做法是拿到数据后先按时间戳对齐成一个完整的连续时间轴缺失的成交量填0、价格用前一条填充。这段写出来是因为很多基于历史数据的策略回测看着收益曲线很漂亮实际是被这种数据空洞喂出来的幻觉收益。4.3 本地落库表结构与增量更新思路历史数据拉下来不落库每次重新拉一遍不仅慢还会撞限频。常见方案是SQLite起步单机跑足够表结构按“标的主键”来设计这样天然支持增量更新。CREATE TABLE IF NOT EXISTS kline ( inst_id TEXT NOT NULL, bar TEXT NOT NULL, ts INTEGER NOT NULL, open REAL NOT NULL, high REAL NOT NULL, low REAL NOT NULL, close REAL NOT NULL, volume REAL NOT NULL, PRIMARY KEY (inst_id, bar, ts) ); CREATE TABLE IF NOT EXISTS orders ( inst_id TEXT NOT NULL, ord_id TEXT NOT NULL, side TEXT NOT NULL, ord_type TEXT NOT NULL, px REAL NOT NULL, sz REAL NOT NULL, state TEXT NOT NULL, ts INTEGER NOT NULL, PRIMARY KEY (ord_id) );设计说明两张表各有一个联合主键。kline表的主键是inst_id加bar加ts同一个交易对同一根K线只存一次重复写入用INSERT OR REPLACE就能实现幂等更新orders表以ord_id为主键无论订单状态怎么变都只有一条记录。用SQLite的好处是零部署、单文件不需要额外装数据库服务跟着RAR一起分发也方便。增量更新的思路是“以本地最新时间戳为游标”。每次启动同步任务时先查库里当前币对的最近ts然后从那个时间点之后开始拉。K线数据的增量更新用时间游标把拉取窗口切小避免全量重拉订单数据因为状态是变化的不仅要增量拉新订单还要定期对最近几天的旧订单做状态同步否则filled之后又canceled这种变动会漏掉。import sqlite3 def incremental_kline_sync(inst_id: str, bar: str): 增量同步K线到SQLite conn sqlite3.connect(okx_data.db) cur conn.cursor() cur.execute( SELECT MAX(ts) FROM kline WHERE inst_id? AND bar?, (inst_id, bar), ) row cur.fetchone() latest_ts row[0] if row[0] else 0 # 从本地最新时间戳之后开始拉 print(f本地最新时间戳: {latest_ts}) conn.close()逻辑说明这段代码展示了增量同步的核心骨架——先查本地最大时间戳然后把它作为拉取起点实际请求拉取的逻辑跟4.2类似只是把after参数设成latest_ts。参数说明latest_ts为0时说明本地还没有数据这次运行会从最早的K线开始拉全量有数据则只拉缺口。写入时用INSERT OR REPLACE即使重复拉到同一根K线也会被覆盖为最新值不产生脏数据。5. OKX API调用避坑与常见问题排查五个高频翻车点5.1 时间戳与签名不一致现象所有需要认证的请求都返回401错误信息指向无效签名或时间戳过期。有时同一段代码今天跑得好好的第二天就全部401。原因最常见的原因是签名串里的requestPath没有包含查询参数。GET请求签的是/api/v5/account/balance但实际请求带上了?ccyBTC两边不一致签名自然校验失败。其次是系统时间漂移虚拟机、双系统环境特别容易出现服务器时间比真实时间快或慢了几十秒OKX会拒绝时间误差超过一定范围的请求。另外如果用毫秒时间戳生成签名又在另一个地方重新取了一次时间戳用于Header两边不一致也会导致401。解决先同步系统时间Windows下打开自动同步Linux下用chrony或ntp再检查签名拼接顺序。调试时可以写一个自检函数用固定时间戳、固定方法和路径替换Secret Key跑一遍签名拿输出的签名字符串和官方文档的样例对比。如果样例对得上那问题一定出在参数拼接或时间戳不一致上。5.2 现货/杠杆参数混用现象同样的下单代码把tdMode从cash改成isolated之后返回错误码提示参数错误或交易模式不支持。或者反过来本想在杠杆账户下单结果成交之后发现用的是现货余额。原因tdMode的取值范围是cash、cross、isolated三种但同一个值在不同账户类型下的含义不一样。现货账户只能用cash杠杆账户只能用cross或isolated如果你开了逐仓模式却传了cross订单会被拒。另一个容易混用的参数是instId杠杆交易和现货交易里的交易对ID虽然看起来一样但有的交易对在现货里有、杠杆里没有下单前最好查一遍支持列表。解决把交易模式和订单参数封装成两个独立函数。现货下单函数内部强制tdModecash杠杆下单函数强制tdModeisolated或cross从入口上杜绝混传。RAR里的代码如果是一个统一的order函数建议改成这种分装结构虽然代码量多一点但能少一次实盘事故。5.3 限频返回429与退避策略现象脚本跑一会儿就开始返回429 Too Many Requests报错信息里能看限频窗口长度。更严重的情况是连续触发限频之后接口被暂时拉黑连公开行情都拉不动。原因OKX对接口按IP维度做限频不同接口的限频阈值差异很大。行情类接口频次上限高下单接口低得多。很多人把所有请求用同一个函数发出没有区分接口类型下单接口和行情接口用同样的频率很快就撞线。另外分页拉历史数据时如果循环里不加sleep也很容易触发限频。import time def request_with_retry(request_func, retries: int 3): 带指数退避的重试包装只处理HTTP 429限频 for attempt in range(retries): response request_func() # 返回requests.Response if response.status_code 429: wait 2 ** attempt # 1秒、2秒、4秒 time.sleep(wait) continue return response.json() return response.json()逻辑说明这个包装要放在requests.Request层所以request_func必须返回Response而不是直接返回JSON。指数退避让第一次重试等1秒第二次等2秒第三次等4秒业务错误HTTP 200但业务码非0不会进入重试分支因为状态码不是429这样避免在业务失败时重复发单。如果你的封装已经返回了JSON可以把判断条件从status_code改成result里的限频错误消息效果一样。5.4 历史数据分页方向搞反现象拉历史订单或K线时循环次数很多但数据就是不增长或者有一段时间的数据一直拉不到。打印出来发现每次返回的都是同一批数据。原因OKX的after/before方向和多数人的直觉相反。after取的是该时间戳之后更新的数据before取的是之前的旧数据。如果只想往前翻旧数据结果按惯例传了before接口就会停留在那一批数据附近循环出不来。解决写分页循环之前先打印第一页所有记录的ts字段确定返回数据的时间顺序再决定用哪个参数做游标。建议统一写成“以当前游标为after取旧数据”的模式因为历史数据只会越来越旧游标单调向前不会遇到新数据插入导致重复翻页的问题。这条规则同时适用于订单接口和K线接口我在4.1和4.2里都用了这个模式。5.5 下单成功但查不到仓位现象市价单显示已经成交订单查询状态是filled但账户持仓接口查不到对应仓位导致自动止损逻辑空转或者重复开仓。原因多数情况是持仓查询接口的参数不对。OKX的持仓接口GET /api/v5/account/positions不传instId会返回所有持仓传了instId则精确到该交易对。问题是杠杆仓位的instId格式和现货一样但仓位可能挂在cross或isolated两个不同账户结构下查询时如果漏掉了mgnMode参数某个模式下的仓位就会被隐藏。解决查询持仓时显式带上instId和mgnMode两个参数并检查返回数据的posSide字段。注意双向持仓模式下同一交易对可能同时存在long和short两笔仓位只看一笔容易误判总敞口。排查时把positions接口的完整返回打印出来先确认仓位在不在再确认自己的查询参数有没有漏。养成这个习惯之后我在策略代码里就很少被“幽灵仓位”坑到了。6. 把封装好的API跑在模拟盘上完整链路与性能基线OKX提供了模拟盘环境和实盘共用一套API路径只是请求头不同。在2.3的OKXClient里我已经留了demo参数创建客户端时传demoTrue所有请求就进入了模拟环境。第一次把这个RAR里的代码跑起来我的习惯是先在模拟盘上把整条链路走一遍包括注册一个单独的模拟盘API Key、往模拟账户里领取测试资金然后把现货下单、杠杆开仓、历史订单同步全部打一遍。验证闭环的核心是“链路完整性”先拉一次ticker确认行情通再设置杠杆倍数然后下一个小额限价单、查订单状态、查持仓、撤销订单、最后拉历史记录和K线数据。每一步都打印返回的code和关键字段确认链路是通的再上实盘。我自己在这上面的一个教训是当年第一次接接口时跳过了模拟盘直接上实盘结果杠杆方向设置反了虽然金额不大但那种“代码在替你亏钱”的感觉非常难受。从那以后新代码上线前必跑一遍模拟盘闭环顺手把数据落库也一起验证。性能基线也值得在模拟盘阶段顺手测量。用2.3的客户端连续发20次行情请求打印平均耗时和限频表现。如果单次耗时超过500毫秒先排查网络代理和DNS问题如果频繁429就调大_min_interval。把这几项基线数据记下来上线后对比实盘表现能提前发现网络环境变化。最后把demo开关提成环境变量打通了之后从模拟盘切实盘只需要改.env里的一个布尔值代码不用动。这套方案跑通之后你手里就有了一套能自动拉行情、下单、管杠杆、归档历史数据的Python工具链。后续不管是接WebSocket做实时监控还是把SQLite换成MySQL做多机共享框架都不用推倒重来。希望帮到你。本文还有配套的精品资源点击获取