# source: https://raw.githubusercontent.com/opythonic/mw-bot/70ab369845778b632b63f302e1e18ab80324594e/user_data/strategies/BBTrailSimpleStrategy.py
# --- Do not remove these libs ---
from freqtrade.strategy import IStrategy, CategoricalParameter, DecimalParameter, IntParameter
from pandas import DataFrame
import freqtrade.vendor.qtpylib.indicators as qtpylib

# --------------------------------
#   Strategy for Freqtrade bot
#
#   Bollinger Bands Crossover Strategy with Trailing Stop-Loss
#
#   Author: Jules (AI Assistant)
#   Version: 1.0
#
#   Entry Conditions:
#   - Long: Price closes above the lower Bollinger Band (after being below or on it).
#   - Short: Price closes below the upper Bollinger Band (after being above or on it).
#
#   Exit Conditions:
#   - Take Profit: 1.5% (full position).
#   - Stop-Loss:
#       - Initial: 1% from entry price.
#       - Trailing: Activates after +1% profit. Stop trails 0.5% behind the peak profit.
#
# --------------------------------

class Github_opythonic_mw_bot__BBTrailSimpleStrategy__20250629_222347(IStrategy):
    """
    Bollinger Bands Crossover Strategy with Trailing Stop-Loss
    """

    # Strategy interface version - attribute needed by Freqtrade
    INTERFACE_VERSION = 3

    # ROI table:
    # Minimal ROI designed for the strategy.
    # This attribute will be overridden if the config file contains "minimal_roi".
    minimal_roi = {
        "0": 0.015  # Take profit at 1.5%
    }

    # Stoploss:
    # Default stoploss for this strategy, pourcentage is expressed as a decimal.
    # This attribute will be overridden if the config file contains "stoploss".
    stoploss = -0.01  # Initial stoploss at -1%

    # Trailing stop:
    # Trailing stoploss for this strategy, pourcentage is expressed as a decimal.
    # This attribute will be overridden if the config file contains "trailing_stop".
    trailing_stop = True
    trailing_stop_positive = 0.005  # Trail by 0.5%
    trailing_stop_positive_offset = 0.01  # Start trailing after 1% profit
    trailing_only_offset_is_reached = True # Wait for +1% profit before trailing activates

    # Optimal timeframe for the strategy.
    timeframe = '5m' # Default, user can change this

    # Can this strategy go short?
    can_short = True

    # 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

    # Optional order type mapping.
    order_types = {
        'entry': 'limit',
        'exit': 'limit',
        'stoploss': 'market',
        'stoploss_on_exchange': False
    }

    # Optional order time in force.
    order_time_in_force = {
        'entry': 'gtc',
        'exit': 'gtc'
    }

    # --- Indicator Definitions ---
    # Bollinger Bands
    buy_bb_period = IntParameter(10, 50, default=20, space="buy")
    buy_bb_stddev = DecimalParameter(1.0, 3.0, default=2.0, space="buy")
    
    sell_bb_period = IntParameter(10, 50, default=20, space="sell")
    sell_bb_stddev = DecimalParameter(1.0, 3.0, default=2.0, space="sell")


    def informative_pairs(self):
        return []

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Adds several different TA indicators to the given DataFrame

        Performance Note: For the best performance be frugal on the number of indicators
        and try to use confirmed TA-Lib indicators.
        """

        # Bollinger Bands - Buy
        bollinger_buy = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), 
                                                window=self.buy_bb_period.value, 
                                                stds=self.buy_bb_stddev.value)
        dataframe['bb_lowerband_buy'] = bollinger_buy['lower']
        dataframe['bb_middleband_buy'] = bollinger_buy['mid']
        dataframe['bb_upperband_buy'] = bollinger_buy['upper']

        # Bollinger Bands - Sell (for shorting)
        # Potentially different parameters for shorting if desired, otherwise use buy params
        if self.can_short:
            bollinger_sell = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), 
                                                     window=self.sell_bb_period.value, 
                                                     stds=self.sell_bb_stddev.value)
            dataframe['bb_lowerband_sell'] = bollinger_sell['lower']
            dataframe['bb_middleband_sell'] = bollinger_sell['mid']
            dataframe['bb_upperband_sell'] = bollinger_sell['upper']
            
        # Other indicators can be added here

        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Based on TA indicators, populates the entry tínals for the given dataframe
        :param dataframe: DataFrame
        :param metadata: Additional information, like current pair
        :return: DataFrame with entry columns populated
        """
        # Long Entry: Price crosses above the lower Bollinger Band
        dataframe.loc[
            (
                qtpylib.crossed_above(dataframe['close'], dataframe['bb_lowerband_buy'])
            ),
            ['enter_long', 'enter_tag']] = (1, 'bb_cross_lower_long')

        if self.can_short:
            # Short Entry: Price crosses below the upper Bollinger Band
            dataframe.loc[
                (
                    qtpylib.crossed_below(dataframe['close'], dataframe['bb_upperband_sell'])
                ),
                ['enter_short', 'enter_tag']] = (1, 'bb_cross_upper_short')

        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Based on TA indicators, populates the exit signals for the given dataframe
        :param dataframe: DataFrame
        :param metadata: Additional information, like current pair
        :return: DataFrame with exit columns populated
        """
        # No custom exit signals beyond ROI, stoploss, and trailing stoploss defined in class variables.
        # If you wanted custom exit signals, you would define them here.
        # For example:
        # dataframe.loc[
        #     (
        #         (qtpylib.crossed_above(dataframe['close'], dataframe['bb_upperband_buy'])) & # Long exit condition
        #         (dataframe['volume'] > 0) # Make sure Volume is not 0
        #     ),
        #     ['exit_long', 'exit_tag']] = (1, 'bb_exit_long_custom')

        # if self.can_short:
        #     dataframe.loc[
        #         (
        #             (qtpylib.crossed_below(dataframe['close'], dataframe['bb_lowerband_sell'])) & # Short exit condition
        #             (dataframe['volume'] > 0) # Make sure Volume is not 0
        #         ),
        #         ['exit_short', 'exit_tag']] = (1, 'bb_exit_short_custom')
        
        return dataframe

    # Note: custom_stoploss can be used if Freqtrade's native trailing stop isn't precise enough.
    # For this version, we rely on the native trailing_stop, trailing_stop_positive,
    # and trailing_stop_positive_offset / trailing_only_offset_is_reached.
    # If these do not provide the exact 0.5% trail from peak after 1% profit,
    # then custom_stoploss would be the place to implement it.

    # Example of custom_stoploss structure if needed later:
    # def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
    #                     current_rate: float, current_profit: float, **kwargs) -> float:
    #     """
    #     Custom stoploss logic, called only if positive_stoploss is enabled.
    #
    #     For this strategy:
    #     - Initial stop: -1% (handled by `stoploss = -0.01`)
    #     - Trailing stop: Activates at +1% profit. Trails by 0.5% from the highest rate achieved.
    #
    #     :param pair: Pair that's currently analyzed
    #     :param trade: trade object.
    #     :param current_time: datetime object, containing the current datetime
    #     :param current_rate: Current rate of the pair
    #     :param current_profit: Current profit (as ratio), calculated based on current_rate.
    #     :param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
    #     :return: New stoploss value, relative to the current rate
    #              If None is returned, original stoploss value is used.
    #     """
    #     # Hard stoploss at -10% to prevent catastrophic loss from bad data / market manipulation
    #     # This is not strictly part of the user's request but good practice.
    #     # if current_profit < -0.10:
    #     #     return -0.99 # Effectively market sell immediately, or a fixed large stop.
    #         # This should be handled by stoploss = -0.01 and Freqtrade's default stoploss mechanism.
    #         # The custom_stoploss is more for dynamic/trailing stops.
    #
    #     # Initial stoploss is -0.01 (1%), handled by the main `stoploss` parameter.
    #     # This function is primarily for the trailing stop logic if native options are insufficient.
    #
    #     if current_profit > 0.01:  # If profit is > 1%
    #         # Calculate the desired stop price: 0.5% below the highest rate since 1% profit was achieved.
    #         # Freqtrade's `trade.max_rate` holds the highest rate observed for the trade.
    #         desired_stop_price = trade.max_rate * (1 - 0.005) # For longs
    #         if trade.is_short:
    #             desired_stop_price = trade.max_rate * (1 + 0.005) # For shorts
    #
    #         # Convert this absolute price back to a relative stoploss from current_rate
    #         # (stoploss_value = (stop_price / current_rate) - 1)
    #         # Important: custom_stoploss returns a *rate* relative to open_rate or an *absolute price*.
    #         # Let's return an absolute price for the stop.
    #         return desired_stop_price
    #
    #     # If profit is not yet > 1%, the initial `stoploss = -0.01` applies.
    #     # Returning -1 or None from here would mean "use `stoploss` parameter".
    #     # However, to be explicit, if we are not yet in trailing mode,
    #     # we don't want to override the initial stoploss.
    #     # Freqtrade's `custom_stoploss` is called frequently.
    #     # The `stoploss` parameter defines the initial stop.
    #     # `trailing_stop` parameters should handle the rest.
    #     # If they don't, then this function needs to be more robust.
    #
    #     # If we rely on native trailing, this function might not even be needed
    #     # or could simply return -1 (or a value that refers to initial stoploss).
    #     # For now, let's assume native trailing works as specified.
    #     # If it doesn't, this is where the logic would go.
    #     return -1 # Indicates to use the stoploss from the configuration
    
    # Note on Hyperopt:
    # The IntParameter and DecimalParameter for BB period and stddev are already set up
    # for hyperoptimization. Other parameters like timeframe, stoploss values,
    # and ROI targets can also be made into hyperoptable parameters if desired.
    # For example:
    # trailing_stop_positive_sell = DecimalParameter(0.001, 0.02, default=0.005, space="sell", optimize=True)
    # trailing_stop_positive_offset_sell = DecimalParameter(0.005, 0.03, default=0.01, space="sell", optimize=True)

    # minimal_roi_param = CategoricalParameter([{"0": 0.01}, {"0": 0.015}, {"0": 0.02}], default={"0": 0.015}, space="buy", optimize=True)
    # stoploss_param = DecimalParameter(-0.05, -0.005, default=-0.01, space="buy", optimize=True)

    # --- Custom methods for easier access to parameters ---
    @property
    def protections(self):
        return [
            {
                "method": "StoplossGuard",
                "lookback_period_candles": 24, # Check for X last candles
                "trade_limit": 2, # Avoid entering X trades in the lookback period
                "stop_duration_candles": 6, # Don't enter new trades for X candles
                "only_per_pair": False # Look at all pairs, or only per pair
            }
        ]

    # --- HYPEROPT PARAMETERS ---
    # Define buy and sell spaces if you want to hyperopt them separately.
    # Otherwise, parameters defined directly in the class are used for both.

    # Example of defining separate hyperopt spaces for buy and sell
    # class HyperOpt:
    #     @staticmethod
    #     def populate_indicators(dataframe: DataFrame, metadata: dict) -> DataFrame:
    #         # Bollinger Bands
    #         bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), 
    #                                             window=20, # Example fixed or from common space
    #                                             stds=2)    # Example fixed or from common space
    #         dataframe['bb_lowerband'] = bollinger['lower']
    #         dataframe['bb_middleband'] = bollinger['mid']
    #         dataframe['bb_upperband'] = bollinger['upper']
    #         return dataframe

    #     @staticmethod
    #     def buy_strategy_generator(params: dict) -> Callable:
    #         def populate_buy_trend(dataframe: DataFrame, metadata: dict) -> DataFrame:
    #             dataframe.loc[
    #                 (
    #                     qtpylib.crossed_above(dataframe['close'], dataframe['bb_lowerband']) &
    #                     (dataframe['bb_period'] == params['bb_period']) # Hypothetical use of param
    #                 ),
    #                 'buy'] = 1
    #             return dataframe
    #         return populate_buy_trend

    #     @staticmethod
    #     def indicator_space() -> list: # For common indicators if needed
    #         return [
    #             IntParameter(10, 50, default=20, name='bb_period'),
    #             DecimalParameter(1.0, 3.0, default=2.0, name='bb_stddev'),
    #         ]
    
    #     @staticmethod
    #     def sell_indicator_space() -> list: # If sell indicators are different
    #         return [
    #             IntParameter(10, 50, default=20, name='sell_bb_period'),
    #             DecimalParameter(1.0, 3.0, default=2.0, name='sell_bb_stddev'),
    #         ]


    # If you want to use the custom_stoploss, uncomment it and ensure it's correctly implemented.
    # For now, relying on Freqtrade's built-in trailing stop mechanisms.
    # If the built-in trailing stop (`trailing_stop = True`, `trailing_stop_positive`, 
    # `trailing_stop_positive_offset`, `trailing_only_offset_is_reached = True`)
    # correctly implements the "trail by 0.5% after 1% profit" logic, then `custom_stoploss` is not strictly needed
    # for that part. The initial `stoploss = -0.01` handles the first static stop.

    # The `custom_stoploss` function is called only if `trailing_stop` is True AND
    # `use_custom_stoploss` is True.
    # However, modern Freqtrade might use `custom_stoploss` if defined, even without `use_custom_stoploss`.
    # The interaction is:
    # 1. `stoploss` (initial fixed stop).
    # 2. If `trailing_stop` is True:
    #    - If `custom_stoploss` is defined, it can override/provide dynamic stop values.
    #    - `trailing_stop_positive*` parameters configure built-in trailing if `custom_stoploss` doesn't take full control or isn't used.

    # Given the requirements:
    # - Initial 1% stop: `stoploss = -0.01`
    # - After +1% profit, trail by 0.5%:
    #   `trailing_stop = True`
    #   `trailing_stop_positive = 0.005` (the amount to trail by)
    #   `trailing_stop_positive_offset = 0.01` (the profit threshold to activate trailing)
    #   `trailing_only_offset_is_reached = True` (ensure trailing only starts after offset is hit)
    # This setup *should* work as intended. `custom_stoploss` can be a fallback or for more complex scenarios.
    pass
