feat(crypto-t-monitor): v2.3.0 新币自动挑选池 (PICKS=2, POOL_MAX=6)
- 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>
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
# Backtest 使用指南 (crypto-t-monitor v2.0.0)
|
||||
|
||||
## 概述
|
||||
|
||||
`backtest.py` 用 OKX 历史 K 线模拟做T策略,验证参数在历史数据上的胜率和盈亏。
|
||||
|
||||
**适用**: 验证 `atr_multiplier` / `t_qty` / `bar` 参数组合,不是高频回测引擎(单 symbol, 单次模拟)。
|
||||
|
||||
## 快速开始
|
||||
|
||||
```bash
|
||||
# 默认 (ETH, 短期 = 1H K线, 7 天) - 用户偏好
|
||||
python3 ~/.hermes/scripts/crypto/backtest.py ETH
|
||||
|
||||
# 显式指定 4H/30 天 趋势模式
|
||||
python3 ~/.hermes/scripts/crypto/backtest.py BTC --mode trend
|
||||
|
||||
# 自定义 ATR 系数 (默认 0.7)
|
||||
python3 ~/.hermes/scripts/crypto/backtest.py ETH \
|
||||
--days 14 --bar 4H --atr-multiplier 0.5 --t-qty 0.03
|
||||
```
|
||||
|
||||
## 参数说明
|
||||
|
||||
| 参数 | 默认 | 选择 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `symbol` (必填) | - | ETH/BTC/SOL/DOGE/XRP | OKX 永续合约 |
|
||||
| `--mode` | **short** (用户偏好) | short/trend | short = 1H+7天 短线;trend = 4H+30天 趋势 |
|
||||
| `--days` | mode 决定 | 1-90 | 回测窗口天数 |
|
||||
| `--bar` | mode 决定 | 1H/4H/1D | K 线周期(以 mode 默认覆盖) |
|
||||
| `--atr-multiplier` | 0.7 (实测甜点) | 0.3-2.0 | ATR 倍数,越大越保守 |
|
||||
| `--t-qty` | 0.05 | 0.01-1 | 单笔张数 |
|
||||
| `--leverage` | 25 | 5-125 | 杠杆倍数 |
|
||||
| `--ct-val` | 0.1 | 看币种 | 合约面值(代码里 SYMBOL_SPECS) |
|
||||
|
||||
## ATR 倍数选择 (实测 2026-07-10, 200根 1H K线)
|
||||
|
||||
| ATR | 交易/7天 | 胜率 | 总盈亏 |
|
||||
|-----|---------|------|-------|
|
||||
| 0.5 | 124 | 79.0% | $16.36 |
|
||||
| **0.7** | 适中 | **81.6%** ⭐ | **$16.41** |
|
||||
| 1.0 | 偏少 | 78.8% | $11.54 |
|
||||
| 1.5 | 很少 | 66.7% | $4.17 |
|
||||
|
||||
**结论**: 短期模式 ATR=0.7 是甜点(已落地 `crypto/okx_t_monitor.py` v2.0.0)。不要用 0.5(交易过度,手续费吃光)或 1.5(过保守,捕捉不到)。
|
||||
|
||||
## 输出解读
|
||||
|
||||
```
|
||||
📊 ETH 1H 回测 (7 天, mode=short)
|
||||
ATR=0.5 t_qty=0.05 lev=25x
|
||||
|
||||
📥 拉到 200 根 K 线
|
||||
|
||||
📈 回测结果:
|
||||
买入: 62 次
|
||||
卖出: 62 次
|
||||
胜率: 79.0%
|
||||
总盈亏: $16.36
|
||||
最终仓位: 0 (全平)
|
||||
```
|
||||
|
||||
**评估表**:
|
||||
|
||||
| 指标 | 达标 | 警告 |
|
||||
|------|------|------|
|
||||
| 胜率 | ≥ 60% | < 50% |
|
||||
| 总盈亏 | > 0 | < -10 USDT |
|
||||
| 交易频率(7天) | 20-50 次 | > 100 (手续费吃光) |
|
||||
| 最大回撤 | < 15% 资金 | > 25% |
|
||||
|
||||
## 参数调优示例
|
||||
|
||||
```bash
|
||||
# 测试不同 ATR 倍数
|
||||
for m in 0.3 0.5 0.7 1.0 1.5; do
|
||||
echo "=== ATR $m ==="
|
||||
python3 ~/.hermes/scripts/crypto/backtest.py ETH --atr-multiplier $m
|
||||
done
|
||||
|
||||
# 测试不同币种找参数稳健性 (避免过拟合单个币种)
|
||||
for sym in ETH BTC SOL; do
|
||||
echo "=== $sym ==="
|
||||
python3 ~/.hermes/scripts/crypto/backtest.py $sym
|
||||
done
|
||||
|
||||
# 找到最佳参数后才落盘到 okx_t_monitor.py
|
||||
```
|
||||
|
||||
## 🛑 使用禁忌
|
||||
|
||||
- **不要过拟合**: 用 200 根 K 线找出来"最佳参数"对外样本(未来)很可能失效。**每月最多调一次参数**。
|
||||
- **不要同时改多个参数**: 1 个 → 验证 → 再改下一个
|
||||
- **不要在战斗日调参**: 周一调参, 周二之前观察,如果连续 2 单连亏 → 立即退回上次稳定参数
|
||||
|
||||
## 已知问题 + 临时绕路
|
||||
|
||||
### 1. Clash 抽风 → subprocess 20s timeout
|
||||
|
||||
**症状**: `[Command timed out after 20 seconds]`
|
||||
**绕路**:
|
||||
- 手动重试 (90% 概率下次成功)
|
||||
- SSH 跑: `ssh openclaw@vps 'python3 ~/.hermes/scripts/crypto/backtest.py ETH'`
|
||||
|
||||
### 2. 4H K 线拉不够 30 天
|
||||
|
||||
**症状**: `--mode trend --days 30 --bar 4H` 报 "❌ 没拉到数据"
|
||||
**原因**: OKX history-candles 4H 翻页逻辑当前代码有 bug
|
||||
**绕路**: 用 `--mode short --days 28 --bar 1H` (200 根 K 线)
|
||||
|
||||
### 3. 回测假设市价滑点 = 0
|
||||
|
||||
实际 4H K 线内会有 0.05-0.1% 滑点 + taker 手续费 0.05%。**真实盈利 ≈ 回测盈利 × 0.85**。**避免把回测当实盘 max**。
|
||||
|
||||
## 实战工作流
|
||||
|
||||
1. 调参前 baseline: `python3 ~/.hermes/scripts/crypto/backtest.py ETH > /tmp/baseline.txt`
|
||||
2. 改 `atr_multiplier`,观察胜率和总盈亏
|
||||
3. 找到最佳参数后,跑 BTC/SOL 验证(避免过拟合单个币种)
|
||||
4. ≥ 3 个币种一致胜率 > 60% 才推到 `okx_t_monitor.py`
|
||||
5. 实战**先用 0.01 张试水 2-3 天**,验证后再扩大规模
|
||||
|
||||
## 相关文件
|
||||
|
||||
- 脚本: `~/.hermes/scripts/crypto/backtest.py`
|
||||
- 主监控: `~/.hermes/scripts/crypto/okx_t_monitor.py`
|
||||
- ATR 算法详解: `references/level-dynamic-calculation.md` (TODO)
|
||||
- 网络重试机制: `references/api-fallback.md` (TODO)
|
||||
@@ -0,0 +1,115 @@
|
||||
# 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 实现。
|
||||
@@ -0,0 +1,94 @@
|
||||
# Symbol Coverage Pitfall (2026-07-10 实战)
|
||||
|
||||
## 症状
|
||||
|
||||
Cron 推送"💤 静默: 无持仓, 无变化"或"💤 无持仓, 跳过做T",但实际账户**持有 1.45 张 SPCX**(或其他币种)。
|
||||
|
||||
**用户原话**: "一直在提示吗... 这个定时任务没改名称, 是币圈还是股票"。后续他从来没问过是否持仓,但 agent 看到 cron 消息也没去查实际持仓,以为没问题。
|
||||
|
||||
## 根因
|
||||
|
||||
`okx_t_monitor.py` 的 `DEFAULT_SYMBOLS` 是静态列表:
|
||||
```python
|
||||
DEFAULT_SYMBOLS = ['ETH', 'BTC', 'SOL', 'DOGE', 'XRP'] # 漏了 SPCX
|
||||
```
|
||||
|
||||
`monitor()` 循环每个币种拉 OKX 持仓,**只在循环里的币种才被监控**。SPCX 不在列表里 → 永远查不到 → 被视为"无持仓"。
|
||||
|
||||
`SYMBOL_SPECS` 也必须包含该币种,否则 `KeyError` 直接崩。
|
||||
|
||||
**变种 1**: 用户开了小众币种(非主流)做T,监控不到
|
||||
**变种 2**: 用户开了 OKX 交易但用 ccxt 报"unexpected type" 失败的币种
|
||||
|
||||
## 修复:三步
|
||||
|
||||
**1. 把持仓币种加进 `DEFAULT_SYMBOLS`**:
|
||||
```python
|
||||
DEFAULT_SYMBOLS = ['ETH', 'BTC', 'SOL', 'DOGE', 'XRP', 'SPCX'] # + 持仓币
|
||||
```
|
||||
|
||||
**2. 把持仓币种加进 `SYMBOL_SPECS`**(避免 KeyError):
|
||||
```python
|
||||
SYMBOL_SPECS = {
|
||||
'ETH': {'ct_val': 0.1, 'leverage': 25, 't_qty': 0.05, 'min_sz': 0.01},
|
||||
...
|
||||
'SPCX': {'ct_val': 1.0, 'leverage': 5, 't_qty': 0.5, 'min_sz': 0.01},
|
||||
# ⚠️ t_qty 是"每次做T张数", NOT "总持仓"
|
||||
}
|
||||
```
|
||||
|
||||
**3. (推荐) 自动检测持仓币种**:
|
||||
|
||||
```python
|
||||
def get_monitored_symbols():
|
||||
"""从 OKX 实际持仓 + 预设列表合并"""
|
||||
syms = set(DEFAULT_SYMBOLS)
|
||||
try:
|
||||
positions = ex.fetch_positions()
|
||||
for p in positions:
|
||||
if abs(float(p.get('contracts', 0))) > 0.001:
|
||||
inst_id = p.get('instId', '') # e.g. "SPCX-USDT-SWAP"
|
||||
sym = inst_id.replace('-USDT-SWAP', '')
|
||||
syms.add(sym)
|
||||
except Exception as e:
|
||||
print(f"⚠️ 拉持仓失败: {e}")
|
||||
return sorted(syms)
|
||||
```
|
||||
|
||||
`monitor()` 第一行调用 `DEFAULT_SYMBOLS = get_monitored_symbols()`。
|
||||
|
||||
## 防御性编程
|
||||
|
||||
**新加币种时(尤其是用户/交易员信号里出现的)**:立刻同步 `DEFAULT_SYMBOLS` 和 `SYMBOL_SPECS`。否则:
|
||||
- 信号来了推不到做T → 错过机会
|
||||
- 平仓信号到了不被检测 → 持仓过夜
|
||||
- 浮盈大幅波动不报警 → 用户不知道风险
|
||||
|
||||
**最稳**:用 `get_monitored_symbols()` 自动合并 + 当新币种出现时让用户填 SYMBOL_SPECS(告警一次后自动用默认 0.01 张)。
|
||||
|
||||
## 实测教训
|
||||
|
||||
**SPCX 1.45 张空 @ 149.15** 在 2026-07-10 上午被实测发现时,DEFAULT_SYMBOLS 没有,SYMBOL_SPECS 也没有,导致:
|
||||
1. cron 跑 N 次,显示"💤 无持仓"
|
||||
2. 实际有 SPCX 持仓但**变化检测 + 做T 触发都跳过**
|
||||
3. 浮盈变化根本不被监控
|
||||
4. 价格触及 buy1/buy2/sell1/sell2 也不会触发平仓
|
||||
|
||||
**修复后**:
|
||||
- DEFAULT_SYMBOLS += ['SPCX']
|
||||
- SYMBOL_SPECS['SPCX'] = {'ct_val': 1.0, 'leverage': 5, 't_qty': **0.5**(不是 1.45!), 'min_sz': 0.01}
|
||||
- 下次 cron 跑 → 检测 SPCX 价格触及 149.15 ± ATR×0.7 → 自动推送"📍 价格触及"或成交
|
||||
|
||||
## t_qty 重要澄清
|
||||
|
||||
`t_qty` 是**每次做T张数**,不是总持仓大小。
|
||||
- SPCX 持仓 1.45 张,t_qty = 0.5 → 每次 low/high 0.5 张,做满要 3 次
|
||||
- 1.45 张仓位 + 做 T 一次就 1 张 → 仓位瞬间变化 67%,太大
|
||||
|
||||
**规则**:`t_qty ≤ 总仓位 × 0.5`(单笔不超过一半仓位)。
|
||||
|
||||
## 相关决策
|
||||
|
||||
- v2.1 C 方案"有变化时推"上线后,**静默覆盖了**"持仓不在列表"的 bug——之前会推"💤 无持仓"噪音,现在静默不推,bug 隐藏更深。
|
||||
- **必须**每 24 小时或新币种出现时,核对 DEFAULT_SYMBOLS 是否包含全部持仓币种
|
||||
- 实战:应该用 `get_monitored_symbols()` 而不是静态列表
|
||||
Reference in New Issue
Block a user