# source: https://raw.githubusercontent.com/DOUGLASGUEDESATRIA/OSIRIS_TRADE/fe3c09567f3798d1cad6d7a306fa4f6131d91e17/user_data/strategies/OsirisGoldV4.py
"""
Github_DOUGLASGUEDESATRIA_OSIRIS_TRADE__OsirisGoldV4__20260329_060843 — Fixed SL/TP Edge Test (BTC/USDT 5m)
=====================================================
HYPOTHESIS TEST: Do the GoldV3 entries have ANY directional edge?

TEST 1: Fixed 1:1 RR (SL=1.0%, TP=1.0%)
  If WR > 52% → entries have edge → optimize from here
  If WR = 50% → entries are noise → need different entries

TEST 2: Fixed 2:1 RR (SL=1.0%, TP=2.0%)
  If WR > 34% → positive EV (barely)

TEST 3: Time exit at 6 candles (30min), emergency SL only
  If avg profit > 0 → entries capture drift → time exit is the edge

Using GoldV3's scoring with configurable threshold.
"""
import logging
import numpy as np
import pandas as pd
from datetime import datetime, timedelta
from pandas import DataFrame

from freqtrade.persistence import Trade
from freqtrade.strategy import (
    IStrategy,
    DecimalParameter,
    IntParameter,
    merge_informative_pair,
)
import talib.abstract as ta

