# execute 后判定成败的完整 self-check (2026-07-08) ## 触发场景 **用户原话(本次 session)**: "要是我没发现,就一直不推了吗? 重试机制呢" **问题链路**: 熬鹰 ETH short 减仓信号 → 紧跟"已平仓提醒"信号 → process_signal dedup 误跳过 → agent 没自动平 → ETH 涨到 1795 → 用户 ETH short 浮亏扩大到 -$6.58 → 用户质问"自动平仓的retry在哪?" ## 三大failure mode必须能识别 ### 1. execute 静默失败(advisor stdout 看起来 OK,实际没下单) **症状**: - advisor --execute 返回完整 JSON(contracts/margin/tp_price 等字段齐全) - 但 raw REST 查持仓:**没新增任何仓** - `frozenBal` 也没变化 **已知受害币种**(2026-07-08 实测): - SPCX-USDT-SWAP (新开仓 long/short 都失败) - MU-USDT-SWAP (新开仓静默失败,加仓成功) - SKHYNIX-USDT-SWAP (新开仓失败,加仓成功) **agent 必须做的 self-check**(在推QQ之前): ```bash # 1. 立刻 raw REST 查持仓 curl -s -X GET "https://www.okx.com/api/v5/account/positions?instId={SYMBOL}-USDT-SWAP" \ -H "OK-ACCESS-KEY: ..." -H "OK-ACCESS-SIGN: ..." \ -H "OK-ACCESS-TIMESTAMP: ..." -H "OK-ACCESS-PASSPHRASE: ..." | jq . # 2. 看 abs(pos) 是否对应 contracts × leverage # 3. 看 frozenBal 变化值 = contracts × markPx / leverage # 4. 若两者都不匹配 → 静默失败 → 立即 raw REST 手动重下 ``` ### 2. execute 返回 order_id 但实际 Rejected **症状**: stdout 有 `order_id` 字段,但实际 status=Rejected。 **agent 必须做的 self-check**: ```bash # OKX 私有 API 反查订单状态 curl -s -X GET "https://www.okx.com/api/v5/trade/order?instId={SYMBOL}-USDT-SWAP&ordId={ID}" \ -H "OK-ACCESS-KEY: ..." ... | jq '.data[0].state' # state 值: filled / live / canceled / Rejected ``` **禁止**: 看到 stdout 有 order_id 就推"✅ 下单成功" —— 必须反查 state。 ### 3. 平仓信号被 dedup 跳过(本次 session 实测) **症状**: process_signal 返回 `⏭️ 重复信号跳过`,但实际持仓仍存在,平仓信号没触发市价平仓。 **agent 必须做的 self-check**(强化 v4.5.12 章节): ```bash # 当 process_signal 返回 ⏭️ 重复信号跳过时: # 1. raw REST 查同币种持仓 # 2. 若有持仓 + 信号是平仓类型 + 持仓方向与信号同方向 → 立即手动 raw REST 平仓 # 3. 不等下次信号,不等用户质问 ``` **关键判断逻辑**(process_signal.py 应该自动化,但 agent 也得会手动): ``` process_signal 返回值: ⏭️ 重复信号跳过 ↓ raw REST 查持仓 持仓: pos = -2.89 (short) 信号类型: 平仓(close) 信号方向: short 方向一致? ✅ 同方向 → 立即 raw REST 市价全平 reduceOnly=true 方向不一致? ⛔ 反向 → 不动,推"⏭️ 反向持仓不跟平" 无持仓? ⏭️ → 不动,推"⏭️ 无持仓可平" ``` ## 三层验证 checklist (agent 每次 execute 后必跑) ### 第 1 层:raw REST 反查持仓(< 1秒) ```python import json,time,hmac,hashlib,base64,requests # 用 v4.5.2 提供的 okx_get 函数模板 result = okx_get('/api/v5/account/positions', {'instId': f'{symbol}-USDT-SWAP'}) for p in result.get('data', []): pos = abs(float(p.get('pos', 0) or 0)) if pos > 0.001: print(f"✅ 实际持仓: {symbol} {p['posSide']} {pos}张 @ {p['avgPx']}") break else: print(f"⚠️ {symbol} 无持仓 - execute 可能静默失败") ``` ### 第 2 层:raw REST 反查余额变化(< 1秒) ```python result = okx_get('/api/v5/account/balance') for d in result.get('data', [{}])[0].get('details', []): if d['ccy'] == 'USDT': frozen = float(d.get('frozenBal', 0)) avail = float(d.get('availBal', 0)) print(f"USDT: avail={avail:.2f} frozen={frozen:.2f}") # 期望: frozen ≥ contracts × markPx / leverage # 若 frozen < 期望 → 静默失败 ``` ### 第 3 层:订单 status 反查(若 advisor stdout 有 order_id)(< 1秒) ```python result = okx_get('/api/v5/trade/order', {'instId': f'{symbol}-USDT-SWAP', 'ordId': order_id}) if result.get('data'): state = result['data'][0].get('state') if state == 'filled': print(f"✅ 订单已成交: {order_id}") elif state == 'live': print(f"⏳ 订单挂单中: {order_id}") elif state == 'Canceled': print(f"🚫 订单已撤: {order_id}") elif state == 'Rejected': print(f"❌ 订单被拒: {order_id} - 查 OKX App 原因") ``` ## 实战决策表 | 第 1 层 | 第 2 层 | 第 3 层 | 综合判定 | agent 动作 | |---|---|---|---|---| | 有持仓 | frozen ≥ 期望 | filled | ✅ 成功 | 推"已跟单 X 张" | | 有持仓 | frozen ≥ 期望 | live | ⏳ 挂单中 | 推"⏳ 挂单中 @ price" | | 无持仓 | frozen < 期望 | 无 order_id | ❌ 静默失败 | raw REST 手动重下 | | 无持仓 | frozen < 期望 | Rejected | ❌ 拒单 | raw REST 重下 + 调小 size | | 无持仓 | frozen < 期望 | Canceled | 🚫 撤单 | raw REST 重下 | | 有持仓但反向 | - | - | ⛔ dedup 反向 | 不动,推"⏭️ 反向持仓不跟" | | 平仓信号 + 有同向持仓 | - | - | ✅ 应平仓 | raw REST 市价全平 | ## 用户质问 → 强制改进 每次用户质问"为什么没自动处理"时,都应触发本 checklist 复盘: 1. 是哪个 failure mode 触发了? 2. 当时为什么没识别? 3. 把识别规则加进 skill 或 process_signal.py **已经触发的质问(2026-07-08)**: - "你分析这些没有用" → v4.5.2 章节 - "回复侧不要数据" → v4.5.3 章节 - "要是我没发现,就一直不推了吗? 重试机制呢" → **本 reference** - "执行成功但agent说没成" → v4.5.0 + 本 reference 第 1 层 ## 修复优先级 未来如果重写 process_signal.py,这 3 层验证应该作为 post-execute 钩子**内置**到脚本里,不是依赖 agent 手动跑: ```python # 建议的 process_signal.py 流程 def process_signal(text): ... # 解析 + advisor + execute # post-execute hook (新增) if signal_type == 'open' or signal_type == 'add': time.sleep(1) verify_execution(symbol, expected_contracts, expected_margin) ``` ## 一句话总结 **不要相信 stdout。要相信 raw REST 反查持仓 + 余额变化。**