# 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 实现。