OK交易所Python API封装实战:现货、杠杆与历史数据调用指南
2026/9/23 23:37:40 网站建设 项目流程

简介:这份Python资源包围绕OKEx交易所Web API的调用展开,面向希望用代码接入加密货币市场的开发者与量化交易初学者。内容覆盖杠杆交易、现货交易、历史记录与历史数据获取等核心场景,并涉及MVC架构下的应用组织方式,适合具备Python基础、想搭建自动化交易或行情分析工具的人群。压缩包共12个文件,全部为py脚本,整体约11KB,按功能拆分为工具函数、常量定义、期货/现货/币币杠杆等业务接口、账户管理、WebSocket推送、客户端封装与异常处理等模块,结构清晰便于按需查阅。目前已有187人学习下载。通过这份代码,读者可以了解如何封装HTTP请求、组织交易指令、拉取K线与成交历史,并参考异常处理与接口分层思路,快速搭建自己的行情监控或策略回测脚本,是入门交易所API对接的实用参考。

1. 从一份 ok 交易所 Web API 的 Python 封装包说起

很多人第一次接触 ok 交易所的 Web API,都是被一个具体需求逼的:想把手里的现货网格跑起来,或者想拉一段分钟级 K 线做回测,结果翻官方文档翻到一半就卡在签名和限频上。这份ok交易所的web api调用应用,杠杆,现货交易,历史记录,历史数据等等python.rar就是冲着这个场景来的——它把 okex 的 REST 和 WebSocket 接口用 Python 重新包了一层,拆成spot_api.pyfutures_api.pyswap_api.pylever_api.pyaccount_api.pyett_api.py几个模块,外加utils.pyconsts.pyclient.pyexceptions.py这些公共件。换句话说,你不用从零去拼 HMAC-SHA256 签名,也不用自己维护限频队列,导入对应类就能下单、查持仓、拉历史数据。适合两类人:一是想快速验证策略、不想在底层通信上耗时间的量化新手;二是已经会用 python 做数据分析、但没系统写过交易接口的从业者。下面按「这套包怎么组织 → 怎么跑起来 → 哪里会翻车」的顺序拆。

2. 拆开这个包:模块划分与 REST 签名机制

2.1 目录结构与各模块职责

拿到压缩包解压后,根目录下是一组平铺的.py文件,没有复杂的包层级,这种设计对二次开发很友好——你可以直接把整个目录丢进自己项目的vendor/下,也可以只挑需要的模块拷走。核心文件的分工大致是这样:

文件职责典型调用场景
client.py底层 HTTP 会话、请求发送、限频控制被各 api 模块内部引用
utils.py签名、时间戳、参数拼装、字典排序所有私有接口的前置处理
consts.py域名、路径常量、业务枚举切换实盘/模拟盘时改这里
exceptions.py自定义异常类型捕获 ok 返回的错误码
spot_api.py现货下单、撤单、查订单、查成交现货交易主入口
lever_api.py杠杆账户、借币还币、杠杆下单杠杆交易主入口
futures_api.py交割合约相关接口合约持仓与委托
swap_api.py永续合约相关接口永续持仓与委托
account_api.py账户余额、账单流水资金核对
ett_api.py组合账户相关接口特定业务线
websocket.py行情与私有推送订阅实时行情、成交回报

从工程角度看,这套划分基本沿用了「按业务线拆 api、按公共职责拆工具」的思路。client.pyutils.py是被复用最多的两个文件,改签名逻辑或换请求库,通常只需要动这两处,不会牵连到业务模块。这一点比很多把签名散落在每个接口里的老代码要清爽。

2.2 REST 请求的签名与参数拼装

ok 的私有接口要求对请求参数做签名,流程是:把参数按 key 的 ASCII 升序排列,拼成key=value&key=value的字符串,再拼上时间戳和密钥做 HMAC-SHA256。这套逻辑在utils.py里通常长这样:

import hmac import hashlib import base64 import time def sign(params: dict, secret_key: str, method: str = "GET") -> dict: # 1. 补时间戳,ok 要求 ISO8601 格式,且与服务器时间偏差不能过大 params["timestamp"] = time.strftime("%Y-%m-%dT%H:%M:%S.000Z", time.gmtime()) # 2. 按 key 的 ASCII 升序排序,这是签名能否通过的关键 sorted_items = sorted(params.items(), key=lambda x: x[0]) # 3. 拼成 key=value&key=value 形式 sign_str = "&".join([f"{k}={v}" for k, v in sorted_items]) # 4. HMAC-SHA256 后 base64 编码 mac = hmac.new(secret_key.encode(), sign_str.encode(), hashlib.sha256) params["sign"] = base64.b64encode(mac.digest()).decode() return params

