from freqtrade.strategy import IStrategy from freqtrade.strategy import BooleanParameter, DecimalParameter from pandas import DataFrame import numpy as np import talib.abstract as ta # Import for RSI # -------------------------------- # Strategy: engulfing # Author: Gemini # Version: 3.2 (Fixed Hyperopt Toggles) # -------------------------------- class engulfing(IStrategy): INTERFACE_VERSION = 3 # ----------------------------------- # HYPEROPT PARAMETERS # NOTE: Only parameters with optimize=True are tuned by Hyperopt. # ----------------------------------- # Buy Filters (FIXED TO OPTIMIZED VALUES) use_volume_filter = BooleanParameter(default=True, space='buy', optimize=False) use_trend_filter = BooleanParameter(default=False, space='buy', optimize=False) use_body_size_filter = BooleanParameter(default=True, space='buy', optimize=False) use_risk_filter = BooleanParameter(default=False, space='buy', optimize=False) # 🛑 ENABLE OPTIMIZATION FOR THE NEW RSI FILTER 🛑 use_rsi_filter = BooleanParameter(default=True, space='buy', optimize=True) # Filter Values (FIXED TO OPTIMIZED VALUES) volume_multiplier = DecimalParameter(1.0, 3.0, decimals=2, default=2.83, space='buy', optimize=False) risk_threshold = DecimalParameter(0.1, 0.8, decimals=2, default=0.33, space='buy', optimize=False) # 🛑 ENABLE OPTIMIZATION FOR THE NEW RSI THRESHOLD 🛑 rsi_buy_threshold = DecimalParameter(30.0, 70.0, decimals=1, default=60.0, space='buy', optimize=True) # Stoploss Parameter (FIXED TO OPTIMIZED VALUE) stoploss_param = DecimalParameter( -0.20, -0.05, decimals=3, default=-0.088, space='stoploss', optimize=False ) # Trailing Stop Parameters (FIXED TO OPTIMIZED VALUES) tsp_param = DecimalParameter(0.005, 0.03, decimals=3, default=0.335, space='trailing_stop', optimize=False) tsp_offset_param = DecimalParameter(0.01, 0.05, decimals=3, default=0.373, space='trailing_stop', optimize=False) # ----------------------------------- # STOPLOSS PROPERTY @property def stoploss(self): return self.stoploss_param.value @stoploss.setter def stoploss(self, value: float): self.stoploss_param.value = value # TRAILING_STOP_POSITIVE PROPERTY @property def trailing_stop_positive(self): return self.tsp_param.value @trailing_stop_positive.setter def trailing_stop_positive(self, value: float): self.tsp_param.value = value # TRAILING_STOP_POSITIVE_OFFSET PROPERTY @property def trailing_stop_positive_offset(self): return self.tsp_offset_param.value @trailing_stop_positive_offset.setter def trailing_stop_positive_offset(self, value: float): self.tsp_offset_param.value = value # Strategy Parameters (Fixed) minimal_roi = { "0": 0.05, "30": 0.03, "60": 0.01 } trailing_stop = True trailing_only_offset_is_reached = False timeframe = '5m' startup_candle_count: int = 20 process_only_new_candles = False def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ Adds required indicators for signals and filters. """ # --- 1. Base Signal: Bullish Engulfing --- dataframe['bullish_engulfing'] = ( (dataframe['close'] > dataframe['open']) & (dataframe['close'].shift(1) < dataframe['open'].shift(1)) & (dataframe['open'] <= dataframe['close'].shift(1)) & (dataframe['close'] >= dataframe['open'].shift(1)) ) # --- 2. Volume Filter Calculation --- dataframe['volume_mean_20'] = dataframe['volume'].rolling(window=20).mean() dataframe['volume_above_average'] = ( dataframe['volume'] > (dataframe['volume_mean_20'] * self.volume_multiplier.value) ) # --- 3. Body Size Filter Calculation --- dataframe['body_size'] = (dataframe['close'] - dataframe['open']).abs() dataframe['body_size_mean_10'] = dataframe['body_size'].rolling(window=10).mean() dataframe['strong_body'] = ( dataframe['body_size'] > dataframe['body_size_mean_10'] ) # --- 4. Trend Filter Calculation --- dataframe['short_term_downtrend'] = ( (dataframe['low'] < dataframe['low'].shift(1)) & (dataframe['low'].shift(1) < dataframe['low'].shift(2)) ) # --- 5. Risk Filter Calculation --- dataframe['low_risk_entry'] = ( dataframe['close'] < dataframe['low'] + (dataframe['high'] - dataframe['low']) * self.risk_threshold.value ) # --- 6. RSI Confirmation Filter --- dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14) dataframe['rsi_confirm'] = (dataframe['rsi'] < self.rsi_buy_threshold.value) return dataframe def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ Combines base signal with all filters. """ conditions = [ # 1. Base Signal (ALWAYS REQUIRED) dataframe['bullish_engulfing'], (dataframe['volume'] > 0), # Freqtrade safety ] # 2. Filter: Volume Confirmation if self.use_volume_filter.value: conditions.append(dataframe['volume_above_average']) # 3. Filter: Trend Confirmation if self.use_trend_filter.value: conditions.append(dataframe['short_term_downtrend']) # 4. Filter: Signal Strength if self.use_body_size_filter.value: conditions.append(dataframe['strong_body']) # 5. Filter: Risk Check if self.use_risk_filter.value: conditions.append(dataframe['low_risk_entry']) # 6. Filter: RSI Check if self.use_rsi_filter.value: conditions.append(dataframe['rsi_confirm']) # Combine all active conditions if conditions: dataframe.loc[ np.all(conditions, axis=0), 'buy'] = 1 return dataframe def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ Uses Bearish Engulfing for exit. """ dataframe['bearish_engulfing'] = ( (dataframe['close'] < dataframe['open']) & (dataframe['close'].shift(1) > dataframe['open'].shift(1)) & (dataframe['open'] >= dataframe['close'].shift(1)) & (dataframe['close'] <= dataframe['open'].shift(1)) ) dataframe.loc[(dataframe['bearish_engulfing']), 'sell'] = 1 return dataframe