# source: https://raw.githubusercontent.com/ElCayi/CayiBot-GridTide-01/3823a5c38ce27777d7099b6b31e8d4a09205517f/freqtrade_cayi_usdc_perp/user_data/strategies/CayiBot_BBPercent_Short.py
from freqtrade.strategy import IStrategy, IntParameter, DecimalParameter, CategoricalParameter
import pandas as pd
import numpy as np

class Github_ElCayi_CayiBot_GridTide_01__CayiBot_BBPercent_Short__20251023_024601(IStrategy):
    """
    Short-only de grindeo bajista con Bollinger %B.
    Entra cuando hay sobreextensión arriba y giro bajista, filtra por EMA200 bajista.
    TP en banda inferior. Muestra BB% y ancho en el plot.
    """
    timeframe = '4h'
    can_short = True
    process_only_new_candles = True
    startup_candle_count = 300  # 300*4h ~ 50 días para medias/ATR cómodos

    # ===== Parámetros tunables =====
    bb_window = IntParameter(10, 80, default=20, space='sell')
    bb_dev    = DecimalParameter(1.0, 3.5, decimals=1, default=2.0, space='sell')
    ma_type   = CategoricalParameter(['SMA', 'EMA'], default='SMA', space='sell')
    price_src = CategoricalParameter(['close', 'hlc3'], default='close', space='sell')

    # Entrada short: sobreextensión previa y giro
    pb_enter_short = DecimalParameter(0.95, 1.30, decimals=2, default=1.10, space='sell')

    # Filtros de tendencia bajista
    ema_long_period = 200
    ema_slope_lookback = 3  # en 4h, 3 velas ~ 12h de pendiente

    # TP/SL
    tp_to_lower = True            # take-profit en banda inferior
    sl_atr_mult = 1.5             # SL defensivo por ATR

    # ===== Plot: BBs y BB% visibles =====
    plot_config = {
        "main_plot": {
            "bb_upper": {},
            "bb_mid": {},
            "bb_lower": {},
            "ema200": {},
        },
        "subplots": {
            "BB%": {
                "pb": {},
            },
            "BB Width": {
                "bb_width": {},
            }
        }
    }

    def version(self) -> str:
        return "1.1-short-bbpercent-4h"

    # ---------- helpers ----------
    @staticmethod
    def _hlc3(df: pd.DataFrame) -> pd.Series:
        return (df['high'] + df['low'] + df['close']) / 3.0

    @staticmethod
    def _ema(s: pd.Series, n: int) -> pd.Series:
        return s.ewm(span=n, adjust=False).mean()

    def _bb(self, df: pd.DataFrame, window: int, dev: float, ma_type: str, price_src: str):
        src = df['close'] if price_src == 'close' else self._hlc3(df)
        basis = self._ema(src, window) if ma_type == 'EMA' else src.rolling(window).mean()
        stdev = src.rolling(window).std(ddof=0)
        upper = basis + dev * stdev
        lower = basis - dev * stdev
        pb    = (src - lower) / (upper - lower)          # BB%
        width = (upper - lower) / basis
        # recorta %B para evitar outliers absurdos en el plot
        return basis, upper, lower, pb.clip(-0.5, 1.5), width

    def populate_indicators(self, df: pd.DataFrame, metadata: dict) -> pd.DataFrame:
        if df.empty:
            return df

        # BB%
        w   = int(self.bb_window.value)
        dev = float(self.bb_dev.value)
        ma  = self.ma_type.value
        src = self.price_src.value
        bb_mid, bb_up, bb_lo, pb, width = self._bb(df, w, dev, ma, src)
        df['bb_mid']   = bb_mid
        df['bb_upper'] = bb_up
        df['bb_lower'] = bb_lo
        df['pb']       = pb
        df['bb_width'] = width

        # EMA 200 + pendiente negativa
        df['ema200'] = self._ema(df['close'], self.ema_long_period)
        df['ema_slope_neg'] = df['ema200'].diff(self.ema_slope_lookback) < 0
        df['below_ema'] = df['close'] < df['ema200']

        # “Giro bajista” de %B tras sobreextensión
        thr = float(self.pb_enter_short.value)
        df['pb_rebound_short'] = (df['pb'].shift(1) > thr) & (df['pb'] < df['pb'].shift(1))

        # ATR simple para SL
        tr1 = (df['high'] - df['low']).abs()
        tr2 = (df['high'] - df['close'].shift()).abs()
        tr3 = (df['low']  - df['close'].shift()).abs()
        df['atr'] = pd.concat([tr1, tr2, tr3], axis=1).max(axis=1).rolling(14).mean()

        return df

    def populate_entry_trend(self, df: pd.DataFrame, metadata: dict) -> pd.DataFrame:
        df['enter_long'] = 0
        df['enter_short'] = 0

        # SHORT-ONLY: vende alto (rechazo desde banda superior) + marea bajista
        cond_short = (
            df['pb_rebound_short'] &
            (df['close'] < df['bb_upper']) &
            (df['bb_width'] > 0) &
            df['below_ema'] & df['ema_slope_neg']
        )

        df.loc[cond_short, 'enter_short'] = 1
        return df

    def populate_exit_trend(self, df: pd.DataFrame, metadata: dict) -> pd.DataFrame:
        df['exit_long'] = 0
        df['exit_short'] = 0

        # TP “abajo”: banda inferior o %B<=0
        if self.tp_to_lower:
            hit_lower = (df['low'] <= df['bb_lower']) | (df['pb'] <= 0.0)
            df.loc[(df['enter_short'].shift().fillna(0) == 1) & hit_lower, 'exit_short'] = 1

        # Falla: cierra si fuerza por encima de la banda superior
        fail = (df['close'] > df['bb_upper'])
        df.loc[(df['enter_short'].shift().fillna(0) == 1) & fail, 'exit_short'] = 1

        return df

    # Stoploss porcentual dinámico relativo al open_rate (usa ATR)
    def custom_stoploss(self, pair: str, trade, current_time, current_rate, current_profit, **kwargs):
        atr = None
        try:
            atr = trade.strategy.safe_dataframe['atr'].iloc[-1]
        except Exception:
            pass
        if not atr or atr <= 0:
            return 0.05  # fallback 5%
        # convierte ATR a porcentaje relativo al precio de entrada
        return max(0.003, (float(self.sl_atr_mult) * atr) / trade.open_rate)
