# 小资金实盘执行核验 Runbook

> 版本 2026-07-28 · 前置裁决见 `docs/strategy-iterations.md` 文末 07-28 两节
> （漏单机会成本、规模上限）。

## 可以上实盘的策略只有一个

**`VolatilityBreakoutRiskCap`** —— 文件
`freqtrade/strategies/volatility_breakout/v5_d48_riskcap.py`，版本
`5.4-d48-riskcap-forward`。

```
--strategy-path /freqtrade/user_data/strategies/volatility_breakout
--strategy VolatilityBreakoutRiskCap
```

仓库里其余七个策略类**都不可用于本次核验**，原因各不相同：

| 类 | 文件 | 为什么不能跑 |
|---|---|---|
| `VolatilityBreakout` | `volatility_breakout/v5_d48.py` | D48 信号/退出**基类**，`5.3-d48-shortgate`。RiskCap 继承它。可独立运行，但定仓分母未按实际 5% 硬止损封顶——**未通过 07-23 风险门槛，不要用它上实盘** |
| `VolatilityBreakoutD36` | `volatility_breakout/v5_d36.py` | D36 周期变体，07-10 裁定不替换，bot2 已停 |
| `LongBreakout1H` | `research/long_1h.py` | 1h 多头待命变体，**未部署**；四窗口 3/4 薄利，且要等 BTC 重上 200DMA |
| `HighVolMeanRev` | `HighVolMeanRev.py` | 已停用 |
| `RisingVolTrend` | `RisingVolTrend.py` | 已停用 |
| `SmartMoneySwing` | `SmartMoneySwing.py` | 回测未通过 |
| — | `volatility_breakout/risk_cap_math.py` | 不是策略，是被 RiskCap import 的纯定仓函数 |

**最容易搞错的是前两个**：`VolatilityBreakout` 和 `VolatilityBreakoutRiskCap` 信号
与出场完全相同，差别只在定仓——RiskCap 把风险分母封顶到实际硬止损。跑错类不会
报错，只会让每笔仓位偏大，而这正是 07-23 修掉的问题。

> ⚠️ Freqtrade 的 `config.timeframe` 会**静默覆盖**策略类属性。回测必须显式传
> `--timeframe 5m`，并核对结果 zip 里 strategy 段的 `timeframe` 字段；部署时
> config 与策略类必须一致。曾因此把 5m 回测误当成 1h。

## 0. 这次核验是什么，不是什么

**是**：花一笔有上限的钱，买三样 dry-run 结构上拿不到的数据——

| 未知项 | dry-run 为什么拿不到 |
|---|---|
| 真实队列位置 / 真实漏单率 | 官方 dry-run 模型是 "Limit orders fill once the price reaches the defined level"，无队列 |
| 交易所端 stop-market 真实滑点 | dry-run 记录恒为 +0.000%，物理上不可能 |
| 真实 API / 挂单 / 杠杆设置稳定性 | 从未用真 key 下过一单 |

**不是**：验证策略赚不赚钱。

每笔期望约 **45 bp**（保证金口径），单笔标准差约 **480 bp**。20 笔的 t 值约
**0.42**——纯噪音。确认 edge 非零需要约 **450 笔 ≈ 225 天**。

> **本次核验的 PnL 不得用于任何策略判断。** 赚了不代表可以放大，亏了不代表策略
> 坏掉。唯一的产出是执行层指标。这是核验期最容易犯的错。

---

## 1. 金额：280 USDT

可行窗口是 **213 ~ 306 USDT**，两端都是硬约束：

| 边界 | 数值 | 原因 |
|---|---:|---|
| 下界 | 213 | BTC `minQty=0.001` = 63.86 USDT 名义；钱包 × 30% 低于它时 **RiskCap 静默返回 0，BTC 永远不开仓**（`stake < min_stake → return 0`），样本会系统性偏掉最流动的对 |
| 上界 | 306 | POWER 容量上限（1% 参与率，按实际入场 K 线成交额） |

**取 280**：BTC 有 32% 余量（不会因 ATR 波动被卡掉），POWER 参与率 0.91%。

各对最小可下单钱包：BTC 213 · ETH 67 · BNB 19 · 其余九对 17。

---

## 2. 上线前检查清单

### 2.1 交易所侧（**只能由用户操作，Claude 不碰凭据**）

- [ ] 币安 API key：**只开合约交易**
- [ ] **提现权限必须关闭**
- [ ] **IP 白名单锁定 `43.164.75.52`**
- [ ] 12 个 symbol 全部设为 **2x 逐仓**（config 只声明 isolated，倍数在账户侧）
- [ ] 合约账户划入 **280 USDT**，不多不少

