# source: https://raw.githubusercontent.com/GoLukeEnviro/trading-hub/f825e26fdfdd6e50e28117e89041c337202d62dc/freqtrade/bots/regime-hybrid/user_data/strategies/RegimeSwitchingHybrid_v4_ATR.py
"""
Github_GoLukeEnviro_trading_hub__RegimeSwitchingHybrid_v4_ATR__20260609_212948
A hybrid strategy that switches between Trend Following (ADX/EMA) 
and Mean Reversion (BB/RSI) based on market regime detection.
Timeframe: 15m
HTF Informative: 1h
Phase 17 - ATR-Adaptive Exits
"""

import logging
from datetime import datetime
from typing import Optional

import talib.abstract as ta
from freqtrade.strategy import IStrategy, IntParameter, DecimalParameter, BooleanParameter, merge_informative_pair
import freqtrade.vendor.qtpylib.indicators as qtpylib
from pandas import DataFrame

logger = logging.getLogger(__name__)

class Github_GoLukeEnviro_trading_hub__RegimeSwitchingHybrid_v4_ATR__20260609_212948(IStrategy):

    INTERFACE_VERSION = 3
    timeframe = "15m"
    informative_timeframe = "1h"
    
    # Strategy settings
    can_short = False
    
    # Base ROI (Locked for Phase 17)
    minimal_roi = {
        "0": 0.06,
        "60": 0.03,
        "120": 0.01,
        "240": 0
    }

    # Failsafe stoploss - real stoploss handled via custom_stoploss()
    stoploss = -0.99
    use_custom_stoploss = True
    trailing_stop = False # Handled by custom_stoploss

    startup_candle_count = 500

    @property
    def protections(self):
        return [
            {"method": "CooldownPeriod", "stop_duration_candles": 5},
            {"method": "StoplossGuard", "lookback_period_candles": 60, "trade_limit": 3, "stop_duration_candles": 60, "only_per_pair": False},
            {"method": "MaxDrawdown", "lookback_period_candles": 480, "trade_limit": 20, "stop_duration_candles": 96, "max_allowed_drawdown": 0.10},
            {"method": "LowProfitPairs", "lookback_period_candles": 1440, "trade_limit": 2, "stop_duration_candles": 60, "required_profit": 0.00}
        ]

    # Hyperoptable parameters (Buy space) - Starting with v3 best
    adx_threshold = IntParameter(20, 35, default=29, space="buy")
    rsi_oversold = IntParameter(20, 40, default=20, space="buy")
    
    # ATR Multipliers (Sell space - new for Phase 17)
    atr_sl_trend = DecimalParameter(1.5, 4.0, default=2.5, space="sell", optimize=True)
    atr_sl_range = DecimalParameter(0.8, 2.5, default=1.5, space="sell", optimize=True)
    atr_tp_trend = DecimalParameter(1.0, 3.0, default=1.5, space="sell", optimize=True)
    
    # Fixed parameters
    rsi_overbought = 66

    def informative_pairs(self):
        pairs = self.dp.current_whitelist()
        return [(pair, self.informative_timeframe) for pair in pairs]

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        if not self.dp: return dataframe
        informative = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe=self.informative_timeframe)
        informative['ema200'] = ta.EMA(informative, timeperiod=200)
        informative['adx'] = ta.ADX(informative)
        informative['rsi'] = ta.RSI(informative)
        dataframe = merge_informative_pair(dataframe, informative, self.timeframe, self.informative_timeframe, ffill=True)
        
        # Local indicators
        dataframe['adx'] = ta.ADX(dataframe)
        dataframe['rsi'] = ta.RSI(dataframe)
        dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50)
        dataframe['ema200'] = ta.EMA(dataframe, timeperiod=200)
        
        # ATR calculation for dynamic stops
        dataframe['atr'] = ta.ATR(dataframe, timeperiod=14)
        dataframe['atr_pct'] = dataframe['atr'] / dataframe['close']
        
        bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
        dataframe['bb_lowerband'] = bollinger['lower']
        dataframe['bb_middleband'] = bollinger['mid']
        dataframe['bb_upperband'] = bollinger['upper']
        dataframe['bb_width'] = (dataframe['bb_upperband'] - dataframe['bb_lowerband']) / dataframe['bb_middleband']
        
        dataframe['volume_mean'] = dataframe['volume'].rolling(window=30).mean()
        
        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        adx_htf = dataframe[f'adx_{self.informative_timeframe}']
        ema200_htf = dataframe[f'ema200_{self.informative_timeframe}']
        
        # Trend Regime Pullback
        trend_long = (
            (adx_htf > self.adx_threshold.value) & 
            (dataframe['close'] > ema200_htf) & 
            (dataframe['close'] > dataframe['ema200']) & 
            (dataframe['close'] < dataframe['ema50']) & 
            (dataframe['rsi'] < 50) & 
            (dataframe['volume'] > 0)
        )
        
        # Range Regime Reversion
        range_long = (
            (adx_htf <= self.adx_threshold.value) & 
            (dataframe['rsi'] < self.rsi_oversold.value) & 
            (dataframe['close'] < dataframe['bb_lowerband']) & 
            (dataframe['volume'] > 0)
        )
        
        dataframe.loc[trend_long, 'enter_long'] = 1
        dataframe.loc[trend_long, 'enter_tag'] = 'trend_pullback'
        
        dataframe.loc[range_long, 'enter_long'] = 1
        dataframe.loc[range_long, 'enter_tag'] = 'range_reversion'
        
        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[
            (dataframe['rsi'] > self.rsi_overbought) | 
            (dataframe['close'] > dataframe['bb_upperband']), 
            'exit_long'
        ] = 1
        return dataframe

    def custom_stoploss(self, pair: str, trade, current_time, current_rate,
                        current_profit: float, **kwargs) -> float:
        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        if dataframe.empty:
            return self.stoploss
            
        last = dataframe.iloc[-1]
        atr_pct = last['atr_pct']
        adx = last['adx']
        
        # Determine regime
        is_trend = adx > self.adx_threshold.value
        
        if is_trend:
            sl_distance = atr_pct * self.atr_sl_trend.value
            # Trailing trigger logic
            if current_profit > (atr_pct * self.atr_tp_trend.value):
                return max(-sl_distance, current_profit - sl_distance)
        else:
            sl_distance = atr_pct * self.atr_sl_range.value
            
        return -sl_distance
