5.1 KiB
5.1 KiB
name, description, version, type, status
| name | description | version | type | status |
|---|---|---|---|---|
| v4.5.53-unknown-trader-gate-2026-08-18 | 2026-08-18 SNDK 做空 0.095 张误开仓 — parse_signal fallback '未知' 没有 gate。v4.5.53 在 process_signal() 主流程加了 trader='未知' 闸。 | 1.0.0 | reference | 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()→ 返回 recexecute_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 之前插闸
_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 假装找到了
防回归检查 (下次类似修复时跑)
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')
# 测试 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 的高发地。
检查清单:
- parse 时找不到 → 返回/标记什么?
- 业务允许这个 fallback 值进入主流程吗?
- 如果不允许,gate 在哪一道(解析后立即?分类前?执行前?)
- fallback 时的通知机制是什么?(静默跳过会让用户以为没信号,显式推 "跳过了" 才对)
这次的具体教训:
- ❌ parser fallback 到 '未知' → 主流程照样执行 → 误开仓
- ✅ parser fallback 到 '未知' → 主流程识别 '未知' → 跳过 + 通知用户
关联
SKILL.mdv4.5.53 章节 — 主 skill 已升级到 4.5.53references/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 之前