# source: https://raw.githubusercontent.com/DOUGLASGUEDESATRIA/OSIRIS_TRADE/fe3c09567f3798d1cad6d7a306fa4f6131d91e17/user_data/strategies/OsirisFunnelV3.py
"""
OSIRIS FUNNEL STRATEGY v3.0 — Ultra-Selective 80% WR Target
=============================================================
Entry: Pullback-to-trend with multi-timeframe alignment.

Base: EMA9 cross close on 5m (entry trigger)
Filter 1: MACD histogram aligned + growing momentum
Filter 2: 1H RSI aligned (>55 long, <45 short) — stronger bias
Filter 3: ADX(14) > 30 (strong trend only, not weak 25-30)
Filter 4: EMA alignment on 5m (9>21>50 bull, 9<21<50 bear)
Filter 5: 1H EMA alignment (trend confirmation)
Filter 6: Volume > 1.0× SMA(20) — above average activity
Filter 7: RSI directional (long: RSI not overbought, short: RSI not oversold)
Filter 8: BB position (long: not top, short: not bottom)

Exit: RR 3:1 — TP = 3 × SL via stoploss + ROI.
"""

import logging
import numpy as np
from pandas import DataFrame

from freqtrade.strategy import IStrategy, merge_informative_pair
import talib.abstract as ta

logger = logging.getLogger(__name__)


