# source: https://raw.githubusercontent.com/remiotore/freqtrade/44beaeb6a420cd8e9f2e4ea93e11d6cfa192ee03/strategies/momentum_mean_reversion_strategy.py
from datetime import datetime, timedelta
import warnings

import talib.abstract as ta
import pandas_ta as pta
from pandas import DataFrame

from freqtrade.strategy.interface import IStrategy
from freqtrade.persistence import Trade
from freqtrade.strategy import IntParameter, DecimalParameter
from functools import reduce

# Suppress runtime warnings from indicator calculations\ nwarnings.simplefilter(action="ignore", category=RuntimeWarning)

# Buffers to track trade IDs for custom exit logic
_pending_hold = []
_pending_hold_alt = []


class Github_remiotore_freqtrade__momentum_mean_reversion_strategy__20260111_210550(IStrategy):
    """
    A momentum + mean-reversion trading strategy for futures with\ ncustom entry & exit rules, dynamic ROI targets, and trailing stops.
    """

    # -- Return targets: minute: ROI
    minimal_roi = {
        "0":    0.12,   # immediate 12%
        "30":   0.05,   #  5% after 30 minutes
        "60":   0.02,   #  2% after 1 hour
        "120":  0.01,   #  1% after 2 hours
        "240":  0.005,  # 0.5% after 4 hours
        "480":  0.0     # break-even at 8 hours
    }

    # Strategy settings
    timeframe = '15m'
    process_only_new_candles = True
    startup_candle_count = 240
    leverage_level = 5

    # Hard stoploss and trailing-stop parameters
    stoploss = -0.28
    trailing_stop = True
    trailing_stop_positive = 0.009
    trailing_stop_positive_offset = 0.026
    trailing_only_offset_is_reached = True

    # -- Optimization flags and hyperparameters
    _opt_enabled = True
    buy_rsi_fast = IntParameter(20, 70, default=40, space='buy', optimize=_opt_enabled)
    buy_rsi_main = IntParameter(15, 50, default=42, space='buy', optimize=_opt_enabled)
    buy_sma_ratio = DecimalParameter(0.900, 1.0, default=0.973, decimals=3, space='buy', optimize=_opt_enabled)
    buy_cti_thresh = DecimalParameter(-1.0, 1.0, default=0.69, decimals=2, space='buy', optimize=_opt_enabled)

    sell_fastk_thresh = IntParameter(50, 100, default=84, space='sell', optimize=True)

    # CCI-based exit loss prevention
    _cci_opt = True
    sell_cci_loss_level = IntParameter(0, 600, default=120, space='sell', optimize=_cci_opt)
    sell_cci_loss_profit = DecimalParameter(-0.15, 0.0, default=-0.15, decimals=2, space='sell', optimize=_cci_opt)

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Calculate and store all necessary indicators in the DataFrame.
        """
        # Simple moving averages
        dataframe['sma_15'] = ta.SMA(dataframe, timeperiod=15)
        dataframe['ma120'] = ta.MA(dataframe, timeperiod=120)
        dataframe['ma240'] = ta.MA(dataframe, timeperiod=240)

        # Relative Strength Index: fast, main, slow
        dataframe['rsi_fast'] = ta.RSI(dataframe, timeperiod=4)
        dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
        dataframe['rsi_slow'] = ta.RSI(dataframe, timeperiod=20)

        # Composite Trend Index for extra momentum detection
        dataframe['cti'] = pta.cti(dataframe['close'], length=20)

        # Stochastic Fast K line
        stoch = ta.STOCHF(dataframe, 5, 3, 0, 3, 0)
        dataframe['fastk'] = stoch['fastk']

        # Commodity Channel Index
        dataframe['cci'] = ta.CCI(dataframe, timeperiod=20)

        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Mark entry signals when momentum dips but price aligns below short-term SMA.
        """
        dataframe['enter_tag'] = ''
        conditions = []

        # Primary entry condition
        cond1 = (
            (dataframe['rsi_slow'] < dataframe['rsi_slow'].shift()) &
            (dataframe['rsi_fast'] < self.buy_rsi_fast.value) &
            (dataframe['rsi'] > self.buy_rsi_main.value) &
            (dataframe['close'] < dataframe['sma_15'] * self.buy_sma_ratio.value) &
            (dataframe['cti'] < self.buy_cti_thresh.value)
        )
        conditions.append(cond1)
        dataframe.loc[cond1, 'enter_tag'] += 'primary'

        # Alternative entry with fixed thresholds
        cond2 = (
            (dataframe['rsi_slow'] < dataframe['rsi_slow'].shift()) &
            (dataframe['rsi_fast'] < 34) &
            (dataframe['rsi'] > 28) &
            (dataframe['close'] < dataframe['sma_15'] * 0.96) &
            (dataframe['cti'] < self.buy_cti_thresh.value)
        )
        conditions.append(cond2)
        dataframe.loc[cond2, 'enter_tag'] += 'fallback'

        # Combine all conditions into enter_long flag
        if conditions:
            combined = reduce(lambda a, b: a | b, conditions)
            dataframe.loc[combined, 'enter_long'] = 1

        return dataframe

    def custom_exit(self, pair: str, trade: Trade, current_time: datetime,
                    current_rate: float, current_profit: float, **kwargs) -> str | None:
        """
        Override exit signals based on profit and indicator crosses.
        """
        df, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe)
        row = df.iloc[-1]

        # Track in/out of MA zones for potential hold flags
        if row['close'] > row['ma120'] or row['close'] > row['ma240']:
            if trade.id not in _pending_hold:
                _pending_hold.append(trade.id)
        else:
            if trade.id not in _pending_hold_alt:
                _pending_hold_alt.append(trade.id)

        # Fast-K profit-taking
        if current_profit > 0 and row['fastk'] > self.sell_fastk_thresh.value:
            return 'fastk_profit_sell'

        # CCI breakout exits
        if row['cci'] > 80 and row['high'] >= trade.open_rate:
            return 'cci_high_sell'

        # Loss exit when deeply negative but CCI spiking
        if current_profit <= -0.15 and row['cci'] > 200:
            return 'cci_loss_sell'

        # Default: no custom exit
        return None

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Placeholder for exit trend; custom_exit handles all logic.
        """
        dataframe[['exit_long', 'exit_tag']] = (0, 'long_out')
        return dataframe

    def leverage(self, pair: str, current_time: datetime, current_rate: float,
                 proposed_leverage: float, max_leverage: float, side: str, **kwargs) -> float:
        """
        Always use a fixed leverage level.
        """
        return self.leverage_level
