# 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！
}
```

**切换模式**（需要主账户权限）：
```python
POST /api/v5/account/set-position-mode
{"posMode": "long_short_mode"}  # 或 "net_mode"
```

---

## 2. OCO订单合并（加仓场景）

**问题**：加仓后，旧OCO只覆盖旧仓位，新OCO只覆盖新仓位，导致多个OCO并存。

**错误示例**：
- 持仓5张，OCO(sell 5, SL=1660, TP=1746)
- 加仓1张 → 持仓6张
- 新建OCO(sell 1, SL=1666.8, TP=1775.1)
- 结果：2个OCO并存，旧OCO触发只平5张，剩1张单独走

**正确流程**：
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覆盖全部持仓

**验证**：
```bash
# 查pending OCO
curl -X GET "https://www.okx.com/api/v5/trade/orders-algo-pending?ordType=oco" \
  -H "OK-ACCESS-KEY: $KEY" ...

# 应该只有一个OCO per instId
```

---

## 3. 补推信号必须先查持仓

**场景**：模型断线后补推积压信号。

**错误做法**：直接用历史信号数据推送推荐（如"4,290 ETH加仓到4,455"）。

**正确做法**：
1. 先查当前持仓：`GET /api/v5/account/positions`
2. 再查当前algo orders：`GET /api/v5/trade/orders-algo-pending?ordType=oco`
3. 用**当前持仓数据**而非历史信号数据生成推荐

**案例**：
- 历史信号说"4,290 ETH"
- 实际持仓已是5张（可能中间已有多次变动）
- 用历史数据推"加仓到4,455"是错误的

---

## 4. 遍历查询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"]
```

**注意**：某些类型可能返回错误（如账户未开通该功能），需忽略错误继续。

---

## 5. 密码中含特殊字符

**问题**：`OKX_PASSPHRASE` 含 `$` 等特殊字符时，bash 会尝试变量展开。

**错误**：`export OKX_PASSPHRASE=mikeOkxID$1` → `$1` 展开为空

**正确**：
```bash
export OKX_PASSPHRASE='mikeOkxID$1'  # 单引号
```

**或从文件读取**：
```python
with open("~/.bashrc", "r") as f:
    for line in f:
        if "OKX_PASSPHRASE" in line:
            passphrase = line.split("=", 1)[1].strip().strip('"').strip("'")
```

---

## 6. 凭证变量名：OKX_SECRET（不是OKX_SECRET_KEY）

bashrc里实际变量名是 `OKX_SECRET`，不是 `OKX_SECRET_KEY`。

**脚本内部**：`load_credentials()` 用 `OKX_\w+` 正则匹配，自动兼容，不受影响。

**手动ccxt初始化**（agent写临时Python时）：
```python
# ❌ 错误 — 会 KeyError
creds['OKX_SECRET_KEY']

# ✅ 正确
creds['OKX_SECRET']

# ✅ 兼容写法
secret = creds.get('OKX_SECRET', '') or creds.get('OKX_SECRET_KEY', '')
```

**三个变量**：`OKX_API_KEY`、`OKX_SECRET`、`OKX_PASSPHRASE`

---

## 7. --execute 必须同时带 --rec-json

脚本代码 `if args.execute and args.rec_json:` 要求两个参数同时存在。

**错误**：只传 `--execute` 不传 `--rec-json` → 静默跳过执行，fall through到推荐流程

**正确两步流程**：
```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)"
```

---

## 8. 余额为零时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)
```

**format_signal.py 已加 try/except 处理此场景。**
