# source: https://raw.githubusercontent.com/DOUGLASGUEDESATRIA/OSIRIS_TRADE/fe3c09567f3798d1cad6d7a306fa4f6131d91e17/user_data/strategies/OsirisFunnelV3Trail.py
"""
OSIRIS FUNNEL STRATEGY v3.1 — Trailing Breakeven + Ride
=========================================================
Core idea: Ultra-selective entry + dynamic risk management.

Entry (same filtered funnel):
  EMA9 cross + MACD + ADX>30 + EMA alignment + 1H trend + 1H RSI

Exit (3-phase trailing):
  Phase 0: Initial SL = -1.0%
  Phase 1: After +1.0% profit → move SL to breakeven (0%)
  Phase 2: After +1.5% profit → trail at 0.7% from peak
  No fixed TP — ride the trend until trailing catches

Expected behavior:
  ~45% stopped at -1% (immediate losers)
  ~20% stopped at ~0% (breakeven — reached +1% but reversed)
  ~35% ride for +2% to +8% (big winners)
  Net: fewer trades, but avg_win >> avg_loss → RR > 3:1
"""

import logging
import numpy as np
from pandas import DataFrame
from datetime import datetime, timedelta

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

logger = logging.getLogger(__name__)


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

    # Initial stoploss: -1.0% — this is the max risk per trade
    stoploss = -0.01

    # No fixed TP — trail captures the profit
    minimal_roi = {}

    # Trailing: managed entirely in custom_stoploss
    trailing_stop = False
    trailing_stop_positive = 0.0
    trailing_stop_positive_offset = 0.0
    trailing_only_offset_is_reached = False

    use_custom_stoploss = True
    use_exit_signal = True  # enable for time-based exit

    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 custom_stoploss(self, pair: str, trade: Trade,
                        current_time: datetime, current_rate: float,
                        current_profit: float, after_fill: bool,
                        **kwargs) -> float:
        """
        3-phase dynamic stoploss:
        Phase 0: Keep initial SL (-1.0%)
        Phase 1: After +1.0% → breakeven (return -0.001 = ~0%)
        Phase 2: After +1.5% → trail at 0.7% from peak
        """
        # Phase 2: Trail tightly once profit reaches 1.5%
        if current_profit >= 0.015:
            return -0.007  # Trail at 0.7% from current

        # Phase 1: Breakeven once profit reaches 1.0%
        if current_profit >= 0.01:
            return -0.001  # Essentially breakeven (tiny buffer for fees)

        # Phase 0: Keep initial stoploss
        return -0.01

    def custom_exit(self, pair: str, trade: Trade,
                    current_time: datetime, current_rate: float,
                    current_profit: float, **kwargs):
        """Time-based exit: if not in profit after 4H, cut it."""
        elapsed = (current_time - trade.open_date_utc).total_seconds()

        # After 4 hours not in profit → exit (reduce losers)
        if elapsed > 4 * 3600 and current_profit < 0.002:
            return "time_exit_4h"

        # After 24 hours → force exit regardless
        if elapsed > 24 * 3600:
            return "time_exit_24h"

        return None

    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"]

        # 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["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", "trend_1h"]].copy()
                dataframe = merge_informative_pair(
                    dataframe, inf_1h, self.timeframe, "1h", ffill=True
                )

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

        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        SELECTIVE ENTRY — 6 filters for quality entries.
        Dropped: volume thresh, BB, RSI directional (analysis showed minimal edge)
        Kept: EMA cross + MACD + ADX>30 + EMA align + 1H trend + 1H RSI
        """
        # Base: 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"])
        )

        # MACD aligned
        macd_bull = dataframe["macd_hist"] > 0
        macd_bear = dataframe["macd_hist"] < 0

        # ADX strong trend
        adx_ok = dataframe["adx14"] > 30

        # 5m EMA alignment
        ema_bull = dataframe["ema_bull"] == 1
        ema_bear = dataframe["ema_bear"] == 1

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

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

        vol_ok = dataframe["volume"] > 0

        # === LONG ===
        dataframe.loc[
            cross_above & macd_bull & adx_ok &
            ema_bull & trend_1h_bull & rsi1h_bull & vol_ok,
            "enter_long",
        ] = 1

        # === SHORT ===
        dataframe.loc[
            cross_below & macd_bear & adx_ok &
            ema_bear & trend_1h_bear & rsi1h_bear & vol_ok,
            "enter_short",
        ] = 1

        return dataframe

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