# 止盈止损策略调整文档 ## 一、背景 策略 `MultiAssetChannelBreakoutV5` 运行在 10x 杠杆的期货模式下(OKX 隔离保证金), timeframe 15m,交易 BTC/USDT、ETH/USDT、TRX/USDT。 原策略的止损阈值是按 **1x 杠杆(无杠杆)** 设计的,但实际运行在 10x 杠杆下。 Freqtrade 的 `current_profit` 和 `stoploss` 都是**保证金维度**的值,10x 杠杆会把价格 变动放大 10 倍,导致所有止损/保本阈值被压缩到原来的 1/10。 ## 二、问题:止损被杠杆压缩到极窄 ### Freqtrade 的杠杆计算公式 ``` current_profit = (价格变动比例) × leverage ← 保证金维度 stop_loss_price = current_price × (1 - stoploss / leverage) ← 实际止损价 ``` ### 改前:阈值被压缩 10 倍 | 参数 | 值(保证金维度) | 实际价格含义 | 问题 | |---|---|---|---| | `stoploss` | -0.08 | 价格跌 **0.8%** 就止损 | 一根 15m K 线正常波动 0.5~1.5%,一根 K 线就被扫出去 | | 保本触发 `>0.05` | 保证金+5% | 价格涨 **0.5%** 就保本 | 刚涨一点就锁死,任何回调都被止 | | 回撤容忍 `return 0.10` | 保证金回撤10% | 容忍价格回撤 **1%** | 正常波动就触发 | | breakeven `open×1.005` | 入场+0.5% | 保本线在入场+**0.5%** | 几乎等于入场价 | ### 实际后果 ``` 入场 → 价格涨 0.5% → 保本启动 → 一根 K 线回调 0.3% → 被止损出局 → 错过后面的趋势 ``` 策略设计是通道突破吃趋势,但止损参数相当于高频炒菜级别,两者严重不匹配。 ## 三、改后:阈值 ×10 还原到价格维度 ### 逐项对照 | 参数 | 改前 | 改后 | 改前价格含义 | 改后价格含义 | |---|---|---|---|---| | `stoploss` | -0.08 | **-0.80** | 价格跌 0.8% 止损 | 价格跌 **8%** 止损 | | 保本触发 | `>0.05` | **`>0.50`** | 价格涨 0.5% 保本 | 价格涨 **5%** 保本 | | 第二档 | `>0.15` | **`>1.50`** | 价格涨 1.5% | 价格涨 **15%** | | 第三档 | `>0.30` | **`>3.00`** | 价格涨 3% | 价格涨 **30%** | | 回撤容忍 | `return 0.10` | **`return 1.0`** | 容忍回撤 1% | 容忍回撤 **10%** | | breakeven | `open×1.005` | **`open×1.05`** | 保本在入场+0.5% | 保本在入场+**5%** | | 兜底 | `return 1.0` | **`return 10.0`** | 止损=$0→被挡→用硬止损 | 同理($0→被挡→用硬止损)| ### 关于 `stoploss` = -0.80 完整 ×10 是 -0.80(保证金-80% = 价格-8%)。10x 杠杆下 OKX 隔离保证金维持保证金率 约 1~2%,价格跌 8~9% 接近强平线。止损设在 -80% 意味着: - 价格跌 8% 时触发止损,离强平还有约 1% 的缓冲 - 最大限度给趋势空间,不会被任何正常波动扫出去 - **风险**:如果止损滑点或急速下跌,可能被交易所强平而止损单未成交 ### 关于兜底 `return 10.0` `return 10.0` 的计算:`stop_price = current × (1 - 10/10) = $0`。止损设在 $0, 比任何现有止损都低,被 Freqtrade 的"止损只升不降"规则挡住,硬止损保留。 这与 1x 下 `return 1.0`(`1 - 1/1 = 0`,也是 $0)行为完全等价。 ## 四、改后代码(trade.leverage 动态联动版) 所有阈值通过 `trade.leverage` 动态计算,当前 10x 时行为与写死值完全一致, 如果未来杠杆变化会自动适配。 ```python # 硬止损(类属性,拿不到 trade.leverage,因 leverage() 固定返回 10.0 故 -0.80 正确) stoploss = -0.80 def custom_stoploss(self, pair, trade, current_time, current_rate, current_profit, **kwargs): L = trade.leverage # 盈利 > 价格+30%: 允许 10% 价格回撤 if current_profit > 0.30 * L: return 0.10 * L # 盈利 > 价格+15%: 允许 10% 价格回撤 if current_profit > 0.15 * L: return 0.10 * L # 盈利 > 价格+5%: 止损上移到保本+5% if current_profit > 0.05 * L: breakeven_target = trade.open_rate * 1.05 return (current_rate - breakeven_target) / current_rate * L # 兜底:$0→被挡→用硬止损(-80% 保证金 = -8% 价格) return L ``` ### 逐项对照(L=10 时与写死值一致) | 表达式 | L=10 | 原写死值 | 价格含义 | |---|---|---|---| | `0.30 × L` | 3.00 | 3.00 | 价格+30% | | `0.15 × L` | 1.50 | 1.50 | 价格+15% | | `0.05 × L` | 0.50 | 0.50 | 价格+5% | | `0.10 × L` | 1.0 | 1.0 | 允许价格回撤10% | | `return L` | 10.0 | 10.0 | $0→被挡→用硬止损 | ## 五、Breakeven 返回值 Bug 修复 ### Bug 描述 freqtrade 内部对 `custom_stoploss` 返回值的处理公式(`trade_model.py:837`): ``` stop_price = current_rate × (1 - 返回值 / leverage) ``` 返回值是**保证金维度**的,freqtrade 内部会再除以 leverage。 原代码的 breakeven 返回值缺少 ×10: ```python # 原代码(有 bug) return (current_rate - breakeven_target) / current_rate # 返回的是价格维度比例,但 freqtrade 当作保证金维度再除以 10 ``` ### Bug 影响 以入场 $100,000、当前价 $108,000 为例: | | 原代码(bug) | 修复后 | |---|---|---| | 返回值 | 0.0278 | 0.278 | | freqtrade 计算 | `108000 × (1 - 0.0278/10)` | `108000 × (1 - 0.278/10)` | | 实际止损价 | **$107,700**(比预期高 $2,700) | **$105,000** ✓ | | 效果 | 止损线被杠杆压缩 10 倍,过于贴近当前价 | 止损线正确钉在保本+5% | 修复方式:返回值乘以 `trade.leverage`(`× L`)。 ## 六、改后行为对比(以 BTC $100,000 入场为例) | 场景 | 改前 | 改后 | |---|---|---| | 入场后跌 0.8% | 保证金-8%,**触发硬止损,亏 $800 出局** | 保证金-8%,离 -80% 远,**继续持有** | | 涨 0.5% 后回调 0.3% | 保本启动,**被止损出局,白干** | 还没到 5% 保本线,**继续持有** | | 涨 5% 后回调 1% | 早被止损了 | 保本启动(止损在+5%),回调 1% **还在持有** | | 涨 15% 后回调 10% | 不可能走到这 | 触发第二档止损,**仍盈利 +5% 出局** | | 涨 30% 后回调 10% | 不可能走到这 | 触发第三档止损,**仍盈利 +20% 出局** | | 跌 8% | 不可能走到这 | **触发硬止损,亏保证金 80% 出局** | ## 七、风险提示 1. **单次止损亏损极大**:从保证金-8% 变为-80%,每次止损亏掉保证金的 80%, 需要严格控制仓位和并发持仓数 2. **接近强平线**:价格-8% 止损与 OKX 强平线(约-8~9%)非常接近, 急速下跌时止损单可能来不及成交就被强平 3. **建议调低仓位**:`max_open_trades` 或 `stake_amount` 适当降低,确保单次止损 不至于严重影响总资金 4. **需观察实盘表现**:参数调整后建议密切观察 1~2 周的交易记录,看止损频率是否下降、 盈亏比是否改善 5. **未改 ROI 止盈**:`minimal_roi = {"0": 100}` 保持不变(等于关闭 ROI 止盈), 仍然依赖信号出场(跌破 141 周期低点),趋势策略让利润跑是合理的 ## 八、部署记录 ### 2026-08-21:止损阈值 ×10 - 将所有保证金维度的止损阈值乘以 10,抵消 10x 杠杆压缩 - `stoploss`: -0.08 → -0.30 → -0.80 - 部署方式:scp + systemctl restart freqtrade - 服务状态:active (running) ### 2026-08-22:trade.leverage 动态联动 + breakeven bug 修复 - 所有写死阈值改为通过 `trade.leverage` 动态计算(6 处改动) - 修复 breakeven 返回值缺少 ×leverage 的 bug(止损线被杠杆压缩 10 倍) - `stoploss` 类属性保持 -0.80(`leverage()` 固定返回 10.0,类属性值正确) - 部署方式:scp + systemctl restart freqtrade - 服务状态:active (running) ### 2026-08-22:config.json stoploss 覆盖修复 - **问题**:前两次部署只改了策略文件,未改 `config.json`。Freqtrade 中 config 的 `stoploss` 会覆盖策略类的 `stoploss` 属性,导致线上实际硬止损仍是旧的 -0.10 (保证金 -10% = 价格 -1%),修改后的 -0.80 从未生效。 - **后果**:trade id=43(ETH/USDT,8/21 22:00 开仓)最高仅 +1.28% 价格, 未达保本触发线,custom_stoploss 返回兜底值被挡,价格跌 1% 即被旧硬止损扫出, 亏保证金 12.45%(-$13.53)。正是本文第二节描述的"改前"失败模式。 - **修复**:`config.json` 的 `"stoploss": -0.1` → `"stoploss": -0.80` - 部署方式:ssh sed + systemctl restart freqtrade - 服务状态:active (running),启动日志 RPC 确认 `Stoploss: -0.8` - **教训**:改策略的 `stoploss` 类属性时,必须同步检查 config.json 是否有 `stoploss` 字段覆盖;两者不一致时 config 优先。后续部署清单应包含此项校验。 - 本地文件:`user_data/strategies/multi_asset_channel_breakout_v5.py` - 远程路径:`vultr:/opt/freqtrade/user_data/strategies/multi_asset_channel_breakout_v5.py` - 远程配置:`vultr:/opt/freqtrade/user_data/config.json`