logger = logging.getLogger(__name__)


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

    # Hard SL only — no trailing, no custom
    stoploss = -0.015  # 1.5% hard stop
    minimal_roi = {"0": 0.015}  # 1.5% ROI = 1:1 RR with SL
    trailing_stop = False
    use_custom_stoploss = False
    startup_candle_count = 200
    process_only_new_candles = True

    # ── Params ───────────────────────────────────────────────────────────────
    score_threshold = IntParameter(8, 14, default=10, space="buy", optimize=True)
    score_threshold_short = IntParameter(8, 14, default=10, space="buy", optimize=True)
    cooldown_candles = IntParameter(1, 6, default=2, space="buy", optimize=True)
    sl_pct = DecimalParameter(
        0.5, 3.0, default=1.5, decimals=1, space="sell", optimize=True
    )
    tp_pct = DecimalParameter(
        0.5, 3.0, default=1.5, decimals=1, space="sell", optimize=True
    )

    _last_entry_time: dict = {}

    def informative_pairs(self):
        pairs = self.dp.current_whitelist() if self.dp else []
        return [(p, "15m") for p in pairs] + [(p, "1h") for p in pairs]

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        pair = metadata["pair"]

        # 5m indicators
        dataframe["rsi14"] = ta.RSI(dataframe, timeperiod=14)
        dataframe["rsi7"] = ta.RSI(dataframe, timeperiod=7)
        dataframe["mfi"] = ta.MFI(dataframe, timeperiod=14)
        macd = ta.MACD(dataframe, fastperiod=12, slowperiod=26, signalperiod=9)
        dataframe["macd_hist"] = macd["macdhist"]
        stoch = ta.STOCH(dataframe, fastk_period=14, slowk_period=3, slowd_period=3)
        dataframe["stoch_k"] = stoch["slowk"]
        dataframe["cci"] = ta.CCI(dataframe, timeperiod=20)

        dataframe["ema9"] = ta.EMA(dataframe, timeperiod=9)
        dataframe["ema21"] = ta.EMA(dataframe, timeperiod=21)
        dataframe["ema50"] = ta.EMA(dataframe, timeperiod=50)
        dataframe["adx"] = ta.ADX(dataframe, timeperiod=14)

        bb = ta.BBANDS(dataframe, timeperiod=20, nbdevup=2.0, nbdevdn=2.0)
        dataframe["bb_upper"] = bb["upperband"]
        dataframe["bb_lower"] = bb["lowerband"]
        dataframe["bb_mid"] = bb["middleband"]

        dataframe["volume_sma"] = ta.SMA(dataframe["volume"], timeperiod=20)
        dataframe["rvol"] = dataframe["volume"] / dataframe["volume_sma"].replace(0, np.nan)
        dataframe["obv"] = ta.OBV(dataframe)
        dataframe["obv_sma"] = ta.SMA(dataframe["obv"], timeperiod=20)

        dataframe["is_green"] = (dataframe["close"] > dataframe["open"]).astype(int)
        dataframe["is_red"] = (dataframe["close"] < dataframe["open"]).astype(int)

        # Candlestick patterns
        body = abs(dataframe["close"] - dataframe["open"])
        candle_range = (dataframe["high"] - dataframe["low"]).replace(0, np.nan)
        dataframe["body_pct"] = body / candle_range
        dataframe["lower_wick"] = (
            np.minimum(dataframe["close"], dataframe["open"]) - dataframe["low"]
        ) / candle_range
        dataframe["upper_wick"] = (
            dataframe["high"] - np.maximum(dataframe["close"], dataframe["open"])
        ) / candle_range

        dataframe["bullish_engulf"] = (
            (dataframe["is_green"] == 1)
            & (dataframe["is_red"].shift(1) == 1)
            & (dataframe["close"] > dataframe["open"].shift(1))
            & (dataframe["open"] < dataframe["close"].shift(1))
        ).astype(int)
        dataframe["hammer"] = (
            (dataframe["lower_wick"] > 0.6) & (dataframe["body_pct"] > 0.1) & (dataframe["upper_wick"] < 0.15)
        ).astype(int)
        dataframe["bearish_engulf"] = (
            (dataframe["is_red"] == 1)
            & (dataframe["is_green"].shift(1) == 1)
            & (dataframe["close"] < dataframe["open"].shift(1))
            & (dataframe["open"] > dataframe["close"].shift(1))
        ).astype(int)
        dataframe["shooting_star"] = (
            (dataframe["upper_wick"] > 0.6) & (dataframe["body_pct"] > 0.1) & (dataframe["lower_wick"] < 0.15)
        ).astype(int)

        # 15m
        inf_15m = self.dp.get_pair_dataframe(pair=pair, timeframe="15m")
        if not inf_15m.empty:
            inf_15m["rsi_15m"] = ta.RSI(inf_15m, timeperiod=14)
            inf_15m["ema50_15m"] = ta.EMA(inf_15m, timeperiod=50)
            macd_15 = ta.MACD(inf_15m, fastperiod=12, slowperiod=26, signalperiod=9)
            inf_15m["macd_hist_15m"] = macd_15["macdhist"]
            m = merge_informative_pair(
                dataframe, inf_15m[["date", "rsi_15m", "ema50_15m", "macd_hist_15m"]],
                self.timeframe, "15m", ffill=True,
            )
            dataframe["rsi_15m"] = m["rsi_15m_15m"].values
            dataframe["ema50_15m"] = m["ema50_15m_15m"].values
            dataframe["macd_15m"] = m["macd_hist_15m_15m"].values
        else:
            dataframe["rsi_15m"] = 50
            dataframe["ema50_15m"] = dataframe["close"]
            dataframe["macd_15m"] = 0

        # 1h
        inf_1h = self.dp.get_pair_dataframe(pair=pair, timeframe="1h")
        if not inf_1h.empty:
            inf_1h["ema9_1h"] = ta.EMA(inf_1h, timeperiod=9)
            inf_1h["ema21_1h"] = ta.EMA(inf_1h, timeperiod=21)
            inf_1h["adx_1h"] = ta.ADX(inf_1h, timeperiod=14)
            inf_1h["rsi_1h"] = ta.RSI(inf_1h, timeperiod=14)
            m1h = merge_informative_pair(
                dataframe, inf_1h[["date", "ema9_1h", "ema21_1h", "adx_1h", "rsi_1h"]],
                self.timeframe, "1h", ffill=True,
            )
            dataframe["ema9_1h"] = m1h["ema9_1h_1h"].values
            dataframe["ema21_1h"] = m1h["ema21_1h_1h"].values
            dataframe["adx_1h"] = m1h["adx_1h_1h"].values
            dataframe["rsi_1h"] = m1h["rsi_1h_1h"].values
        else:
            dataframe["ema9_1h"] = dataframe["close"]
            dataframe["ema21_1h"] = dataframe["close"]
            dataframe["adx_1h"] = 25
            dataframe["rsi_1h"] = 50

        # Derived
        dataframe["trend_1h_up"] = (dataframe["ema9_1h"] > dataframe["ema21_1h"]).astype(int)
        dataframe["trend_1h_dn"] = (dataframe["ema9_1h"] < dataframe["ema21_1h"]).astype(int)
        dataframe["m15_pos"] = (dataframe["macd_15m"] > 0).astype(int)
        dataframe["m15_neg"] = (dataframe["macd_15m"] < 0).astype(int)
        dataframe["ema_ribbon_up"] = (
            (dataframe["ema9"] > dataframe["ema21"]) & (dataframe["ema21"] > dataframe["ema50"])
        ).astype(int)
        dataframe["ema_ribbon_dn"] = (
            (dataframe["ema9"] < dataframe["ema21"]) & (dataframe["ema21"] < dataframe["ema50"])
        ).astype(int)

        # ═══ VECTORIZED SCORING ═════════════════════════════════════════════
        # Long score
        sl = np.zeros(len(dataframe))
        sl += (dataframe["trend_1h_up"] == 1) * 2
        sl += (dataframe["ema_ribbon_up"] == 1) * 1
        sl += (dataframe["adx_1h"] > 20) * 1
        sl += (dataframe["rsi14"] < 35) * 2
        sl += ((dataframe["rsi14"] >= 35) & (dataframe["rsi14"] < 45)) * 1
        sl += (dataframe["mfi"] < 30) * 1
        sl += (dataframe["stoch_k"] < 25) * 1
        sl += (dataframe["cci"] < -100) * 1
        sl += (dataframe["rvol"] > 1.5) * 2
        sl += ((dataframe["rvol"] > 1.0) & (dataframe["rvol"] <= 1.5)) * 1
        sl += (dataframe["obv"] > dataframe["obv_sma"]) * 1
        sl += (dataframe["is_green"] == 1) * 1
        sl += (
            (dataframe["close"] <= dataframe["bb_mid"])
            & (dataframe["low"] <= dataframe["bb_lower"] * 1.005)
        ) * 1
        sl += (dataframe["bullish_engulf"] == 1) * 1
        sl += (dataframe["hammer"] == 1) * 1
        sl += (dataframe["m15_pos"] == 1) * 1
        sl += (dataframe["rsi_15m"] > 40) * 1
        sl += (dataframe["close"] > dataframe["ema50_15m"]) * 1
        sl += ((dataframe["rsi_1h"] > 35) & (dataframe["rsi_1h"] < 65)) * 1
        dataframe["score_long"] = sl

        # Short score
        ss = np.zeros(len(dataframe))
        ss += (dataframe["trend_1h_dn"] == 1) * 2
        ss += (dataframe["ema_ribbon_dn"] == 1) * 1
        ss += (dataframe["adx_1h"] > 20) * 1
        ss += (dataframe["rsi14"] > 65) * 2
        ss += ((dataframe["rsi14"] <= 65) & (dataframe["rsi14"] > 55)) * 1
        ss += (dataframe["mfi"] > 70) * 1
        ss += (dataframe["stoch_k"] > 75) * 1
        ss += (dataframe["cci"] > 100) * 1
        ss += (dataframe["rvol"] > 1.5) * 2
        ss += ((dataframe["rvol"] > 1.0) & (dataframe["rvol"] <= 1.5)) * 1
        ss += (dataframe["obv"] < dataframe["obv_sma"]) * 1
        ss += (dataframe["is_red"] == 1) * 1
        ss += (
            (dataframe["close"] >= dataframe["bb_mid"])
            & (dataframe["high"] >= dataframe["bb_upper"] * 0.995)
        ) * 1
        ss += (dataframe["bearish_engulf"] == 1) * 1
        ss += (dataframe["shooting_star"] == 1) * 1
        ss += (dataframe["m15_neg"] == 1) * 1
        ss += (dataframe["rsi_15m"] < 60) * 1
        ss += (dataframe["close"] < dataframe["ema50_15m"]) * 1
        ss += ((dataframe["rsi_1h"] > 35) & (dataframe["rsi_1h"] < 65)) * 1
        dataframe["score_short"] = ss

        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        thr_l = int(self.score_threshold.value)
        thr_s = int(self.score_threshold_short.value)
        has_data = dataframe["rsi14"].notna() & (dataframe["volume"] > 0)

        long_sig = has_data & (dataframe["score_long"] >= thr_l)
        short_sig = has_data & (dataframe["score_short"] >= thr_s)

        dataframe.loc[long_sig, "enter_long"] = 1
        dataframe.loc[long_sig, "enter_tag"] = "L" + dataframe.loc[long_sig, "score_long"].astype(int).astype(str)
        dataframe.loc[short_sig, "enter_short"] = 1
        dataframe.loc[short_sig, "enter_tag"] = "S" + dataframe.loc[short_sig, "score_short"].astype(int).astype(str)
        return dataframe

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

    def confirm_trade_entry(
        self, pair, order_type, amount, rate, time_in_force,
        current_time, entry_tag, side, **kwargs,
    ) -> bool:
        last = self._last_entry_time.get(pair)
        cd = int(self.cooldown_candles.value) * 300
        if last is not None and (current_time - last).total_seconds() < cd:
            return False
        self._last_entry_time[pair] = current_time
        return True

    def leverage(self, pair, current_time, current_rate, proposed_leverage,
                 max_leverage, entry_tag, side, **kwargs) -> float:
        return 1.0
