# 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 (容易写反)