203 lines
8.3 KiB
Markdown
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, 用于手动锁)
|