# source: https://raw.githubusercontent.com/remiotore/ccxt-freqtrade/44beaeb6a420cd8e9f2e4ea93e11d6cfa192ee03/strategies/SOMY2.py
from datetime import datetime, timedelta
import talib.abstract as ta
import pandas_ta as pta
from freqtrade.persistence import Trade
from freqtrade.strategy.interface import IStrategy
from pandas import DataFrame
from freqtrade.strategy import DecimalParameter, IntParameter
from functools import reduce
from technical import qtpylib
import warnings

warnings.simplefilter(action="ignore", category=RuntimeWarning)


class Github_remiotore_ccxt_freqtrade__SOMY2__20260111_210550(IStrategy):
    """
    Github_remiotore_ccxt_freqtrade__SOMY2__20260111_210550 Strategy with Safer, Confluence-Based Entries 
    """
    # Set a default timeframe
    timeframe = '5m'

    # --- 1. Define Base Unleveraged Parameters ---
    base_stoploss = -0.15
    base_minimal_roi = {
        "0": 0.12,
        "60": 0.05,
        "180": 0.02,
        "360": 0
    }
    base_trailing_offset = 0.05
    base_trailing_positive = 0.02

    # --- 2. DYNAMIC INITIALIZATION ---
    def __init__(self, config: dict):
        super().__init__(config)
        trading_mode = self.config.get('trading_mode', 'spot')
        leverage = self.config.get('leverage', 1.0)

        if trading_mode == 'futures' and leverage > 1:
            self.leverage = leverage
            self.stoploss = self.base_stoploss / self.leverage
            self.trailing_stop_positive_offset = self.base_trailing_offset / self.leverage
            self.trailing_stop_positive = self.base_trailing_positive / self.leverage
            self.minimal_roi = {str(k): v / self.leverage for k, v in self.base_minimal_roi.items()}
        else:
            self.leverage = 1.0
            self.stoploss = self.base_stoploss
            self.minimal_roi = self.base_minimal_roi
            self.trailing_stop_positive_offset = self.base_trailing_offset
            self.trailing_stop_positive = self.base_trailing_positive

    # --- 3. Static Strategy Properties ---
    process_only_new_candles = True
    startup_candle_count = 300
    trailing_stop = True
    trailing_only_offset_is_reached = True

    # --- Buy/sell parameters ---
    is_optimize_32 = True
    buy_rsi_fast_32 = IntParameter(20, 70, default=40, space='buy', optimize=is_optimize_32)
    buy_rsi_32 = IntParameter(15, 50, default=42, space='buy', optimize=is_optimize_32)
    buy_sma15_32 = DecimalParameter(0.900, 1, default=0.973, decimals=3, space='buy', optimize=is_optimize_32)
    buy_cti_32 = DecimalParameter(-1, 1, default=0.69, decimals=2, space='buy', optimize=is_optimize_32)
    buy_24h_min_pct = DecimalParameter(-30.0, 0.0, default=-24.3, decimals=1, space='buy', optimize=True)
    buy_24h_max_pct = DecimalParameter(0.0, 200.0, default=24.3, decimals=1, space='buy', optimize=True)
    sell_fastx = IntParameter(50, 100, default=84, space='sell', optimize=True)

    # --- 4. Protections ---
    @property
    def protections(self):
        return [
            {"method": "CooldownPeriod", "stop_duration_candles": 96},
            {
                "method": "MaxDrawdown",
                "lookback_period_candles": 144, "trade_limit": 20,
                "stop_duration_candles": 12, "max_allowed_drawdown": 0.15
            },
            {
                "method": "StoplossGuard",
                "lookback_period_candles": 24, "trade_limit": 3,
                "stop_duration_candles": 12, "only_per_pair": False
            }
        ]

    # --- 5. Indicator Population ---
    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # Original Entry indicators
        dataframe['sma_15'] = ta.SMA(dataframe, timeperiod=15)
        dataframe['cti'] = pta.cti(dataframe["close"], length=20)
        dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
        dataframe['rsi_fast'] = ta.RSI(dataframe, timeperiod=4)
        dataframe['rsi_slow'] = ta.RSI(dataframe, timeperiod=20)
        dataframe['24h_change_pct'] = (dataframe['close'].pct_change(periods=288) * 100)

        # Trend Indicators
        dataframe['sma_50'] = ta.SMA(dataframe, timeperiod=50) # Changed ftt.sma to ta.SMA
        dataframe['sma_200'] = ta.SMA(dataframe, timeperiod=200) # Changed ftt.sma to ta.SMA

        # Confluence Indicators for Safer Entries
        dataframe['adx'] = ta.ADX(dataframe) # Changed ftt.adx to ta.ADX
        macd = ta.MACD(dataframe) # Changed ftt.macd to ta.MACD
        dataframe['macd'] = macd['macd']
        dataframe['macdsignal'] = macd['macdsignal']

        # Bollinger Bands: corrected ftt.bollinger_bands to ta.BBANDS and variable name 'bollinger' to 'Bollinger'
        bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
        dataframe['bb_lowerband'] = bollinger['lower']
        dataframe['bb_upperband'] = bollinger['upper']

        dataframe['atr'] = ta.ATR(dataframe, timeperiod=14) # Changed ftt.atr to ta.ATR
        dataframe['atr_sma'] = ta.SMA(dataframe['atr'], timeperiod=20) # Changed ftt.sma to ta.SMA

        # Williams %R
        dataframe['willr'] = ta.WILLR(dataframe, timeperiod=14)

        # Add longer-term EMA for trend confirmation
        dataframe['ema_50'] = ta.EMA(dataframe, timeperiod=50) # Changed ftt.ema to ta.EMA
        dataframe['ema_100'] = ta.EMA(dataframe, timeperiod=100) # Changed ftt.ema to ta.EMA

        # Exit indicators
        stoch_fast = ta.STOCHF(dataframe, 5, 3, 0, 3, 0)
        dataframe['fastk'] = stoch_fast['fastk']
        dataframe['cci'] = ta.CCI(dataframe, timeperiod=20)
        
        return dataframe

    # --- 6. Entry Logic ---
    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # Original buy conditions
        buy_conditions_1 = (
            (dataframe['rsi_slow'] < dataframe['rsi_slow'].shift(1)) &
            (dataframe['rsi_fast'] < self.buy_rsi_fast_32.value) &
            (dataframe['rsi'] > self.buy_rsi_32.value) &
            (dataframe['close'] < dataframe['sma_15'] * self.buy_sma15_32.value) &
            (dataframe['cti'] < self.buy_cti_32.value) &
            (dataframe['24h_change_pct'] > self.buy_24h_min_pct.value) &
            (dataframe['24h_change_pct'] < self.buy_24h_max_pct.value)
        )
        dataframe.loc[buy_conditions_1, ['enter_long', 'enter_tag']] = (1, 'buy_original')

        # --- SAFER, CONFLUENCE-BASED PULLBACK ENTRIES ---
        safe_long_conditions = (
            (dataframe['sma_50'] > dataframe['sma_200']) &
            (dataframe['adx'] > 25) &
            (dataframe['macd'] > dataframe['macdsignal']) &
            (dataframe['close'] < dataframe['bb_lowerband'])
        )
        dataframe.loc[safe_long_conditions, ['enter_long', 'enter_tag']] = (1, 'safe_pullback_long')

        safe_short_conditions = (
            (dataframe['sma_50'] < dataframe['sma_200']) &
            (dataframe['adx'] > 25) &
            (dataframe['macd'] < dataframe['macdsignal']) &
            (dataframe['close'] > dataframe['bb_upperband'])
        )
        dataframe.loc[safe_short_conditions, ['enter_short', 'enter_tag']] = (1, 'safe_pullback_short')

        # --- NEW: VOLATILITY-CONFIRMED BREAKOUT ENTRIES ---
        
        # ATR Breakout Long: Bullish trend + Break of recent high + High volatility
        atr_breakout_long_conditions = (
            (dataframe['sma_50'] > dataframe['sma_200']) &  # 1. Bullish Trend
            (dataframe['close'] > dataframe['high'].shift(1).rolling(20).max()) & # 2. Breakout of 20-candle high
            (dataframe['atr'] > dataframe['atr_sma'] * 1.25) # 3. Volatility is 25% above average
        )
        dataframe.loc[atr_breakout_long_conditions, ['enter_long', 'enter_tag']] = (1, 'atr_breakout_long')

        # ATR Breakout Short: Bearish trend + Break of recent low + High volatility
        atr_breakout_short_conditions = (
            (dataframe['sma_50'] < dataframe['sma_200']) &  # 1. Bearish Trend
            (dataframe['close'] < dataframe['low'].shift(1).rolling(20).min()) & # 2. Breakout of 20-candle low
            (dataframe['atr'] > dataframe['atr_sma'] * 1.25) # 3. Volatility is 25% above average
        )
        dataframe.loc[atr_breakout_short_conditions, ['enter_short', 'enter_tag']] = (1, 'atr_breakout_short')

        # DIP LONG ENTRY: Bullish trend + Pullback to MA + Oversold Oscillator
        dip_long_conditions = (
            # 1. Trend Confirmation: EMA 50 is above EMA 100
            (dataframe['ema_50'] > dataframe['ema_100']) &
            
            # 2. Pullback Confirmation: Price closes near or below the middle Bollinger Band
            (dataframe['close'] < dataframe['bb_lowerband'].shift(1)) &

            # 3. Oscillator Confirmation: Williams %R is oversold
            (dataframe['willr'] < -75) &

            # 4. Momentum Confirmation: MACD histogram is starting to turn up
            (dataframe['macd'] > dataframe['macdsignal'])
        )
        dataframe.loc[dip_long_conditions, ['enter_long', 'enter_tag']] = (1, 'dip_long')

        # DIP SHORT ENTRY: Bearish trend + Rally to MA + Overbought Oscillator
        dip_short_conditions = (
            # 1. Trend Confirmation: EMA 50 is below EMA 100
            (dataframe['ema_50'] < dataframe['ema_100']) &

            # 2. Pullback Confirmation: Price closes near or above the middle Bollinger Band
            (dataframe['close'] > dataframe['bb_upperband'].shift(1)) &

            # 3. Oscillator Confirmation: Williams %R is overbought
            (dataframe['willr'] > -25) &
            
            # 4. Momentum Confirmation: MACD histogram is starting to turn down
            (dataframe['macd'] < dataframe['macdsignal'])
        )
        dataframe.loc[dip_short_conditions, ['enter_short', 'enter_tag']] = (1, 'dip_short')

        return dataframe

    # --- 7. Exit Logic ---
    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[(dataframe['fastk'] > self.sell_fastx.value), ['exit_long', 'exit_tag']] = (1, 'exit_long_fastk')
        dataframe.loc[(dataframe['fastk'] < (100 - self.sell_fastx.value)), ['exit_short', 'exit_tag']] = (1, 'exit_short_fastk')
        return dataframe

    # --- 8. Custom Stoploss ---
    def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
                        current_rate: float, current_profit: float, **kwargs) -> float:
        if current_time - timedelta(hours=10) > trade.open_date_utc and current_profit > -0.10:
            return 0.001
        if current_time - timedelta(hours=7) > trade.open_date_utc and current_profit > -0.05:
            return 0.001
        return 1.0

    # --- 9. Custom Exit Signal ---
    def custom_exit(self, pair: str, trade: 'Trade', current_time: 'datetime', current_rate: float,
                    current_profit: float, dataframe: DataFrame, **kwargs):
        last_candle = dataframe.iloc[-1].squeeze()
        if current_profit > -0.03:
            if trade.is_short and last_candle["cci"] < -80:
                return "cci_exit_short"
            if not trade.is_short and last_candle["cci"] > 80:
                return "cci_exit_long"
        return None
