💬 Forum

BearPullbackShortBearV3

🏆 League #405 / 1941

Yohalloo/freqtrade/user_data/strategies/BearPullbackShortBearV3.py · ★2 · first seen 2026-07-16 · repo updated 2025-11-22

Basics mode: futures timeframe: 1h interface version: 3
Settings stoploss: -0.15 has minimal roi custom stoploss protections hyperopt hyperopt params: 6
Indicators ADX ATR Bollinger_Bands EMA MACD RSI talib
Concepts mean_reversion risk_management
2 related strategies ( identical code, similar name)

Each tile is a different kind of check — from an instant code lint to full sandboxed backtests and forward tests on recent data. Not sure what a check actually proves? See the FAQ →

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
# -*- coding: utf-8 -*-
"""
BearPullbackShortBearV3 - 熊市回调做空策略(强化版 Regime 过滤)

核心思想:
- 只在“真正的熊市”做空:
  - EMA20 < EMA50 < EMA200
  - MACD < 0 且在 0 轴下持续一段时间
  - ADX > 阈值(趋势强度)
  - 熊市条件连续维持 N 根 K(regime_persistence)后才允许交易
- 熊市中等待反弹到布林上轨附近 + RSI 过热才开空;
- 止损:ATR * mult(2~4),再加 1%~2.2% 的全局上限;
- 止盈:超卖信号 + 盈利 >= 3% / 6% 分两档;
- 移动止损:在大盈利时锁定部分收益;
- 额外的 soft_stop:开仓后短时间内明显走反,在 -1% 附近直接认错。

用途:
- 专门在类似 2022 这种趋势性熊市中使用;
- 对 2021 这种“伪熊市 + 宽震荡”应当被 Regime 过滤掉,不交易。
"""

from datetime import datetime

import numpy as np
import talib.abstract as ta
from pandas import DataFrame

from freqtrade.strategy import IStrategy, IntParameter, DecimalParameter
from freqtrade.persistence import Trade


