# --- Do not remove these imports --- from freqtrade.strategy import IStrategy, IntParameter, DecimalParameter, CategoricalParameter from pandas import DataFrame # -------------------------------- # Add your lib to import here import talib.abstract as ta import freqtrade.vendor.qtpylib.indicators as qtpylib class SimultaneousHedgingStrategy(IStrategy): """ Strategy to open simultaneous long and short positions for hedging. This example attempts to always hold both a long and a short position. Includes Hyperopt spaces. """ # Strategy interface version INTERFACE_VERSION = 3 # Enable shorting can_short = True # THIS IS CRUCIAL for allowing simultaneous long and short on the SAME PAIR strategy_supports_edged_trading = True # --- Hyperopt Spaces --- # ROI table space roi_profit_target = DecimalParameter(0.005, 0.05, default=0.02, decimals=3, space="buy", optimize=True) # Stoploss space - Freqtrade expects this to be negative. # Naming this 'stoploss' (matching the class attribute) allows Freqtrade to auto-assign its .value. stoploss = DecimalParameter(-0.05, -0.005, default=-0.01, decimals=3, space="buy", optimize=True) # Trailing stoploss parameters # Naming these to match strategy attributes allows Freqtrade to auto-assign their .value. trailing_stop = CategoricalParameter([True, False], default=True, space="buy", optimize=True) trailing_stop_positive = DecimalParameter(0.005, 0.05, default=0.01, decimals=3, space="buy", optimize=True) trailing_stop_positive_offset = DecimalParameter(0.002, 0.03, default=0.005, decimals=3, space="buy", optimize=True) trailing_only_offset_is_reached = CategoricalParameter([True, False], default=True, space="buy", optimize=True) # Bollinger Bands parameters bb_window = IntParameter(10, 50, default=20, space="buy", optimize=True) bb_stds = DecimalParameter(1.5, 3.5, default=2.0, decimals=1, space="buy", optimize=True) # --- Class Attributes --- # minimal_roi will be populated in __init__ # stoploss, trailing_stop etc. are now directly defined as Hyperopt parameters above. def __init__(self, config: dict): super().__init__(config) # Construct the minimal_roi dictionary using the value from the hyperopt parameter self.minimal_roi = { "0": self.roi_profit_target.value } # Optimal timeframe for the strategy. timeframe = '5m' # Run "populate_indicators()" only for new candle. process_only_new_candles = True # These values can be overridden in the config. use_exit_signal = True exit_profit_only = False ignore_roi_if_entry_signal = False # Set to True if you don't want ROI exits to prevent re-entry # Number of candles the strategy requires before producing valid signals # This should be dynamic based on bb_window.value if possible, or set to a max sensible value. # For simplicity, we'll use a fixed value, but for robust hyperopt, this should be considered. @property def startup_candle_count(self) -> int: return self.bb_window.value # Or max(self.bb_window.value, any_other_lookback_period) # Optional order type mapping. order_types = { 'entry': 'limit', 'exit': 'limit', 'stoploss': 'market', 'stoploss_on_exchange': False # Set to True if supported and desired } # Optional order time in force. order_time_in_force = { 'entry': 'gtc', 'exit': 'gtc' } plot_config = { 'main_plot': { 'bb_upperband': {'color': 'blue'}, 'bb_middleband': {'color': 'orange'}, 'bb_lowerband': {'color': 'blue'}, }, 'subplots': {} # No subplots needed for this simple BB strategy } # def informative_pairs(self): # return [] def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ Adds Technical Analysis indicators to the given DataFrame """ # Bollinger Bands bollinger = qtpylib.bollinger_bands( qtpylib.typical_price(dataframe), window=self.bb_window.value, stds=self.bb_stds.value ) dataframe['bb_lowerband'] = bollinger['lower'] dataframe['bb_middleband'] = bollinger['mid'] dataframe['bb_upperband'] = bollinger['upper'] return dataframe def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ Always attempt to have a long and a short position. Freqtrade will manage not opening a new position if one of that side already exists. """ # Set conditions to True to always attempt to enter if a position for that side is not already open. # Freqtrade handles the logic of not opening duplicate positions (e.g. if a long is open, it won't open another long). # With strategy_supports_edged_trading=True, it allows one long AND one short simultaneously for the same pair. # Long entry: Price closes below lower Bollinger Band (Reversal) dataframe.loc[ ( (dataframe['close'] < dataframe['bb_lowerband']) & (dataframe['volume'] > 0) ), ['enter_long', 'enter_tag']] = (1, 'bb_lower_reversal_long') # Short entry: Price closes above upper Bollinger Band (Reversal) dataframe.loc[ ( (dataframe['close'] > dataframe['bb_upperband']) & (dataframe['volume'] > 0) ), ['enter_short', 'enter_tag']] = (1, 'bb_upper_reversal_short') return dataframe def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ Exit conditions based on Bollinger Bands. These custom exits will work alongside ROI, stoploss, and trailing stoploss. """ # Exit long conditions: Price touches or crosses middle band from below dataframe.loc[ ( (dataframe['close'] >= dataframe['bb_middleband']) & (dataframe['volume'] > 0) ), ['exit_long', 'exit_tag']] = (1, 'bb_middle_exit_long') # Exit short conditions: Price touches or crosses middle band from above dataframe.loc[ ( (dataframe['close'] <= dataframe['bb_middleband']) & (dataframe['volume'] > 0) ), ['exit_short', 'exit_tag']] = (1, 'bb_middle_exit_short') return dataframe