Files
Hermes-Skills/okx-auto-position/references/v4.5.80-2026-09-09-agent-manual-execute-pitfalls-and-proxy-fallback.md
T

186 lines
7.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# v4.5.80 (2026-09-09) Agent 手动 execute 路径的坑 + Proxy 抽风回退
## 🎯 核心教训:Agent 主动调 raw REST 时的 4 个踩坑
### 坑 1: 我用 reduceOnly 做了"测试下单",但**真实成交了**
**现场 (2026-09-09)**: 我测试 proxy 是否恢复,用了 `reduceOnly:True, sz:0.01` 下了一笔 SKHYNIX 卖单,**预期是空动作**(reduceOnly 只减仓不会开新仓,且 0.01 张最小)。结果真把 SKHYNIX long 0.407 张减仓到 0.397 张。
**根因**:
- reduceOnly 是"如果会开新仓就拒绝",不会因为 sz 小就跳过
- 0.01 张是 OKX 最小变动,真实成交
- 我以为是"测试性 dry-run",实际是**真下单**
**铁律**:
- ❌ 禁止以"测试"为由调 raw REST 下单 (即使是 0.01 张)
- ✅ 调 raw REST 前必须明确这是用户指令或 process_signal 已确认的交易
- ✅ 想测试连接?`check_account.py` (只读) 或 `okx_proxy_test.sh` (纯探活) 都是只读路径
- ❌ 禁止"先下 0.01 张试试",这是真钱
### 坑 2: `_okx_raw_request(method, path, body)` 第三个参数是 `params`,不是 `body`
**现场 (2026-09-09)**: 我加 `adjust_leverage_raw()` 函数,第一次调用 OKX 返回 `"Incorrect json data format"`。
**根因**:
```python
def _okx_raw_request(method, path, params=None, body=None, timeout=15):
...
```
签名是 `body=body` 关键字参数,不是位置参数。我直接传了 `body={...}`,被当成了 `params`,但 POST 不读 params,导致 body 是空 dict → OKX 拒绝。
**修复**:
```python
# 错
resp = _okx_raw_request('POST', '/api/v5/account/set-leverage', body)
# 对
resp = _okx_raw_request('POST', '/api/v5/account/set-leverage', body=body)
```
**铁律**:
- ✅ 调 `_okx_raw_request` 始终用关键字参数 (`body=body, params=params`)
- ❌ 禁止位置参数 (太容易传错)
### 坑 3: 调杠杆失败的 OKX 59108 错误 ≠ 真正"余额不足"
**现场 (2026-09-09)**: 熬鹰调 SKHYNIX 杠杆 5→2,我的 SKHYNIX long 0.39 张@5x (已加仓),想降到 2x:
```
{"code": "59108", "msg": "Your account leverage is too low and has insufficient margins. Please increase the leverage."}
```
**根因**:
- OKX cross 模式账户级杠杆 = max(各币种杠杆)
- 0.39 张 × $1,229 × 1/2 = $240 所需保证金, 你分账户可用 $35
- 但 OKX 实际报错"账户杠杆太低",意思是 cross 模式下账户整体杠杆 < 2x
- 即使只动一个币种, OKX 也按账户级校验
**修不通**:
- ❌ 没法直接降杠杆
- ✅ 路径 A: 加仓 (增大持仓名义价值, 让 2x 保证金要求 < 你账户可用)
- ✅ 路径 B: 平仓 (减小持仓)
- ✅ 路径 C: 切到逐仓模式 `mgnMode: isolated` (需要先平仓再开,信号会被打断)
**给 process_signal 的教训**:
- ✅ 调杠杆失败时,不要无脑重试(肯定还是失败)
- ✅ 在 QQ 推送里说清楚"OKX 拒绝降杠杆,需要加仓或平仓才能降",让 user 决定
- ❌ 禁止"调杠杆失败 → 自动加仓补足 → 错上加错"
### 坑 4: Proxy 抽风时 process_signal 返回 None,我**手动**重试 OKX API 但**没记录** position_history
**现场 (2026-09-09)**: 多次 close 信号因 proxy 抽风报 `_okx_raw_request 返回 None`,我手动 execute `/api/v5/trade/order` 平了 ZEC/SKHYNIX/ETH/SPCX/DOGE。但 `position_history` 没 record → **ghost close** (本次 session 反复出现)。
**修复**:
- ✅ 手动 execute 后,**手动补 record** `signal_history.db::position_history` (不然后续 audit 找不到)
- ✅ 手动 execute 时,trader 标 "manual" 而不是 "熬鹰资本" (区分自动信号 vs 手动救火)
- ✅ 手动 execute 前必须用 `check_account.py` 验证当前实际持仓,不要相信 process_signal 的内部状态
**代码模板** (用于手动救火时记录):
```python
cat > /tmp/log_manual.py << 'PYEOF'
import sys
sys.path.insert(0, '/home/openclaw/.hermes/skills/trading/okx-auto-position/scripts')
from signal_tracker import record_confirmed
record_confirmed(
trader='manual_rescue',
symbol='ZEC-USDT-SWAP',
side='short',
leverage=5,
trader_size=0,
trader_entry=0,
trader_pnl=0,
raw_text='proxy 抽风手动平仓',
outcome='manual_close'
)
PYEOF
```
---
## 🎯 Proxy 抽风稳定回退路径 (2026-09-09 实战提炼)
### 优先级
```
1. process_signal 报错 → sleep 20s 重试 1 次
2. 还是失败 → 不要无限重试, sleep 60s 查 proxy 状态
3. proxy 仍不稳 → 手动 execute raw REST (见下方)
4. 手动 execute 也失败 → 告诉 user 用 OKX app 手动平
5. OKX app 平后 → process_signal 那条 close 信号已被 dedup (msg_hash 已 record), 系统不会再处理
```
### Proxy 状态检测 (不靠 raw REST)
```bash
# 1. 公开 endpoint (不需签名)
timeout 10 curl -sS https://www.okx.com/api/v5/public/time
# 2. proxy 是否在运行
docker ps | grep clash
ss -tlnp | grep 7890
```
### 手动 execute 模板 (process_signal 失败时)
放在 `/tmp/order_XXX.py`, 用完即删:
```python
import json,time,hmac,hashlib,base64,requests
def okx_post(path, body, t=15):
creds=open('/home/openclaw/.bashrc').read()
k=s=pw=None
for l in creds.splitlines():
if l.startswith('export OKX_API_KEY='): k=l.split('=',1)[1].strip().strip('"')
elif l.startswith('export OKX_SECRET='): s=l.split('=',1)[1].strip().strip('"')
elif l.startswith('export OKX_PASSPHRASE='): pw=l.split('=',1)[1].strip().strip('"')
ts=time.strftime('%Y-%m-%dT%H:%M:%S.000Z', time.gmtime())
method='POST'; body_str=json.dumps(body)
pre=ts+method+path+body_str
sig=base64.b64encode(hmac.new(s.encode(),pre.encode(),hashlib.sha256).digest()).decode()
h={'OK-ACCESS-KEY':k,'OK-ACCESS-SIGN':sig,'OK-ACCESS-TIMESTAMP':ts,'OK-ACCESS-PASSPHRASE':pw,'Content-Type':'application/json'}
r=requests.post('https://www.okx.com'+path,headers=h,data=body_str,timeout=t,
proxies={'http':'http://127.0.0.1:7890','https':'http://127.0.0.1:7890'})
return r.json()
# 平仓示例 (reduceOnly=True)
body={"instId":"XXX-USDT-SWAP","tdMode":"cross","side":"buy", # long→buy平,short→sell平
"posSide":"net","ordType":"market","sz":"0.222","reduceOnly":True}
print(json.dumps(okx_post('/api/v5/trade/order', body), ensure_ascii=False, indent=2))
```
**注意**:
- 平 long 用 `side:buy`
- 平 short 用 `side:sell`
- `reduceOnly=True` 必须,避免意外开仓
- sz 必须 ≤ 持仓数量(否则 reject)
- proxy 必须 `127.0.0.1:7890`(国内 VPS 直连会被 GFW 拦)
---
## 🎯 process_signal vs 手动 execute 路径对比
| 维度 | process_signal (自动化) | 手动 execute (救火) |
|---|---|---|
| 触发 | TG 信号到达 | proxy 抽风或信号未处理 |
| 信号源 | 自动从消息解析 | 用户手动复制 |
| 风控 | 白名单闸 + advisor + dedup | 无 (人脑判断) |
| audit trail | 自动 record position_history | **必须手动 record** |
| 用户确认 | 无(全自动) | 用户明确要平 |
| 失败兜底 | sleep 重试 → 推 QQ | 告诉用户用 OKX app |
**关键**:
- process_signal 失败时, 不要无脑手动 execute (跳过风控)
- 手动 execute 必须明确告诉用户"信号 X 触发平仓 Y"
- 手动 execute 后必须 record position_history
---
## 🎯 本节铁律汇总
1. **禁止以"测试"为由调 raw REST 下单**(即使是 0.01 张,也是真钱)
2. **`_okx_raw_request` 始终用关键字参数**(`body=body`, 不要位置参数)
3. **OKX 59108 错误**(调杠杆失败)不要无脑重试,要让 user 决定加仓还是平仓
4. **手动 execute 后必须 record position_history** (audit trail)
5. **proxy 抽风 5 级回退**: process_signal 重试 → 检查 proxy → 手动 execute → 推 user 用 OKX app → 静默
6. **手动平 long 用 buy**, 平 short 用 sell (容易写反)