### 2.2 用新容器，不要改 bot1

**bot1 dry-run 保持运行**，实盘另起一个服务。这样同一批信号同时有模拟臂和真实臂，
可以直接对比 dry-run 的乐观成交模型和现实差多少——这正是本次要测的东西。
bot3 已停机，容器槽位是空的，2C4G 跑得下。

`docker-compose.yml` 新增（沿用 bot1 的 volumes/重启策略）：

```yaml
  freqtrade-live:
    profiles: ["live"]
    image: freqtradeorg/freqtrade:stable
    restart: unless-stopped
    container_name: freqtrade-live
    volumes:
      - "./user_data:/freqtrade/user_data"
    ports:
      - "127.0.0.1:8083:8080"     # 只绑本机，不接 Cloudflare 隧道
    environment:                  # key 走环境变量，绝不写进 config 文件
      FREQTRADE__EXCHANGE__KEY:    "${BINANCE_KEY}"
      FREQTRADE__EXCHANGE__SECRET: "${BINANCE_SECRET}"
    command: >
      trade
      --logfile /freqtrade/user_data/logs/freqtrade-live-verify.log
      --db-url sqlite:////freqtrade/user_data/tradesv3-live-verify.sqlite
      --config /freqtrade/user_data/config-live-verify.json
      --strategy-path /freqtrade/user_data/strategies/volatility_breakout
      --strategy VolatilityBreakoutRiskCap
```

`BINANCE_KEY` / `BINANCE_SECRET` 放 `/data/freqtrade/.env`（已 gitignore）。
`profiles: ["live"]` 保证日常 `docker compose up -d` **不会**误启实盘容器；
启动必须显式 `--profile live`。

`config-live-verify.json` 相对 bot1 的 `config.json` 只改三处：

```diff
- "dry_run": true,
+ "dry_run": false,
- "dry_run_wallet": 1000,
- "force_entry_enable": true,
+ "force_entry_enable": false,      # 实盘关掉手动开仓入口
```

其余**一律不动**：`timeframe 5m`、`max_open_trades 4`、`entry_pricing.price_side
same`、`unfilledtimeout.entry 10`、`stoploss_on_exchange true`、12 对白名单、
`api_server` 端口保持 8080（容器内），由 compose 映射到宿主 8083。

> ⚠️ 改任何第四项都会让核验失去单因素性质。这次唯一的变量是「真钱 vs 模拟」。

### 2.3 上线前复跑（不依赖实盘，先做）

```bash
# 容量没漂（feather 数据会滞后几天，开仓前重跑）
ssh -i ~/Downloads/tx.pem ubuntu@43.164.75.52 \
  'docker exec -i freqtrade python3 - --entries' \
  < freqtrade/scripts/liquidity_capacity.py

# 单测
python3 -m unittest discover -s freqtrade/scripts/tests
```

确认 POWER 参与率仍 < 1%、BTC 仍可下单，再继续。

### 2.4 起点留痕

- [ ] 备份当前 compose：`cp docker-compose.yml docker-compose.yml.pre-live-<日期>`
- [ ] 数据库 `tradesv3-live-verify.sqlite`、日志 `freqtrade-live-verify.log`，
      **全新文件**，不续用 `tradesv3-v5.sqlite`——实盘与 dry-run 记录必须分离
- [ ] 记录起点 UTC 时间戳，后续所有审计用 `--since` 对齐
- [ ] Observatory 注册表加一条：`id=live`、`label="实盘核验"`、
      `baseUrl=http://freqtrade-live:8080`、`expectedState=online`、
      **`modeHint="live"`**（Observatory 对 live 拒绝管理类操作，这是有意的保护）

启动：

```bash
cd /data/freqtrade && docker compose --profile live up -d freqtrade-live
docker compose logs -f freqtrade-live   # 确认已连上交易所、余额 280、杠杆 2x
```

**第一根 K 线前先确认**：日志里交易所连接成功、`dry_run=false`、钱包余额读到
280 USDT。任一不符立刻 `docker compose stop freqtrade-live`。

---

## 3. 中止条件（**开仓前写死，触发即停，不得事后放宽**）

任一触发即 `docker compose stop`，回来分析后再决定是否继续：

