""" Bollinger Bands Bounce Strategy Buy when price touches/crosses below lower Bollinger Band then bounces back. Sell when price touches/crosses above upper Bollinger Band. Uses RSI as confirmation to avoid catching falling knives. Timeframe: 1h Pairs: BTC/USDT, ETH/USDT, SOL/USDT, BNB/USDT """ import numpy as np import pandas as pd from datetime import datetime, timedelta, timezone from pandas import DataFrame from typing import Optional, Union from freqtrade.strategy import ( IStrategy, Trade, Order, PairLocks, BooleanParameter, CategoricalParameter, DecimalParameter, IntParameter, RealParameter, ) import talib.abstract as ta from technical import qtpylib class BollingerBounceStrategy(IStrategy): """ Bollinger Bands Bounce strategy. Buy: Price closes below lower band and RSI confirms oversold. Sell: Price closes above upper band or RSI confirms overbought. """ INTERFACE_VERSION = 3 can_short: bool = False minimal_roi = { "0": 0.05, "60": 0.03, "120": 0.015, "240": 0.0, } stoploss = -0.06 # 6% stop loss # Trailing stop trailing_stop = True trailing_stop_positive = 0.01 trailing_stop_positive_offset = 0.025 trailing_only_offset_is_reached = True timeframe = "1h" process_only_new_candles = True use_exit_signal = True exit_profit_only = False ignore_roi_if_entry_signal = False # Hyperoptable parameters buy_bb_window = IntParameter(15, 30, default=20, space="buy", optimize=True) buy_bb_stds = DecimalParameter(1.5, 3.0, default=2.0, decimals=1, space="buy", optimize=True) buy_rsi_limit = IntParameter(20, 45, default=35, space="buy", optimize=True) sell_rsi_limit = IntParameter(60, 85, default=75, space="sell", optimize=True) startup_candle_count: int = 50 order_types = { "entry": "limit", "exit": "limit", "stoploss": "market", "stoploss_on_exchange": False, } order_time_in_force = {"entry": "GTC", "exit": "GTC"} plot_config = { "main_plot": { "bb_lowerband": {"color": "green"}, "bb_middleband": {"color": "orange"}, "bb_upperband": {"color": "red"}, }, "subplots": { "RSI": { "rsi": {"color": "blue"}, }, }, } def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """Calculate Bollinger Bands and RSI.""" # Bollinger Bands with multiple windows for hyperopt for window in range(15, 31): for std in [1.5, 2.0, 2.5, 3.0]: suffix = f"_{window}_{str(std).replace('.', '')}" bollinger = qtpylib.bollinger_bands( qtpylib.typical_price(dataframe), window=window, stds=std ) dataframe[f"bb_lowerband{suffix}"] = bollinger["lower"] dataframe[f"bb_middleband{suffix}"] = bollinger["mid"] dataframe[f"bb_upperband{suffix}"] = bollinger["upper"] # RSI for confirmation dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14) return dataframe def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """Buy when price is below lower Bollinger Band and RSI confirms oversold.""" std_str = str(self.buy_bb_stds.value).replace(".", "") suffix = f"_{self.buy_bb_window.value}_{std_str}" dataframe.loc[ ( (dataframe["close"] < dataframe[f"bb_lowerband{suffix}"]) & (dataframe["rsi"] < self.buy_rsi_limit.value) & (dataframe["volume"] > 0) ), "enter_long", ] = 1 return dataframe def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """Sell when price is above upper Bollinger Band or RSI overbought.""" std_str = str(self.buy_bb_stds.value).replace(".", "") suffix = f"_{self.buy_bb_window.value}_{std_str}" dataframe.loc[ ( ( (dataframe["close"] > dataframe[f"bb_upperband{suffix}"]) | (dataframe["rsi"] > self.sell_rsi_limit.value) ) & (dataframe["volume"] > 0) ), "exit_long", ] = 1 return dataframe