# source: https://raw.githubusercontent.com/aldrinmagno/FreqtradeStrategies/138ec155e8081989d36db6e419843eaa78f66d94/user_data/strategies/CombinedBinHAndClucV8Hyper.py
"""
Github_aldrinmagno_FreqtradeStrategies__CombinedBinHAndClucV8Hyper__20260305_002153 — Hyperopt-Ready Combined Dip-Buy Scalper

Source: https://github.com/freqtrade/freqtrade-strategies (BinHV45 + ClucMay72018)
Timeframe: 5m (with 1h informative)
Description: Combines BinHV45 and ClucMay72018 dip-buy signals with full hyperopt
             parameter support. Four independently togglable entry conditions with
             SSL Channel and MFI indicators.
"""

from functools import reduce
from freqtrade.strategy import (
    IStrategy,
    BooleanParameter,
    DecimalParameter,
    IntParameter,
    merge_informative_pair,
)
from pandas import DataFrame
import numpy as np
import pandas_ta as pta


class Github_aldrinmagno_FreqtradeStrategies__CombinedBinHAndClucV8Hyper__20260305_002153(IStrategy):

    INTERFACE_VERSION: int = 3

    timeframe: str = "5m"
    informative_timeframe: str = "1h"

    minimal_roi: dict = {
        "0": 0.15,
        "30": 0.05,
        "60": 0.02,
        "120": 0.01,
    }

    stoploss: float = -0.274

    use_exit_signal: bool = True
    exit_profit_only: bool = False

    # -----------------------------------------------------------------------
    # Buy hyperopt parameters — Condition 1 (BB40 squeeze)
    # -----------------------------------------------------------------------
    buy_cond1_enabled = BooleanParameter(default=True, space="buy")
    buy_cond1_bbdelta = DecimalParameter(0.01, 0.05, default=0.025, space="buy")
    buy_cond1_closedelta_factor = DecimalParameter(0.5, 1.5, default=1.0, space="buy")
    buy_cond1_tail_factor = DecimalParameter(0.1, 1.0, default=0.5, space="buy")

    # -----------------------------------------------------------------------
    # Buy hyperopt parameters — Condition 2 (BB20 lower band dip)
    # -----------------------------------------------------------------------
    buy_cond2_enabled = BooleanParameter(default=True, space="buy")
    buy_cond2_close_factor = DecimalParameter(0.97, 1.0, default=0.99, space="buy")
    buy_cond2_volume_factor = DecimalParameter(0.5, 2.0, default=1.0, space="buy")

    # -----------------------------------------------------------------------
    # Buy hyperopt parameters — Condition 3 (RSI 1h divergence)
    # -----------------------------------------------------------------------
    buy_cond3_enabled = BooleanParameter(default=True, space="buy")
    buy_cond3_rsi_1h = IntParameter(30, 50, default=40, space="buy")
    buy_cond3_rsi_diff = DecimalParameter(0.5, 3.0, default=1.5, space="buy")

    # -----------------------------------------------------------------------
    # Buy hyperopt parameters — Condition 4 (EMA momentum)
    # -----------------------------------------------------------------------
    buy_cond4_enabled = BooleanParameter(default=True, space="buy")
    buy_cond4_ema_ratio = DecimalParameter(0.99, 1.01, default=1.0, space="buy")

    # -----------------------------------------------------------------------
    # Sell hyperopt parameters
    # -----------------------------------------------------------------------
    sell_rsi_threshold = IntParameter(60, 80, default=70, space="sell")

    def informative_pairs(self) -> list:
        pairs = self.dp.current_whitelist()
        return [(pair, self.informative_timeframe) for pair in pairs]

    def _ssl_channel(self, dataframe: DataFrame, length: int = 10) -> DataFrame:
        """SSL Channel indicator — ATR + SMA hybrid."""
        atr = pta.atr(dataframe["high"], dataframe["low"], dataframe["close"], length=length)
        sma_high = pta.sma(dataframe["high"], length=length)
        sma_low = pta.sma(dataframe["low"], length=length)

        hlv = np.where(
            dataframe["close"] > sma_high,
            1,
            np.where(dataframe["close"] < sma_low, -1, np.nan),
        )
        hlv = DataFrame({"hlv": hlv}).ffill().values.flatten()

        dataframe["ssl_down"] = np.where(hlv < 0, sma_high, sma_low)
        dataframe["ssl_up"] = np.where(hlv < 0, sma_low, sma_high)
        dataframe["ssl_atr"] = atr
        return dataframe

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # --- 1h informative ---
        inf_tf = self.dp.get_pair_dataframe(pair=metadata["pair"], timeframe=self.informative_timeframe)
        inf_tf["rsi_1h"] = pta.rsi(inf_tf["close"], length=14)
        inf_tf["sma200_1h"] = pta.sma(inf_tf["close"], length=200)
        dataframe = merge_informative_pair(dataframe, inf_tf, self.timeframe, self.informative_timeframe, ffill=True)

        # --- Bollinger Bands (20, 2) ---
        bb20 = pta.bbands(dataframe["close"], length=20, std=2.0)
        dataframe["bb20_low"] = bb20["BBL_20_2.0"]
        dataframe["bb20_mid"] = bb20["BBM_20_2.0"]
        dataframe["bb20_upp"] = bb20["BBU_20_2.0"]

        # --- Bollinger Bands (40, 2) for condition 1 ---
        bb40 = pta.bbands(dataframe["close"], length=40, std=2.0)
        dataframe["bb40_low"] = bb40["BBL_40_2.0"]
        dataframe["bb40_mid"] = bb40["BBM_40_2.0"]

        # --- BB40 deltas for condition 1 ---
        dataframe["bb40_delta"] = (dataframe["bb40_mid"] - dataframe["bb40_low"]).abs()
        dataframe["closedelta"] = (dataframe["close"] - dataframe["close"].shift(1)).abs()
        dataframe["tail"] = (dataframe["low"] - dataframe["close"]).abs()

        # --- RSI ---
        dataframe["rsi"] = pta.rsi(dataframe["close"], length=14)

        # --- EMAs ---
        dataframe["ema50"] = pta.ema(dataframe["close"], length=50)
        dataframe["ema200"] = pta.ema(dataframe["close"], length=200)
        dataframe["sma200"] = pta.sma(dataframe["close"], length=200)

        # --- MFI (Money Flow Index, 14) ---
        dataframe["mfi"] = pta.mfi(
            dataframe["high"], dataframe["low"], dataframe["close"], dataframe["volume"], length=14
        )

        # --- SSL Channel ---
        dataframe = self._ssl_channel(dataframe, length=10)

        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        conditions: list = []

        # Condition 1 — BB40 squeeze
        if self.buy_cond1_enabled.value:
            cond1 = (
                (dataframe["bb40_delta"] > self.buy_cond1_bbdelta.value)
                & (dataframe["closedelta"] > dataframe["bb40_delta"] * self.buy_cond1_closedelta_factor.value)
                & (dataframe["tail"] < dataframe["bb40_delta"] * self.buy_cond1_tail_factor.value)
                & (dataframe["close"] < dataframe["bb40_low"])
                & (dataframe["volume"] > 0)
            )
            conditions.append(cond1)

        # Condition 2 — BB20 lower band dip
        if self.buy_cond2_enabled.value:
            cond2 = (
                (dataframe["close"] < dataframe["bb20_low"])
                & (dataframe["close"] < dataframe["close"].shift(1) * self.buy_cond2_close_factor.value)
                & (dataframe["volume"] > dataframe["volume"].shift(1) * self.buy_cond2_volume_factor.value)
            )
            conditions.append(cond2)

        # Condition 3 — RSI 1h divergence
        if self.buy_cond3_enabled.value:
            cond3 = (
                (dataframe[f"rsi_1h_{self.informative_timeframe}"] < self.buy_cond3_rsi_1h.value)
                & (dataframe["rsi"] < dataframe["rsi"].shift(1))
                & (dataframe["rsi"] > dataframe["rsi"].shift(1) + self.buy_cond3_rsi_diff.value)
                & (dataframe["volume"] > 0)
            )
            conditions.append(cond3)

        # Condition 4 — EMA momentum
        if self.buy_cond4_enabled.value:
            cond4 = (
                (dataframe["close"] < dataframe["ema50"])
                & (dataframe["ema50"] < dataframe["ema200"] * self.buy_cond4_ema_ratio.value)
                & (dataframe["volume"] > 0)
            )
            conditions.append(cond4)

        if conditions:
            dataframe.loc[reduce(lambda x, y: x | y, conditions), "enter_long"] = 1

        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[
            (
                (dataframe["rsi"] > self.sell_rsi_threshold.value)
                & (dataframe["volume"] > 0)
            ),
            "exit_long",
        ] = 1

        return dataframe

    def custom_stoploss(
        self,
        pair: str,
        trade: "Trade",
        current_time: "datetime",
        current_rate: float,
        current_profit: float,
        **kwargs,
    ) -> float:
        """Check SMA200 direction — tighten stop if 1h trend is bearish."""
        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        if len(dataframe) < 1:
            return self.stoploss

        last_candle = dataframe.iloc[-1]

        # If price is above SMA200 on 1h, use wider stop
        if last_candle.get(f"sma200_1h_{self.informative_timeframe}", 0) > 0:
            if last_candle["close"] > last_candle[f"sma200_1h_{self.informative_timeframe}"]:
                return -0.274
            else:
                return -0.15

        return self.stoploss
