# source: https://raw.githubusercontent.com/yccodr/py-trade/20b6d3d4d3182065910543d5aec51b696dd3c003/strategies/adapt_ma_scalping.py
from freqtrade.strategy import IStrategy
from pandas import DataFrame
import numpy as np
import talib.abstract as ta
from freqtrade.strategy import IntParameter, DecimalParameter
from datetime import datetime, timedelta


class Github_yccodr_py_trade__adapt_ma_scalping__20250120_174325(IStrategy):
    """
    Adaptive MA Scalping Strategy
    Combines Kaufman's Adaptive Moving Average (KAMA) with MACD histogram
    for momentum-adaptive trading decisions.
    """

    # Strategy interface version
    INTERFACE_VERSION = 3

    # Minimal ROI designed for the strategy
    minimal_roi = {"0": 0.1}

    # Optimal stoploss designed for the strategy
    stoploss = -0.05

    # Trailing stoploss
    trailing_stop = False

    # Timeframe for the strategy
    timeframe = "5m"

    # Parameters
    malen = IntParameter(90, 110, default=100, space="buy")
    fast_length = IntParameter(20, 28, default=6, space="buy")
    slow_length = IntParameter(48, 56, default=12, space="buy")
    signal_length = IntParameter(16, 20, default=18, space="buy")

    # RSI Parameters
    rsi_period = IntParameter(10, 20, default=6, space="buy")
    rsi_oversold = IntParameter(20, 35, default=10, space="buy")
    rsi_overbought = IntParameter(65, 80, default=90, space="buy")

    # Exit Parameters
    exit_profit_only = False  # Enable exits in loss
    exit_profit_offset = 0.001  # Minimum profit to trigger trailing stop

    # Custom exit parameters
    profit_threshold_1 = DecimalParameter(0.01, 0.03, default=0.02, space="sell")
    profit_threshold_2 = DecimalParameter(0.03, 0.06, default=0.04, space="sell")
    rsi_exit_threshold = IntParameter(85, 95, default=90, space="sell")

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Calculates the Momentum-Adaptive Moving Average (MOMA)
        using KAMA and MACD histogram.
        """

        # Calculate KAMA
        dataframe["kama"] = ta.KAMA(dataframe, timeperiod=self.malen.value)

        # Calculate MACD
        macd = ta.MACD(
            dataframe,
            fastperiod=self.fast_length.value,
            slowperiod=self.slow_length.value,
            signalperiod=self.signal_length.value,
        )

        # Extract MACD histogram
        dataframe["macd_hist"] = macd["macd"] - macd["macdsignal"]

        # Calculate MOMA (Momentum-Adaptive Moving Average)
        dataframe["moma"] = dataframe["kama"] + dataframe["macd_hist"]

        # Calculate RSI
        dataframe["rsi"] = ta.RSI(dataframe, timeperiod=self.rsi_period.value)
        dataframe["trend_strength"] = abs(dataframe["close"] - dataframe["moma"])
        dataframe["trend_strength_ma"] = (
            dataframe["trend_strength"].rolling(window=5).mean()
        )
        dataframe["trend_direction"] = np.where(
            dataframe["close"] > dataframe["moma"], 1, -1
        )

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

        # Bollinger Bands
        bollinger = ta.BBANDS(dataframe, timeperiod=20, nbdevup=2.0, nbdevdn=2.0)
        dataframe["bb_upperband"] = bollinger["upperband"]
        dataframe["bb_middleband"] = bollinger["middleband"]
        dataframe["bb_lowerband"] = bollinger["lowerband"]

        # Volume indicators
        dataframe["volume_ma"] = dataframe["volume"].rolling(window=20).mean()
        dataframe["volume_ratio"] = dataframe["volume"] / dataframe["volume_ma"]

        # Volatility
        dataframe["atr"] = ta.ATR(dataframe, timeperiod=14)
        dataframe["atr_percent"] = (dataframe["atr"] / dataframe["close"]) * 100
        dataframe["atr_percent_ma"] = dataframe["atr_percent"].rolling(window=20).mean()

        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Generates buy signals based on price crossing above MOMA.
        """
        dataframe.loc[
            (
                # Price crosses above MOMA
                (dataframe["close"] > dataframe["moma"])
                & (dataframe["close"].shift(1) <= dataframe["moma"].shift(1))
                &
                # RSI conditions
                (dataframe["rsi"] > self.rsi_oversold.value)  # Not oversold
                & (dataframe["rsi"] < self.rsi_overbought.value)  # Not overbought
                &
                # Trend strength conditions
                (
                    dataframe["trend_strength"]
                    > dataframe["trend_strength"].rolling(window=5).mean()
                )  # Strong trend
                & (dataframe["macd_hist"] > 0)  # Positive momentum
            ),
            "enter_long",
        ] = 1

        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Generates sell signals based on price crossing below MOMA.
        """
        dataframe.loc[
            (
                # Price crosses below MOMA
                (dataframe["close"] < dataframe["moma"])
                & (dataframe["close"].shift(1) >= dataframe["moma"].shift(1))
                &
                # RSI conditions for exit
                (
                    (dataframe["rsi"] > self.rsi_overbought.value)  # Overbought
                    | (
                        dataframe["rsi"].shift(1) > self.rsi_overbought.value
                    )  # Was overbought
                    | (dataframe["macd_hist"] < 0)  # Negative momentum
                )
            ),
            "exit_long",
        ] = 1

        return dataframe

    def custom_exit(
        self,
        pair: str,
        trade: "Trade",
        current_time: datetime,
        current_rate: float,
        current_profit: float,
        **kwargs,
    ):
        """
        Sophisticated custom exit logic
        """
        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        last_candle = dataframe.iloc[-1].squeeze()

        # Time-based profit scaling
        trade_duration = current_time - trade.open_date

        # Quick profit-taking for small gains in short time
        if trade_duration < timedelta(minutes=10):
            if current_profit > self.profit_threshold_1.value:
                return "quick_profit_target_reached"

        # Higher profit target for longer trades
        if trade_duration > timedelta(minutes=30):
            if current_profit > self.profit_threshold_2.value:
                return "extended_profit_target_reached"

        # Exit conditions based on technical indicators
        if current_profit > 0:
            # Exit on extreme RSI with profit
            if last_candle["rsi"] > self.rsi_exit_threshold.value:
                return "rsi_extreme_exit"

            # Exit on high volatility with profit
            if last_candle["atr_percent"] > last_candle["atr_percent_ma"] * 1.5:
                return "high_volatility_exit"

            # Exit on volume spike with profit
            if last_candle["volume_ratio"] > 2.0:
                return "volume_spike_exit"

            # Exit on trend weakness with profit
            if (
                last_candle["trend_strength"] < last_candle["trend_strength_ma"] * 0.7
                and last_candle["adx"] < 20
            ):
                return "trend_weakness_exit"

        # Risk management exits
        if current_profit < 0:
            # Exit if trend is against us and ADX shows strong trend
            if last_candle["trend_direction"] == -1 and last_candle["adx"] > 30:
                return "strong_adverse_trend"

            # Exit on increasing losses with high volume
            if current_profit < -0.03 and last_candle["volume_ratio"] > 1.5:
                return "volume_based_loss_exit"

        return None
