Files
Hermes-Skills/okx-auto-position/references/v4.5.77-2026-09-09-close-whitelist-classify-update-only.md

203 lines
8.3 KiB
Markdown

# v4.5.77 — close 信号过白名单闸 + classify_signal 新分类 + 配置驱动白名单
**实战日期**: 2026-09-09
**触发 trader**: "熬鹰FOMO合约实盘" / "熬鹰交易信号" (新出现, ≠白名单"熬鹰资本")
**关键 user 原话**: "那是新trader, 不在白名单, 为什么要平?"
---
## 1. 背景
之前白名单闸只拦 `open/add/reduce/adjust_leverage`, `close` 信号走"平仓兜底"逻辑 —— 即便 trader 不匹配,也尝试平仓(因为"平仓是兜底, 即使来源不明也应执行"——这是 v4.5.61 的旧假设)。
但用户实际原意是"只跟熬鹰资本"。其他 trader 的 close 信号**不能**触发平仓。
## 2. 实战复盘
### 触发链路
1. **2026-09-09 12:34 UTC+8**: 新 trader "熬鹰交易信号" 发 ZEC 平仓信号(亏损-$8,348,5x short 334.88张)
2. **process_signal.py** 解析: trader="熬鹰交易信号", signal_type="close"
3. **白名单闸** (line 802 原版): 只拦 open/add/reduce/adjust_leverage, **close 走独立通道**
4. **close 路径** (line 848): 调 `close_position_raw("ZEC", "short")`
5. **proxy 抽风**, `_okx_raw_request` 返回 None → `close_result=None`
6. **TypeError**: `close_result['action']` → 整个 process_signal 崩了(用户看到 "❌ 平仓失败: _okx_raw_request 返回 None (proxy 抽风?) | ZEC")
虽然本次因 proxy 抽风没真执行,但**逻辑漏洞清楚**: 下次 proxy 恢复,这条 close 就会真平你 ZEC short 42 张(盈利约+$0.21)。
### 类似问题:"熬鹰FOMO合约实盘" 也是非白名单 trader
在"熬鹰交易信号"之前, 2026-09-09 早些时候"熬鹰FOMO合约实盘" 也发过 ZEC close 信号,同样是 close 兜底绕过白名单,同样因 proxy 抽风没真平。
## 3. 修复
### 3.1 白名单闸扩展 (process_signal.py)
```python
# 旧 (line 802-803 原版)
if _sig_type_for_gate in ('open', 'add', 'reduce', 'adjust_leverage') and trader not in _TRADER_WHITELIST:
return f"⏭️ 跳过: trader [{trader}] 不在白名单 | {symbol} {side}"
# 新 (v4.5.77)
if _sig_type_for_gate in ('open', 'add', 'reduce', 'adjust_leverage', 'close') and trader not in _TRADER_WHITELIST:
return f"⏭️ 跳过: trader [{trader}] 不在白名单 | {symbol} {side}"
```
### 3.2 classify_signal 新分类 (process_signal.py:247-269)
```python
def classify_signal(text):
if '平仓' in text or '止盈' in text or '止损' in text:
return 'close'
if '减仓' in text:
return 'reduce'
if '加仓' in text or '追仓' in text:
return 'add'
if '修改了杠杆' in text or '调整了杠杆' in text or '调杠杆' in text:
return 'adjust_leverage' # ← 新增
if '仓位实时更新' in text or '实时更新' in text:
return 'update_only' # ← 新增
return 'open'
```
### 3.3 adjust_leverage_raw 函数 (process_signal.py:523)
```python
def adjust_leverage_raw(symbol_usdt, new_leverage):
"""用 raw REST 直接设置杠杆 (跨币种通用)"""
inst_id = f"{symbol_usdt}-USDT-SWAP"
if symbol_usdt == '1000PEPE':
inst_id = 'PEPE-USDT-SWAP'
body = {'instId': inst_id, 'lever': str(new_leverage), 'mgnMode': 'cross'}
resp = _okx_raw_request('POST', '/api/v5/account/set-leverage', body=body) # 注意 body= 关键字
if resp.get('code') == '0':
return {'ok': True, 'raw': resp}
return {'ok': False, 'error': resp.get('msg', 'unknown'), 'raw': resp}
```
### 3.4 adjust_leverage 路径 (process_signal.py:883-899)
```python
if signal_type == 'adjust_leverage':
dedup_conn = init_dedup_db()
if is_duplicate(dedup_conn, text, symbol, trader):
dedup_conn.close()
return "⏭️ 重复信号,跳过"
record_signal(dedup_conn, text, symbol, trader)
dedup_conn.close()
symbol_usdt = fields['symbol']
new_lev = int(fields.get('leverage') or '10')
lev_result = adjust_leverage_raw(symbol_usdt, new_lev)
if lev_result and lev_result.get('ok'):
msg = f"⚙️ **杠杆已调整** | {symbol_usdt} → {new_lev}x"
push_to_qq(msg)
return f"✅ 调杠杆: {symbol_usdt} → {new_lev}x"
else:
err = lev_result.get('error', '未知') if lev_result else 'None 返回'
return f"❌ 调杠杆失败: {err} | {symbol_usdt}"
```
### 3.5 update_only skip 闸 (process_signal.py:805-808)
```python
# 2026-09-01: 实时仓位更新是状态推送,跳过不执行
if _sig_type_for_gate == 'update_only':
return f"⏭️ 跳过: 实时仓位更新信号(非真实开/加/减/平仓) | {symbol} {side}"
```
### 3.6 _okx_raw_request null guard (process_signal.py:841-845)
```python
symbol_usdt = fields['symbol']
signal_side_en = fields.get('side_en', 'long')
close_result = close_position_raw(symbol_usdt, signal_side_en)
# 2026-09-09: proxy 抽风可能让 _okx_raw_request 返回 None, 加 null guard
if not close_result:
return f"❌ 平仓失败: _okx_raw_request 返回 None (proxy 抽风?) | {symbol_usdt}"
action = close_result['action']
```
## 4. whitelist.json 配置
**scripts/whitelist.json**:
```json
{
"_comment": "跟单白名单配置 - 不要在代码里硬编码,改这里。trader=跟单交易员名字,设置在 trader_whitelist 数组。",
"trader_whitelist": ["熬鹰资本"]
}
```
**process_signal.py** 加载逻辑:
```python
_WL_PATH = Path(__file__).parent / 'whitelist.json'
_TRADER_WHITELIST = {'熬鹰资本'} # fallback 默认值(防止配置文件损坏)
try:
import json as _json
_wl = json.loads(_WL_PATH.read_text())
if isinstance(_wl, dict) and isinstance(_wl.get('trader_whitelist'), list):
_TRADER_WHITELIST = set(_wl['trader_whitelist'])
except Exception:
pass # 配置文件失败时用 fallback
```
**铁律**:
- ✅ trader / 币种 / 阈值 / 比例 / flag — 都放配置文件, **绝不硬编码**
- ❌ 禁止 `TRADER_WHITELIST = {'熬鹰资本'}` 直接写在代码里
- 改白名单 → 改 `whitelist.json`, 不需要重写代码或重启
## 5. 本次 session 实战过程中触发的代码修改 (timeline)
| 时间 | 修改 | 文件 | 行号 |
|---|---|---|---|
| 2026-09-09 04:38 | 调杠杆信号处理 (adjust_leverage_raw) | process_signal.py | 523, 883 |
| 2026-09-09 04:42 | 白名单闸 (含配置加载) | process_signal.py | 792-802 |
| 2026-09-09 04:50 | classify_signal 新分类 | process_signal.py | 263-269 |
| 2026-09-09 05:10 | whitelist.json 创建 | scripts/whitelist.json | new |
| 2026-09-09 05:20 | 去掉"性价比低"判断 + 推送去除"性价比"字样 | process_signal.py | 938-940, 730, 1071 |
| 2026-09-09 12:40 | close 闸白名单扩展 + update_only | process_signal.py | 802, 805-808 |
| 2026-09-09 12:42 | _okx_raw_request null guard | process_signal.py | 841-845 |
| 2026-09-09 12:45 | _okx_raw_request body= 参数名修复 | process_signal.py | 530 |
## 6. 测试验证
### 6.1 close 信号白名单闸测试
```bash
sqlite3 ~/.hermes/trading/signal_dedup.db "DELETE FROM processed WHERE 1=1;"
python3 process_signal.py << 'EOF'
【熬鹰交易信号】
🚨 已平仓提醒
【币种】: ZEC
EOF
```
**期望输出**: `⏭️ 跳过: trader [熬鹰交易信号] 不在白名单 | ZEC short`
### 6.2 调杠杆信号测试
```bash
python3 process_signal.py << 'EOF'
【熬鹰资本】
🔧 注意,大佬修改了杠杆 3→5
【币种】: SKHYNIXUSDT|永续|5x
...
EOF
```
**期望输出**: `✅ 调杠杆: SKHYNIX → 5x`
### 6.3 update_only 测试
```bash
python3 process_signal.py << 'EOF'
【熬鹰资本】
📊 仓位实时更新
【币种】: SKHYNIXUSDT|永续|3x
...
EOF
```
**期望输出**: `⏭️ 跳过: 实时仓位更新信号(非真实开/加/减/平仓) | SKHYNIX long`
## 7. 反模式
- ❌ close 信号走"平仓兜底"豁免白名单 → trader 不匹配也平仓,误平风险
- ❌ classify_signal 默认 'open' → "📊 仓位实时更新" 被当 open execute
- ❌ 调杠杆信号走 advisor 加仓路径 → advisor 报"性价比低" 跳过
- ❌ 硬编码白名单 → user 立刻怒, "应该用配置"
- ❌ trader='未知' 闸只拦 open/add/reduce → "熬鹰FOMO" 这种带"熬鹰"前缀但 trader≠白名单 的会绕过
- ❌ _okx_raw_request 不带 null guard → proxy 抽风时崩 TypeError
## 8. 关联文件
- `scripts/whitelist.json` (新)
- `scripts/process_signal.py` (改: classify_signal + 白名单闸 + adjust_leverage_raw + null guard)
- `scripts/okx_position_advisor.py` (未改, 但 adjust_leverage 信号绕开它)
- `scripts/signal_tracker.py` (acquire_lock / release_lock, 用于手动锁)