130 lines
5.1 KiB
Markdown
130 lines
5.1 KiB
Markdown
---
|
||
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 之前 |