# source: https://raw.githubusercontent.com/Mateusz-Nalezinski/CryptoBOT/ff191ca57447739040d01675b834056eefbc1e6f/services/freqtrade/user_data/strategies/RegimeHybridV0.py
from datetime import datetime
from typing import Dict, List, Tuple

from freqtrade.strategy import IStrategy, merge_informative_pair
from pandas import DataFrame

from mods import exits, filters, indicators, logging as decision_log, protections as prot, regime, signals_range, signals_trend


class Github_Mateusz_Nalezinski_CryptoBOT__RegimeHybridV0__20251220_160211(IStrategy):
    """
    Dispatcher strategii: wybiera sygnały trendowe lub range w zależności od reżimu.
    Wspólne wskaźniki z TrendV0/RangeV0, MTF 1h/4h, long-only, dry_run friendly.
    """

    timeframe = "30m"
    informative_timeframes: List[str] = ["1h", "4h"]
    process_only_new_candles = True
    startup_candle_count: int = 210
    use_exit_signal = True
    exit_profit_only = False
    ignore_buying_expired_candle_after = 20
    minimal_roi = {"0": 0.04}
    stoploss = -0.08
    entry_context: Dict[str, Dict] = {}
    protections = prot.DEFAULT_PROTECTIONS

    def informative_pairs(self) -> List[Tuple[str, str]]:
        pairs = self.dp.current_whitelist() if self.dp else []
        return [(pair, tf) for pair in pairs for tf in self.informative_timeframes]

    @staticmethod
    def _merge(df: DataFrame, informative: DataFrame, tf: str) -> DataFrame:
        base = df.copy()
        if "date" not in base.columns:
            base["date"] = base.index
        inf = indicators.add_base_indicators(informative)
        if "date" not in inf.columns:
            inf["date"] = inf.index
        return merge_informative_pair(base, inf, "30m", tf, ffill=True, append_timeframe=True)

    def populate_indicators(self, dataframe: DataFrame, metadata: Dict) -> DataFrame:
        df = indicators.add_base_indicators(dataframe)
        if self.dp:
            for tf in self.informative_timeframes:
                inf = self.dp.get_pair_dataframe(pair=metadata["pair"], timeframe=tf)
                df = self._merge(df, inf, tf)

        df["regime"] = regime.detect_regime(df)
        return df

    def populate_entry_trend(self, dataframe: DataFrame, metadata: Dict) -> DataFrame:
        df = dataframe.copy()
        vol_ok = filters.volume_ok(df, min_rel=1.1)
        calm = filters.no_high_vol(df)
        signals_tr = signals_trend.trend_long_signals(df)
        signals_rg = signals_range.range_long_signals(df)

        trend_mask = df["regime"].isin([regime.TREND_UP, regime.TREND_DOWN]) & signals_tr
        range_mask = (df["regime"] == regime.RANGE) & signals_rg
        entry_mask = (trend_mask | range_mask) & vol_ok & calm & filters.daily_trade_cap(df)

        df.loc[:, "enter_long"] = 0
        df.loc[entry_mask, "enter_long"] = 1
        # tagi dla trade
        df.loc[trend_mask, "enter_tag"] = decision_log.short_tag(df.loc[trend_mask, "regime"], "trend", "ok")
        df.loc[range_mask, "enter_tag"] = decision_log.short_tag(df.loc[range_mask, "regime"], "range", "ok")

        if metadata and metadata.get("pair"):
            pair = metadata["pair"]
            idx = entry_mask[entry_mask].index
            if not idx.empty:
                row = df.loc[idx[-1]]
                engine = "trend" if trend_mask.loc[idx[-1]] else "range"
                self.entry_context[pair] = {
                    "regime": row.get("regime"),
                    "engine": engine,
                    "filters": {"volume_ok": bool(vol_ok.iloc[idx[-1]]), "calm": bool(calm.iloc[idx[-1]])},
                    "indicators": {
                        "rsi14": float(row.get("rsi14", 0)),
                        "bb_pos": float(row.get("bb_position", 0)),
                        "volume_rel": float(row.get("volume_rel", 0)),
                    },
                }
        decision_log.decision_log(
            event="entry",
            regime="hybrid",
            reason="dispatcher",
            extra={
                "trend_signals": int(trend_mask.sum()),
                "range_signals": int(range_mask.sum()),
                "accepted": int(entry_mask.sum()),
                "ts": datetime.utcnow().isoformat(),
            },
        )
        return df

    def populate_exit_trend(self, dataframe: DataFrame, metadata: Dict) -> DataFrame:
        df = dataframe.copy()
        trend_exit_mask = df["regime"].isin([regime.TREND_UP, regime.TREND_DOWN]) & exits.trend_exit(df)
        range_exit_mask = (df["regime"] == regime.RANGE) & exits.range_exit(df)
        exit_mask = trend_exit_mask | range_exit_mask
        df.loc[:, "exit_long"] = 0
        df.loc[exit_mask, "exit_long"] = 1
        decision_log.decision_log(
            event="exit",
            regime="hybrid",
            reason="dispatcher_exit",
            extra={"trend_exits": int(trend_exit_mask.sum()), "range_exits": int(range_exit_mask.sum())},
        )
        return df

    def custom_stoploss(self, pair: str, trade, current_time: datetime, current_rate: float, current_profit: float, **kwargs) -> float:
        return self.stoploss

    def custom_exit(self, pair: str, trade, current_time: datetime, current_rate: float, current_profit: float, **kwargs):
        try:
            trade.set_custom_data("decision_exit", {"exit_at": current_time.isoformat(), "profit": current_profit, "regime": trade.enter_tag if hasattr(trade, "enter_tag") else None})
            if trade.get_custom_data("decision_entry") is None and pair in self.entry_context:
                trade.set_custom_data("decision_entry", self.entry_context[pair])
        except Exception:
            pass
        return None
