- NEW_COIN_PICKS=2: 每次扫描后筛 2 个 (按 24h vol 排序) - NEW_COIN_POOL_MAX=6: 永久保留上限, 超限自动裁最早加入 - 30 天内新币 + 24h vol > $1M, 自动加入监控池 - 推 QQ 仅在变化时 (与 v2.1 C 方案一致) - references/: 新增 backtest-usage.md, change-driven-push.md, symbol-coverage-pitfall.md 用户原话: 新币最多保留六个, 每次扫描后筛选 Co-Authored-By: Claude <noreply@anthropic.com>
116 lines
4.7 KiB
Markdown
116 lines
4.7 KiB
Markdown
# Change-Driven Push 推送策略细节 (v2.1)
|
||
|
||
`okx_t_monitor.py` v2.1 的核心: **只在有变化时推 QQ**,其他静默。
|
||
|
||
## 📋 推送触发矩阵
|
||
|
||
| 触发条件 | 推送条目 | 频率 |
|
||
|----------|---------|------|
|
||
| 持仓变化 (`abs(new_pos - old_pos) > 0.001`) | 🔄 "持仓: X → Y 张" | **每次变化** |
|
||
| 价格触及 buy1/buy2/sell1/sell2 (距 < 0.5%) | 📍 "价格触及 buy2=Z (距 W%)" | **每次扫描** |
|
||
| 浮盈 ≥ 5% 且变化 ≥ 3% | 📈/📉 "浮盈变化: X% → Y% (Z%)" | **变化触发** |
|
||
| 做 T 实际成交 | ✅ 完整做T记录 (保留 v2.0 格式) | **每次成交** |
|
||
| 做 T 失败 (API code != 0) | ❌ 失败原因 | **每次失败** |
|
||
| 静默 (无以上 5 种) | (本地 print `💤 静默:`) | **静默** |
|
||
|
||
## 🔧 实现细节 (代码视角)
|
||
|
||
### `find_nearest_level(price, levels, traded_levels) -> str|None`
|
||
|
||
```python
|
||
def find_nearest_level(price, levels, traded_levels):
|
||
"""找价格 0.5% 内最近的关键位, 跨 4 个候选 (buy2/buy1/sell1/sell2)"""
|
||
threshold = 0.005 # 0.5%
|
||
nearest = None
|
||
min_dist = float('inf')
|
||
for name in ['buy2', 'buy1', 'sell1', 'sell2']:
|
||
if levels.get(name) is None:
|
||
continue
|
||
dist = abs(price - levels[name]) / price
|
||
if dist < threshold and dist < min_dist:
|
||
min_dist = dist
|
||
nearest = name
|
||
return nearest
|
||
```
|
||
|
||
**关键**: 同时检查 buy2 (距 -0.7×ATR) 和 sell1 (距 +0.49×ATR)。两个临界区都在 0.5% 内,**只汇报最近的一个**。
|
||
|
||
### `check_changes(sym, price, pos_qty, avg_px, upl, levels, state) -> list[str]`
|
||
|
||
```python
|
||
def check_changes(sym, price, pos_qty, avg_px, upl, levels, state):
|
||
"""返回事件 list (空 list = 无变化)"""
|
||
events = []
|
||
# 1. 持仓变化
|
||
prev_pos = state.get(f'{sym}_prev_pos')
|
||
if prev_pos is not None and abs(pos_qty - prev_pos) > 0.001:
|
||
events.append(f'🔄 持仓变化: {prev_pos:.2f} → {pos_qty:.2f} 张')
|
||
# 2. 价格触及
|
||
nearest = find_nearest_level(price, levels, [])
|
||
if nearest:
|
||
level_price = levels[nearest]
|
||
dist_pct = abs(price - level_price) / price * 100
|
||
events.append(f'📍 价格触及 {nearest}={level_price:.2f} (距 {dist_pct:.2f}%)')
|
||
# 3. 浮盈变化 (多空方向修正)
|
||
if avg_px > 0 and pos_qty != 0:
|
||
leverage = SYMBOL_SPECS.get(sym, {}).get('leverage', 25)
|
||
pos_sign = 1 if pos_qty > 0 else -1
|
||
upl_pct = (price - avg_px) / avg_px * 100 * leverage * pos_sign
|
||
prev_upl_pct = state.get(f'{sym}_prev_upl_pct')
|
||
if prev_upl_pct is not None and abs(upl_pct) >= 5:
|
||
upl_diff = upl_pct - prev_upl_pct
|
||
if abs(upl_diff) >= 3:
|
||
emoji = '📈' if upl_diff > 0 else '📉'
|
||
events.append(f'{emoji} 浮盈变化: {prev_upl_pct:.1f}% → {upl_pct:.1f}% ({upl_diff:+.1f}%)')
|
||
return events
|
||
```
|
||
|
||
### State 持久化
|
||
|
||
`state` 字典除了原有 `traded_levels` 之外,新增两类 key:
|
||
- `{sym}_prev_pos`: 浮点数, 上次持仓张数
|
||
- `{sym}_prev_upl_pct`: 浮点数 or None, 上次浮盈百分比
|
||
|
||
**重要**: **首次 cron 跑** 这些 key 不存在, 不会触发变化 (因为没"上次"可比)。**这是有意为之** — 不让首次运行就推"持仓从无 → 1.45 张"的噪音。
|
||
|
||
## 🚨 Pitfall
|
||
|
||
### 1. 浮盈方向
|
||
**计算**:`(price - avg_px) / avg_px * 100 * leverage * pos_sign`
|
||
|
||
`pos_sign` 是关键 — **空头**是 `pos_qty < 0`, 价格跌时浮盈大, 所以乘 `-1` 让"价格下跌 = 浮盈增加"。
|
||
|
||
不乘 `pos_sign` 会把空头的 📈/📉 推反 — 价格跌了你说"📉 浮盈变小"是反的。
|
||
|
||
### 2. 静默不等于"系统没跑"
|
||
- cron 触发 → `monitor()` 跑 → 无变化 → 本地 print `💤 静默: 有持仓但无变化` + **不推 QQ**
|
||
- 系统正常, 只是没新事件
|
||
|
||
监控 cron 故障排查: 看本地 print 日志 (cron 输出文件 `~/.hermes/cron/output/db03f9255ad0/`)
|
||
|
||
### 3. 首次 cron 的"假静默"
|
||
- 第 1 次跑: 没有 `prev_pos` 基准 → 全是"无变化" → 静默
|
||
- 第 2 次跑起: 才有真正的变化检测
|
||
|
||
**这是有意为之**, 不是 bug。
|
||
|
||
## 🧪 验证测试
|
||
|
||
第一次部署改这版后, 建议:
|
||
1. 手动 `python3 ~/.hermes/scripts/crypto/okx_t_monitor.py` 看本地输出
|
||
2. 确认 cron 跑 5 次 (约 75 分钟) 后**没推 QQ** (静默)
|
||
3. 触发一次手动市价调整 (App 改 1 张), 看下次 cron 跑是否推"持仓变化"
|
||
|
||
## 🔄 v2.1 升级步骤
|
||
|
||
如果你 fork 改了 v2.0 想升 v2.1:
|
||
1. 加 `push_qq(msg)` helper (提取 subprocess 调用)
|
||
2. 加 `find_nearest_level()` 函数
|
||
3. 加 `check_changes()` 函数
|
||
4. 重构 `monitor()` 分两阶段:
|
||
- Phase 1: 收集 `syms_to_check`, 跑变化检测, 推 QQ
|
||
- Phase 2: 只对有持仓币种做 T
|
||
5. 静默分支合并到最后
|
||
|
||
参考 `crypto/okx_t_monitor.py` 的 v2.1 实现。
|