逻辑说明:第一步补时间戳,ok 服务端会校验请求时间与服务器时间的偏差,偏差过大直接返回签名错误,所以本地机器时间要准。第二步排序是签名最容易出错的地方,Python 的sorted默认按字符串比较,和 ok 要求的 ASCII 升序一致,但如果参数里混入了非字符串类型(比如 int 的杠杆倍数),拼字符串前要先str()转换,否则会抛类型错误。第三步拼接时,value 里如果含特殊字符,需要确认是否要 URL 编码——ok 的规则是签名用原始值,发送时才编码,这两步不能混。第四步的secret_key来自 API 管理页面,只显示一次,丢了只能重新生成。

参数说明:params是业务参数加时间戳的字典;secret_key是私钥字符串;method在部分实现里用于区分 GET 和 POST 的签名串构造,ok 的 GET 和 POST 签名规则略有差异,POST 的 body 也要参与签名。如果你拿到的包在 POST 请求上签名总失败,先检查 body 是否被正确序列化并参与了签名串拼接。

2.3 限频与请求节流

ok 对每个接口都有频率限制,比如现货下单通常是每秒若干次,行情类接口按 IP 或按用户维度限。client.py里一般会维护一个简单的令牌桶或时间窗口计数器:

import time class RateLimiter: def __init__(self, max_calls: int, period: float): self.max_calls = max_calls # 窗口内允许的最大请求数 self.period = period # 窗口长度,单位秒 self.calls = [] # 记录每次请求的时间戳 def acquire(self): now = time.time() # 剔除窗口外的旧记录 self.calls = [t for t in self.calls if now - t < self.period] if len(self.calls) >= self.max_calls: sleep_time = self.period - (now - self.calls[0]) time.sleep(sleep_time) self.calls = self.calls[1:] self.calls.append(time.time())

逻辑说明:每次发请求前调用acquire(),如果当前窗口内请求数已达上限,就 sleep 到最早那次请求滑出窗口为止。参数max_callsperiod要按 ok 官方文档里对应接口的限制来设,设大了会被服务端拒绝,设小了会拖慢策略。常见做法是把限频值设成官方限制的 80% 左右,留一点余量应对网络抖动和重试。

3. 跑通第一笔现货与杠杆调用:从初始化到下单

3.1 初始化客户端与账户信息查询

在动真金白银之前,先用只读接口验证签名和网络是否通。以现货为例,初始化通常需要 API Key、Secret Key 和 Passphrase 三样东西:

