- 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>
4.7 KiB
4.7 KiB
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
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]
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。
🧪 验证测试
第一次部署改这版后, 建议:
- 手动
python3 ~/.hermes/scripts/crypto/okx_t_monitor.py看本地输出 - 确认 cron 跑 5 次 (约 75 分钟) 后没推 QQ (静默)
- 触发一次手动市价调整 (App 改 1 张), 看下次 cron 跑是否推"持仓变化"
🔄 v2.1 升级步骤
如果你 fork 改了 v2.0 想升 v2.1:
- 加
push_qq(msg)helper (提取 subprocess 调用) - 加
find_nearest_level()函数 - 加
check_changes()函数 - 重构
monitor()分两阶段:- Phase 1: 收集
syms_to_check, 跑变化检测, 推 QQ - Phase 2: 只对有持仓币种做 T
- Phase 1: 收集
- 静默分支合并到最后
参考 crypto/okx_t_monitor.py 的 v2.1 实现。