| # | 条件 | 为什么是这个阈值 |
|---|---|---|
| 1 | **真实漏单率 > 20%** | 头号观测项。`same/other` 裁决的安全边际只有 2.9 倍，漏单率到 25% 就打平。dry-run 的 8.5% 是**下界** |
| 2 | 连续 4 笔硬止损 | 8 窗口压力测试中带 Guard 最大回撤 6.64%；连 4 笔止损已超出历史分布 |
| 3 | stop-market 实际价格滑点 > 0.5% | dry-run 报 0.000%；0.5% 在 2x 后是 1pp 账户损失，超过则定仓假设失效 |
| 4 | 任一次 API 故障 / 挂单失败 / 杠杆不符 | 执行基建问题，与策略无关但必须先修 |

`StoplossGuard`（2h 内 2 次止损 → 全局暂停 1h）仍在，是自动的，不替代上面的人工中止。

### 3.1 中止操作（**停容器不等于平仓**）

配置里 `cancel_open_orders_on_exit: false`，所以停掉容器后交易所上的单**不会消失**：

```bash
docker compose stop freqtrade-live
```

停完必须**去币安手工确认三件事**，Claude 不代做：

1. **挂着的入场限价单要撤掉** —— bot 已经不在了，它若成交会留下一个无人管理的仓位
2. **stop-market 止损单要留着** —— 这是持仓的保护，不要动
3. **未平仓位**：决定是留着让交易所端止损管，还是手工平掉；不要留下既无 bot 又无
   止损单的裸仓

> 中止不是紧急撤离。除非是 4 号条件（基建故障），否则先停 bot、保住止损、再分析。

---

## 4. 每日观测

```bash
# 实盘臂：成交率 / 成交延迟 / stop 滑点（--since 用起点时间戳）
ssh -i ~/Downloads/tx.pem ubuntu@43.164.75.52 python3 - \
  --db /data/freqtrade/user_data/tradesv3-live-verify.sqlite \
  --log /data/freqtrade/user_data/logs/freqtrade-live-verify.log.1 \
  --log /data/freqtrade/user_data/logs/freqtrade-live-verify.log \
  --since <起点ISO> < freqtrade/scripts/slippage_audit.py

# 模拟臂对照（同一时间窗，同一批信号）
ssh -i ~/Downloads/tx.pem ubuntu@43.164.75.52 python3 - \
  --db /data/freqtrade/user_data/tradesv3-v5.sqlite \
  --log /data/freqtrade/user_data/logs/freqtrade.log.1 \
  --log /data/freqtrade/user_data/logs/freqtrade.log \
  --since <起点ISO> < freqtrade/scripts/slippage_audit.py
```

**两臂之差就是本次核验的主产出**：dry-run 的成交率是「永远排队首位」假设下的
上界，实盘减去它就是队列位置的真实代价。

每天记录四个数，其余忽略：

| 指标 | dry-run 基线 | 中止线 |
|---|---|---|
| 入场漏单率 | 8.5% | > 20% |
| 入场成交延迟 中位 / P90 / 最大 | 6.6s / 39.8s / 57.0s | 接近 600s 超时线即预警 |
| stop-market 价格滑点 | +0.000%（假） | > 0.5% |
| 连续硬止损 | — | 4 |

> 日志轮转会带走漏单事件（`freqtrade.log.1`）。**审计必须同时扫轮转文件**，否则
> 分母缺失、漏单率被低估。`slippage_audit.py` 支持重复传入 `--log`，并会对轮转边界
> 的重复事件去重。

---

## 5. 结束与回填

**结束条件**：累计 ≥ 20 笔已平仓（按 1.6~2.2 笔/天约需 10~14 天），或任一中止条件触发。

结束后按顺序做：

1. **回填执行假设**：用实测漏单率与 stop 滑点重跑双段回测。若真实漏单率显著高于
   8.5%，`--fee 0.00035` 的入场 maker 前提要重估——它成立的前提就是限价单真的成交。
2. **复核 07-28 裁决**：漏单率 > 20% 则 `same/other` 结论重开（成本 11 bp vs
   收益 = 漏单率 × 45 bp）。
3. **写入实验日志**：`docs/strategy-iterations.md` 正文追加一节，速览裁决表加一行；
   若改变了生产形态或协议标准，同步更新对应小节与「最后更新」日期。
4. **决定资金量**：只有在 1~2 都通过后才谈放大，且**上限仍是容量算出来的
   306 USDT**（12 对），不因核验结果好而上调。

---

## 6. 预期管理

- 280 USDT 跑 20 笔，正常波动范围约 **±40 USDT**；一段坏运气亏 15~20% 完全正常。
- 这笔钱是**测量费用**，亏光也是合格结果。
- 系统当前只做空（BTC 在 200DMA 下方 11.8%）。若 BTC 站上 200DMA，闸门会让系统
  接近闲置——那时核验会停滞，属预期行为，不是故障。
