# OKX API 关键Pitfalls ## 1. posMode=net_mode vs long_short_mode **问题**:账户可能是 `net_mode`(净头寸)而非 `long_short_mode`(多空分离)。 **检查方法**: ```python GET /api/v5/account/config # 响应中 "posMode": "net_mode" 或 "long_short_mode" ``` **影响**: - `net_mode`:**禁止传 `posSide` 参数**,否则报错 `sCode=51000 "Parameter posSide error"` - `long_short_mode`:**必须传 `posSide`** (long/short) **下单示例(net_mode)**: ```python { "instId": "ETH-USDT-SWAP", "tdMode": "cross", "side": "buy", # buy=开多/平空, sell=开空/平多 "ordType": "market", "sz": "1" # 不传posSide! } ``` **设置杠杆(net_mode)**: ```python { "instId": "ETH-USDT-SWAP", "lever": "25", "mgnMode": "cross" # 不传posSide! } ``` --- ## 2. OCO订单合并(加仓场景) **问题**:加仓后,旧OCO只覆盖旧仓位,新OCO只覆盖新仓位,导致多个OCO并存。 **正确流程**: 1. 查现有OCO:`GET /api/v5/trade/orders-algo-pending?ordType=oco` 2. 找到同instId的旧OCO algoId 3. 删除旧OCO:`POST /api/v5/trade/cancel-algo` → `[{instId, algoId}]` 4. 创建新OCO覆盖全部持仓 --- ## 3. 条件单API参数(2026-07-05新增) **Trigger订单(做T用)**: ```python okx_post('/api/v5/trade/order-algo', { "instId": "ETH-USDT-SWAP", "tdMode": "cross", "side": "buy", "ordType": "trigger", # 用trigger不是conditional "sz": "4", "triggerPx": "1770", "triggerPxType": "last", "orderPx": "-1" # 参数名是orderPx不是ordPx }) ``` **关键错误**: - ❌ `"ordPx": "-1"` → 报错`50014: Parameter orderPx can not be empty` - ❌ 加`"reduceOnly": "true"` → 报错`51205: Reduce Only is not available` - ❌ 用`conditional`类型 → SL触发价不能低于当前价 **Trigger vs Conditional vs OCO**: | 类型 | 用途 | 触发方向 | |------|------|----------| | trigger | 价格到任意方向触发 | 任意 | | conditional | 止损/止盈 | SL不能低于现价 | | oco | 同时设TP+SL | 双腿 | 详见 `references/okx-trigger-orders.md` --- ## 4. OCO的sz必须是lot_sz的整数倍 **问题**:加仓后position=14.77张,但OCO设置sz=14.77时报错 `"Order quantity must be a multiple of the lot size"`。 **解决**:OCO的sz向下取整到lot_sz: ```python oco_sz = int(position_contracts) # 14.77 → 14 ``` --- ## 5. 凭证变量名:OKX_SECRET(不是OKX_SECRET_KEY) bashrc里实际变量名是 `OKX_SECRET`,不是 `OKX_SECRET_KEY`。 **三个变量**:`OKX_API_KEY`、`OKX_SECRET`、`OKX_PASSPHRASE` --- ## 6. --execute 必须同时带 --rec-json 脚本代码 `if args.execute and args.rec_json:` 要求两个参数同时存在。 **正确两步流程**: ```bash # 第1步:获取推荐JSON python3 okx_position_advisor.py --symbol HYPE --side long --leverage 10 --json > /tmp/rec.json # 第2步:执行下单(必须同时带 --execute 和 --rec-json) python3 okx_position_advisor.py --symbol HYPE --side long --leverage 10 --execute --json --rec-json "$(cat /tmp/rec.json)" ``` --- ## 7. 余额为零时ZeroDivisionError **问题**:`recommend_position()` 函数在计算 `margin_pct = total_margin / acct_info['usdt_free'] * 100` 时,如果 `usdt_free=0`(用户满仓),会抛出 `ZeroDivisionError`。 **修复**:在调用 `recommend_position()` 前检查余额: ```python if acct_info['usdt_free'] < 0.01: print("⚠️ 余额不足(可用0 USDT),无法开仓") sys.exit(0) ``` --- ## 8. 密码中含特殊字符 **问题**:`OKX_PASSPHRASE` 含 `$` 等特殊字符时,bash 会尝试变量展开。 **正确**:从文件读取: ```python with open("~/.bashrc", "r") as f: for line in f: if "OKX_PASSPHRASE" in line: passphrase = line.split("=", 1)[1].strip().strip('"').strip("'") ``` --- ## 9. 遍历查询algo orders **问题**:`ordType` 参数不能组合查询,需逐个类型查。 ```python for algo_type in ["oco", "conditional", "trigger", "move_order_stop"]: result = okx_get(f"/api/v5/trade/orders-algo-pending?ordType={algo_type}") # 处理 result["data"] ``` --- ## 10. raw API posSide显示"net" **问题**:net_mode下,`/api/v5/account/positions` 返回的 `posSide` 字段是 `"net"` 而非 `"long"`/`"short"`。 **解决**:用 `pos` 字段判断方向: ```python side = "long" if float(pos) >= 0 else "short" ``` --- ## 11. --close是全平,部分平仓需手动下单(2026-07-06新增) **问题**:advisor脚本的 `--close` 参数会平掉该币种**全部仓位**,无法指定平仓数量。 **部分平仓方法**: ```python from okx_position_advisor import load_credentials, create_exchange creds = load_credentials() exchange = create_exchange(creds) # 卖出指定张数(平多) order = exchange.create_order( symbol='ETH/USDT:USDT', type='market', side='sell', # sell=平多, buy=平空 amount=4, # 指定张数 params={'tdMode': 'cross'} ) ``` **减仓百分比计算**: ```python import math current_contracts = 14.09 reduce_pct = 0.30 # 减三成 close_contracts = math.floor(current_contracts * reduce_pct) # 4张 ``` **⚠️ 注意**:ccxt的`create_order`直接下单,不会自动清理OCO。部分平仓后,旧OCO可能覆盖已不存在的仓位(OKX会自动处理,但最好手动检查)。 --- ## 12. 直接curl调OKX API返回403但ccxt正常(2026-07-06新增) **问题**:用curl+proxy直接调OKX REST API返回403 Forbidden,但通过ccxt(同样走proxy)正常工作。 **可能原因**: - OKX API key绑定了IP白名单,ccxt的请求头与curl不同 - ccxt自动处理了某些认证细节(如nonce、签名格式) **解决**:所有API操作统一用ccxt,不要手写curl。只有ccxt超时时才回退到curl。 **ccxt标准用法**: ```python from okx_position_advisor import load_credentials, create_exchange creds = load_credentials() exchange = create_exchange(creds) exchange.timeout = 30000 # 30s超时 # 查持仓 positions = exchange.fetch_positions(['ETH/USDT:USDT']) active = [p for p in positions if abs(float(p.get('contracts', 0))) > 0] # 查余额 bal = exchange.fetch_balance() free_usdt = bal.get('free', {}).get('USDT', 0) # 下单 order = exchange.create_order('ETH/USDT:USDT', 'market', 'buy', 4, {'tdMode': 'cross'}) ``` --- ## 13. advisor脚本的acct_free和contracts可能不准(2026-07-06新增) **问题**:`okx_position_advisor.py --json` 返回的 `acct_free` 和 `contracts` 字段可能与实际不符。 **实测案例**: - advisor返回:`acct_free=0.97, contracts=0.04` - 实际ccxt查:持仓14.09张ETH多,可用0 USDT(满仓) **原因**:advisor的`recommend_position()`内部计算逻辑可能截断或取整异常,且`acct_free`只反映当时快照(可能已过时)。 **解决**:查持仓和余额必须用ccxt直接查询: ```python positions = exchange.fetch_positions() bal = exchange.fetch_balance() active = [p for p in positions if abs(float(p.get('contracts', 0))) > 0] for p in active: side = '多' if float(p['contracts']) > 0 else '空' print(f"{p['symbol']}: {p['contracts']}张 {side} | 均价: {p.get('entryPrice','-')} | 浮盈: {p.get('unrealizedPnl','-')}") print(f"可用: {bal.get('free',{}).get('USDT',0)} USDT") ``` **advisor脚本用途**: - ✅ 算TP/SL/ATR/性价比 - ❌ 查实际持仓数量(不准) - ❌ 查可用余额(不准)