# source: https://raw.githubusercontent.com/ythere-y/QuantLab/4c16942a9bfed55206e58cee087641540e38c1fc/user_data/strategies/BollingerBounceStrategy.py
"""
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 Github_ythere_y_QuantLab__BollingerBounceStrategy__20260502_162050(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
