--- name: v4.5.53-unknown-trader-gate-2026-08-18 description: "2026-08-18 SNDK 做空 0.095 张误开仓 — parse_signal fallback '未知' 没有 gate。v4.5.53 在 process_signal() 主流程加了 trader='未知' 闸。" version: 1.0.0 type: reference status: active --- # v4.5.53 (2026-08-18) 信号源缺失闸 — trader='未知' 直接跳过 ## 事件 **时间**: 2026-08-18 **信号原文**: ``` ✅ SNDK 做空 🟥 2x 新开仓 📊 信号源: 未知 158.00 SNDK(价值$?) 📐 性价比检查 • 盈亏比: 3.0:1 ✅ • 盈利额: +21.40 USDT ✅ ... ``` **实际执行**: - 入场: $1724.76 - 仓位: **0.095 张** ⚠️ (不该开) - TP: $1499.38 - SL: $1799.79 **用户反馈**: "这个没信号源的就不要跟单了" ## 根因分析 `parse_signal()` 在 trader 字段提取不出来时,会**硬编码** `fields['trader'] = '未知'`(参见 `process_signal.py` 第 86 行, 2026-08-07 加的注释: "实在找不到 trader 标签 — 标 '未知' (不要硬编码 'X聚合社区', 会误导)")。 但**主流程 process_signal() 没有任何 gate 去尊重这个 '未知' 标记** —— 它继续往下走: - `is_signal_stale()` → 通过(没时间戳算新鲜) - `classify_signal()` → 'open' - `is_duplicate()` → 新信号 - `run_advisor()` → 返回 rec - `execute_order()` → **真的下了 0.095 张** **为什么有这条 fallback 设计却没 gate**: - 2026-08-07 加 fallback 的目的是:**显示**给用户看时标 "未知",**不要硬编码 'X聚合社区'** 误导 - 当时没人想到"主流程应该拒绝执行 '未知' 来源" - 这是一个**典型 antipattern**: "parser 加了 fallback 让字段看着像合法值,但下游没加 'reject if fallback'" — parser 兜底 ≠ 业务允许 ## 改动 (v4.5.53) **位置**: `scripts/process_signal.py::process_signal()` 在 `is_signal_stale` 之前插闸 ```python _sig_type_for_gate = classify_signal(fields) if trader == '未知' and _sig_type_for_gate in ('open', 'add', 'reduce'): msg = f"""⏭️ **跳过跟单: 信号源未知** ⏭️ 📊 币种: {symbol} {side} {leverage}x 🚫 信号文本里没有【交易员】/👉 跟单就选 / 任何 trader 标签 ❌ 不自动跟单,避免执行来路不明的开仓 💡 如果这条信号确实要跟, 请手动开仓 + 告诉我 trader 名字, 我会加到名单。 """ push_to_qq(msg) # 留痕: tracker outcome=skipped_unknown_source _tracker_record(trader='未知', ..., outcome='skipped_unknown_source') return f"⏭️ 跳过: 信号源未知 | {symbol} {side}" ``` **关键设计**: - **只阻断 open/add/reduce** — close (平仓) 不阻断, 平仓是兜底必须执行 - **推 QQ 通知** — 用户能看到"为什么没跟", 不是静默 - **写 tracker** — outcome='skipped_unknown_source' 留痕, 可查询 - **不修改 parse_signal** — fallback '未知' 保留, 这是正确的语义("我没找到 trader, 我不知道"), 是闸拒绝执行, 不是 parser 假装找到了 ## 防回归检查 (下次类似修复时跑) ```python import sys sys.path.insert(0, '~/.hermes/skills/trading/okx-auto-position/scripts') from process_signal import parse_signal, classify_signal # 测试 1: 合法结构 + trader 缺失 → 应该 skip text = '''【币种】SNDKUSDT 【方向】做空 【杠杆】2x 【仓位大小】158 【仓位价值】$ 【开仓价】1724.76 ''' fields = parse_signal(text) assert fields['trader'] == '未知', f"fallback broken: {fields.get('trader')}" assert classify_signal(fields) == 'open' print('✓ parser fallback + classifier still work — gate will trigger') ``` ```python # 测试 2: 完整 process_signal() 跑一遍 (mock push_to_qq + tracker) import process_signal captured = [] process_signal.push_to_qq = lambda msg: (captured.append(msg), True)[1] result = process_signal.process_signal(text) assert result.startswith('⏭️ 跳过: 信号源未知') assert any('跳过跟单: 信号源未知' in m for m in captured) print('✓ gate fires correctly') ``` ## 通用教训 (class-level pitfall) **Parser fallback without gate is a silent execution hazard.** 任何"parser 提取不到字段 → fallback 到默认值"的代码,如果这个默认值会让**下游误以为是合法值继续执行**,都是这种 bug 的高发地。 检查清单: 1. **parse** 时找不到 → 返回/标记什么? 2. **业务允许**这个 fallback 值进入主流程吗? 3. 如果不允许,**gate 在哪一道**(解析后立即?分类前?执行前?) 4. fallback 时的**通知**机制是什么?(静默跳过会让用户以为没信号,显式推 "跳过了" 才对) 这次的具体教训: - ❌ parser fallback 到 '未知' → 主流程照样执行 → 误开仓 - ✅ parser fallback 到 '未知' → 主流程识别 '未知' → 跳过 + 通知用户 ## 关联 - `SKILL.md` v4.5.53 章节 — 主 skill 已升级到 4.5.53 - `references/signal-source-drift-2026-07-15.md` — trader 名单失效 (相关但不同问题):那个是"知道是哪个 trader 但名单可能错",这个是"连是哪个 trader 都不知道" - `scripts/process_signal.py:86` — parse_signal 的 fallback 源头 - `scripts/process_signal.py` 新闸位置 — 在 is_signal_stale 之前