# source: https://raw.githubusercontent.com/PabloM03/freqtrade/a604b29b7c4f06b8d981207d8a452179ca427465/user_data/strategies/FreqAILightGBM15m.py
# directory_url: https://github.com/PabloM03/freqtrade/blob/develop/user_data/strategies/
# User: PabloM03
# Repository: freqtrade
# --------------------"""
Github_PabloM03_freqtrade__FreqAILightGBM15m__20260827_190157 v2 — ML con LightGBM Regressor + filtros de calidad

Mejoras sobre v1:
- Target más largo: 96 velas (24h) en lugar de 32 (8h) → más señal, menos ruido
- Entry threshold más alto: 2.5% por defecto → solo entradas de alta convicción
- Filtro de tendencia triple (ema50_ok) → bloquea entradas en downtrend
- HARD_TP = 0.50 → deja correr movimientos grandes (BONK, WIF, etc.)
- Solo pares con historial largo: BTC, ETH, SOL (más datos = mejor entrenamiento)

Walk-forward real:
- train_period_days=90, backtest_period_days=30 → cero look-ahead bias

Para backtesting (usar config.freqai_v2.json):
  freqtrade backtesting -c config.json -c config.backtest.freqai.json -c config.freqai.json
    -s Github_PabloM03_freqtrade__FreqAILightGBM15m__20260827_190157 --timerange 20240101-20241231 --cache none
