Python实战:OKX V5 API全流程交易指南(含模拟盘与签名避坑)
最近在帮朋友搭建量化交易系统时,发现OKX的V5 API文档虽然全面,但实际接入过程中有几个关键细节容易踩坑。特别是签名生成和请求头构造部分,不同语言的实现差异可能导致莫名其妙的50113错误。本文将用最直白的方式,从密钥配置到完整下单,手把手带你跑通全流程。
1. 环境准备与密钥配置
在开始前,请确保已创建OKX账户并完成API密钥申请。登录OKX官网,进入"API管理"页面时,注意勾选"交易"权限,并妥善保存以下三组信息:
- API Key:形如
aef0bf16-xxxx-xxxx-xxxx-xxxxxxxxxxxx - Secret Key:32位随机字符串
- Passphrase:自定义的交易密码
推荐使用Python 3.8+环境,主要依赖库如下:
pip install requests python-dotenv安全起见,建议将敏感信息存储在.env文件中:
# .env 示例 OKX_API_KEY=your_api_key OKX_SECRET_KEY=your_secret_key OKX_PASSPHRASE=your_password通过环境变量加载配置更安全:
from dotenv import load_dotenv import os load_dotenv() API_KEY = os.getenv('OKX_API_KEY') SECRET_KEY = os.getenv('OKX_SECRET_KEY') PASSPHRASE = os.getenv('OKX_PASSPHRASE')2. 签名生成的核心细节
签名(Sign)是API请求中最容易出错的环节。V5 API要求对以下要素进行HMAC-SHA256加密:
- 时间戳(UTC格式,精确到毫秒)
- HTTP方法(GET/POST等大写形式)
- 请求路径(如
/api/v5/trade/order) - 请求体(GET请求为空字符串)
关键实现要点:
import base64 import hmac from datetime import datetime def generate_signature(timestamp, method, path, body, secret_key): message = f"{timestamp}{method}{path}{body}" digest = hmac.new( bytes(secret_key, 'utf-8'), bytes(message, 'utf-8'), digestmod='sha256' ).digest() return base64.b64encode(digest).decode('utf-8') # bytes转str关键步骤特别注意:Python的json.dumps()默认会在冒号/逗号后添加空格,而OKX的签名验证对此敏感。比较下面两种JSON格式:
# 默认生成(带空格) {"instId": "BTC-USDT", "tdMode": "cash"} # 紧凑模式(无空格) {"instId":"BTC-USDT","tdMode":"cash"}虽然肉眼难辨,但签名结果完全不同。建议统一使用:
json.dumps(data, separators=(',', ':')) # 移除多余空格3. 请求头构造与模拟交易
完整的请求头需要包含6个关键字段:
| 头字段 | 说明 | 示例值 |
|---|---|---|
| OK-ACCESS-KEY | API密钥 | aef0bf16-... |
| OK-ACCESS-SIGN | 签名 | 8G9gLwJvV... |
| OK-ACCESS-TIMESTAMP | UTC时间戳 | 2024-03-20T08:00:00.000Z |
| OK-ACCESS-PASSPHRASE | 交易密码 | MyPassword123 |
| CONTENT-TYPE | 固定值 | application/json |
| x-simulated-trading | 模拟交易开关 | 1 |
封装成请求构造器:
class OKXClient: BASE_URL = 'https://www.okx.com' def __init__(self, api_key, secret_key, passphrase): self.api_key = api_key self.secret_key = secret_key self.passphrase = passphrase def _get_headers(self, method, path, body=''): timestamp = datetime.utcnow().isoformat()[:-3] + 'Z' return { 'OK-ACCESS-KEY': self.api_key, 'OK-ACCESS-SIGN': generate_signature( timestamp, method, path, body, self.secret_key), 'OK-ACCESS-TIMESTAMP': timestamp, 'OK-ACCESS-PASSPHRASE': self.passphrase, 'Content-Type': 'application/json', 'x-simulated-trading': '1' # 1启用模拟盘,0为实盘 }4. 现货与合约下单实战
4.1 市价单基础参数
不同交易品种的核心参数差异:
现货交易:
tdMode: cash(现金交易)instId: 币对名称,如BTC-USDT
合约交易:
tdMode: cross(全仓)或isolated(逐仓)instId: 合约名称,如BTC-USDT-SWAP
4.2 完整下单示例
import json def place_market_order(client, inst_id, side, amount, td_mode='cash'): path = '/api/v5/trade/order' url = f"{client.BASE_URL}{path}" body = json.dumps({ "instId": inst_id, "tdMode": td_mode, "side": side, # buy/sell "ordType": "market", "sz": str(amount) }, separators=(',', ':')) headers = client._get_headers('POST', path, body) response = requests.post(url, headers=headers, data=body) return response.json() # 使用示例 client = OKXClient(API_KEY, SECRET_KEY, PASSPHRASE) # 现货市价买入0.01 BTC print(place_market_order( client, inst_id="BTC-USDT", side="buy", amount=0.01 )) # 合约全仓市价卖出1 BTC print(place_market_order( client, inst_id="BTC-USDT-SWAP", side="sell", amount=1, td_mode="cross" ))4.3 常见错误排查
当遇到50113 Invalid Sign错误时,建议按以下顺序检查:
- 时间戳是否同步(误差需在30秒内)
- JSON体是否按字母顺序排序
- 冒号/逗号后是否有空格
- 签名前的字符串拼接顺序是否正确
- Secret Key是否包含特殊字符需要转义
一个实用的调试技巧是打印出签名前的原始消息:
print(f"Signing message: {timestamp}{method}{path}{body}")5. 高级功能扩展
5.1 批量下单
通过/api/v5/trade/batch-orders接口,单次最多可提交20个订单:
orders = [ {"instId": "BTC-USDT", "side": "buy", "sz": "0.01"}, {"instId": "ETH-USDT", "side": "sell", "sz": "0.1"} ] body = json.dumps(orders, separators=(',', ':'))5.2 条件单触发
设置止损止盈订单示例:
{ "instId": "BTC-USDT-SWAP", "tdMode": "cross", "side": "sell", "ordType": "trigger", "triggerPx": "50000", # 触发价格 "orderPx": "-1", # -1表示市价 "sz": "1" }5.3 资金费率套利
结合/api/v5/public/funding-rate接口查询费率:
def get_funding_rate(client, inst_id): path = f'/api/v5/public/funding-rate?instId={inst_id}' headers = client._get_headers('GET', path) return requests.get(f"{client.BASE_URL}{path}", headers=headers).json()在实盘切换前,强烈建议先用模拟盘验证策略。记得在测试完成后将x-simulated-trading头改为0,否则所有请求将继续发送到模拟环境。