# source: https://raw.githubusercontent.com/hassanbht/TradingBot/dd2e35941f439e7a527ffbb31f04e870719a021f/user_data/strategies/Gemieni/MetaRegimeV2.py
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
import pandas as pd
import talib.abstract as ta
from freqtrade.strategy import IStrategy, IntParameter, DecimalParameter
from pandas import DataFrame
from datetime import datetime

class Github_hassanbht_TradingBot__MetaRegimeV2__20260601_204013(IStrategy):
    """
    Github_hassanbht_TradingBot__MetaRegimeV2__20260601_204013.1 (Bulletproof Edition)
    - MTF Simulated natively on 1H chart to prevent NaN dropping
    - Pure TA-Lib (No pandas-ta dependencies)
    - Vectorized Scoring Engine
    """
    INTERFACE_VERSION = 3
    timeframe = '1h'
    # برای محاسبه EMA 800 (معادل EMA200 چهار ساعته) به حداقل 900 کندل نیاز داریم
    startup_candle_count: int = 900 

    # --- Hyperopt Parameters ---
    adx_threshold = IntParameter(15, 35, default=20, space="buy")
    score_threshold = IntParameter(40, 90, default=60, space="buy")
    
    rsi_long = IntParameter(50, 65, default=55, space="buy")
    rsi_mr = IntParameter(20, 35, default=30, space="buy")
    
    atr_sl_multiplier = DecimalParameter(1.5, 3.5, default=2.5, space="sell")
    risk_per_trade = 0.02

    stoploss = -0.99
    trailing_stop = False
    roi = {"0": 100}

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # 1. شبیه‌سازی رژیم 4 ساعته روی چارت 1 ساعته
        dataframe['ema800'] = ta.EMA(dataframe, timeperiod=800) # معادل EMA 200 در 4H
        dataframe['adx_long'] = ta.ADX(dataframe, timeperiod=56) # معادل ADX 14 در 4H
        
        # 2. اندیکاتورهای سیگنال 1 ساعته
        dataframe['ema20'] = ta.EMA(dataframe, timeperiod=20)
        dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50)
        dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
        dataframe['atr'] = ta.ATR(dataframe, timeperiod=14)
        
        # 3. محاسبه حجم و نوسان
        dataframe['vol_sma'] = ta.SMA(dataframe['volume'], timeperiod=20)
        dataframe['volume_spike'] = dataframe['volume'] > (dataframe['vol_sma'] * 1.2)
        
        # Bollinger & Donchian
        bollinger = ta.BBANDS(dataframe, timeperiod=20, nbdevup=2.0, nbdevdn=2.0)
        dataframe['bb_lower'] = bollinger['lowerband']
        dataframe['donchian_high'] = dataframe['high'].rolling(20).max().shift(1)
        
        # برای حد ضرر متحرک
        dataframe['highest_high'] = dataframe['high'].rolling(20).max()

        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # --- تشخیص رژیم بازار ---
        # فقط وقتی وارد می‌شویم که EMA800 مقدار گرفته باشد (جلوگیری از باگ NaN)
        valid_regime = dataframe['ema800'].notnull()
        
        regime_bull = valid_regime & (dataframe['close'] > dataframe['ema800']) & (dataframe['adx_long'] > self.adx_threshold.value)
        regime_range = valid_regime & (dataframe['adx_long'] < 20)

        # --- سیستم امتیازدهی (Scoring Engine) ---
        dataframe['score'] = 0
        
        # Trend (معادل سوپرترند): 40 امتیاز
        dataframe.loc[(dataframe['ema20'] > dataframe['ema50']) & (dataframe['close'] > dataframe['ema20']), 'score'] += 40
        
        # Volume: 20 امتیاز
        dataframe.loc[dataframe['volume_spike'] == True, 'score'] += 20
        
        # RSI: 15 امتیاز
        dataframe.loc[dataframe['rsi'] > self.rsi_long.value, 'score'] += 15
        
        # Breakout: 25 امتیاز
        dataframe.loc[dataframe['close'] > dataframe['donchian_high'], 'score'] += 25

        # --- تریگرهای ورود ---
        bull_signal = regime_bull & (dataframe['score'] >= self.score_threshold.value)
        range_signal = regime_range & (dataframe['close'] < dataframe['bb_lower']) & (dataframe['rsi'] < self.rsi_mr.value)

        dataframe.loc[bull_signal | range_signal, 'enter_long'] = 1

        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # خروج استراکچرال (وقتی روند کاملا برمی‌گردد)
        dataframe.loc[(dataframe['ema20'] < dataframe['ema50']) & (dataframe['close'] < dataframe['ema50']), 'exit_long'] = 1
        return dataframe

    def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, current_rate: float, current_profit: float, **kwargs) -> float:
        """ ATR-Based Dynamic Stoploss """
        try:
            dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
            last_candle = dataframe.iloc[-1].squeeze()

            # محاسبه حد ضرر اولیه و متحرک
            initial_sl = trade.open_rate - (last_candle['atr'] * self.atr_sl_multiplier.value)
            trailing_sl = last_candle['highest_high'] - (last_candle['atr'] * 1.5)
            
            sl_price = max(initial_sl, trailing_sl)
            
            # مطمئن می‌شویم استاپ لاس بالاتر از قیمت فعلی نباشد تا ربات الکی خارج نشود
            if 0 < sl_price < current_rate:
                return (current_rate - sl_price) / current_rate
                
            return self.stoploss
        except Exception:
            return self.stoploss

    def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float, proposed_stake: float, min_stake: float, max_stake: float, leverage: float, entry_tag: str, side: str, **kwargs) -> float:
        """ Risk Engine: Position Size = Risk / Stop Distance """
        try:
            dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
            last_candle = dataframe.iloc[-1].squeeze()
            
            stop_distance = last_candle['atr'] * self.atr_sl_multiplier.value
            if stop_distance <= 0:
                return proposed_stake
                
            risk_amount = max_stake * self.risk_per_trade
            target_size_crypto = risk_amount / stop_distance
            target_size_usdt = target_size_crypto * current_rate
            
            final_stake = min(target_size_usdt, max_stake)
            
            if min_stake is not None:
                final_stake = max(final_stake, min_stake)
                
            return final_stake
        except Exception:
            return proposed_stake