# --- Do not remove these libs --- from freqtrade.strategy.interface import IStrategy import freqtrade.vendor.qtpylib.indicators as qtpylib from freqtrade.persistence import Trade from freqtrade.strategy import stoploss_from_open from pandas import DataFrame import talib.abstract as ta from freqtrade.strategy import (BooleanParameter, CategoricalParameter, DecimalParameter, IStrategy, IntParameter) import talib.abstract as ta import freqtrade.vendor.qtpylib.indicators as qtpylib from datetime import datetime from calendar import monthrange from freqtrade.exchange import timeframe_to_minutes from datetime import timedelta # -------------------------------- class BusyGuy(IStrategy): """ Busy Guy - An active and bold trading strategy that uses the 200 SMA as a reference. Indicators: 200 SMA, 50 SMA, RSI, Bollinger Bands author@: Marcos Freitas github@: https://github.com/marcosfreitas/freqtrade-strategies version@: 1.0.0-2024-08-08 Strategy's logic explained: With low timeframes, the strategy is designed to be active and agressive, providing a high number of trades. A buffer margin is used to ensure that trades can be executed even when the price is near the 200 SMA, enhancing the strategy's responsiveness and trade frequency. Checkout buy and sell signal functions for more details on the strategy's logic. Extra notes: - The strategy uses the 200 SMA as a reference to determine the market's direction. - The strategy uses the 50 SMA to confirm the trend. - The strategy also uses the RSI and Bollinger Bands to provide additional confirmation for the signals. - Designed to timeframe of 1h - Added Hypeopt attributes, optimized by SharpeHyperOptLossDaily loss function are loaded from BusyGuy.json """ # Strategy interface version - allow new iterations of the strategy interface. # Check the documentation or the Sample strategy to get the latest version. INTERFACE_VERSION = 2 # Minimal ROI designed for the strategy. # Optimal timeframe for the strategy. timeframe = '1h' # Number of candles the strategy requires before producing valid signals # considering the 1h timeframe, 200hs = 8 days startup_candle_count: int = 200 timeframe_mins = timeframe_to_minutes(timeframe) # Minimal ROI designed for the strategy. minimal_roi = { "0": 1, # 100% for the first 3 candles str(timeframe_mins * 3): 0.50, # 50% after 3 candles str(timeframe_mins * 6): 0.20, str(timeframe_mins * 9): 0.10, str(timeframe_mins * 12): 0.05, } # Stoploss: stoploss = -0.7 # Stoploss at 70% # Trailing stoploss trailing_stop = True trailing_stop_positive = 0.03 # Trailing stop activates at 3% profit # 5% from peak, trailing stop starts at 2% profit # should be lower than minimal_roi trailing_stop_positive_offset = 0.05 # stop will actually be 10% below the peak price once the price has moved 2% in your favor. trailing_only_offset_is_reached = True # Run "populate_indicators()" only for new candle instead of running for past candles on every loop # @tip: set to false to plot points for past candles also. process_only_new_candles = False # These values can be overridden in the "ask_strategy" section in the config. use_sell_signal = True sell_profit_only = True ignore_roi_if_buy_signal = False buy_buffer = DecimalParameter(0.50, 1.0, default=0.98, space='buy') sell_buffer = DecimalParameter(0.50, 1.0, default=0.98, space='sell') #sma50_crossed_sma200_above = IntParameter(1, 30, default=48, space='buy') # Window of validity in hours close_crossed_above_sma200_validity_period = IntParameter(1, 72, default=72, space='buy') # Window of validity in hours close_crossed_below_sma200_validity_period = IntParameter(1, 72, default=72, space='sell') # Window of validity in hours #bands_crossover_validity_period = IntParameter(1, 30, default=24, space='buy') # Window of validity in hours # Define hyperopt parameters #buy_rsi = IntParameter(10, 40, default=30, space='buy') #sell_rsi = IntParameter(60, 90, default=70, space='sell') # @todo add strategy flags to enable/disable buy/sell signals # Optional order type mapping. order_types = { 'buy': 'limit', 'sell': 'limit', 'stoploss': 'market', 'stoploss_on_exchange': False } # Optional order time in force. order_time_in_force = { 'buy': 'gtc', 'sell': 'gtc' } plot_config = { 'main_plot': { 'upper_bband': {'color': 'green'}, 'middle_bband': {'color': 'orange'}, 'lower_bband': {'color': 'red'}, 'sma200': {'color': '#00e1ff'}, 'sma50': {'color': '#fff700'} }, 'subplots': { 'RSI': { 'rsi': {'color': 'red'} }, 'MACD': { 'macd': {'color': 'orange'}, 'macdsignal': {'color': 'red'}, 'macdhist': {'color': 'blue'}, }, 'Stochastic': { 'slowk': {'color': 'blue'}, 'slowd': {'color': 'orange'}, }, 'ADX': { 'adx': {'color': 'green'} } } } """ Add TA indicators to the given dataframe """ def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: def isFriday(date): return date.weekday() == 4 def isFridayNight(date): return date.weekday() == 4 & date.hour == 23 & date.minute == 59 def isEndOfMonth(date): return date.day == monthrange(date.year, date.month)[1] dataframe['isFriday'] = dataframe['date'].apply(isFriday) dataframe['isFridayNight'] = dataframe['date'].apply(isFridayNight) dataframe['isEndOfMonth'] = dataframe['date'].apply(isEndOfMonth) # Momentum #dataframe['mom'] = ta.MOM(dataframe,14) # Calculate the N-period SMA dataframe['sma200'] = ta.SMA(dataframe['close'], timeperiod=200) dataframe['sma50'] = ta.SMA(dataframe['close'], timeperiod=50) # RSI dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14) # MACD dataframe['macd'], dataframe['macdsignal'], dataframe['macdhist'] = ta.MACD(dataframe, fastperiod=12, slowperiod=26, signalperiod=9) # Bollinger Bands bollinger = ta.BBANDS(dataframe, timeperiod=20) dataframe['upper_bband'] = bollinger['upperband'] dataframe['middle_bband'] = bollinger['middleband'] dataframe['lower_bband'] = bollinger['lowerband'] # Stochastic Oscillator stoch = ta.STOCH(dataframe) dataframe['slowk'] = stoch['slowk'] dataframe['slowd'] = stoch['slowd'] # ADX dataframe['adx'] = ta.ADX(dataframe) # sma / price crossover events dataframe['golden_cross'] = qtpylib.crossed_above(dataframe['sma50'], dataframe['sma200']) dataframe['death_cross'] = qtpylib.crossed_below(dataframe['sma50'], dataframe['sma200']) dataframe['close_crossed_above_sma200'] = qtpylib.crossed_above(dataframe['close'], dataframe['sma200']) dataframe['close_crossed_below_sma200'] = qtpylib.crossed_below(dataframe['close'], dataframe['sma200']) dataframe['upper_bband_crossed'] = qtpylib.crossed_above(dataframe['close'], dataframe['upper_bband']) dataframe['lower_bband_crossed'] = qtpylib.crossed_below(dataframe['close'], dataframe['lower_bband']) # Identify downtrend by checking if previous 'n' candles had lower close prices n = 3 # Number of previous candles to check for a downtrend dataframe['downtrend'] = ( (dataframe['close'].shift(n) > dataframe['close'].shift(n-1)) & (dataframe['close'].shift(n-1) > dataframe['close'].shift(n-2)) ) # Define the number of candles to check for a reversal n = 3 # Number of candles to check for reversal # Check if the close price has started to increase after the downtrend dataframe['downtrend_reversal'] = ( (dataframe['close'] > dataframe['close'].shift(1)) & (dataframe['close'].shift(1) > dataframe['close'].shift(2)) & (dataframe['close'].shift(2) <= dataframe['close'].shift(3)) # Ensure the previous trend was a downtrend ) # Calculate the rolling mean of volume and fill NaNs with 0 great_volume_threshold = 2.5 # This represents 2.5x the average volume dataframe['average_volume'] = dataframe['volume'].rolling(window=20, min_periods=1).mean().fillna(0) # Check if the last volume is greater than 2.5 times the average volume dataframe['has_great_volume'] = dataframe['volume'].iloc[-1] > (dataframe['average_volume'].iloc[-1] * great_volume_threshold) dataframe['price_trending_up_above_sma200_end_of_month'] = ( (dataframe['close_crossed_above_sma200']) & (dataframe['close'] > dataframe['close'].shift(1)) & dataframe['has_great_volume'] ) # % distance from sma200 # ex: 10 (%) distant from sma200 # use the values of the last candle dataframe['current_price_distance_from_sma200'] = (dataframe['sma200'].iloc[-1] - dataframe['close'].iloc[-1]) / dataframe['sma200'].iloc[-1] * 100 # dataframe['golden_cross'] = ( # (dataframe['sma50_crossed_sma200_above'].all()) & # (dataframe['sma50'].iloc[-1] <= dataframe['sma200'].shift(1).iloc[-1]) # Ensure the golden cross happened on the last candle # ) return dataframe def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe['enter_tag'] = None # Define an entry condition based on golden cross dataframe.loc[ ( (dataframe['golden_cross']) & # Confirm a golden cross occurred (dataframe['close'] > dataframe['sma200']) & # Ensure price is above SMA200 (dataframe['close'] < dataframe['upper_bband']) & # Price below upper Bollinger Band (dataframe['volume'] > dataframe['average_volume'] * 2.5) # Ensure volume is significantly high ), ['enter_long', 'enter_tag'] ] = (1, 'golden_cross_price_above_sma200_high_volume') # Entry condition for price far from SMA200 and ending of a downtrend dataframe.loc[ ( (dataframe['current_price_distance_from_sma200'] >= 2) & (dataframe['close'] > dataframe['close'].shift(3)) & # Ensure price is trending up (dataframe['close'] < dataframe['sma200']) & # Ensure price is below SMA200 (dataframe['downtrend']) # Confirm previous downtrend ), ['enter_long', 'enter_tag'] ] = (1, 'price_far_from_sma200_great_volume') if dataframe['price_trending_up_above_sma200_end_of_month'].all(): dataframe.loc[:, ['enter_long', 'enter_tag']] = (1, 'price_trending_up_price_above_sma200_great_volume') dataframe.loc[ ( (dataframe['golden_cross']) & (dataframe['close'] > dataframe['close'].shift(1)) # Ensure price is trending up & (dataframe['close'] < dataframe['upper_bband']) # price below upper band & dataframe['has_great_volume'] ), ['enter_long', 'enter_tag'] ] = (1, 'golden_cross_price_trending_up_price_below_upper_band_great_volume') # Stochastic and ADX dataframe.loc[ ( (dataframe['slowk'] < 20) & # Stochastic K below 20 (oversold) (dataframe['slowd'] < 20) & # Stochastic D below 20 (oversold) (dataframe['adx'] > 25) & # Strong trend (dataframe['close'] > dataframe['sma200']) # Price above SMA200 ), ['enter_long', 'enter_tag'] ] = (1, 'stochastic_oversold_adx_strong_trend') # RSI and price above SMA200 dataframe.loc[ ( (dataframe['rsi'] < 30) & # RSI below 30 (oversold) (dataframe['close'] > dataframe['sma200']) # Price above SMA200 ), ['enter_long', 'enter_tag'] ] = (1, 'rsi_oversold_price_above_sma200') return dataframe def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe['exit_tag'] = None # Define an exit condition based on death cross dataframe.loc[ ( (dataframe['death_cross']) & # Confirm a death cross occurred (dataframe['sma50'] < dataframe['sma200']) # Ensure the death cross happened on the last candle ), ['exit_long', 'exit_tag'] ] = (1, 'death_cross_price_below_sma200') dataframe.loc[ ( (dataframe['golden_cross']) & (dataframe['close'] > (dataframe['sma200'])) ), ['exit_long', 'enter_tag'] ] = (1, 'death_cross_price_trending_down') return dataframe