# source: https://raw.githubusercontent.com/nyp4o/Freqtrade-strategies/4df7da1a1fecda17f475a3a70b5f5e939f4b13d7/Selfie.py
# --- Hyperopt buy_params as generated/placed by Freqtrade optimizer ---
buy_params = {
        "adx_entry_long_thresh": 15,
        "adx_exit_short_thresh": 17,
        "adx_period": 7,
        "bbands_std": 2.336,
        "bbands_window": 10,
        "cci_entry_long_thresh": -119,
        "cci_exit_short_thresh": -146,
        "cci_period": 17,
        "macd_fast": 10,
        "macd_signal": 6,
        "macd_slow": 14,
        "mfi_entry_long_thresh": 39,
        "mfi_exit_short_thresh": 30,
        "mfi_period": 13,
        "rsi_entry_long_thresh": 36,
        "rsi_exit_short_thresh": 15,
        "rsi_period": 11,
        "sar_af": 0.01,
        "sar_max": 0.27,
        "stoch_d": 3,
        "stoch_k": 14,
        "use_adx_mode": 1,
        "use_bbands_mode": 14,
        "use_cci_mode": 0,
        "use_htsine_mode": 4,
        "use_macd_mode": 1,
        "use_mfi_mode": 7,
        "use_rsi_mode": 5,
        "use_sar_mode": 9,
        "use_stoch_mode": 14,
        "adx_entry_short_thresh": 22,
        "adx_exit_long_thresh": 16,
        "cci_entry_short_thresh": 125,
        "cci_exit_long_thresh": 60,
        "mfi_entry_short_thresh": 80,
        "mfi_exit_long_thresh": 70,
        "rsi_entry_short_thresh": 62,
        "rsi_exit_long_thresh": 71,
        "leverage_opt": 1
}

# --- Optimization global switches ---
Indicator_optimize = True
Optimize_use = True

from freqtrade.strategy import IStrategy
from pandas import DataFrame
import talib.abstract as ta
import numpy as np
from freqtrade.strategy import IntParameter, DecimalParameter
from datetime import datetime


