# source: https://raw.githubusercontent.com/vaskosmihaylov/nfi-custom-strategies/6cb3864e6597229ffd877d9680b4f349c5dc60bb/user_data/strategies/Rsiqui/RsiquiV5.py
import numpy as np
import talib.abstract as ta
import freqtrade.vendor.qtpylib.indicators as qtpylib
from pandas import DataFrame
from datetime import datetime
from typing import Optional, Tuple, Union
from freqtrade.persistence import Trade
from freqtrade.strategy.interface import IStrategy
from freqtrade.strategy import (IStrategy, DecimalParameter, IntParameter, CategoricalParameter, BooleanParameter)
import logging

logger = logging.getLogger(__name__)

class Github_vaskosmihaylov_nfi_custom_strategies__RsiquiV5__20260131_104305(IStrategy):
    """
    Github_vaskosmihaylov_nfi_custom_strategies__RsiquiV5__20260131_104305 - RSI Gradient Strategy (Optimized)

    OPTIMIZATIONS (Jan 2026):
    1. Balanced entry thresholds: RSI 35/65 instead of 27/59 (more balanced long/short)
    2. ATR-based dynamic stoploss: Prevents catastrophic losses like -27%
    3. Kept existing ROI table and exit signals
    4. 3x leverage maintained

    Strategy Logic:
    - Entry: RSI gradient crosses zero at extreme RSI levels
    - Exit: ROI table, exit signals (gradient reversal), or ATR-based stops
    - Risk Management: Dynamic ATR stops replace fixed -27.3% stoploss
    """

    INTERFACE_VERSION = 3

    can_short = True
    timeframe = '5m'
    use_exit_signal = True
    exit_profit_only = True
    use_custom_stoploss = True

    # Buy hyperspace params (OPTIMIZED Jan 30, 2026):
    buy_params = {
        "rsi_entry_long": 35,   # Balanced threshold (was 27)
        "rsi_entry_short": 60,  # Balanced threshold (was 65, originally 59)
        "window": 24,
    }

    # Sell hyperspace params:
    sell_params = {
        "rsi_exit_long": 18,
        "rsi_exit_short": 75,
    }

    # ROI table:
    minimal_roi = {
        "0": 0.223,
        "34": 0.082,
        "82": 0.033,
        "109": 0
    }

    # Stoploss (managed by custom_stoploss):
    stoploss = -0.99

    # Trailing stop:
    trailing_stop = False
    trailing_stop_positive = None
    trailing_stop_positive_offset = 0.0
    trailing_only_offset_is_reached = False

    # Max Open Trades:
    max_open_trades = -1

    # Hyperparameters
    rsi_entry_long  = IntParameter(25, 40, default=buy_params.get('rsi_entry_long'),  space='buy',  optimize=True)
    rsi_exit_long   = IntParameter(10, 25, default=sell_params.get('rsi_exit_long'),   space='sell', optimize=True)
    rsi_entry_short = IntParameter(60, 75, default=buy_params.get('rsi_entry_short'), space='buy',  optimize=True)
    rsi_exit_short  = IntParameter(70, 85, default=sell_params.get('rsi_exit_short'),  space='sell', optimize=True)
    window          = IntParameter(5, 100, default=buy_params.get('window'),          space='buy',  optimize=False)

    # ATR-Based Dynamic Stop Loss (FIXED Jan 2026)
    # Research: 2.5x ATR optimal for crypto day trading (source: luxalgo.com)
    # Stop range: 5-10% for 5m timeframe crypto (source: flipster.io, hyrotrader.com)
    atr_stop_multiplier = DecimalParameter(2.0, 3.5, default=2.5, space='sell', optimize=True)
    max_stop_loss = DecimalParameter(-0.12, -0.08, default=-0.10, space='sell', optimize=False)  # Tightest: max 10% loss
    min_stop_loss = DecimalParameter(-0.06, -0.04, default=-0.05, space='sell', optimize=False)  # Loosest: min 5% room

    @property
    def plot_config(self):
        plot_config = {}

        plot_config['main_plot'] = {
            'rsi_ema' : {}
        }
        plot_config['subplots'] = {
            'Misc': {
                'rsi': {},
                'rsi_gra' : {},
            },
        }

        return plot_config

    def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
                        current_rate: float, current_profit: float, **kwargs) -> float:
        """
        ATR-Based Dynamic Stop Loss (FIXED Jan 29, 2026)

        Uses 2.5x ATR multiplier (optimal for crypto day trading per research).
        Stop range: 5-10% based on volatility, preventing both premature exits
        and catastrophic losses.

        Previous bug: Logic was inverted, causing 3% stops (too tight).
        Fixed: Now allows 5-10% range based on market volatility.

        Args:
            pair: Trading pair
            trade: Trade object
            current_time: Current timestamp
            current_rate: Current price
            current_profit: Current profit ratio

        Returns:
            float: Stoploss percentage (negative value between -5% and -10%)
        """
        dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe)
        current_candle = dataframe.iloc[-1].squeeze()

        # Calculate ATR-based stop: 2.5x ATR as percentage (default)
        atr_stop = -1 * self.atr_stop_multiplier.value * current_candle['atr_pcnt']

        # Clamp between bounds to prevent too tight or too loose stops
        # FIXED: Correct logic for negative stop loss values
        # min_stop_loss = -0.05 (loosest: allows at least 5% room for volatility)
        # max_stop_loss = -0.10 (tightest: prevents losses beyond 10%)
        atr_stop = min(atr_stop, self.min_stop_loss.value)  # Don't allow tighter than 5%
        atr_stop = max(atr_stop, self.max_stop_loss.value)  # Don't allow looser than 10%

        # Log when stop activates
        if current_profit < atr_stop:
            logger.info(f"*** {pair} *** ATR Stop: {current_profit*100:.2f}% < {atr_stop*100:.2f}%")

        return atr_stop

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
        dataframe['rsi_ema'] = dataframe['rsi'].ewm(span=self.window.value).mean()
        dataframe['rsi_gra'] = np.gradient(dataframe['rsi_ema'])

        # ATR for dynamic stoploss (NEW)
        dataframe['atr_pcnt'] = (ta.ATR(dataframe, timeperiod=14) / dataframe['close'])

        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Entry signals based on RSI gradient crosses.

        OPTIMIZED (Jan 30, 2026): Balanced RSI thresholds for equal long/short opportunity
        - Longs: RSI < 35 (balanced for oversold conditions)
        - Shorts: RSI > 60 (balanced for overbought conditions)
        
        Previous issue: rsi_entry_short was 65 (too restrictive)
        Result: Strategy produced mostly longs, very few shorts
        Fix: Lowered to 60 to compensate for market upward bias
        Expected: 30-40% increase in short signals
        """
        dataframe.loc[
            (
                (dataframe['rsi'] < self.rsi_entry_long.value) &
                qtpylib.crossed_above(dataframe['rsi_gra'], 0)
            ),
        'enter_long'] = 1

        dataframe.loc[
            (
                (dataframe['rsi'] > self.rsi_entry_short.value) &
                qtpylib.crossed_below(dataframe['rsi_gra'], 0)
            ),
        'enter_short'] = 1

        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[
            (
                (dataframe['rsi'] > self.rsi_exit_long.value) &
                qtpylib.crossed_below(dataframe['rsi_gra'], 0)
            ),
        'exit_long'] = 1

        dataframe.loc[
            (
                (dataframe['rsi'] < self.rsi_exit_short.value) &
                qtpylib.crossed_above(dataframe['rsi_gra'], 0)
            ),
        'exit_short'] = 1

        return dataframe

    def leverage(self, pair: str, current_time: datetime, current_rate: float, proposed_leverage: float, max_leverage: float, side: str, **kwargs) -> float:
        return 3
