7.6 KiB
7.6 KiB
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"。
根因:
def _okx_raw_request(method, path, params=None, body=None, timeout=15):
...
签名是 body=body 关键字参数,不是位置参数。我直接传了 body={...},被当成了 params,但 POST 不读 params,导致 body 是空 dict → OKX 拒绝。
修复:
# 错
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 的内部状态
代码模板 (用于手动救火时记录):
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)
# 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, 用完即删:
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
🎯 本节铁律汇总
- 禁止以"测试"为由调 raw REST 下单(即使是 0.01 张,也是真钱)
_okx_raw_request始终用关键字参数(body=body, 不要位置参数)- OKX 59108 错误(调杠杆失败)不要无脑重试,要让 user 决定加仓还是平仓
- 手动 execute 后必须 record position_history (audit trail)
- proxy 抽风 5 级回退: process_signal 重试 → 检查 proxy → 手动 execute → 推 user 用 OKX app → 静默
- 手动平 long 用 buy, 平 short 用 sell (容易写反)