class Github_DOUGLASGUEDESATRIA_OSIRIS_TRADE__OsirisFunnelV3__20260329_060843(IStrategy):
    INTERFACE_VERSION = 3
    can_short = True
    timeframe = "5m"

    # RR 3:1: SL=1.0%, TP=3.0% — grid-searchable
    minimal_roi = {"0": 0.03}
    stoploss = -0.01

    # Trailing: OFF
    trailing_stop = False
    trailing_stop_positive = 0.0
    trailing_stop_positive_offset = 0.0
    trailing_only_offset_is_reached = False

    use_custom_stoploss = False
    use_exit_signal = False

    startup_candle_count = 200
    process_only_new_candles = True

    def informative_pairs(self):
        pairs = self.dp.current_whitelist()
        return [(pair, "1h") for pair in pairs]

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # === 5m indicators ===
        dataframe["ema9"] = ta.EMA(dataframe, timeperiod=9)
        dataframe["ema21"] = ta.EMA(dataframe, timeperiod=21)
        dataframe["ema50"] = ta.EMA(dataframe, timeperiod=50)
        dataframe["adx14"] = ta.ADX(dataframe, timeperiod=14)
        dataframe["rsi14"] = ta.RSI(dataframe, timeperiod=14)

        macd = ta.MACD(dataframe, fastperiod=12, slowperiod=26, signalperiod=9)
        dataframe["macd_hist"] = macd["macdhist"]
        dataframe["macd_hist_prev"] = dataframe["macd_hist"].shift(1)

        # Bollinger Bands
        bb = ta.BBANDS(dataframe, timeperiod=20)
        dataframe["bb_upper"] = bb["upperband"]
        dataframe["bb_lower"] = bb["lowerband"]
        dataframe["bb_mid"] = bb["middleband"]

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

        # EMA9 cross detection
        dataframe["prev_close"] = dataframe["close"].shift(1)
        dataframe["prev_ema9"] = dataframe["ema9"].shift(1)

        # EMA alignment flags
        dataframe["ema_bull"] = (
            (dataframe["ema9"] > dataframe["ema21"]) &
            (dataframe["ema21"] > dataframe["ema50"])
        ).astype(int)
        dataframe["ema_bear"] = (
            (dataframe["ema9"] < dataframe["ema21"]) &
            (dataframe["ema21"] < dataframe["ema50"])
        ).astype(int)

        # === 1H indicators ===
        if self.dp:
            inf_1h = self.dp.get_pair_dataframe(
                pair=metadata["pair"], timeframe="1h"
            )
            if not inf_1h.empty:
                inf_1h["rsi_1h"] = ta.RSI(inf_1h, timeperiod=14)
                inf_1h["adx_1h"] = ta.ADX(inf_1h, timeperiod=14)
                inf_1h["ema9_1h"] = ta.EMA(inf_1h, timeperiod=9)
                inf_1h["ema21_1h"] = ta.EMA(inf_1h, timeperiod=21)
                inf_1h["ema50_1h"] = ta.EMA(inf_1h, timeperiod=50)
                inf_1h["trend_1h"] = (
                    ((inf_1h["ema9_1h"] > inf_1h["ema21_1h"]) &
                     (inf_1h["ema21_1h"] > inf_1h["ema50_1h"])).astype(int) -
                    ((inf_1h["ema9_1h"] < inf_1h["ema21_1h"]) &
                     (inf_1h["ema21_1h"] < inf_1h["ema50_1h"])).astype(int)
                )
                inf_1h = inf_1h[["date", "rsi_1h", "adx_1h", "trend_1h"]].copy()
                dataframe = merge_informative_pair(
                    dataframe, inf_1h, self.timeframe, "1h", ffill=True
                )

        for col in ["rsi_1h_1h", "adx_1h_1h", "trend_1h_1h"]:
            if col not in dataframe.columns:
                dataframe[col] = 50.0 if "rsi" in col else (20.0 if "adx" in col else 0.0)

        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        ULTRA-SELECTIVE ENTRY — 8 filters for maximum quality.
        """
        # === BASE SIGNAL: EMA9 cross ===
        cross_above = (
            (dataframe["prev_close"] < dataframe["prev_ema9"]) &
            (dataframe["close"] > dataframe["ema9"])
        )
        cross_below = (
            (dataframe["prev_close"] > dataframe["prev_ema9"]) &
            (dataframe["close"] < dataframe["ema9"])
        )

        # === FILTER 1: MACD histogram aligned + growing ===
        macd_bull = (dataframe["macd_hist"] > 0)
        macd_bear = (dataframe["macd_hist"] < 0)

        # === FILTER 2: 1H RSI strong bias ===
        rsi1h_bull = dataframe["rsi_1h_1h"] > 55
        rsi1h_bear = dataframe["rsi_1h_1h"] < 45

        # === FILTER 3: ADX > 30 (strong trend only) ===
        adx_strong = dataframe["adx14"] > 30

        # === FILTER 4: 5m EMA alignment ===
        ema_aligned_bull = dataframe["ema_bull"] == 1
        ema_aligned_bear = dataframe["ema_bear"] == 1

        # === FILTER 5: 1H trend alignment ===
        trend_1h_bull = dataframe["trend_1h_1h"] == 1
        trend_1h_bear = dataframe["trend_1h_1h"] == -1

        # === FILTER 6: Volume above average ===
        vol_above = dataframe["volume"] > dataframe["vol_sma20"]

        # === FILTER 7: RSI directional (not overextended) ===
        rsi_long_ok = (dataframe["rsi14"] > 40) & (dataframe["rsi14"] < 65)
        rsi_short_ok = (dataframe["rsi14"] > 35) & (dataframe["rsi14"] < 60)

        # === FILTER 8: BB position (room to run) ===
        bb_range = dataframe["bb_upper"] - dataframe["bb_lower"]
        bb_range = bb_range.replace(0, 1)
        bb_pos = (dataframe["close"] - dataframe["bb_lower"]) / bb_range
        bb_long_ok = bb_pos < 0.7   # Not at top
        bb_short_ok = bb_pos > 0.3  # Not at bottom

        # === LONG ===
        dataframe.loc[
            cross_above & macd_bull & rsi1h_bull & adx_strong &
            ema_aligned_bull & trend_1h_bull & vol_above &
            rsi_long_ok & bb_long_ok,
            "enter_long",
        ] = 1

        # === SHORT ===
        dataframe.loc[
            cross_below & macd_bear & rsi1h_bear & adx_strong &
            ema_aligned_bear & trend_1h_bear & vol_above &
            rsi_short_ok & bb_short_ok,
            "enter_short",
        ] = 1

        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        return dataframe
