💬 Forum

BollingerBounce_Shorts

vaskosmihaylov/nfi-custom-strategies/user_data/strategies/Bounce/BollingerBounce_Shorts.py · ★3 · ⑂2 · first seen 2026-07-16 · repo updated 2026-07-27

Basics mode: futures interface version: 3
Settings trailing protections startup candle count: 20 hyperopt hyperopt params: 7
Indicators EMA MACD MFI RSI SAR SMA Stochastic talib
Concepts risk_management trailing
Other Config
15 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 →

Source

Download Raw
  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
# --- Do not remove these libs ---
from freqtrade.strategy.interface import IStrategy
from functools import reduce
from pandas import DataFrame
# --------------------------------

import talib.abstract as ta
import freqtrade.vendor.qtpylib.indicators as qtpylib
import numpy
from freqtrade.strategy import CategoricalParameter, DecimalParameter
import Config


class BollingerBounce_Shorts(IStrategy):
    """
    Shorts-only inverse of BollingerBounce.
    Enters on rejection from upper Bollinger band and exits on lower-band flush.
    """

    INTERFACE_VERSION = 3
    can_short: bool = True

    # Keep parameter names aligned with long strategy for familiarity.
    buy_mfi = DecimalParameter(10, 40, decimals=0, default=13.0, space="buy")
    buy_fisher = DecimalParameter(-1, 1, decimals=2, default=-0.81, space="buy")
    buy_bb_gain = DecimalParameter(0.01, 0.10, decimals=2, default=0.04, space="buy")

    buy_mfi_enabled = CategoricalParameter([True, False], default=False, space="buy")
    buy_fisher_enabled = CategoricalParameter([True, False], default=True, space="buy")

    sell_fisher = DecimalParameter(-1, 1, decimals=2, default=-0.62, space="sell")
    sell_hold = CategoricalParameter([True, False], default=True, space="sell")

    startup_candle_count = 20

    minimal_roi = Config.minimal_roi
    trailing_stop = Config.trailing_stop
    trailing_stop_positive = Config.trailing_stop_positive
    trailing_stop_positive_offset = Config.trailing_stop_positive_offset
    trailing_only_offset_is_reached = Config.trailing_only_offset_is_reached
    stoploss = Config.stoploss
    timeframe = Config.timeframe
    process_only_new_candles = Config.process_only_new_candles
    use_exit_signal = Config.use_exit_signal
    exit_profit_only = Config.exit_profit_only
    ignore_roi_if_entry_signal = Config.ignore_roi_if_entry_signal
    order_types = Config.order_types

    @property
    def protections(self):
        return [
            {
                "method": "StoplossGuard",
                "lookback_period_candles": Config.stoploss_guard_lookback_candles,
                "trade_limit": Config.stoploss_guard_trade_limit,
                "stop_duration_candles": Config.stoploss_guard_duration_candles,
                "only_per_pair": True,
            }
        ]

    def informative_pairs(self):
        return []

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe['mfi'] = ta.MFI(dataframe)

        dataframe['sma'] = ta.SMA(dataframe, timeperiod=40)

        macd = ta.MACD(dataframe)
        dataframe['macd'] = macd['macd']
        dataframe['macdsignal'] = macd['macdsignal']

        stoch_fast = ta.STOCHF(dataframe)
        dataframe['fastd'] = stoch_fast['fastd']
        dataframe['fastk'] = stoch_fast['fastk']

        dataframe['rsi'] = ta.RSI(dataframe)
        rsi = 0.1 * (dataframe['rsi'] - 50)
        dataframe['fisher_rsi'] = (numpy.exp(2 * rsi) - 1) / (numpy.exp(2 * rsi) + 1)

        bollinger = qtpylib.weighted_bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
        dataframe['bb_upperband'] = bollinger['upper']
        dataframe['bb_middleband'] = bollinger['mid']
        dataframe['bb_lowerband'] = bollinger['lower']
        dataframe['bb_gain'] = ((dataframe['bb_upperband'] - dataframe['close']) / dataframe['close'])
        dataframe['bb_drop'] = ((dataframe['close'] - dataframe['bb_lowerband']) / dataframe['close'])

        dataframe['ema5'] = ta.EMA(dataframe, timeperiod=5)
        dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10)
        dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50)
        dataframe['ema100'] = ta.EMA(dataframe, timeperiod=100)

        dataframe['sar'] = ta.SAR(dataframe)

        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        conditions = []

        if self.buy_mfi_enabled.value:
            conditions.append(dataframe['mfi'] >= (100 - self.buy_mfi.value))

        if self.buy_fisher_enabled.value:
            conditions.append(dataframe['fisher_rsi'] > (-1 * self.buy_fisher.value))

        # Potential downside room to lower band
        conditions.append(dataframe['bb_drop'] >= self.buy_bb_gain.value)

        # Red rejection candle at upper band
        conditions.append(dataframe['close'] < dataframe['open'])
        conditions.append(
            (dataframe['open'] > dataframe['bb_upperband']) &
            (dataframe['close'] <= dataframe['bb_upperband'])
        )

        conditions.append(dataframe['volume'] > 0)

        if conditions:
            dataframe.loc[
                reduce(lambda x, y: x & y, conditions),
                'enter_short'] = 1

        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        if self.sell_hold.value:
            dataframe.loc[(dataframe['close'].notnull()), 'exit_short'] = 0
            return dataframe

        dataframe.loc[
            (
                (
                    (dataframe['open'] < dataframe['bb_lowerband']) |
                    (dataframe['close'] < dataframe['bb_lowerband'])
                ) |
                (
                    (dataframe['fisher_rsi'] < self.sell_fisher.value) &
                    (dataframe['sar'] < dataframe['close'])
                )
            ),
            'exit_short'] = 1
        return dataframe

    def leverage(self, pair: str, current_time, current_rate: float,
                 proposed_leverage: float, max_leverage: float, entry_tag,
                 side: str, **kwargs) -> float:
        return min(Config.trade_leverage, max_leverage)