# source: https://raw.githubusercontent.com/4tie/fortiesr/d5cc40760e3cbaff381058a0ea8733d2be469578/user_data/strategies/AutoQuantRobustV1.py
from freqtrade.strategy import IntParameter, DecimalParameter, IStrategy
from pandas import DataFrame
import talib.abstract as ta
from freqtrade.strategy import merge_informative_pair


class Github_4tie_fortiesr__AutoQuantRobustV1__20260709_213639(IStrategy):
    INTERFACE_VERSION: int = 3

    # ROI Table - realistic targets
    minimal_roi = {
        "0": 0.06,
        "30": 0.04,
        "60": 0.02,
        "120": 0.01,
        "240": 0,
    }

    # Stoploss - ATR-based will be calculated dynamically
    stoploss = -0.05

    # Timeframe
    timeframe = "1h"

    # Trailing stop
    trailing_stop = True
    trailing_stop_positive = 0.015
    trailing_stop_positive_offset = 0.03
    trailing_only_offset_is_reached = True

    # Process only new candles
    process_only_new_candles = True
    startup_candle_count = 120

    # ── Tunable Parameters for Optimizer ──
    
    # Trend filter parameters
    ema_fast = IntParameter(8, 20, default=12, space="buy", optimize=True)
    ema_slow = IntParameter(21, 50, default=26, space="buy", optimize=True)
    
    # Momentum parameters
    rsi_period = IntParameter(10, 20, default=14, space="buy", optimize=True)
    rsi_oversold = IntParameter(25, 40, default=30, space="buy", optimize=True)
    rsi_overbought = IntParameter(60, 75, default=70, space="sell", optimize=True)
    
    # Volatility filter
    atr_period = IntParameter(10, 20, default=14, space="buy", optimize=True)
    atr_multiplier = DecimalParameter(1.0, 3.0, default=1.5, decimals=1, space="buy", optimize=True)
    
    # Volume filter
    volume_factor = DecimalParameter(0.8, 2.0, default=1.2, decimals=1, space="buy", optimize=True)
    
    # Stoploss parameters
    atr_stoploss_multiplier = DecimalParameter(1.5, 4.0, default=2.5, decimals=1, space="sell", optimize=True)
    
    # ROI parameters
    roi_0 = DecimalParameter(0.04, 0.10, default=0.06, decimals=3, space="sell", optimize=True)
    roi_30 = DecimalParameter(0.02, 0.06, default=0.04, decimals=3, space="sell", optimize=True)
    roi_60 = DecimalParameter(0.01, 0.03, default=0.02, decimals=3, space="sell", optimize=True)

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # Trend indicators
        dataframe["ema_fast"] = ta.EMA(dataframe, timeperiod=self.ema_fast.value)
        dataframe["ema_slow"] = ta.EMA(dataframe, timeperiod=self.ema_slow.value)
        
        # Momentum
        dataframe["rsi"] = ta.RSI(dataframe, timeperiod=self.rsi_period.value)
        
        # Volatility
        dataframe["atr"] = ta.ATR(dataframe, timeperiod=self.atr_period.value)
        
        # Volume
        dataframe["volume_mean"] = dataframe["volume"].rolling(window=20).mean()
        dataframe["volume_ratio"] = dataframe["volume"] / dataframe["volume_mean"]
        
        # MACD for additional confirmation
        macd = ta.MACD(dataframe, fastperiod=12, slowperiod=26, signalperiod=9)
        dataframe["macd"] = macd["macd"]
        dataframe["macd_signal"] = macd["macdsignal"]
        dataframe["macd_hist"] = macd["macdhist"]
        
        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Entry conditions - long-only for better signal generation:
        1. Trend filter: EMA fast > EMA slow (uptrend for long)
        2. Momentum: RSI above midline
        3. Basic volume check
        """
        dataframe.loc[
            (
                # Trend filter - long in uptrend
                (dataframe["ema_fast"] > dataframe["ema_slow"]) &
                # Momentum - above midline
                (dataframe["rsi"] > 50) &
                # Basic volume check
                (dataframe["volume_ratio"] > 0.5)
            ),
            "enter_long",
        ] = 1

        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Exit conditions - long-only:
        1. RSI overbought (>70)
        2. Trend reversal (EMA fast < EMA slow)
        """
        dataframe.loc[
            (
                # RSI overbought
                (dataframe["rsi"] > 70) |
                # Trend reversal
                (dataframe["ema_fast"] < dataframe["ema_slow"])
            ),
            "exit_long",
        ] = 1

        return dataframe

    def custom_stoplevel(self, pair: str, current_rate: float, current_profit: float, 
                        entry_rate: float, current_time, dataframe: DataFrame, 
                        side: str, **kwargs) -> float:
        """
        Dynamic ATR-based stoploss
        """
        if len(dataframe) < self.atr_period.value:
            return self.stoploss
        
        atr_value = dataframe["atr"].iloc[-1]
        atr_multiplier = self.atr_stoploss_multiplier.value
        
        # Calculate ATR-based stoploss
        atr_stop = atr_value * atr_multiplier / entry_rate
        
        # Convert to percentage
        if side == "short":
            stoploss_pct = atr_stop
        else:
            stoploss_pct = -atr_stop
            
        # Ensure it's not too extreme
        stoploss_pct = max(min(stoploss_pct, -0.02), -0.15)
        
        return stoploss_pct

    def custom_roi(self, pair: str, current_profit: float, current_time, 
                   dataframe: DataFrame, trade, **kwargs) -> float | None:
        """
        Dynamic ROI based on optimizer parameters
        """
        roi_dict = {
            "0": self.roi_0.value,
            "30": self.roi_30.value,
            "60": self.roi_60.value,
            "120": 0.01,
            "240": 0,
        }
        
        # Find the appropriate ROI based on trade duration
        if trade:
            duration_minutes = (current_time - trade.open_date_utc).total_seconds() / 60
            
            for minutes, roi in sorted(roi_dict.items()):
                if duration_minutes <= int(minutes):
                    return roi
                    
        return None
