# source: https://raw.githubusercontent.com/johan1727/botrading-/9c5636fb434674b596e81b99f82c4b516e2f64a5/user_data/strategies/GeminiBacktest.py
"""
Github_johan1727_botrading___GeminiBacktest__20260511_222253 — versión sin API para backtesting rápido.
Replica la lógica de entrada/salida de GeminiStrategy usando solo indicadores técnicos.
Usa las mismas reglas que Gemini aplica en producción: RSI, EMA, MACD, BB, volumen.
"""
import pandas as pd
from pandas import DataFrame
from freqtrade.strategy import IStrategy, IntParameter


def _calc_ema(series: pd.Series, period: int) -> pd.Series:
    return series.ewm(span=period, adjust=False).mean()


class Github_johan1727_botrading___GeminiBacktest__20260511_222253(IStrategy):
    """
    Replica las reglas de GeminiStrategy sin llamadas a API.
    Útil para backtesting rápido con datos históricos reales.
    """

    INTERFACE_VERSION = 3
    timeframe = "5m"
    can_short = False
    max_open_trades = 5
    stoploss = -0.015
    minimal_roi = {"0": 0.04, "30": 0.025, "60": 0.02, "120": 0.01}
    trailing_stop = True
    trailing_stop_positive = 0.006
    trailing_stop_positive_offset = 0.015
    trailing_only_offset_is_reached = True
    startup_candle_count: int = 210

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe["rsi"] = dataframe["close"].diff().pipe(
            lambda d: pd.concat([
                d.clip(lower=0).rolling(14).mean(),
                (-d.clip(upper=0)).rolling(14).mean()
            ], axis=1).apply(lambda r: 100 - 100 / (1 + r.iloc[0] / r.iloc[1]) if r.iloc[1] != 0 else 50, axis=1)
        )
        dataframe["ema20"] = _calc_ema(dataframe["close"], 20)
        dataframe["ema50"] = _calc_ema(dataframe["close"], 50)
        dataframe["ema200"] = _calc_ema(dataframe["close"], 200)
        ema12 = _calc_ema(dataframe["close"], 12)
        ema26 = _calc_ema(dataframe["close"], 26)
        dataframe["macd"] = ema12 - ema26
        dataframe["macd_signal"] = _calc_ema(dataframe["macd"], 9)
        dataframe["macd_hist"] = dataframe["macd"] - dataframe["macd_signal"]
        bb_mid = dataframe["close"].rolling(20).mean()
        bb_std = dataframe["close"].rolling(20).std()
        dataframe["bb_upper"] = bb_mid + 2 * bb_std
        dataframe["bb_lower"] = bb_mid - 2 * bb_std
        dataframe["bb_pct"] = (dataframe["close"] - dataframe["bb_lower"]) / (dataframe["bb_upper"] - dataframe["bb_lower"]) * 100
        vol_ma = dataframe["volume"].rolling(20).mean()
        dataframe["volume_ratio"] = dataframe["volume"] / vol_ma

        high_low = dataframe["high"] - dataframe["low"]
        high_close = (dataframe["high"] - dataframe["close"].shift()).abs()
        low_close = (dataframe["low"] - dataframe["close"].shift()).abs()
        true_range = pd.concat([high_low, high_close, low_close], axis=1).max(axis=1)
        dataframe["atr"] = true_range.ewm(span=14, adjust=False).mean()
        dataframe["atr_pct"] = dataframe["atr"] / dataframe["close"] * 100

        # ADX (14)
        high_diff = dataframe["high"].diff()
        low_diff = dataframe["low"].diff()
        plus_dm = high_diff.where((high_diff > 0) & (high_diff > -low_diff), 0.0)
        minus_dm = (-low_diff).where((-low_diff > 0) & (-low_diff > high_diff), 0.0)
        atr14 = dataframe["atr"]
        plus_di = 100 * (plus_dm.ewm(span=14, adjust=False).mean() / atr14.replace(0, 1e-10))
        minus_di = 100 * (minus_dm.ewm(span=14, adjust=False).mean() / atr14.replace(0, 1e-10))
        dx = (abs(plus_di - minus_di) / (plus_di + minus_di + 1e-10)) * 100
        dataframe["adx"] = dx.ewm(span=14, adjust=False).mean()

        # MFI (14)
        typical_price = (dataframe["high"] + dataframe["low"] + dataframe["close"]) / 3
        money_flow = typical_price * dataframe["volume"]
        tp_prev = typical_price.shift(1)
        pos_flow = money_flow.where(typical_price > tp_prev, 0.0).rolling(14).sum()
        neg_flow = money_flow.where(typical_price < tp_prev, 0.0).rolling(14).sum()
        mfi_ratio = pos_flow / neg_flow.replace(0, 1e-10)
        dataframe["mfi"] = 100 - (100 / (1 + mfi_ratio))

        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Reglas de entrada que replica lo que Gemini decide:
        - Señal principal: tendencia alcista en todos los indicadores
        - Señal oportunista: RSI sobreventa + BB bajo
        """
        above_ema200 = dataframe["close"] > dataframe["ema200"]
        ema_bull = dataframe["ema20"] > dataframe["ema50"]
        macd_bull = dataframe["macd_hist"] > 0
        rsi_ok = dataframe["rsi"] < 50
        vol_ok = dataframe["volume_ratio"] > 1.0
        bb_mid_zone = dataframe["bb_pct"] < 70

        adx_ok = dataframe["adx"] > 25
        señal_principal = above_ema200 & ema_bull & macd_bull & rsi_ok & vol_ok & bb_mid_zone & adx_ok

        rsi_oversold = dataframe["rsi"] < 35
        bb_low = dataframe["bb_pct"] < 25
        vol_min = dataframe["volume_ratio"] > 0.8
        mfi_oversold = dataframe["mfi"] < 25
        señal_oportunista = rsi_oversold & bb_low & vol_min & mfi_oversold & adx_ok

        dataframe.loc[señal_principal | señal_oportunista, "enter_long"] = 1
        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Reglas de salida:
        - RSI sobrecomprado + MACD bajando
        - Divergencia RSI (precio sube, RSI baja)
        """
        rsi_high = dataframe["rsi"] > 70
        macd_bear = dataframe["macd_hist"] < 0

        rsi_prev = dataframe["rsi"].shift(3)
        close_prev = dataframe["close"].shift(3)
        rsi_divergence = (dataframe["close"] > close_prev) & (dataframe["rsi"] < rsi_prev - 5)

        dataframe.loc[(rsi_high & macd_bear) | rsi_divergence, "exit_long"] = 1
        return dataframe