"""

import logging
import talib.abstract as ta
import numpy as np
from pandas import DataFrame
from freqtrade.strategy import IStrategy, DecimalParameter

logger = logging.getLogger(__name__)


class Github_PabloM03_freqtrade__FreqAILightGBM15m__20260827_190157(IStrategy):

    INTERFACE_VERSION = 3
    timeframe = "15m"
    startup_candle_count = 300

    stoploss = -0.06
    trailing_stop = False
    minimal_roi = {"0": 0.999}

    use_exit_signal = True
    exit_profit_only = False
    ignore_roi_if_entry_signal = True
    process_only_new_candles = True

    # TP duro para dejar correr grandes movimientos
    HARD_TP = 0.50

    # Umbral de predicción — más alto = menos trades pero más calidad
    entry_threshold = DecimalParameter(1.5, 4.0, default=2.5, decimals=1, space="buy", optimize=True)
    exit_threshold  = DecimalParameter(-4.0, -1.0, default=-1.5, decimals=1, space="sell", optimize=True)

    # Filtro de tendencia (ema50_ok)
    buy_ema50_close_pct = DecimalParameter(0.920, 0.998, default=0.970, decimals=3, space="buy", optimize=True)
    buy_ema50_slope     = DecimalParameter(0.960, 0.998, default=0.985, decimals=3, space="buy", optimize=True)

    def feature_engineering_expand_all(
        self, dataframe: DataFrame, period: int, metadata: dict, **kwargs
    ) -> DataFrame:
        """
        Features por cada período en indicator_periods_candles [10, 20, 40].
        Naming literal "period" — FreqAI renombra internamente.
        """
        dataframe["%-rsi-period"]      = ta.RSI(dataframe, timeperiod=period)
        dataframe["%-adx-period"]      = ta.ADX(dataframe, timeperiod=period)
        dataframe["%-mfi-period"]      = ta.MFI(dataframe, timeperiod=period)
        dataframe["%-roc-period"]      = ta.ROC(dataframe, timeperiod=period)
        dataframe["%-macdhist-period"] = ta.MACD(dataframe)["macdhist"]
        dataframe["%-willr-period"]    = ta.WILLR(dataframe, timeperiod=period)
        dataframe["%-cci-period"]      = ta.CCI(dataframe, timeperiod=period)

        bb = ta.BBANDS(dataframe, timeperiod=period)
        upper = bb["upperband"]
        lower = bb["lowerband"]
        mid   = bb["middleband"]
        dataframe["%-bb_pct-period"]   = (dataframe["close"] - lower) / (upper - lower + 1e-8)
        dataframe["%-bb_width-period"] = (upper - lower) / (mid + 1e-8)

        return dataframe

    def feature_engineering_expand_basic(
        self, dataframe: DataFrame, metadata: dict, **kwargs
    ) -> DataFrame:
        """Features por cada timeframe incluido."""
        dataframe["%-pct_1"]     = dataframe["close"].pct_change(1)
        dataframe["%-pct_4"]     = dataframe["close"].pct_change(4)
        dataframe["%-pct_16"]    = dataframe["close"].pct_change(16)
        dataframe["%-pct_32"]    = dataframe["close"].pct_change(32)
        dataframe["%-vol_ratio"] = dataframe["volume"] / (dataframe["volume"].rolling(20).mean() + 1e-8)
        dataframe["%-vol_ratio_4"] = dataframe["volume"] / (dataframe["volume"].rolling(4).mean() + 1e-8)
        dataframe["%-raw_price"]  = dataframe["close"]
        dataframe["%-raw_volume"] = dataframe["volume"]

        return dataframe

    def feature_engineering_standard(
        self, dataframe: DataFrame, metadata: dict, **kwargs
    ) -> DataFrame:
        """Features globales + TARGET."""
        ema200 = ta.EMA(dataframe, timeperiod=200)
        ema80  = ta.EMA(dataframe, timeperiod=80)
        ema50  = ta.EMA(dataframe, timeperiod=50)
        dataframe["%-close_ema200"] = dataframe["close"] / (ema200 + 1e-8)
        dataframe["%-close_ema80"]  = dataframe["close"] / (ema80 + 1e-8)
        dataframe["%-close_ema50"]  = dataframe["close"] / (ema50 + 1e-8)
        dataframe["%-ema50_ema200"] = ema50 / (ema200 + 1e-8)
        dataframe["%-ema200_slope"] = ema200 / (ema200.shift(96) + 1e-8)
        dataframe["%-ema50_slope"]  = ema50  / (ema50.shift(32) + 1e-8)

        atr = ta.ATR(dataframe, timeperiod=14)
        dataframe["%-atr_ratio"] = atr / (dataframe["close"] + 1e-8)

        stoch = ta.STOCHRSI(dataframe, timeperiod=14, fastk_period=3, fastd_period=3)
        dataframe["%-stoch_k"] = stoch["fastk"]
        dataframe["%-stoch_d"] = stoch["fastd"]

        # TARGET: % de cambio en las próximas 96 velas (24h a 15m) — más señal que 8h
        dataframe["&-s_target"] = (
            dataframe["close"].shift(-96) / dataframe["close"] - 1
        ) * 100

        # Para filtro ema50_ok (no es un feature ML — se usa solo en populate_entry_trend)
        dataframe["ema50_ht"] = ema80   # EMA80@15m = 20h — "ema_fast" equivalente
        dataframe["ema200_ht"] = ema200  # EMA200@15m = 50h — "ema_slow"

        return dataframe

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe = self.freqai.start(dataframe, metadata, self)
        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # Filtro de tendencia: EMA200 no bajando en 48h + precio cerca de EMA200
        ema50_ok = (
            (dataframe["ema200_ht"] >= dataframe["ema200_ht"].shift(192) * self.buy_ema50_slope.value) &
            (dataframe["close"] >= dataframe["ema200_ht"] * self.buy_ema50_close_pct.value)
        )

        conditions = [
            dataframe["&-s_target"] > self.entry_threshold.value,
            dataframe["do_predict"] == 1,
            ema50_ok,
            dataframe["volume"] > 0,
        ]
        dataframe.loc[np.all(conditions, axis=0), "enter_long"] = 1
        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        conditions = [
            dataframe["&-s_target"] < self.exit_threshold.value,
            dataframe["do_predict"] == 1,
            dataframe["volume"] > 0,
        ]
        dataframe.loc[np.all(conditions, axis=0), "exit_long"] = 1
        return dataframe

    def custom_exit(self, pair, trade, current_time, current_rate, current_profit, **kwargs):  # noqa: ARG002
        """Hard TP para dejar correr grandes movimientos."""
        if current_profit is not None and current_profit >= self.HARD_TP:
            return "hard_tp"
        return None
