104 lines
4.5 KiB
Markdown
104 lines
4.5 KiB
Markdown
# v4.5.27 静默 execute 失败模式 (2026-07-08 实测)
|
|
|
|
## 现象
|
|
|
|
`process_signal.py` 处理加仓信号时,advisor `--execute` 步骤因 SSL/ReadTimeout 抛异常,但 process_signal 只返回:
|
|
```
|
|
⚠️ advisor错误: Traceback (most recent call last)
|
|
File ".../urllib3/response.py", line 905, in _error_catcher
|
|
```
|
|
此时**用户实际持仓无变化**(无冻结余额增加、无可用USDT减少),但 process_signal 已经返回"已推送"给 QQ,**QQ 上写着"已跟单 X 张"是假阳性**。
|
|
|
|
## 真实案例(2026-07-08)
|
|
|
|
- 风寻 SKHY 加仓: advisor SSL抽风,QQ推了但实际未execute
|
|
- 熬鹰 SPCX long 新开仓:advisor SSL抽风,QQ推但实际未下单(连续2次都失败)
|
|
- 熬鹰 SKHY 空单新开仓:同上
|
|
- 熬鹰 MU 新开仓:同上
|
|
|
|
这4个都中了,**用户实际未跟单,但 QQ 显示已跟单**。
|
|
|
|
## 根因
|
|
|
|
1. `process_signal.py` 调 `run_advisor()` 返回 `{'error': '...'}`
|
|
2. 接着进入 `format_message(rec)` 路径,format 不检查 error 状态
|
|
3. 然后 `push_to_qq()` + `record_signal()`,**没有 execute 失败回滚**
|
|
4. 结果:QQ 拿到"已跟单 X 张"推送,但实际持仓0张变化
|
|
|
|
## 二次校验硬约束(已写 SKILL.md v4.5.18)
|
|
|
|
`scripts/process_signal.py` 流程:
|
|
```
|
|
1. parse → fields
|
|
2. classify → signal_type
|
|
3. close 信号 → 走独立通道
|
|
4. 其他信号 → run_advisor() → rec
|
|
5. 若 rec.error:
|
|
❌ 当前: 直接 format + push,QQ看到假的"已跟单"
|
|
✅ 必须: 查 raw REST /api/v5/account/positions,若该币种 pos==0 或未变化,
|
|
推"⚠️ advisor 错误,建议仓位=X张,但 execute 失败,需手动"
|
|
```
|
|
|
|
## 验证execute是否真下单
|
|
|
|
```bash
|
|
# 每次 advisor --execute 后,必须反查
|
|
timeout 30 python3 -c "
|
|
import json,time,hmac,hashlib,base64,requests
|
|
def okx(p,params=None,t=15):
|
|
creds=open('/home/openclaw/.bashrc').read()
|
|
k=s=pw=None
|
|
for l in creds.split('\n'):
|
|
if l.startswith('export OKX_API_KEY='): k=l.split('=',1)[1].strip().strip('\"').strip(\"'\")
|
|
elif l.startswith('export OKX_SECRET='): s=l.split('=',1)[1].strip().strip('\"').strip(\"'\")
|
|
elif l.startswith('export OKX_PASSPHRASE='): pw=l.split('=',1)[1].strip().strip('\"').strip(\"'\")
|
|
ts=time.strftime('%Y-%m-%dT%H:%M:%S.000Z',time.gmtime())
|
|
msg=ts+'GET'+p+(json.dumps(params) if params else '')
|
|
sig=base64.b64encode(hmac.new(s.encode(),msg.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'}
|
|
px={'http':'http://127.0.0.1:7890','https':'http://127.0.0.1:7890'}
|
|
return requests.get(f'https://www.okx.com{p}',params=params or {},headers=h,proxies=px,timeout=t).json()
|
|
pos=okx('/api/v5/account/positions')
|
|
for p in pos.get('data',[]):
|
|
if float(p.get('pos','0') or 0)!=0:
|
|
print(f\"{p['instId']:25s} {p['pos']:>10s}张 avgPx={p['avgPx']} mark={p['markPx']} upl={p['upl']}\")
|
|
bal=okx('/api/v5/account/balance')
|
|
for d in bal['data'][0].get('details',[]):
|
|
if d['ccy']=='USDT':
|
|
print(f\"USDT: availBal={d['availBal']} frozenBal={d['frozenBal']} eq={d['eq']}\")
|
|
"
|
|
```
|
|
|
|
如果该币种 pos 没新增 + frozenBal 没增加 → execute 失败。
|
|
|
|
## 修复方向(待实施)
|
|
|
|
**短期**:在 `process_signal.py` 加 execute 失败兜底:
|
|
|
|
```python
|
|
if 'error' in rec:
|
|
# advisor报错,验证一下真没execute
|
|
time.sleep(3)
|
|
actual_pos = fetch_position(symbol)
|
|
if actual_pos is None or actual_pos == existing_pos:
|
|
# 真没execute
|
|
msg = f"⚠️ {symbol} {side} advisor失败且未下单: {rec['error'][:100]}"
|
|
push_to_qq(msg)
|
|
return f"⚠️ execute失败: {rec['error'][:50]}"
|
|
```
|
|
|
|
**长期**:advisor --execute 路径加 retry + 真错误识别(SSL抽风不算 fatal,应重试一次)。
|
|
|
|
## 实战经验(2026-07-08)
|
|
|
|
1. **advisor SSL抽风时,等30-60秒再 retry 通常可恢复** — OKX API 暂时性网络问题
|
|
2. **小张数(< 0.5张)信号经常 fail execute** — 因为OKX最小变动单位不匹配
|
|
3. **新币种(SPCX/SKHYNIX)首次 execute 经常 fail** — 可能合约 metadata 未完全加载
|
|
4. **失败时advisor 返回 JSON 看起来正常,但无 order_id** — 这就是 v4.5.18 execute 反查硬约束的根因
|
|
|
|
## v4.5.27 决策规则(可入主 SKILL.md)
|
|
|
|
1. advisor 报 SSL/Timeout/Connection 错误 → 自动 retry 一次,间隔 10s
|
|
2. retry 仍失败 → 推"⚠️ {symbol} execute失败,需手动"到 QQ,不推"已跟单"
|
|
3. 用户看到这条 → 自己手动 retry 或 等下次信号
|
|
4. **绝对禁止**在 advisor 报错时推"已跟单 X 张"格式 |