# source: https://raw.githubusercontent.com/Mateusz-Nalezinski/CryptoBOT/ff191ca57447739040d01675b834056eefbc1e6f/services/freqtrade/user_data/strategies/TrendV0.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_trend


class Github_Mateusz_Nalezinski_CryptoBOT__TrendV0__20251220_160211(IStrategy):
    """
    Strategia trendowa (long-only) na 30m z MTF (1h/4h) i detekcją reżimu.
    Wskaźniki bazowe: ema20/50/200, rsi14, atr14, bbands, volume_rel, roc, dist_ema50, ema50_slope.
    Reżim z 1h/4h: TREND_UP/TREND_DOWN/RANGE/HIGH_VOL_UNCERTAIN (patrz mods/regime).
    """

    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.05}
    stoploss = -0.1
    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()
        in_regime = df["regime"].isin([regime.TREND_UP, regime.TREND_DOWN])
        vol_ok = filters.volume_ok(df)
        calm = filters.no_high_vol(df)
        signals = signals_trend.trend_long_signals(df)
        entry_mask = in_regime & vol_ok & calm & filters.daily_trade_cap(df) & signals
        df.loc[:, "enter_long"] = 0
        df.loc[entry_mask, "enter_long"] = 1
        df.loc[entry_mask, "enter_tag"] = decision_log.short_tag(df.loc[entry_mask, "regime"], "trend", "ok")

        # zapisz kontekst dla custom_data
        if metadata and metadata.get("pair"):
            pair = metadata["pair"]
            idx = entry_mask[entry_mask].index
            if not idx.empty:
                row = df.loc[idx[-1]]
                self.entry_context[pair] = {
                    "regime": row.get("regime"),
                    "engine": "trend",
                    "filters": {"volume_ok": bool(vol_ok.iloc[idx[-1]]), "calm": bool(calm.iloc[idx[-1]])},
                    "indicators": {
                        "rsi14": float(row.get("rsi14", 0)),
                        "roc10": float(row.get("roc10", 0)),
                        "roc20": float(row.get("roc20", 0)),
                        "bb_pos": float(row.get("bb_position", 0)),
                        "volume_rel": float(row.get("volume_rel", 0)),
                    },
                }

        decision_log.decision_log(
            event="entry",
            regime="trend",
            reason="aggregate",
            extra={
                "candidates": int(signals.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()
        exit_mask = exits.trend_exit(df)
        df.loc[:, "exit_long"] = 0
        df.loc[exit_mask, "exit_long"] = 1
        decision_log.decision_log(
            event="exit",
            regime="trend",
            reason="trend_exit",
            extra={"signals": int(exit_mask.sum())},
        )
        return df

    def custom_stoploss(self, pair: str, trade, current_time: datetime, current_rate: float, current_profit: float, **kwargs) -> float:
        # Uproszczone: korzystamy ze stałego stoplossu zdefiniowanego w klasie.
        return self.stoploss

    def custom_exit(self, pair: str, trade, current_time: datetime, current_rate: float, current_profit: float, **kwargs):
        # zachowaj dane wyjścia i uzupełnij custom_data
        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
