186 lines
7.6 KiB
Markdown
186 lines
7.6 KiB
Markdown
# 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 (容易写反) |