# source: https://raw.githubusercontent.com/DOUGLASGUEDESATRIA/OSIRIS_TRADE/fe3c09567f3798d1cad6d7a306fa4f6131d91e17/user_data/strategies/OsirisMultiScalp.py
"""
Github_DOUGLASGUEDESATRIA_OSIRIS_TRADE__OsirisMultiScalp__20260329_060843 — Multi-Pair Mean Reversion Day Trade
========================================================
DATA-DRIVEN strategy built from empirical analysis of 10 pairs x 809 days.

PROVEN EDGE (maker fee 0.02%/side):
  - SHORT mean-rev: RSI > 60 → short, TP=0.6% SL=0.2% (3:1 R:R)
    ~3/day/pair, 34% WR, +711% net across 10 pairs
  - LONG mean-rev: RSI < 35 → long, TP=0.7% SL=0.23% (3:1 R:R)
    ~2/day/pair, 35% WR, +672% net across 10 pairs
  - Combined: ~50 ops/day across 10 pairs

KEY REQUIREMENTS:
  - MUST use limit orders (maker fee 0.02% vs taker 0.04%)
  - Multi-pair mandatory — edge is thin per pair, aggregated over many
  - BTC alone is NOT profitable — altcoins (SOL, DOGE, ADA, XRP) carry the edge

PARAMETERS (from fine-grained sweep):
  - LONG:  RSI < 35, TP=0.7%, SL=0.23%, max_hold=12 candles (3h)
  - SHORT: RSI > 60, TP=0.6%, SL=0.20%, max_hold=18 candles (4.5h)
  - R:R = 3:1 on both sides
  - Avg hold: 1.7-4.4 candles (25min-1h)
"""

import logging
from pandas import DataFrame
from datetime import datetime, timedelta

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

try:
    from freqtrade.strategy import stoploss_from_open
except ImportError:
    def stoploss_from_open(open_relative_stop, current_profit, is_short=False):
        if current_profit == 0:
            return 1
        if is_short:
            return -1 + ((1 - open_relative_stop) / (1 - current_profit))
        return 1 - ((1 + open_relative_stop) / (1 + current_profit))

logger = logging.getLogger(__name__)


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

    # Wide stoploss — real exit via custom_stoploss
    stoploss = -0.05
    minimal_roi = {"0": 100}
    trailing_stop = False
    use_custom_stoploss = True
    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,
    }

    # ── SHORT params ──────────────────────────────────────────────────
    short_rsi_threshold = IntParameter(55, 75, default=60, space="buy", optimize=True)
    short_tp = DecimalParameter(0.3, 1.0, default=0.6, decimals=1, space="sell", optimize=True)
    short_sl = DecimalParameter(0.1, 0.5, default=0.2, decimals=2, space="sell", optimize=True)
    short_max_hold = IntParameter(6, 36, default=18, space="sell", optimize=True)

    # ── LONG params ───────────────────────────────────────────────────
    long_rsi_threshold = IntParameter(25, 45, default=35, space="buy", optimize=True)
    long_tp = DecimalParameter(0.3, 1.2, default=0.7, decimals=1, space="sell", optimize=True)
    long_sl = DecimalParameter(0.1, 0.5, default=0.23, decimals=2, space="sell", optimize=True)
    long_max_hold = IntParameter(6, 36, default=12, space="sell", optimize=True)

    # ── Shared ────────────────────────────────────────────────────────
    rsi_period = IntParameter(10, 20, default=14, space="buy", optimize=True)

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

        # Fresh entry filter: RSI wasn't already extreme in last 2 candles
        # This prevents stacking entries in the same move
        dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14)

        return dataframe

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

        # LONG: RSI drops below threshold (mean reversion from oversold)
        # Fresh entry: RSI wasn't below threshold 1-2 candles ago
        long_cond = (
            (dataframe[rsi_col] < self.long_rsi_threshold.value)
            & (
                (dataframe[rsi_col].shift(1) >= self.long_rsi_threshold.value)
                | (dataframe[rsi_col].shift(2) >= self.long_rsi_threshold.value)
            )
            & (dataframe["volume"] > 0)
        )

        # SHORT: RSI rises above threshold (mean reversion from overbought)
        short_cond = (
            (dataframe[rsi_col] > self.short_rsi_threshold.value)
            & (
                (dataframe[rsi_col].shift(1) <= self.short_rsi_threshold.value)
                | (dataframe[rsi_col].shift(2) <= self.short_rsi_threshold.value)
            )
            & (dataframe["volume"] > 0)
        )

        dataframe.loc[long_cond, "enter_long"] = 1
        dataframe.loc[short_cond, "enter_short"] = 1

        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # Exit via custom_stoploss (TP/SL/timeout)
        dataframe["exit_long"] = 0
        dataframe["exit_short"] = 0
        return dataframe

    def custom_stoploss(
        self,
        pair: str,
        trade: Trade,
        current_time: datetime,
        current_rate: float,
        current_profit: float,
        after_fill: bool,
        **kwargs,
    ) -> float:
        """
        Implements TP, SL, and timeout exit logic.
        Returns stoploss value relative to current price.
        """
        is_short = trade.is_short

        # Determine params based on trade direction
        if is_short:
            tp_pct = self.short_tp.value / 100
            sl_pct = self.short_sl.value / 100
            max_hold_candles = self.short_max_hold.value
        else:
            tp_pct = self.long_tp.value / 100
            sl_pct = self.long_sl.value / 100
            max_hold_candles = self.long_max_hold.value

        # TP hit — close immediately
        if current_profit >= tp_pct:
            return stoploss_from_open(tp_pct * 0.95, current_profit, is_short=is_short)

        # Timeout — close at market
        trade_duration = (current_time - trade.open_date_utc).total_seconds()
        max_duration = max_hold_candles * 15 * 60  # 15m per candle
        if trade_duration >= max_duration:
            return stoploss_from_open(-0.001, current_profit, is_short=is_short)

        # SL — normal stoploss from open
        return stoploss_from_open(-sl_pct, current_profit, is_short=is_short)
