# source: https://raw.githubusercontent.com/aragier/tradingStrategies/ba3f2fd3be7a23d51a2f518cb043f0db05091e41/user_data/strategies/BreakoutBBSqueeze_5m_v1.py
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
# flake8: noqa: F401
# isort: skip_file
# --- Do not remove these imports ---
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,
    informative,
    BooleanParameter,
    CategoricalParameter,
    DecimalParameter,
    IntParameter,
    RealParameter,
    timeframe_to_minutes,
    timeframe_to_next_date,
    timeframe_to_prev_date,
    merge_informative_pair,
    stoploss_from_absolute,
    stoploss_from_open,
)

# --------------------------------
import talib.abstract as ta
from technical import qtpylib


class Github_aragier_tradingStrategies__BreakoutBBSqueeze_5m_v1__20260327_161149(IStrategy):
    """
    Bollinger Band Squeeze Breakout Strategy.

    Detects BB squeezes using Keltner Channel overlap, then enters
    on breakout with volume confirmation. No signal-based exits —
    relies entirely on ROI and trailing stop.

    Family: Breakout
    Timeframe: 5m with 1h informative
    Direction: Long + Short
    """

    INTERFACE_VERSION = 3
    can_short: bool = True

    # --- Minimal ROI (wider for breakouts) ---
    minimal_roi = {
        "60": 0.01,
        "30": 0.02,
        "0": 0.04,
    }

    # --- Stoploss ---
    stoploss = -0.025

    # --- Trailing Stop ---
    trailing_stop = True
    trailing_stop_positive = 0.008
    trailing_stop_positive_offset = 0.015
    trailing_only_offset_is_reached = True

    # --- Timeframe ---
    timeframe = "5m"

    # --- NO exit signal ---
    use_exit_signal = False

    process_only_new_candles = True
    exit_profit_only = False
    ignore_roi_if_entry_signal = False

    # --- Hyperoptable Parameters ---
    bb_period = IntParameter(15, 25, default=20, space="buy", optimize=True, load=True)
    bb_std = DecimalParameter(1.5, 2.5, default=2.0, decimals=1, space="buy", optimize=True, load=True)

    kc_period = IntParameter(15, 25, default=20, space="buy", optimize=True, load=True)
    kc_atr_mult = DecimalParameter(1.0, 2.0, default=1.5, decimals=1, space="buy", optimize=True, load=True)

    squeeze_lookback = IntParameter(3, 8, default=5, space="buy", optimize=True, load=True)

    rsi_long_min = IntParameter(45, 55, default=50, space="buy", optimize=True, load=True)
    rsi_short_max = IntParameter(45, 55, default=50, space="sell", optimize=True, load=True)

    volume_factor = DecimalParameter(1.0, 2.0, default=1.2, decimals=1, space="buy", optimize=True, load=True)

    ema_1h_lookback = IntParameter(1, 5, default=3, space="buy", optimize=True, load=True)

    # Startup: EMA50 on 1h needs 50 candles = 600 on 5m + BB/KC buffer
    startup_candle_count: int = 650

    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_upper": {"color": "blue"},
            "bb_lower": {"color": "blue"},
            "bb_mid": {"color": "grey"},
            "kc_upper": {"color": "orange"},
            "kc_lower": {"color": "orange"},
        },
        "subplots": {
            "RSI": {"rsi": {"color": "red"}},
            "BB Width": {"bb_width": {"color": "green"}},
            "Squeeze": {"squeeze_on": {"color": "yellow", "type": "bar"}},
        },
    }

    # --- 1h Informative ---
    @informative("1h")
    def populate_indicators_1h(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe["ema_50"] = ta.EMA(dataframe, timeperiod=50)
        return dataframe

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # --- Bollinger Bands ---
        bollinger = qtpylib.bollinger_bands(
            qtpylib.typical_price(dataframe),
            window=self.bb_period.value,
            stds=self.bb_std.value,
        )
        dataframe["bb_upper"] = bollinger["upper"]
        dataframe["bb_lower"] = bollinger["lower"]
        dataframe["bb_mid"] = bollinger["mid"]
        dataframe["bb_width"] = (dataframe["bb_upper"] - dataframe["bb_lower"]) / dataframe["bb_mid"]

        # --- Keltner Channel ---
        kc_mid = ta.EMA(dataframe, timeperiod=self.kc_period.value)
        kc_atr = ta.ATR(dataframe, timeperiod=self.kc_period.value)
        dataframe["kc_upper"] = kc_mid + (kc_atr * self.kc_atr_mult.value)
        dataframe["kc_lower"] = kc_mid - (kc_atr * self.kc_atr_mult.value)

        # --- Squeeze Detection ---
        # Squeeze is ON when BB is INSIDE Keltner Channel
        dataframe["squeeze_on"] = (
            (dataframe["bb_upper"] < dataframe["kc_upper"])
            & (dataframe["bb_lower"] > dataframe["kc_lower"])
        ).astype(int)

        # Was squeeze active in the last N candles?
        dataframe["squeeze_recent"] = dataframe["squeeze_on"].rolling(
            window=self.squeeze_lookback.value
        ).max()

        # --- RSI ---
        dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14)

        # --- Volume SMA ---
        dataframe["volume_sma"] = ta.SMA(dataframe["volume"], timeperiod=20)

        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # --- Long: Squeeze release + breakout above upper BB ---
        dataframe.loc[
            (
                (dataframe["squeeze_recent"] == 1)
                & (dataframe["close"] > dataframe["bb_upper"])
                & (dataframe["rsi"] > self.rsi_long_min.value)
                & (dataframe["ema_50_1h"] > dataframe["ema_50_1h"].shift(self.ema_1h_lookback.value))
                & (dataframe["volume"] > (dataframe["volume_sma"] * self.volume_factor.value))
            ),
            "enter_long",
        ] = 1

        # --- Short: Squeeze release + breakout below lower BB ---
        dataframe.loc[
            (
                (dataframe["squeeze_recent"] == 1)
                & (dataframe["close"] < dataframe["bb_lower"])
                & (dataframe["rsi"] < self.rsi_short_max.value)
                & (dataframe["ema_50_1h"] < dataframe["ema_50_1h"].shift(self.ema_1h_lookback.value))
                & (dataframe["volume"] > (dataframe["volume_sma"] * self.volume_factor.value))
            ),
            "enter_short",
        ] = 1

        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # NO exit signals — rely on ROI and trailing stop only
        # (Evolution insight: signal-based exits consistently lose money)
        return dataframe
