# 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, 用于手动锁)