from spot_api import SpotAPI api = SpotAPI( api_key="your_api_key", secret_key="your_secret_key", passphrase="your_passphrase", is_simulated=True # 先走模拟盘,确认无误再切实盘 ) # 查询账户余额,验证签名是否通过 balance = api.get_account_info() print(balance)

逻辑说明:is_simulated=True时,consts.py里的域名常量会指向模拟盘地址,这一步能过滤掉大部分签名和权限问题。如果这里就报签名错误,先别怀疑代码,去检查三件事:本地时间是否同步、API Key 是否绑定了 IP 白名单、Passphrase 是否输错。参数说明:api_keysecret_key在 ok 的 API 管理页生成,passphrase是创建时自己设的口令,三者缺一不可。查询余额返回的是嵌套字典,里面按币种列出可用、冻结、总额,建议先打印完整结构再取值,不要凭猜。

3.2 现货下单与订单状态轮询

签名通了之后,下一笔最小额度的限价单。现货下单接口一般需要交易对、方向、价格、数量这几个参数:

# 下一笔限价买单,价格和数量按交易对精度要求传 order = api.take_order( instrument_id="BTC-USDT", # 交易对 side="buy", # buy 或 sell price="20000", # 限价,字符串形式避免精度丢失 size="0.001", # 数量,注意最小交易单位 order_type="0" # 0 限价,1 市价,具体看常量定义 ) print(order) # 用返回的 order_id 查状态 detail = api.get_order_info(instrument_id="BTC-USDT", order_id=order["order_id"]) print(detail)

逻辑说明:价格和数量用字符串传,是因为浮点数在序列化时可能出现20000.0这种形式,部分接口对格式敏感。order_type的取值以consts.py里的枚举为准,不同业务线(现货、杠杆、合约)的枚举值可能不一样,别跨模块套用。下单成功后返回的order_id是后续撤单和查询的唯一凭据,建议落库保存,不要只放内存。参数说明:instrument_id是交易对标识,现货是BTC-USDT这种格式;size要满足最小交易量,太小会被拒;市价单不需要price,但部分接口仍要求传占位值。

3.3 杠杆交易的借币、下单与风控参数

杠杆交易比现货多了一层借币逻辑,流程是:先查可借额度,借入后下单,平仓后还币。lever_api.py里通常会有对应方法:

from lever_api import LeverAPI lever = LeverAPI(api_key, secret_key, passphrase, is_simulated=True) # 查某币种的可借额度和已借数量 info = lever.get_borrow_info(currency="USDT") print(info) # 借入 USDT,用于加杠杆买入 lever.borrow(currency="USDT", amount="100", leverage="3") # 杠杆限价买入 lever.take_order( instrument_id="BTC-USDT", side="buy", price="20000", size="0.005", order_type="0" )

逻辑说明:杠杆倍数在借币时指定,不同币种和交易对支持的倍数不同,get_borrow_info返回里会标明上限。借币会产生利息,按小时计,所以借了不用会持续吃成本,策略里要有还币逻辑。参数说明:amount是借入数量,leverage是杠杆倍数,这两个参数共同决定你能开多大仓位。风控上,杠杆账户有维持保证金率,低于阈值会触发强平,代码里要定期查get_account_info里的风险指标,别等爆仓了才发现。

4. 历史数据与 WebSocket:回测数据源和实时推送

4.1 拉取 K 线与成交历史

历史数据是回测的粮食。ok 的 K 线接口一般支持按时间范围分页拉取,spot_api.py里会有类似get_kline的方法:

# 拉取 BTC-USDT 的 1 分钟 K 线,granularity 单位是秒 klines = api.get_kline( instrument_id="BTC-USDT", granularity=60, # 60 秒 = 1 分钟 start="2024-01-01T00:00:00.000Z", end="2024-01-02T00:00:00.000Z" ) for k in klines: print(k)

逻辑说明:granularity的取值是秒数,常见的有 60、300、900、3600、86400,对应 1 分钟到 1 天。分页拉取时,单次返回条数有上限,要循环调整startend直到覆盖目标区间。返回的每条 K 线通常是[时间戳, 开, 高, 低, 收, 量]的数组,落库前先确认字段顺序,不同接口可能略有差异。参数说明:时间用 ISO8601 格式,带毫秒和 Z 后缀;如果接口对单次时间跨度有限制,就按天或按小时切片拉。

4.2 WebSocket 订阅行情与私有回报

实时性要求高的场景,轮询 REST 不够用,得走 WebSocket。websocket.py一般封装了连接、订阅、心跳和重连:

from websocket import WsClient def on_message(msg): # 行情推送和私有成交回报都从这里出来,按 channel 区分 print(msg) ws = WsClient( url="wss://real.okex.com:8443/ws/v3", api_key=api_key, secret_key=secret_key, passphrase=passphrase ) ws.subscribe(["spot/ticker:BTC-USDT", "spot/order:BTC-USDT"]) ws.run(on_message)

逻辑说明:公共频道(行情)不需要鉴权,私有频道(订单、持仓)需要登录,登录消息里要带签名。心跳一般每 30 秒发一次,超时未收会断连,所以run里要有重连逻辑。参数说明:urlconsts.py里定义,模拟盘和实盘地址不同;订阅频道字符串的格式要严格按文档写,写错了不会报错,只是收不到数据,这种静默失败最坑。

5. 避坑与排查:签名、限频、精度这些血泪经验

5.1 签名总失败,先查时间和排序

现象:私有接口一律返回签名错误,公共接口正常。原因:九成是本地时间偏差超过服务端容忍范围,或者参数排序时混入了非字符串值。解决:先ntpdate同步时间,再在utils.py的签名函数里打印排序后的sign_str,和官方文档的示例逐字符比对。如果参数里有嵌套字典或数组,确认序列化方式是否和文档一致。

5.2 下单被拒,多半是精度和最小量

现象:下单返回参数错误,但价格数量看着没问题。原因:交易对的价格精度和数量精度有单独限制,比如 BTC-USDT 价格最多两位小数,数量有最小交易单位。解决:调get_instruments类接口拿到tick_sizelot_size,下单前对价格和数量做取整或截断,别用四舍五入,截断更安全。

5.3 限频触发后疯狂重试,越试越封

现象:请求返回限频错误,代码里立刻重试,结果被封更久。原因:限频是按时间窗口算的,触发后立即重试会持续占用窗口。解决:捕获限频异常后做指数退避,第一次等 1 秒,第二次 2 秒,依次翻倍,同时检查client.py里的限频器参数是否设得比官方限制还大。

5.4 WebSocket 断连不重连,策略变瞎子

现象:跑了一夜,早上发现行情停在凌晨三点。原因:WebSocket 连接被服务端断开后没有重连逻辑,或者重连了但没重新订阅。解决:在run方法里加心跳超时检测,断连后重连并重新发送订阅消息,重连间隔用退避策略,别一秒连十次。

5.5 模拟盘和实盘常量混用

现象:模拟盘跑通的代码,切实盘后接口全 404。原因:consts.py里模拟盘和实盘的域名、路径不同,切换时只改了is_simulated但没同步改其他常量。解决:把所有环境相关的常量集中到一处,切换时只改一个开关,别散落在各个模块里手动改。

6. 把历史数据落库并做一次简单回测验证

数据拉下来不落库,下次还得重拉,既慢又容易触发限频。我一般会先把 K 线写进 SQLite 或本地 Parquet,再做策略验证。以 SQLite 为例:

import sqlite3 conn = sqlite3.connect("klines.db") cur = conn.cursor() cur.execute(""" CREATE TABLE IF NOT EXISTS kline ( instrument_id TEXT, ts INTEGER, open REAL, high REAL, low REAL, close REAL, volume REAL, PRIMARY KEY (instrument_id, ts) ) """) # klines 是前面接口返回的列表,逐条插入,用 INSERT OR IGNORE 去重 for k in klines: cur.execute( "INSERT OR IGNORE INTO kline VALUES (?,?,?,?,?,?,?)", ("BTC-USDT", int(k[0]), float(k[1]), float(k[2]), float(k[3]), float(k[4]), float(k[5])) ) conn.commit()

逻辑说明:主键用(instrument_id, ts)保证同一交易对同一时间戳只存一条,重复拉取时用INSERT OR IGNORE自动跳过,省去手动比对。时间戳统一存整数秒,查询时好做范围过滤。参数说明:ts是 K 线起始时间戳,ok 返回的可能是毫秒,落库前统一转成秒,避免后续查询时单位混乱。

落库之后,用一段简单的均线交叉验证数据可用性:

import pandas as pd df = pd.read_sql("SELECT * FROM kline WHERE instrument_id='BTC-USDT' ORDER BY ts", conn) df["ma5"] = df["close"].rolling(5).mean() df["ma20"] = df["close"].rolling(20).mean() df["signal"] = (df["ma5"] > df["ma20"]).astype(int) df["position"] = df["signal"].shift(1) # 信号次日生效,避免未来函数 df["ret"] = df["close"].pct_change() * df["position"] print("累计收益:", (1 + df["ret"]).prod() - 1)

逻辑说明:shift(1)是关键,信号在收盘后产生,次日才能持仓,否则就是用未来数据回测,结果虚高。参数说明:ma5ma20是短长均线,窗口可以按策略调整;position为 1 表示持有多头,0 表示空仓。这段代码的目的不是验证策略赚不赚钱,而是确认拉下来的数据在时间序列上连续、无重复、无缺口——如果pct_change出现异常大的跳变,多半是数据有断档或单位错误。

从那以后我每次拿到新的交易接口封装包,都强制先跑一遍「查余额 → 下一笔最小单 → 撤单 → 拉一段 K 线落库」这条链路,确认签名、精度、限频、数据格式四件事都对了,再往上叠策略。这套 ok 的 Python 封装把底层通信的脏活包掉了,但签名排序、精度截断、限频退避这些细节,还是得自己心里有数。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询