# source: https://raw.githubusercontent.com/assinscreedFC/trading_strategie/0af1528a47681364ff3f27f13fa04ec07bf00da4/freqtrade/strategies/SmartMoneyConcepts.py
# ══════════════════════════════════════════════════════════════
# anis solidscale - Elite Spot Trading Suite
# STRATÉGIE : Github_assinscreedFC_trading_strategie__SmartMoneyConcepts__20260319_054253
# CATÉGORIE : Nouvelle — Smart Money / Price Action
# ══════════════════════════════════════════════════════════════
#
# LOGIQUE :
# 1. Break of Structure bullish (close > swing high récent)
# 2. Prix a retracé dans une zone Fair Value Gap (FVG)
# 3. RSI < seuil d'entrée (pas de surachat)
# 4. Sortie : BOS bearish (close < swing low récent) OU RSI > seuil exit
# ══════════════════════════════════════════════════════════════

import sys
from pathlib import Path

import numpy as np
from pandas import DataFrame

from freqtrade.strategy import IStrategy, IntParameter, DecimalParameter

sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent))
from utils.indicators import CommonIndicators
from utils.logging_utils import TradeLogger
from utils.telegram_notifier import TelegramNotifier


class Github_assinscreedFC_trading_strategie__SmartMoneyConcepts__20260319_054253(IStrategy):
    INTERFACE_VERSION = 3
    can_short = False
    timeframe = "1h"
    startup_candle_count = 100

    minimal_roi = {"0": 0.10, "360": 0.05, "720": 0.02}
    stoploss = -0.05
    trailing_stop = True
    trailing_stop_positive = 0.015
    trailing_stop_positive_offset = 0.03
    trailing_only_offset_is_reached = True

    # ── Buy params ──
    swing_lookback = IntParameter(3, 10, default=5, space="buy")
    rsi_period = IntParameter(7, 21, default=14, space="buy")
    rsi_entry = IntParameter(40, 65, default=60, space="buy")
    volume_period = IntParameter(10, 50, default=20, space="buy")

    # ── Sell params ──
    rsi_exit = IntParameter(65, 85, default=75, space="sell")

    _logger = None
    _notifier = None

    def _init_utils(self) -> None:
        if self._logger is None:
            self._logger = TradeLogger(strategy_name="Github_assinscreedFC_trading_strategie__SmartMoneyConcepts__20260319_054253")
            self._notifier = TelegramNotifier()

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        self._init_utils()

        # Pre-calculer RSI pour TOUTES les valeurs possibles (hyperopt-safe)
        for rsi_p in range(self.rsi_period.low, self.rsi_period.high + 1):
            dataframe = CommonIndicators.add_rsi(dataframe, period=rsi_p)

        # Pre-calculer volume SMA pour TOUTES les valeurs possibles
        for vol_p in range(self.volume_period.low, self.volume_period.high + 1):
            dataframe = CommonIndicators.add_volume_sma(dataframe, period=vol_p)

        # Swing highs/lows pour TOUTES les valeurs de lookback
        for lookback in range(self.swing_lookback.low, self.swing_lookback.high + 1):
            sh_col = f"swing_high_{lookback}"
            sl_col = f"swing_low_{lookback}"
            dataframe[sh_col] = np.nan
            dataframe[sl_col] = np.nan

            for i in range(lookback, len(dataframe) - lookback):
                high_val = dataframe["high"].iloc[i]
                is_swing = True
                for j in range(1, lookback + 1):
                    if high_val <= dataframe["high"].iloc[i - j] or high_val <= dataframe["high"].iloc[i + j]:
                        is_swing = False
                        break
                if is_swing:
                    dataframe.iloc[i, dataframe.columns.get_loc(sh_col)] = high_val

                low_val = dataframe["low"].iloc[i]
                is_swing = True
                for j in range(1, lookback + 1):
                    if low_val >= dataframe["low"].iloc[i - j] or low_val >= dataframe["low"].iloc[i + j]:
                        is_swing = False
                        break
                if is_swing:
                    dataframe.iloc[i, dataframe.columns.get_loc(sl_col)] = low_val

            dataframe[f"recent_swing_high_{lookback}"] = dataframe[sh_col].ffill()
            dataframe[f"recent_swing_low_{lookback}"] = dataframe[sl_col].ffill()

        # Fair Value Gap bullish : low[i] > high[i-2] (gap entre bougie i et i-2)
        dataframe["fvg_bullish"] = (
            dataframe["low"] > dataframe["high"].shift(2)
        ).astype(int)

        # Zone FVG : on marque aussi quand le prix est dans la zone d'un FVG récent
        # FVG zone = entre high[i-2] et low[i] quand le gap existe
        dataframe["fvg_top"] = np.where(
            dataframe["fvg_bullish"] == 1,
            dataframe["low"],
            np.nan,
        )
        dataframe["fvg_bottom"] = np.where(
            dataframe["fvg_bullish"] == 1,
            dataframe["high"].shift(2),
            np.nan,
        )
        # Forward-fill les zones FVG pour les utiliser dans les bougies suivantes
        dataframe["fvg_top"] = dataframe["fvg_top"].ffill()
        dataframe["fvg_bottom"] = dataframe["fvg_bottom"].ffill()

        # Prix dans la zone FVG (pullback dans le gap)
        dataframe["in_fvg_zone"] = (
            (dataframe["low"] <= dataframe["fvg_top"])
            & (dataframe["high"] >= dataframe["fvg_bottom"])
        ).astype(int)

        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        rsi_col = f"rsi_{self.rsi_period.value}"
        sh_col = f"recent_swing_high_{self.swing_lookback.value}"

        # Break of Structure bullish : close dépasse le dernier swing high
        bos_bullish = (
            (dataframe["close"] > dataframe[sh_col])
            & (dataframe["close"].shift(1) <= dataframe[sh_col].shift(1))
        )

        conditions = (
            bos_bullish
            & (dataframe["in_fvg_zone"] == 1)
            & (dataframe[rsi_col] < self.rsi_entry.value)
            & (dataframe["volume"] > 0)
        )

        dataframe.loc[conditions, "enter_long"] = 1
        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        rsi_col = f"rsi_{self.rsi_period.value}"
        sl_col = f"recent_swing_low_{self.swing_lookback.value}"

        # Break of Structure bearish : close passe sous le dernier swing low
        bos_bearish = dataframe["close"] < dataframe[sl_col]

        conditions = (
            bos_bearish
            | (dataframe[rsi_col] > self.rsi_exit.value)
        )

        dataframe.loc[conditions, "exit_long"] = 1
        return dataframe