class BearPullbackShortBearV3(IStrategy):
    """熊市回调做空策略(熊市 Regime 强化版)"""

    INTERFACE_VERSION = 3

    timeframe = "1h"
    can_short = True

    # 兜底硬止损(真正逻辑由 custom_exit / custom_stoploss 接管)
    stoploss = -0.15

    # ROI 完全交给 custom_exit
    minimal_roi = {"0": 100}

    # === 参数区(后续可用 hyperopt 调优) ===
    # 判趋势用 ADX 阈值(熊市要求适中,不必极强)
    adx_threshold = IntParameter(15, 30, default=18, space="buy")

    # 入场过热条件:RSI 阈值(熊市反弹一般 55~70 区间)
    rsi_entry = IntParameter(55, 75, default=60, space="buy")

    # 布林带标准差
    bb_std = DecimalParameter(1.8, 2.6, default=2.0, space="buy")

    # 超时(小时):超过这个时间,盈利或小亏就平仓
    timeout_hours = IntParameter(12, 48, default=24, space="sell")

    # ATR 止损倍数(最大允许亏损 = min(ATR_pct * mult, cap))
    atr_mult = DecimalParameter(2.0, 4.0, default=3.0, space="sell")

    # Regime 持续性要求:熊市条件至少连续多少根 K 后才算真正进入熊市
    regime_persistence = IntParameter(12, 72, default=24, space="buy")

    # === 保护机制 ===
    @property
    def protections(self):
        return [
            {"method": "CooldownPeriod", "stop_duration_candles": 1},
            {
                "method": "StoplossGuard",
                "lookback_period_candles": 24,
                "trade_limit": 5,
                "stop_duration_candles": 6,
                "only_per_pair": False,
            },
        ]

    # === 指标计算 ===
    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # 布林带
        bb = ta.BBANDS(
            dataframe,
            timeperiod=20,
            nbdevup=float(self.bb_std.value),
            nbdevdn=float(self.bb_std.value),
        )
        dataframe["bb_upper"] = bb["upperband"]
        dataframe["bb_middle"] = bb["middleband"]
        dataframe["bb_lower"] = bb["lowerband"]

        # RSI / ADX
        dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14)
        dataframe["adx"] = ta.ADX(dataframe)

        # EMA 趋势线
        dataframe["ema_20"] = ta.EMA(dataframe, timeperiod=20)
        dataframe["ema_50"] = ta.EMA(dataframe, timeperiod=50)
        dataframe["ema_200"] = ta.EMA(dataframe, timeperiod=200)

        # MACD(用于确认长期处于 0 轴下方)
        macd = ta.MACD(dataframe, fastperiod=12, slowperiod=26, signalperiod=9)
        dataframe["macd"] = macd["macd"]
        dataframe["macdsignal"] = macd["macdsignal"]
        dataframe["macdhist"] = macd["macdhist"]

        # ATR 用于动态止损
        dataframe["atr"] = ta.ATR(dataframe, timeperiod=14)

        # --- 熊市 Regime 检测 ---
        adx_thr = int(self.adx_threshold.value)
        pers = int(self.regime_persistence.value)

        # 基础熊市条件:价格与均线空头排列 + MACD < 0
        base_bear = (
            (dataframe["close"] < dataframe["ema_200"])
            & (dataframe["ema_20"] < dataframe["ema_50"])
            & (dataframe["ema_50"] < dataframe["ema_200"])
            & (dataframe["macd"] < 0)
        )

        # 趋势强度:ADX > 阈值
        strong_trend = dataframe["adx"] > adx_thr

        # 原始熊市判定(未考虑持续性)
        raw_bear = base_bear & strong_trend
        dataframe["bear_raw"] = np.where(raw_bear, 1, 0)

        # 持续性过滤:要求 raw_bear 连续 pers 根 K 都为 True
        if pers > 1:
            bear_regime = (
                raw_bear.astype("int").rolling(window=pers, min_periods=pers).sum()
                == pers
            )
        else:
            bear_regime = raw_bear

        dataframe["bear_regime"] = np.where(bear_regime, 1, 0)

        # 预备列
        if "enter_tag" not in dataframe.columns:
            dataframe["enter_tag"] = ""
        if "exit_short" not in dataframe.columns:
            dataframe["exit_short"] = 0

        return dataframe

    # === 入场:熊市中的反弹 + 过热 ===
    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # 只做首次突破上轨的K线,避免上轨附近连环抄顶
        cross_upper = (
            (dataframe["close"] > dataframe["bb_upper"])
            & (dataframe["close"].shift(1) <= dataframe["bb_upper"].shift(1))
        )

        rsi_thr = int(self.rsi_entry.value)

        # 仅在“已确认熊市 Regime”下允许做空
        cond = (
            (dataframe["bear_regime"] == 1)
            & cross_upper
            & (dataframe["rsi"] > rsi_thr)
            & (dataframe["volume"] > 0)
        )

        dataframe.loc[cond, ["enter_short", "enter_tag"]] = (1, "bear_strong")

        return dataframe

    # === 出场信号基准(超卖位置) ===
    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """给出一个“超卖”信号,供 custom_exit 使用:
        价格跌破布林下轨 或 RSI < 30。
        """
        if "exit_short" not in dataframe.columns:
            dataframe["exit_short"] = 0

        oversold = (
            (dataframe["close"] < dataframe["bb_lower"]) | (dataframe["rsi"] < 30)
        ) & (dataframe["volume"] > 0)

        dataframe.loc[oversold, "exit_short"] = 1
        return dataframe

    # === 工具:ATR 动态止损(返回最大允许亏损百分比,正数) ===
    def _calc_max_loss_pct(self, candle) -> float:
        atr = float(candle.get("atr", 0) or 0)
        close = float(candle.get("close", 0) or 1)
        atr_pct = atr / close if close > 0 else 0.01

        mult = float(self.atr_mult.value)
        raw = atr_pct * mult

        # 根据 enter_tag 设置不同的上限(预留扩展位,当前只用 bear_strong)
        enter_tag = candle.get("enter_tag", "") or "bear_strong"

        if enter_tag == "bear_strong":
            cap = 0.022  # 强熊市允许稍宽
        else:
            cap = 0.018  # 预留:弱熊市更窄

        floor = 0.010  # 至少 1%
        return max(floor, min(raw, cap))

    # === 核心:自定义出场逻辑 ===
    def custom_exit(
        self,
        pair: str,
        trade: Trade,
        current_time: datetime,
        current_rate: float,
        current_profit: float,
        **kwargs,
    ):
        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        try:
            df = dataframe.loc[dataframe["date"] <= current_time]
            last = df.iloc[-1] if not df.empty else dataframe.iloc[-1]
        except Exception:
            last = dataframe.iloc[-1]

        is_bear = int(last.get("bear_regime", 0)) == 1
        exit_signal = int(last.get("exit_short", 0))
        max_loss_pct = self._calc_max_loss_pct(last)

        # 交易存活时长(小时)
        duration_hrs = (current_time - trade.open_date_utc).total_seconds() / 3600
        timeout = int(self.timeout_hours.value)

        # 1) 硬止损:动态 ATR 止损
        if current_profit <= -max_loss_pct:
            return "bear_stop_loss"

        # 2) soft_stop:开仓后不久就走反,-1% 附近直接认错
        #    避免拖到最大止损
        if is_bear and duration_hrs < 6:  # 开仓 6 小时内
            if current_profit <= -0.01:
                return "soft_stop"

        # 3) Regime Flip:不再是熊市时,盈利或小亏就走
        if not is_bear:
            if current_profit > -0.005:  # -0.5% 以内
                return "regime_flip"
            # 更深亏损交由硬止损处理

        # 4) 正常熊市中,根据盈利和超卖信号出场
        if exit_signal == 1:
            # 第二目标:盈利 >= 6%
            if current_profit >= 0.06:
                return "tp2_exit"
            # 第一目标:盈利 >= 3%
            if current_profit >= 0.03:
                return "tp1_exit"

        # 5) 超时处理:超过 timeout,小亏/小赢也离场
        if duration_hrs > timeout:
            tag = f"timeout_exit_({timeout}h)"
            if current_profit > 0:
                return tag
            if current_profit > -0.005:
                return tag

        # 其它情况不强制退出
        return None

    # === 动态移动止盈:在大盈利时保护仓位 ===
    def custom_stoploss(
        self,
        pair: str,
        trade: Trade,
        current_time: datetime,
        current_rate: float,
        current_profit: float,
        **kwargs,
    ) -> float:
        """根据浮盈动态上调止损价位:
        - 盈利 > 3%  -> 锁定 0.5%
        - 盈利 > 5%  -> 锁定 2%
        - 盈利 > 8%  -> 锁定 4%
        - 盈利 > 12% -> 锁定 6%
        其它情况返回 1,让 custom_exit / 兜底止损处理。
        """

        if current_profit > 0.12:
            return 0.06
        if current_profit > 0.08:
            return 0.04
        if current_profit > 0.05:
            return 0.02
        if current_profit > 0.03:
            return 0.005

        return 1