class Github_nyp4o_Freqtrade_strategies__Selfie__20250529_080740(IStrategy):


    INTERFACE_VERSION = 3
    minimal_roi = {
        "0": 0.02,
        "10": 0.01,
        "60": 0
    }
    stoploss = -0.1
    timeframe = '5m'

    # Битова маска: 1=Long, 2=Short, 4=Entry, 8=Exit

    use_rsi_mode = IntParameter(0, 15, default=buy_params['use_rsi_mode'], space="buy", optimize=Optimize_use)
    use_macd_mode = IntParameter(0, 15, default=buy_params['use_macd_mode'], space="buy", optimize=Optimize_use)
    use_stoch_mode = IntParameter(0, 15, default=buy_params['use_stoch_mode'], space="buy", optimize=Optimize_use)
    use_bbands_mode = IntParameter(0, 15, default=buy_params['use_bbands_mode'], space="buy", optimize=Optimize_use)
    use_adx_mode = IntParameter(0, 15, default=buy_params['use_adx_mode'], space="buy", optimize=Optimize_use)
    use_mfi_mode = IntParameter(0, 15, default=buy_params['use_mfi_mode'], space="buy", optimize=Optimize_use)
    use_cci_mode = IntParameter(0, 15, default=buy_params['use_cci_mode'], space="buy", optimize=Optimize_use)
    use_sar_mode = IntParameter(0, 15, default=buy_params['use_sar_mode'], space="buy", optimize=Optimize_use)
    use_htsine_mode = IntParameter(0, 15, default=buy_params['use_htsine_mode'], space="buy", optimize=Optimize_use)

        # RSI (по-стегнати граници за скалпинг)
    rsi_period = IntParameter(7, 16, default=buy_params['rsi_period'], space="buy", optimize=Indicator_optimize)
    rsi_entry_long_thresh = IntParameter(30, 45, default=buy_params['rsi_entry_long_thresh'], space="buy", optimize=Indicator_optimize)
    rsi_exit_long_thresh = IntParameter(55, 75, default=buy_params['rsi_exit_long_thresh'], space="sell", optimize=Indicator_optimize)
    rsi_entry_short_thresh = IntParameter(60, 80, default=buy_params['rsi_entry_short_thresh'], space="sell", optimize=Indicator_optimize)
    rsi_exit_short_thresh = IntParameter(15, 40, default=buy_params['rsi_exit_short_thresh'], space="buy", optimize=Indicator_optimize)

    # MACD (по-реактивен за скалпинг)
    macd_fast = IntParameter(6, 14, default=buy_params['macd_fast'], space="buy", optimize=Indicator_optimize)
    macd_slow = IntParameter(12, 24, default=buy_params['macd_slow'], space="buy", optimize=Indicator_optimize)
    macd_signal = IntParameter(5, 10, default=buy_params['macd_signal'], space="buy", optimize=Indicator_optimize)

    # Stochastic
    stoch_k = IntParameter(5, 14, default=buy_params['stoch_k'], space="buy", optimize=Indicator_optimize)
    stoch_d = IntParameter(3, 7, default=buy_params['stoch_d'], space="buy", optimize=Indicator_optimize)

    # Bollinger Bands
    bbands_window = IntParameter(10, 22, default=buy_params['bbands_window'], space="buy", optimize=Indicator_optimize)
    bbands_std = DecimalParameter(1.5, 2.5, default=buy_params['bbands_std'], space="buy", optimize=Indicator_optimize)

    # ADX
    adx_period = IntParameter(7, 14, default=buy_params['adx_period'], space="buy", optimize=Indicator_optimize)
    adx_entry_long_thresh = IntParameter(10, 25, default=buy_params['adx_entry_long_thresh'], space="buy", optimize=Indicator_optimize)
    adx_exit_long_thresh = IntParameter(10, 25, default=buy_params['adx_exit_long_thresh'], space="sell", optimize=Indicator_optimize)
    adx_entry_short_thresh = IntParameter(10, 25, default=buy_params['adx_entry_short_thresh'], space="sell", optimize=Indicator_optimize)
    adx_exit_short_thresh = IntParameter(10, 25, default=buy_params['adx_exit_short_thresh'], space="buy", optimize=Indicator_optimize)

    # MFI
    mfi_period = IntParameter(7, 16, default=buy_params['mfi_period'], space="buy", optimize=Indicator_optimize)
    mfi_entry_long_thresh = IntParameter(20, 40, default=buy_params['mfi_entry_long_thresh'], space="buy", optimize=Indicator_optimize)
    mfi_exit_long_thresh = IntParameter(60, 80, default=buy_params['mfi_exit_long_thresh'], space="sell", optimize=Indicator_optimize)
    mfi_entry_short_thresh = IntParameter(60, 80, default=buy_params['mfi_entry_short_thresh'], space="sell", optimize=Indicator_optimize)
    mfi_exit_short_thresh = IntParameter(20, 40, default=buy_params['mfi_exit_short_thresh'], space="buy", optimize=Indicator_optimize)

    # CCI
    cci_period = IntParameter(7, 20, default=buy_params['cci_period'], space="buy", optimize=Indicator_optimize)
    cci_entry_long_thresh = IntParameter(-150, -50, default=buy_params['cci_entry_long_thresh'], space="buy", optimize=Indicator_optimize)
    cci_exit_long_thresh = IntParameter(50, 150, default=buy_params['cci_exit_long_thresh'], space="sell", optimize=Indicator_optimize)
    cci_entry_short_thresh = IntParameter(50, 150, default=buy_params['cci_entry_short_thresh'], space="sell", optimize=Indicator_optimize)
    cci_exit_short_thresh = IntParameter(-150, -50, default=buy_params['cci_exit_short_thresh'], space="buy", optimize=Indicator_optimize)

    # SAR
    sar_af = DecimalParameter(0.01, 0.05, default=buy_params['sar_af'], decimals=2, space="buy", optimize=Indicator_optimize)
    sar_max = DecimalParameter(0.1, 0.3, default=buy_params['sar_max'], decimals=2, space="buy", optimize=Indicator_optimize)

    leverage_opt = IntParameter(1, 5, default=buy_params["leverage_opt"], space="buy", optimize=Indicator_optimize)

    @property
    def protections(self):
        return [
            {
                "method": "CooldownPeriod",
                "stop_duration_candles": 2  # само 10 минути пауза
            },
            {
                "method": "MaxDrawdown",
                "lookback_period_candles": 24,  # 2 часа назад
                "trade_limit": 10,
                "stop_duration_candles": 2,  # 10 минути стоп след drawdown
                "max_allowed_drawdown": 0.15  # по-строго за скалпинг
            },
            {
                "method": "StoplossGuard",
                "lookback_period_candles": 12,  # 1 час назад
                "trade_limit": 2,
                "stop_duration_candles": 2,
                "only_per_pair": True  # по-добре само за всяка двойка
            },
            {
                "method": "LowProfitPairs",
                "lookback_period_candles": 3,  # 15 мин назад
                "trade_limit": 2,
                "stop_duration_candles": 20,  # 100 минути стоп
                "required_profit": 0.01  # 1% е по-реалистично за скалпинг
            },
            {
                "method": "LowProfitPairs",
                "lookback_period_candles": 24,  # 2 часа
                "trade_limit": 4,
                "stop_duration_candles": 2,  # 10 минути
                "required_profit": 0.005  # 0.5% за по-мек филтър
            }
        ]

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # RSI
        if self.use_rsi_mode.value > 0:
            # noinspection PyUnresolvedReferences
            dataframe["rsi"] = ta.RSI(dataframe, timeperiod=self.rsi_period.value)
        # MACD
        if self.use_macd_mode.value > 0:
            # noinspection PyUnresolvedReferences
            macd = ta.MACD(
                dataframe,
                fastperiod=self.macd_fast.value,
                slowperiod=self.macd_slow.value,
                signalperiod=self.macd_signal.value
            )
            dataframe["macd"] = macd["macd"]
            dataframe["macdsignal"] = macd["macdsignal"]
        # Stochastic
        if self.use_stoch_mode.value > 0:
            # noinspection PyUnresolvedReferences
            stoch = ta.STOCHF(
                dataframe,
                fastk_period=self.stoch_k.value,
                fastd_period=self.stoch_d.value,
                fastd_matype=0
            )
            dataframe["fastk"] = stoch["fastk"]
            dataframe["fastd"] = stoch["fastd"]
        # Bollinger Bands
        if self.use_bbands_mode.value > 0:
            bbands = self.calculate_bollinger_bands(
                dataframe,
                window=self.bbands_window.value,
                stds=self.bbands_std.value
            )
            dataframe["bb_upperband"] = bbands["upper"]
            dataframe["bb_lowerband"] = bbands["lower"]
            dataframe["bb_middleband"] = bbands["mid"]
        # ADX
        if self.use_adx_mode.value > 0:
            # noinspection PyUnresolvedReferences
            dataframe["adx"] = ta.ADX(dataframe, timeperiod=self.adx_period.value)
        # MFI
        if self.use_mfi_mode.value > 0:
            # noinspection PyUnresolvedReferences
            dataframe["mfi"] = ta.MFI(dataframe, timeperiod=self.mfi_period.value)
        # CCI
        if self.use_cci_mode.value > 0:
            # noinspection PyUnresolvedReferences
            dataframe["cci"] = ta.CCI(dataframe, timeperiod=self.cci_period.value)
        # SAR
        if self.use_sar_mode.value > 0:
            # noinspection PyUnresolvedReferences
            dataframe["sar"] = ta.SAR(
                dataframe,
                acceleration=self.sar_af.value,
                maximum=self.sar_max.value
            )
        # HTSINE
        if self.use_htsine_mode.value > 0:
            # noinspection PyUnresolvedReferences
            htsine = ta.HT_SINE(dataframe)
            dataframe["htsine"] = htsine["sine"]
            dataframe["htleadsine"] = htsine["leadsine"]
        return dataframe

    @staticmethod
    def calculate_bollinger_bands(dataframe: DataFrame, window: int, stds: float):
        tp = (dataframe["high"] + dataframe["low"] + dataframe["close"]) / 3
        mid = tp.rolling(window).mean()
        std = tp.rolling(window).std()
        upper = mid + stds * std
        lower = mid - stds * std
        return {"mid": mid, "upper": upper, "lower": lower}

    # noinspection PyUnusedLocal
    def build_entry_conditions_long(self, dataframe: DataFrame, metadata: dict) -> list:
        conditions = []
        if (self.use_rsi_mode.value & 1) and (self.use_rsi_mode.value & 4):
            conditions.append(dataframe["rsi"] < self.rsi_entry_long_thresh.value)
        if (self.use_macd_mode.value & 1) and (self.use_macd_mode.value & 4):
            conditions.append(dataframe["macd"] > dataframe["macdsignal"])
        if (self.use_stoch_mode.value & 1) and (self.use_stoch_mode.value & 4):
            conditions.append((dataframe["fastk"] < 30) & (dataframe["fastd"] < 30))
        if (self.use_bbands_mode.value & 1) and (self.use_bbands_mode.value & 4):
            conditions.append(dataframe["close"] < dataframe["bb_lowerband"])
        if (self.use_adx_mode.value & 1) and (self.use_adx_mode.value & 4):
            conditions.append(dataframe["adx"] > self.adx_entry_long_thresh.value)
        if (self.use_mfi_mode.value & 1) and (self.use_mfi_mode.value & 4):
            conditions.append(dataframe["mfi"] < self.mfi_entry_long_thresh.value)
        if (self.use_cci_mode.value & 1) and (self.use_cci_mode.value & 4):
            conditions.append(dataframe["cci"] < self.cci_entry_long_thresh.value)
        if (self.use_sar_mode.value & 1) and (self.use_sar_mode.value & 4):
            conditions.append(dataframe["sar"] > dataframe["close"])
        if (self.use_htsine_mode.value & 1) and (self.use_htsine_mode.value & 4):
            conditions.append(dataframe["htsine"] < dataframe["htleadsine"])
        return conditions

    # noinspection PyUnusedLocal
    def build_exit_conditions_long(self, dataframe: DataFrame, metadata: dict) -> list:
        conditions = []
        if (self.use_rsi_mode.value & 1) and (self.use_rsi_mode.value & 8):
            conditions.append(dataframe["rsi"] > self.rsi_exit_long_thresh.value)
        if (self.use_macd_mode.value & 1) and (self.use_macd_mode.value & 8):
            conditions.append(dataframe["macd"] < dataframe["macdsignal"])
        if (self.use_stoch_mode.value & 1) and (self.use_stoch_mode.value & 8):
            conditions.append((dataframe["fastk"] > 70) & (dataframe["fastd"] > 70))
        if (self.use_bbands_mode.value & 1) and (self.use_bbands_mode.value & 8):
            conditions.append(dataframe["close"] > dataframe["bb_upperband"])
        if (self.use_adx_mode.value & 1) and (self.use_adx_mode.value & 8):
            conditions.append(dataframe["adx"] < self.adx_exit_long_thresh.value)
        if (self.use_mfi_mode.value & 1) and (self.use_mfi_mode.value & 8):
            conditions.append(dataframe["mfi"] > self.mfi_exit_long_thresh.value)
        if (self.use_cci_mode.value & 1) and (self.use_cci_mode.value & 8):
            conditions.append(dataframe["cci"] > self.cci_exit_long_thresh.value)
        if (self.use_sar_mode.value & 1) and (self.use_sar_mode.value & 8):
            conditions.append(dataframe["sar"] < dataframe["close"])
        if (self.use_htsine_mode.value & 1) and (self.use_htsine_mode.value & 8):
            conditions.append(dataframe["htsine"] > dataframe["htleadsine"])
        return conditions

    # noinspection PyUnusedLocal
    def build_entry_conditions_short(self, dataframe: DataFrame, metadata: dict) -> list:
        conditions = []
        if (self.use_rsi_mode.value & 2) and (self.use_rsi_mode.value & 4):
            conditions.append(dataframe["rsi"] > self.rsi_entry_short_thresh.value)
        if (self.use_macd_mode.value & 2) and (self.use_macd_mode.value & 4):
            conditions.append(dataframe["macd"] < dataframe["macdsignal"])
        if (self.use_stoch_mode.value & 2) and (self.use_stoch_mode.value & 4):
            conditions.append((dataframe["fastk"] > 70) & (dataframe["fastd"] > 70))
        if (self.use_bbands_mode.value & 2) and (self.use_bbands_mode.value & 4):
            conditions.append(dataframe["close"] > dataframe["bb_upperband"])
        if (self.use_adx_mode.value & 2) and (self.use_adx_mode.value & 4):
            conditions.append(dataframe["adx"] > self.adx_entry_short_thresh.value)
        if (self.use_mfi_mode.value & 2) and (self.use_mfi_mode.value & 4):
            conditions.append(dataframe["mfi"] > self.mfi_entry_short_thresh.value)
        if (self.use_cci_mode.value & 2) and (self.use_cci_mode.value & 4):
            conditions.append(dataframe["cci"] > self.cci_entry_short_thresh.value)
        if (self.use_sar_mode.value & 2) and (self.use_sar_mode.value & 4):
            conditions.append(dataframe["sar"] > dataframe["close"])
        if (self.use_htsine_mode.value & 2) and (self.use_htsine_mode.value & 4):
            conditions.append(dataframe["htsine"] > dataframe["htleadsine"])
        return conditions

    # noinspection PyUnusedLocal
    def build_exit_conditions_short(self, dataframe: DataFrame, metadata: dict)  -> list:
        conditions = []
        if (self.use_rsi_mode.value & 2) and (self.use_rsi_mode.value & 8):
            conditions.append(dataframe["rsi"] < self.rsi_exit_short_thresh.value)
        if (self.use_macd_mode.value & 2) and (self.use_macd_mode.value & 8):
            conditions.append(dataframe["macd"] > dataframe["macdsignal"])
        if (self.use_stoch_mode.value & 2) and (self.use_stoch_mode.value & 8):
            conditions.append((dataframe["fastk"] < 30) & (dataframe["fastd"] < 30))
        if (self.use_bbands_mode.value & 2) and (self.use_bbands_mode.value & 8):
            conditions.append(dataframe["close"] < dataframe["bb_lowerband"])
        if (self.use_adx_mode.value & 2) and (self.use_adx_mode.value & 8):
            conditions.append(dataframe["adx"] < self.adx_exit_short_thresh.value)
        if (self.use_mfi_mode.value & 2) and (self.use_mfi_mode.value & 8):
            conditions.append(dataframe["mfi"] < self.mfi_exit_short_thresh.value)
        if (self.use_cci_mode.value & 2) and (self.use_cci_mode.value & 8):
            conditions.append(dataframe["cci"] < self.cci_exit_short_thresh.value)
        if (self.use_sar_mode.value & 2) and (self.use_sar_mode.value & 8):
            conditions.append(dataframe["sar"] < dataframe["close"])
        if (self.use_htsine_mode.value & 2) and (self.use_htsine_mode.value & 8):
            conditions.append(dataframe["htsine"] < dataframe["htleadsine"])
        return conditions

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        long_conditions = self.build_entry_conditions_long(dataframe, metadata)
        short_conditions = self.build_entry_conditions_short(dataframe, metadata)
        if long_conditions:
            dataframe.loc[np.all(long_conditions, axis=0), 'enter_long'] = 1
        if short_conditions:
            dataframe.loc[np.all(short_conditions, axis=0), 'enter_short'] = 1
        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        long_exit_conditions = self.build_exit_conditions_long(dataframe, metadata)
        short_exit_conditions = self.build_exit_conditions_short(dataframe, metadata)
        if long_exit_conditions:
            dataframe.loc[np.all(long_exit_conditions, axis=0), 'exit_long'] = 1
        if short_exit_conditions:
            dataframe.loc[np.all(short_exit_conditions, axis=0), 'exit_short'] = 1
        return dataframe

    def leverage(self, pair: str, current_time: datetime, current_rate: float,
                 proposed_leverage: float, max_leverage: float, entry_tag: str, side: str,
                 **kwargs) -> float:
        return float(self.leverage_opt.value)

    def custom_stoploss(self, pair: str, trade, current_time, current_rate, current_profit, after_fill, **kwargs):
        if not trade:
            return self.stoploss

        # Трейлващ стоп към break-even
        if current_profit > 0.01:  # при 1% печалба
            return max(-0.002, self.stoploss)  # стоп на -0.2%
        if current_profit > 0.005:  # при 0.5% печалба
            return max(-0.003, self.stoploss)  # стоп на -0.3%
        return self.stoploss