# source: https://raw.githubusercontent.com/DOUGLASGUEDESATRIA/OSIRIS_TRADE/fe3c09567f3798d1cad6d7a306fa4f6131d91e17/user_data/strategies/OsirisMultiScalpV2.py
"""
OsirisMultiScalp v2 — Multi-Pair Mean Reversion (ATR-aware)
============================================================
DATA FINDINGS:
  - ATR on 15m = 0.4-0.7% for top 10 pairs
  - SL must be >= 1x ATR to survive intra-candle noise
  - With SL=1%, TP=2% (2:1), fee is only 2-4% of TP
  - RSI mean-reversion has genuine statistical edge

SIGNALS:
  - LONG:  RSI fresh cross below threshold (oversold → buy)
  - SHORT: RSI fresh cross above threshold (overbought → sell)
  - Fresh = RSI wasn't extreme 1-2 candles ago (no stacking)

EXIT: Native Freqtrade stoploss + minimal_roi (rock-solid simulation)
"""

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

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

logger = logging.getLogger(__name__)


class Github_DOUGLASGUEDESATRIA_OSIRIS_TRADE__OsirisMultiScalpV2__20260329_060843(IStrategy):
    INTERFACE_VERSION = 3
    can_short = True
    timeframe = "15m"

    startup_candle_count = 50
    process_only_new_candles = True

    # Order type: LIMIT for maker fees
    order_types = {
        "entry": "limit",
        "exit": "limit",
        "stoploss": "market",
        "stoploss_on_exchange": False,
    }

    # ── Native SL/TP ─────────────────────────────────────────────────
    stoploss = -0.01
    minimal_roi = {"0": 100}  # disabled — exit via custom_stoploss

    trailing_stop = False
    use_custom_stoploss = True

    # ── TP/SL params (hyperoptable) ──────────────────────────────────
    tp_pct = DecimalParameter(0.5, 4.0, default=2.0, decimals=1, space="sell", optimize=True)
    sl_pct = DecimalParameter(0.3, 2.0, default=1.0, decimals=1, space="sell", optimize=True)
    max_hold = IntParameter(8, 96, default=48, space="sell", optimize=True)

    # ── Entry params (hyperoptable) ──────────────────────────────────
    long_rsi = IntParameter(20, 40, default=35, space="buy", optimize=True)
    short_rsi = IntParameter(60, 80, default=65, space="buy", optimize=True)
    rsi_period = IntParameter(10, 20, default=14, space="buy", optimize=True)
    use_bb_filter = IntParameter(0, 1, default=0, space="buy", optimize=True)
    use_adx_filter = IntParameter(0, 1, default=0, space="buy", optimize=True)
    adx_min = IntParameter(15, 30, default=20, space="buy", optimize=True)

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # RSI for multiple periods
        for period in range(10, 21):
            dataframe[f"rsi_{period}"] = ta.RSI(dataframe, timeperiod=period)

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

        # ADX
        dataframe["adx"] = ta.ADX(dataframe, timeperiod=14)

        return dataframe

    def custom_stoploss(
        self,
        pair: str,
        trade: Trade,
        current_time: datetime,
        current_rate: float,
        current_profit: float,
        after_fill: bool,
        **kwargs,
    ) -> float:
        is_short = trade.is_short
        tp = self.tp_pct.value / 100
        sl = self.sl_pct.value / 100

        # TP: close when profit exceeds target
        if current_profit >= tp:
            return 0.001  # tiny positive = close immediately

        # Timeout: close after max_hold candles
        duration_min = (current_time - trade.open_date_utc).total_seconds() / 60
        max_min = self.max_hold.value * 15  # 15m candle
        if duration_min >= max_min:
            return 0.001

        # SL: fixed from open
        return -sl

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        rsi = dataframe[f"rsi_{self.rsi_period.value}"]
        rsi_1 = rsi.shift(1)
        rsi_2 = rsi.shift(2)

        # Fresh long entry
        long_fresh = (rsi_1 >= self.long_rsi.value) | (rsi_2 >= self.long_rsi.value)
        long_base = (rsi < self.long_rsi.value) & long_fresh & (dataframe["volume"] > 0)

        # Fresh short entry
        short_fresh = (rsi_1 <= self.short_rsi.value) | (rsi_2 <= self.short_rsi.value)
        short_base = (rsi > self.short_rsi.value) & short_fresh & (dataframe["volume"] > 0)

        # Optional BB filter
        if self.use_bb_filter.value == 1:
            long_base = long_base & (dataframe["close"] <= dataframe["bb_lower"] * 1.005)
            short_base = short_base & (dataframe["close"] >= dataframe["bb_upper"] * 0.995)

        # Optional ADX filter (trend strength)
        if self.use_adx_filter.value == 1:
            adx_ok = dataframe["adx"] > self.adx_min.value
            long_base = long_base & adx_ok
            short_base = short_base & adx_ok

        dataframe.loc[long_base, "enter_long"] = 1
        dataframe.loc[short_base, "enter_short"] = 1

        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # All exits via stoploss + minimal_roi
        dataframe["exit_long"] = 0
        dataframe["exit_short"] = 0
        return dataframe
