# source: https://raw.githubusercontent.com/Logashan/freqtrade-news-ml/ea2203157448cacf407f6ea6c1df2e17be5f11d5/user_data/strategies/IntradayScalp5m.py
import logging
from typing import Dict

import numpy as np
import pandas as pd
from pandas import DataFrame

from freqtrade.strategy import IStrategy, IntParameter, DecimalParameter


logger = logging.getLogger(__name__)


class Github_Logashan_freqtrade_news_ml__IntradayScalp5m__20250814_154308(IStrategy):
    timeframe = "5m"
    can_short = True
    process_only_new_candles = True
    # Должно покрывать объёмный квантиль и Keltner
    startup_candle_count = 320

    # Короткая лесенка ROI (будет переоптимизировано hyperopt'ом)
    minimal_roi = {"0": 0.006, "15": 0.004, "45": 0.002, "90": 0.0}

    # Типы ордеров
    order_types = {
        "entry": "limit",
        "exit": "limit",
        "stoploss": "limit",
        "stoploss_on_exchange": True,
        "stoploss_on_exchange_interval": 60,
        "stoploss_on_exchange_limit_ratio": 0.99,
    }
    order_time_in_force = {"entry": "gtc", "exit": "gtc"}

    # Узкий SL и трейлинг (будут переоптимизированы)
    stoploss = -0.004
    trailing_stop = True
    trailing_stop_positive = 0.003
    trailing_stop_positive_offset = 0.006
    trailing_only_offset_is_reached = True

    # -----------------
    # ПАРАМЕТРЫ (для гиперопта/тонкой настройки)
    # -----------------
    # Время торговли 07:00–20:00 UTC фиксировано фильтром времени

    # Keltner: EMA20 +/- atr_mult * ATR(14)
    atr_mult = DecimalParameter(1.0, 2.0, default=1.2, decimals=2, space="buy", optimize=True)

    # Волатильность: ATR% в диапазоне
    atr_min_pct = DecimalParameter(0.001, 0.010, default=0.0020, decimals=4, space="buy", optimize=True)
    atr_max_pct = DecimalParameter(0.005, 0.020, default=0.0120, decimals=4, space="buy", optimize=True)

    # Объём: значение >= rolling-quantile(volume, q)
    vol_q_window = IntParameter(96, 300, default=144, space="buy", optimize=True)
    vol_q_thres = DecimalParameter(0.50, 0.85, default=0.60, decimals=2, space="buy", optimize=True)

    # Protection params (для hyperopt пространства "protection")
    mdd_stop_candles = IntParameter(200, 400, default=288, space="protection", optimize=True)
    mdd_max_dd = DecimalParameter(0.010, 0.020, default=0.015, decimals=3, space="protection", optimize=True)
    lpp_lb_candles = IntParameter(5, 10, default=8, space="protection", optimize=True)
    lpp_stop_candles = IntParameter(5, 12, default=8, space="protection", optimize=True)
    lpp_required_profit = DecimalParameter(-0.001, 0.002, default=0.0, decimals=3, space="protection", optimize=True)

    def informative_pairs(self):
        return []

    @property
    def protections(self):
        # 5m: ~288 свечей = 1 день
        return [
            # Дневной стоп по счёту 1.5%, кулдаун ~до конца дня
            {
                "method": "MaxDrawdown",
                "lookback_period_candles": 288,
                "trade_limit": 1,
                "stop_duration_candles": int(self.mdd_stop_candles.value),
                "max_allowed_drawdown": float(self.mdd_max_dd.value),
            },
            # Кулдаун после убыточной сделки (на пару)
            {
                "method": "LowProfitPairs",
                "lookback_period_candles": int(self.lpp_lb_candles.value),
                "trade_limit": 1,
                "stop_duration_candles": int(self.lpp_stop_candles.value),
                "required_profit": float(self.lpp_required_profit.value),
            },
        ]

    # -----------------
    # Индикаторы
    # -----------------
    def populate_indicators(self, df: DataFrame, metadata: Dict) -> DataFrame:
        # EMA20 (центральная линия Keltner)
        df["ema20"] = df["close"].ewm(span=20, adjust=False).mean()

        # ATR(14)
        prev_close = df["close"].shift(1)
        tr = np.maximum(
            df["high"] - df["low"],
            np.maximum((df["high"] - prev_close).abs(), (df["low"] - prev_close).abs()),
        )
        df["atr"] = tr.ewm(alpha=1 / 14, adjust=False).mean()
        df["atr_pct"] = df["atr"] / df["close"]

        # Keltner Channels
        atr_mult_val = float(self.atr_mult.value)
        df["kc_upper"] = df["ema20"] + atr_mult_val * df["atr"]
        df["kc_lower"] = df["ema20"] - atr_mult_val * df["atr"]

        # Объёмный перцентиль (rolling quantile)
        win = int(self.vol_q_window.value)
        q = float(self.vol_q_thres.value)
        # Используем min_periods=win для стабильности порога
        df["vol_q"] = df["volume"].rolling(win, min_periods=win).quantile(q)
        df["vol_ok"] = (df["volume"] >= df["vol_q"]).astype(int)

        # Фильтр времени UTC: 07:00–20:00
        hours = df["date"].dt.hour
        df["in_session"] = ((hours >= 7) & (hours <= 20)).astype(int)

        df.replace([np.inf, -np.inf], np.nan, inplace=True)
        df.ffill(inplace=True)
        df.bfill(inplace=True)
        return df

    # -----------------
    # Входы
    # -----------------
    def populate_entry_trend(self, df: DataFrame, metadata: Dict) -> DataFrame:
        # Волатильность в заданном диапазоне
        vol_ok = (df["atr_pct"] >= float(self.atr_min_pct.value)) & (
            df["atr_pct"] <= float(self.atr_max_pct.value)
        )

        base_filters = vol_ok & (df["vol_ok"] == 1) & (df["in_session"] == 1)

        # LONG: выход ниже нижней ленты и возврат внутрь (пересечение снизу-вверх)
        long_revert = (df["close"].shift(1) < df["kc_lower"].shift(1)) & (df["close"] > df["kc_lower"])

        # SHORT: зеркально по верхней ленте (пересечение сверху-вниз)
        short_revert = (df["close"].shift(1) > df["kc_upper"].shift(1)) & (df["close"] < df["kc_upper"])

        df["enter_long"] = 0
        df.loc[base_filters & long_revert, "enter_long"] = 1

        df["enter_short"] = 0
        df.loc[base_filters & short_revert, "enter_short"] = 1
        return df

    # -----------------
    # Выходы управляются ROI/SL/Trailing — явных сигналов выхода не задаём
    # -----------------
    def populate_exit_trend(self, df: DataFrame, metadata: Dict) -> DataFrame:
        df["exit_long"] = 0
        df["exit_short"] = 0
        return df


