# source: https://raw.githubusercontent.com/GoLukeEnviro/trading-hub/f4b272b5c189235a3cdd507907942d16f2c48470/freqtrade/bots/regime-hybrid/user_data/strategies/RegimeSwitchingHybrid_v6_1_Fett.py
"""
Github_GoLukeEnviro_trading_hub__RegimeSwitchingHybrid_v6_1_Fett__20260602_130327
Fett ueberarbeitete Hybrid-Strategie – Phase 20
Bessere Exits, fixter Custom Stoploss, echtes Trailing, Short-Logik, besseres Risk:Reward
Timeframe: 15m | HTF: 1h

Fixes vs. Original-Entwurf:
  - Klassenname: Punkt durch Unterstrich ersetzt (Python-Syntax)
  - **kwargs in confirm_trade_entry und custom_stoploss (Freqtrade-Contract)
  - merge_informative_pair Import hinzugefuegt
  - enter_tag Ueberschreibungs-Bug behoben (Priorisierung: trend vor range)
  - trailing_stop auf True gesetzt (Frequtrade-native, ergaenzt custom_stoploss)
"""

import logging
import sys
from datetime import datetime, timedelta
from typing import Optional

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

sys.path.insert(0, "/freqtrade/shared")
from primo_signal import primo_gate_allows
from fleetguard_v1 import FleetGuard, FleetGuardConfig

logger = logging.getLogger(__name__)


