8.7 KiB
止盈止损策略调整文档
一、背景
策略 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 隔离保证金维持保证金率
约 12%,价格跌 89% 接近强平线。止损设在 -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 时行为与写死值完全一致,
如果未来杠杆变化会自动适配。
# 硬止损(类属性,拿不到 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:
# 原代码(有 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% 出局 |
七、风险提示
- 单次止损亏损极大:从保证金-8% 变为-80%,每次止损亏掉保证金的 80%, 需要严格控制仓位和并发持仓数
- 接近强平线:价格-8% 止损与 OKX 强平线(约-8~9%)非常接近, 急速下跌时止损单可能来不及成交就被强平
- 建议调低仓位:
max_open_trades或stake_amount适当降低,确保单次止损 不至于严重影响总资金 - 需观察实盘表现:参数调整后建议密切观察 1~2 周的交易记录,看止损频率是否下降、 盈亏比是否改善
- 未改 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