255 lines
7.4 KiB
Markdown
255 lines
7.4 KiB
Markdown
# 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/性价比
|
||
- ❌ 查实际持仓数量(不准)
|
||
- ❌ 查可用余额(不准)
|