class Github_GoLukeEnviro_trading_hub__RegimeSwitchingHybrid_v6_1_Fett__20260602_130327(IStrategy):

    INTERFACE_VERSION = 3
    timeframe = "15m"
    informative_timeframe = "1h"

    can_short = True

    # Aggressiveres ROI fuer besseres RR
    minimal_roi = {
        "0": 0.085,
        "45": 0.045,
        "90": 0.02,
        "180": 0,
    }

    # Failsafe stoploss — custom_stoploss() ist die eigentliche SL-Logik.
    # 9% Notaus, falls custom_stoploss() versagt.
    stoploss = -0.09
    use_custom_stoploss = True
    trailing_stop = True
    trailing_stop_positive = 0.01
    trailing_stop_positive_offset = 0.02
    trailing_only_offset_is_reached = True

    startup_candle_count = 500

    # ---- FleetGuard v1 entry safety (conservative limits) ----
    _fleetguard = FleetGuard(FleetGuardConfig(
        max_open_trades=3,
        max_open_shorts=2,
        max_open_longs=2,
    ))

    @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, "only_per_side": True},
            {"method": "MaxDrawdown", "lookback_period_candles": 480,
             "trade_limit": 20, "stop_duration_candles": 96,
             "max_allowed_drawdown": 0.06},
            {"method": "LowProfitPairs", "lookback_period_candles": 1440,
             "trade_limit": 2, "stop_duration_candles": 60,
             "required_profit": -0.01},
        ]

    # Hyperoptable parameters (Buy space)
    adx_rel_threshold = DecimalParameter(0.8, 1.4, default=1.0, space="buy")
    rsi_oversold = IntParameter(20, 40, default=25, space="buy")

    # Fixed parameters
    rsi_overbought = 68

    # ATR Multipliers (Sell space) — enger als v6 fuer besseres RR
    atr_sl_trend = 3.5
    atr_sl_range = DecimalParameter(2.0, 4.0, default=2.8, space="sell", optimize=True)
    atr_tp_trend = DecimalParameter(1.0, 2.5, default=1.8, space="sell", optimize=True)

    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['adx_sma'] = dataframe['adx'].rolling(window=50).mean()
        dataframe['adx_rel'] = dataframe['adx'] / dataframe['adx_sma']

        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['volume_mean'] = dataframe['volume'].rolling(window=30).mean()

        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        ema200_htf = dataframe[f'ema200_{self.informative_timeframe}']
        pair = metadata.get("pair")
        long_gate = primo_gate_allows(pair, "long")
        short_gate = primo_gate_allows(pair, "short")

        # --- LONG ENTRIES ---

        # Trend Regime Pullback (Long)
        trend_long = (
            (dataframe['adx_rel'] > self.adx_rel_threshold.value) &
            (dataframe['close'] > ema200_htf) &
            (dataframe['close'] > dataframe['ema200']) &
            (dataframe['close'] < dataframe['ema50']) &
            (dataframe['rsi'] < 50) &
            (dataframe['volume'] > dataframe['volume_mean']) &
            long_gate
        )

        # Range Regime Reversion (Long)
        range_long = (
            (dataframe['adx_rel'] <= self.adx_rel_threshold.value) &
            (dataframe['rsi'] < self.rsi_oversold.value) &
            (dataframe['close'] < dataframe['bb_lowerband']) &
            (dataframe['volume'] > dataframe['volume_mean']) &
            long_gate
        )

        # Combine long entries — trend prioritized over range via mask
        long_entries = trend_long | range_long
        dataframe.loc[long_entries, 'enter_long'] = 1
        # enter_tag: trend wins when both match (set range first, then overwrite with trend)
        dataframe.loc[long_entries, 'enter_tag'] = 'range_reversion_long'
        dataframe.loc[trend_long, 'enter_tag'] = 'trend_pullback_long'

        # --- SHORT ENTRIES ---

        # Trend Regime Pullback (Short)
        trend_short = (
            (dataframe['adx_rel'] > self.adx_rel_threshold.value) &
            (dataframe['close'] < ema200_htf) &
            (dataframe['close'] < dataframe['ema200']) &
            (dataframe['close'] > dataframe['ema50']) &
            (dataframe['rsi'] > 50) &
            (dataframe['volume'] > dataframe['volume_mean']) &
            short_gate
        )

        # Range Regime Reversion (Short)
        range_short = (
            (dataframe['adx_rel'] <= self.adx_rel_threshold.value) &
            (dataframe['rsi'] > 75) &
            (dataframe['close'] > dataframe['bb_upperband']) &
            (dataframe['volume'] > dataframe['volume_mean']) &
            short_gate
        )

        # Combine short entries — trend prioritized over range via mask
        short_entries = trend_short | range_short
        dataframe.loc[short_entries, 'enter_short'] = 1
        dataframe.loc[short_entries, 'enter_tag'] = 'range_reversion_short'
        dataframe.loc[trend_short, 'enter_tag'] = 'trend_pullback_short'

        return dataframe

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

        # Short exits
        dataframe.loc[
            (dataframe['rsi'] < 30) |
            (dataframe['close'] < dataframe['bb_lowerband']),
            'exit_short'
        ] = 1

        return dataframe

    def confirm_trade_entry(self, pair: str, order_type: str, amount: float,
                            rate: float, time_in_force: str, current_time: datetime,
                            entry_tag: Optional[str], side: str, **kwargs) -> bool:
        """FleetGuard entry safety check with real trade data."""
        open_trades = []
        recent_closed = []
        current_drawdown = 0.0

        try:
            from freqtrade.persistence import Trade

            for t in Trade.get_trades_proxy(is_open=True):
                open_trades.append({"pair": t.pair, "is_short": t.is_short})

            cutoff = current_time - timedelta(hours=24)
            for t in Trade.get_trades_proxy(is_open=False):
                if t.close_date and t.close_date >= cutoff:
                    recent_closed.append({
                        "pair": t.pair,
                        "is_short": t.is_short,
                        "close_profit": t.close_profit or 0.0,
                    })

            total_profit = Trade.get_total_closed_profit()
            starting_balance = (self.wallets.get_starting_balance()
                                if hasattr(self, 'wallets') and self.wallets else 1000.0)
            if starting_balance > 0:
                current_drawdown = abs(min(0, total_profit / starting_balance))
        except Exception as e:
            logger.warning(f"FleetGuard data gathering fallback: {e}")
            try:
                from freqtrade.persistence import Trade
                for t in Trade.get_trades_proxy(is_open=True):
                    open_trades.append({"pair": t.pair, "is_short": t.is_short})
            except Exception:
                pass

        allowed, reason = self._fleetguard.check_entry(
            pair=pair, side=side, open_trades=open_trades,
            recent_closed_trades=recent_closed, current_drawdown_pct=current_drawdown
        )
        if not allowed:
            logger.info(f"FleetGuard REJECT: {pair} {side} — {reason}")
            return False
        return True

    def custom_stoploss(self, pair: str, trade, current_time, current_rate,
                        current_profit: float, **kwargs) -> float:
        """ATR-based dynamic stoploss with regime-aware trailing."""
        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_rel = last.get('adx_rel', 1.0)
        is_trend = adx_rel > self.adx_rel_threshold.value

        if is_trend:
            sl_distance = atr_pct * self.atr_sl_trend
            tp_trigger = atr_pct * self.atr_tp_trend.value
            if current_profit > tp_trigger:
                # Trailing: SL folgt dem Gewinn, aber nie weiter als sl_distance zurueck
                return max(-sl_distance, current_profit - sl_distance)
            return -sl_distance
        else:
            # Range-Regime: engerer Stop
            sl_distance = atr_pct * self.atr_sl_range.value
            return -sl_distance
