from freqtrade.strategy import IStrategy, IntParameter, DecimalParameter, merge_informative_pair, informative
from pandas import DataFrame
import talib as ta
import numpy as np
import pandas as pd
from datetime import datetime, timezone, timedelta
import pytz
from scipy.signal import argrelextrema
import json
import os
import arrow
import logging
import joblib
import freqtrade.vendor.qtpylib.indicators as qtpylib

logger = None

def setup_logger(runmode: str):
    global logger
    print(f"[setup_logger] Inicializando logger para runmode: {runmode}")

    date_str = datetime.now().strftime('%Y-%m-%d')
    #log_file_path = f"/root/freqtrade/logfile_{__name__}_{runmode}_{date_str}.log"
    log_file_path = os.path.expanduser(f"~/freqtrade/logfile_{__name__}_{runmode}_{date_str}.log")

    logger = logging.getLogger(f"strategy_logger_{__name__}_{runmode}")
    logger.setLevel(logging.INFO)

    # 🔐 Asegura que se agrega el handler si no existe
    if not any(isinstance(h, logging.FileHandler) and h.baseFilename == log_file_path for h in logger.handlers):
        handler = logging.FileHandler(log_file_path, mode="a", encoding="utf-8")
        formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
        handler.setFormatter(formatter)
        logger.addHandler(handler)

    # ✔ Prueba de que el logger está configurado
    print(f"[setup_logger] Logger inicializado en ruta: {log_file_path}")
    logger.info("Logger configurado correctamente")

class Hermes_v9_confirm(IStrategy):
    
    timeframe = '1m'
    inf_timeframes = ['5m', '1d']
    optimized_params = {}
    trades_count = 0
    minimal_roi = {
        "0": 0.06
    }
    use_custom_stoploss = True
    use_custom_exit = True
    trailing_stop = False
        
    def __init__(self, config: dict) -> None:
        super().__init__(config)
        self.load_optimized_parameters()
        runmode = self.config.get("runmode", "unknown")
        setup_logger(runmode)
        logger.info(f"Logger de estrategia {__name__} inicializado.")
        if logger and logger.hasHandlers():
            print(f"Logger inicializado en ruta: {logger.handlers[0].baseFilename}")
        else:
            print("⚠️ Logger no tiene handlers. No se ha inicializado correctamente.")
            
    def load_optimized_parameters(self):
        """
        Cargar todos los archivos .json de parámetros optimizados en una carpeta.
        """
        if self.is_hyperopt():
            return {}
        directory = 'user_data/strategies/'
        normalized_class = self.__class__.__name__.replace('_short', '')
        for filename in os.listdir(directory):
            if filename.endswith('.json') and filename.startswith(normalized_class):
                
                pair_name = filename.replace('.json', '').replace(f"{normalized_class}_", '').replace('_', '/')
                print(f"Buscamos el archivo {filename} con el pair_name {pair_name}")
                with open(os.path.join(directory, filename)) as f:
                    normalized_pair = pair_name.replace(':USDC', '')
                    print(f"Cargamos el json de {filename} con el pair_name {normalized_pair}")
                    self.optimized_params[normalized_pair] = json.load(f)

    def get_pair_params(self, pair: str) -> dict:
        """
        Devuelve los parámetros para el par dado.
        """
        result = self.optimized_params.get(pair, {})
        return result
    
    def is_hyperopt(self) -> bool:
        return self.config.get('runmode') == 'hyperopt'
    
    @informative('5m')
    def populate_indicators_5m(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe['rsi_5m'] = ta.RSI(dataframe['close'], timeperiod=14)
        dataframe['sma_rsi_5m'] = ta.SMA(dataframe['rsi_5m'], timeperiod=14)
        
        dataframe['ma_7'] = ta.SMA(dataframe['close'], timeperiod=7)
        dataframe['ma_25'] = ta.SMA(dataframe['close'], timeperiod=25)
        dataframe['ma_99'] = ta.SMA(dataframe['close'], timeperiod=99)
        
        
        dataframe['ema_7'] = ta.SMA(dataframe['close'], timeperiod=7)
        dataframe['ema_25'] = ta.SMA(dataframe['close'], timeperiod=25)
        dataframe['ema_99'] = ta.SMA(dataframe['close'], timeperiod=99)
        
        dataframe['rsi'] = ta.RSI(dataframe['close'], timeperiod=14)
        
        window = 5
        
        rsi_mins = argrelextrema(dataframe['rsi'].round(2).values, np.less_equal, order=window)[0]
        price_mins = argrelextrema(dataframe['close'].values, np.less_equal, order=window)[0]
        
        bullish_divs = []
        bullish_divs_hidden = []
            
        for i in range(1, len(rsi_mins)):
            i1 = rsi_mins[i - 1]
            i2 = rsi_mins[i]
            if i1 in price_mins and i2 in price_mins:
                
                # Evita usar divergencias que terminen fuera de la penúltima vela (solo en dry_run y live)
                # if runmode in ("dry_run", "live") and i2 >= len(dataframe) - 2:
                #     continue
                
                rsi1, rsi2 = dataframe.loc[dataframe.index[[i1, i2]], 'rsi']
                price1, price2 = dataframe.loc[dataframe.index[[i1, i2]], 'close']
                date1, date2 = dataframe.loc[dataframe.index[[i1, i2]], 'date']
                
                date1_datetime = date1
                date2_datetime = date2
                if isinstance(date1_datetime, str):
                    date1_datetime = pd.to_datetime(date1_datetime)
                if isinstance(date2_datetime, str):
                    date2_datetime = pd.to_datetime(date2_datetime)
                diff_minutes = (date2_datetime - date1_datetime).total_seconds() / 60
                
                if rsi2 < rsi1 and price2 > price1:
                    bullish_divs_hidden.append(i2)
                    
                    # y = mx + b -> y = pendiente * x + ordenada
                    pendiente_rsi = (rsi1 - rsi2) / (i1 - i2)
                    # b = y - mx -> ordenada = y - pendiente * x
                    ordenada_rsi = rsi2 - pendiente_rsi * i2
                    
                    pendiente_precio = (price1 - price2) / (i1 - i2)
                    ordenada_precio = price2 - pendiente_precio * i2
                    
                    for idx in range(i1, i2 + 1):
                        # y = mx + b -> y = pendiente * x + ordenada
                        y_rsi = pendiente_rsi * idx + ordenada_rsi
                        dataframe.loc[dataframe.index[idx], 'divergencia_rsi_alcista_oculta_rsi'] = y_rsi
                        y_precio = pendiente_precio * idx + ordenada_precio
                        dataframe.loc[dataframe.index[idx], 'divergencia_rsi_alcista_oculta_precio'] = y_precio
                    
                if rsi2 > rsi1 and price2 < price1:

                    # Guardar info solo si hay divergencia
                    dataframe.loc[dataframe.index[i1], 'rsi_div1'] = rsi1
                    dataframe.loc[dataframe.index[i2], 'rsi_div2'] = rsi2
                    dataframe.loc[dataframe.index[i2], 'price_div1'] = price1
                    dataframe.loc[dataframe.index[i2], 'price_div2'] = price2
                    dataframe.loc[dataframe.index[i2], 'date_div1'] = date1
                    dataframe.loc[dataframe.index[i2], 'date_div2'] = date2
                    dataframe.loc[dataframe.index[i2], 'diff_div'] = diff_minutes
                    dataframe.loc[dataframe.index[i2], 'rsi_div_diff'] = rsi2 - rsi1
                    dataframe.loc[dataframe.index[i2], 'price_div_diff'] = price1 - price2
                    dataframe.loc[dataframe.index[i2], 'divergence_strength'] = (rsi2 - rsi1) / max(rsi1, 1)
                    dataframe.loc[dataframe.index[i2], 'divergence_strength_price'] = (price1 - price2) / max(price1, 1)
                    dataframe.loc[dataframe.index[i2], 'divergence_window'] = window
                    
                    # y = mx + b -> y = pendiente * x + ordenada
                    pendiente_rsi = (rsi1 - rsi2) / (i1 - i2)
                    # b = y - mx -> ordenada = y - pendiente * x
                    ordenada_rsi = rsi2 - pendiente_rsi * i2
                    
                    pendiente_precio = (price1 - price2) / (i1 - i2)
                    ordenada_precio = price2 - pendiente_precio * i2
                    
                    for idx in range(i1, i2 + 1):
                        # y = mx + b -> y = pendiente * x + ordenada
                        y_rsi = pendiente_rsi * idx + ordenada_rsi
                        dataframe.loc[dataframe.index[idx], 'divergencia_rsi_alcista_rsi'] = y_rsi
                        y_precio = pendiente_precio * idx + ordenada_precio
                        dataframe.loc[dataframe.index[idx], 'divergencia_rsi_alcista_precio'] = y_precio
                    
                    bullish_divs.append(i2)
                    
                    now = datetime.now(timezone.utc)
                    if isinstance(date2, str):
                        date2 = pd.to_datetime(date2)
                
                    #logger.info(f"date2 type: {type(date2)}, value: {date2}, tzinfo: {getattr(date2, 'tzinfo', None)} runmode: {runmode}")
                    #logger.info(f"Evaluando log: now={now}, date2={date2}, diferencia={(now - date2)}, runmode={runmode}")
                    if (now - date2 < timedelta(minutes=30)) and runmode in ("dry_run", "live"):
                        logger.info(f"--→[{metadata['pair']}][{now}][{i2}] Divergencia alcista detectada - "
                            f"[{date1} → {date2}] diff: {diff_minutes} "
                            f"RSI: {rsi1:.3f} → {rsi2:.3f}, "
                            f"Precio: {price1:.3f} → {price2:.3f}, "
                            f"Window: {window}  diff: {diff_minutes}")
        dataframe.loc[dataframe.index[bullish_divs], 'bullish_divergence'] = 1
        dataframe.loc[dataframe.index[bullish_divs_hidden], 'bullish_divergence_hidden'] = 1
        
        return dataframe
        
    def calc_local_usdc_strength(self, dataframe: DataFrame, dataframe_dict: dict):
        ratio_positives = []
        average_changes = []
    
        for idx, row in dataframe.iterrows():
            fecha = row['date']
            total = 0
            positivos = 0
            cambios = []
    
            print(f"\n🕒 VELA {idx} - {fecha}")
    
            for pair, df in dataframe_dict.items():
                if '/USDC' in pair and 'currency_pct_green' in df.columns:
                    match = df[df['date'] == fecha]
    
                    if not match.empty:
                        val = match['currency_pct_green'].values[0]
                        estado = "🟢" if val > 0 else "🔴"
                        # print(f"  📈 {pair} [{fecha}]: cambio={val:.5f} {estado}")
                        total += 1
                        cambios.append(val)
                        if val > 0:
                            positivos += 1
    
            ratio = (positivos / total) * 100 if total else np.nan
            avg = np.mean(cambios) if cambios else np.nan
    
            print(f"✅ Total pares evaluados: {total}, positivos: {positivos}, ratio: {ratio:.2f}%, promedio cambios: {avg:.5f}")
            ratio_positives.append(ratio)
            average_changes.append(avg)
    
        dataframe['usdc_pairs_ratio_positive'] = ratio_positives
        dataframe['usdc_pairs_avg_change'] = average_changes
    
        return dataframe

    @informative('1h')
    def populate_indicators_1h(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        
        dataframe['ma_7'] = ta.SMA(dataframe['close'], timeperiod=7)
        dataframe['ma_25'] = ta.SMA(dataframe['close'], timeperiod=25)
        dataframe['ma_99'] = ta.SMA(dataframe['close'], timeperiod=99)
        
        dataframe['ema_7'] = ta.EMA(dataframe['close'], timeperiod=7)
        dataframe['ema_25'] = ta.EMA(dataframe['close'], timeperiod=25)
        dataframe['ema_99'] = ta.EMA(dataframe['close'], timeperiod=99)
        
        dataframe['rsi'] = ta.RSI(dataframe['close'], timeperiod=14)
        
        macd, macdsignal, macdhist = ta.MACD(dataframe["close"])
        dataframe['cruce_macd'] = (macd.iloc[-1] < macdsignal.iloc[-1]) and (macd >= macdsignal)
        dataframe['macd'] = macd
        dataframe['macdsignal'] = macdsignal
        dataframe['macdhist'] = macdhist
        
        dataframe['macd_ant'] = macd.shift(1)
        dataframe['macdsignal_ant'] = macdsignal.shift(1)
        dataframe['macdhist_ant'] = macdhist.shift(1)
        
        dataframe['macd_sube'] = macd > macd.shift(1)
        dataframe['macd_baja'] = macd < macd.shift(1)
        dataframe['macdsignal_sube'] = macdsignal > macdsignal.shift(1)
        dataframe['macdsignal_baja'] = macdsignal < macdsignal.shift(1)
        dataframe['macdhist_sube'] = macdhist > macdhist.shift(1)
        dataframe['macdhist_baja'] = macdhist < macdhist.shift(1)
        
        print(f"populate_indicators_1h")
        print(f"-- metadata_pair: {metadata['pair']} stake_currency: {self.config['stake_currency']}")
        
        return dataframe
        
    @informative('1d')
    def populate_indicators_1d(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe['rsi_1d'] = ta.RSI(dataframe['close'], timeperiod=14)
        dataframe['sma_rsi_1d'] = ta.SMA(dataframe['rsi_1d'], timeperiod=14)
        
        dataframe['pct'] = ((dataframe['close'] - dataframe['open']) / dataframe['open']) * 100
        dataframe['pct_prev'] = dataframe['pct'].shift(1)
        
        dataframe['ma_7'] = ta.SMA(dataframe['close'], timeperiod=7)
        dataframe['ma_25'] = ta.SMA(dataframe['close'], timeperiod=25)
        dataframe['ma_99'] = ta.SMA(dataframe['close'], timeperiod=99)
        
        dataframe['ema_7'] = ta.EMA(dataframe['close'], timeperiod=7)
        dataframe['ema_25'] = ta.EMA(dataframe['close'], timeperiod=25)
        dataframe['ema_99'] = ta.EMA(dataframe['close'], timeperiod=99)
        return dataframe

    
    
    # Parámetros configurables
    volume_ratio_threshold_2 = DecimalParameter(0.05, 1.0, default=0.2, decimals=2, space='buy', optimize=True)
    atr_multiplier_stoploss = DecimalParameter(0.1, 3.0, default=0.8, decimals=2, space='sell', optimize=True)
    atr_multiplier_roi = DecimalParameter(1.0, 5.0, default=2.0, decimals=2, space='sell', optimize=True)
    divergence_window = IntParameter(3, 10, default=5, space='buy', optimize=False)
    
    rsi_entry_threshold = IntParameter(40, 60, default=50, space='buy', optimize=True, load=True)
    macd_hist_rising_confirm = IntParameter(1, 5, default=2, space='buy', optimize=True, load=True)
    bullish_candle_confirm_window = IntParameter(1, 10, default=3, space='buy', optimize=True, load=True)
    divergence_strength_threshold = DecimalParameter(0.01, 0.5, default=0.1, decimals=2, space='buy', optimize=True, load=True)
    divergence_confirmation_volume_ratio = DecimalParameter(1.0, 2.5, default=1.2, decimals=2, space='buy', optimize=True, load=True)
    sell_on_rsi_drop_threshold = IntParameter(40, 70, default=50, space='sell', optimize=True, load=True)
    take_profit_multiplier = DecimalParameter(1.0, 4.0, default=2.0, decimals=2, space='sell', optimize=True, load=True)


    def is_causal_minimum(self, series: pd.Series, idx: int, window: int) -> bool:
        """
        Verifica si el índice `idx` es un mínimo local mirando solo hacia atrás.
        """
        if idx < window:
            return False
        prev_window = series[idx - window:idx + 1]  # incluye el actual
        
        return series[idx] == min(prev_window)
        
    def is_causal_maximum(self, series: pd.Series, idx: int, window: int) -> bool:
        """
        Verifica si el índice `idx` es un mínimo local mirando solo hacia atrás.
        """
        if idx < window:
            return False
        prev_window = series[idx - window:idx + 1]  # incluye el actual
        
        return series[idx] == max(prev_window)
    
    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        
        print(f"populate_indicators")
        
        # Verifica que el dataframe no esté vacío y contenga la columna 'close'
        if dataframe is None or dataframe.empty or 'close' not in dataframe.columns:
            return dataframe
        
        print(f"buscamos los parámetros para el par {metadata['pair']}")
        params = self.get_pair_params(metadata['pair'])

        volume_ratio_threshold_2 = self.volume_ratio_threshold_2.value
        atr_multiplier_stoploss = self.atr_multiplier_stoploss.value
        atr_multiplier_roi = self.atr_multiplier_roi.value
        divergence_window = self.divergence_window.value
        
        rsi_entry_threshold = self.rsi_entry_threshold.value
        macd_hist_rising_confirm = self.macd_hist_rising_confirm.value
        bullish_candle_confirm_window = self.bullish_candle_confirm_window.value
        divergence_strength_threshold = self.divergence_strength_threshold.value
        divergence_confirmation_volume_ratio = self.divergence_confirmation_volume_ratio.value
        sell_on_rsi_drop_threshold = self.sell_on_rsi_drop_threshold.value
        take_profit_multiplier = self.take_profit_multiplier.value

        # Si existen valores optimizados para este par, los usamos
        if params and not self.is_hyperopt():
            print(f"parámetros encontrados para el par {metadata['pair']}")
            buy_params = params.get('params', {}).get('buy', {})
            sell_params = params.get('params', {}).get('sell', {})
                
            volume_ratio_threshold_2 = buy_params.get('volume_ratio_threshold_2', 0.2)
            atr_multiplier_stoploss = sell_params.get('atr_multiplier_stoploss', 0.8)
            atr_multiplier_roi = sell_params.get('atr_multiplier_roi', 2.0)
            divergence_window = buy_params.get('divergence_window', 6)
            
            rsi_entry_threshold = buy_params.get('rsi_entry_threshold', 50)
            macd_hist_rising_confirm = buy_params.get('macd_hist_rising_confirm', 2)
            bullish_candle_confirm_window = buy_params.get('bullish_candle_confirm_window', 3)
            divergence_strength_threshold = buy_params.get('divergence_strength_threshold', 0.1)
            divergence_confirmation_volume_ratio = buy_params.get('divergence_confirmation_volume_ratio', 1.2)
            sell_on_rsi_drop_threshold = sell_params.get('sell_on_rsi_drop_threshold', 50)
            take_profit_multiplier = sell_params.get('take_profit_multiplier', 2.0)
        
        divergence_window = 5
        
        #logger.info(f"[{metadata['pair']}] Cálculo de indicadores iniciado.")
        
        velas = 60
        dataframe['change_pct'] = (dataframe['close'] - dataframe['close'].shift(velas)) / dataframe['close'].shift(velas) * 100
        dataframe['acceleration'] = dataframe['change_pct'] - dataframe['change_pct'].shift(1)
        
        # Calcula el RSI
        dataframe['rsi'] = ta.RSI(dataframe['close'], timeperiod=14)
        dataframe['rsi_4'] = ta.RSI(dataframe['close'], timeperiod=4)
        dataframe['rsi_4_sma_14'] = ta.SMA(dataframe['rsi_4'], timeperiod=14)

        dataframe['rsi_sobrecompra'] = 70
        dataframe['rsi_sobrecompra_extrema'] = 80
        dataframe['rsi_medio'] = 50
        dataframe['rsi_sobreventa'] = 30
        dataframe['rsi_sobreventa_extrema'] = 20
        
        # Calcula las medias móviles
        dataframe['ma_7'] = ta.SMA(dataframe['close'], timeperiod=7)
        dataframe['ma_25'] = ta.SMA(dataframe['close'], timeperiod=25)
        dataframe['ma_99'] = ta.SMA(dataframe['close'], timeperiod=99)
        
        dataframe['ma_7_above_25'] = dataframe['ma_7'] > dataframe['ma_25']
        dataframe['ma_7_above_99'] = dataframe['ma_7'] > dataframe['ma_99']
        dataframe['ma_25_above_99'] = dataframe['ma_25'] > dataframe['ma_99']
        
        dataframe['ma_14'] = ta.SMA(dataframe['close'], timeperiod=14)
        dataframe['adx'] = ta.ADX(dataframe['high'], dataframe['low'], dataframe['close'], timeperiod=14)
        dataframe['di_plus'] = ta.PLUS_DI(dataframe['high'], dataframe['low'], dataframe['close'], timeperiod=14)
        dataframe['di_minus'] = ta.MINUS_DI(dataframe['high'], dataframe['low'], dataframe['close'], timeperiod=14)
        
        macd, macdsignal, macdhist = ta.MACD(dataframe["close"])
        dataframe['cruce_macd'] = (macd.iloc[-1] < macdsignal.iloc[-1]) and (macd >= macdsignal)
        dataframe['macd'] = macd
        dataframe['macdsignal'] = macdsignal
        dataframe['macdhist'] = macdhist
        
        dataframe['macd_ant'] = macd.shift(1)
        dataframe['macdsignal_ant'] = macdsignal.shift(1)
        dataframe['macdhist_ant'] = macdhist.shift(1)
        
        dataframe['macd_sube'] = macd > macd.shift(1)
        dataframe['macd_baja'] = macd < macd.shift(1)
        dataframe['macdsignal_sube'] = macdsignal > macdsignal.shift(1)
        dataframe['macdsignal_baja'] = macdsignal < macdsignal.shift(1)
        dataframe['macdhist_sube'] = macdhist > macdhist.shift(1)
        dataframe['macdhist_baja'] = macdhist < macdhist.shift(1)

        
        
        upperband, middleband, lowerband = ta.BBANDS(dataframe["close"], timeperiod=15)
        dataframe["boll_ub"] = upperband
        dataframe["boll_lb"] = lowerband
        dataframe["boll_dist"] = dataframe["close"] - middleband
        dataframe["price_below_boll_lb"] = dataframe["close"] < dataframe["boll_lb"]
        
        
        dataframe["ema_7"] = ta.EMA(dataframe["close"], timeperiod=7)
        dataframe["ema_25"] = ta.EMA(dataframe["close"], timeperiod=25)
        dataframe["ema_99"] = ta.EMA(dataframe["close"], timeperiod=99)
        dataframe["ema_dist_pct"] = (abs(dataframe["ema_25"] - dataframe["ema_99"]) / dataframe["ema_99"]) * 100
        
        
        dataframe['ma_7_above_25_1d'] = dataframe['ma_7_1d'] > dataframe['ma_25_1d']
        dataframe['ma_7_above_99_1d'] = dataframe['ma_7_1d'] > dataframe['ma_99_1d']
        dataframe['ma_25_above_99_1d'] = dataframe['ma_25_1d'] > dataframe['ma_99_1d']
        
        dataframe['price_above_ema_7_1d'] = dataframe['close'] > dataframe['ema_7_1d']
        dataframe['price_above_ema_25_1d'] = dataframe['close'] > dataframe['ema_25_1d']
        dataframe['price_above_ema_7_1h'] = dataframe['close'] > dataframe['ema_7_1h']
        dataframe['price_above_ema_25_1h'] = dataframe['close'] > dataframe['ema_25_1h']
        dataframe['price_above_ema_7_5m'] = dataframe['close'] > dataframe['ema_7_5m']
        dataframe['price_above_ema_25_5m'] = dataframe['close'] > dataframe['ema_25_5m']
        dataframe['price_above_ema_7'] = dataframe['close'] > dataframe['ema_7']
        dataframe['price_above_ema_25'] = dataframe['close'] > dataframe['ema_25']
        
        # Calcula la pendiente de la MA(99) en los últimos 30 minutos
        
        # Calcula la oscilación absoluta del precio por minuto en los últimos 30 minutos
        dataframe['abs_oscillation_pct'] = ((dataframe['high'] - dataframe['low']) / dataframe['close']) * 100
        
        
        # BEGIN - Detectar divergencias (usamos una ventana móvil de N barras para identificar picos/valles)
        window = divergence_window

        # Detectar mínimos locales en RSI y precio
        dataframe['rsi_min_local'] = 0
        dataframe['price_min_local'] = 0
        dataframe['rsi_max_local'] = 0
        dataframe['price_max_local'] = 0
        dataframe['bullish_divergence'] = 0
        dataframe['bearish_divergence'] = 0
        dataframe['bullish_divergence_causal'] = 0
        dataframe['bearish_divergence_causal'] = 0
        dataframe['bullish_divergence_hidden'] = 0
        dataframe['bearish_divergence_hidden'] = 0
        dataframe['bullish_divergence_causal_hidden'] = 0
        dataframe['bearish_divergence_causal_hidden'] = 0
        
        # Detectar si la última vela está abierta
        if 'date' in dataframe.columns:
            last_candle_time = dataframe['date'].iloc[-1]
        elif 'timestamp' in dataframe.columns:
            last_candle_time = dataframe['timestamp'].iloc[-1]
        else:
            raise ValueError("No hay columna de fecha para comprobar la hora de la última vela.")
        timeframe_minutes = int(self.timeframe.replace('m', ''))
        now = arrow.utcnow().floor('minute')
        expected_close_time = now.shift(minutes=-now.minute % timeframe_minutes)
        
        logger.info(f"[{metadata['pair']}][{now}] Cálculo de indicadores. expected_close_time: {expected_close_time} last_candle_time: {last_candle_time} dw: {divergence_window}")
        
        # MMLExtreme
        lookback_mml = 100
        dataframe['mml_high'] = dataframe['high'].rolling(lookback_mml).max()
        dataframe['mml_low'] = dataframe['low'].rolling(lookback_mml).min()
        dataframe['mml_step'] = (dataframe['mml_high'] - dataframe['mml_low']) / 8
        dataframe['mml_-2_8'] = dataframe['mml_low']
        dataframe['mml_+2_8'] = dataframe['mml_high']
        dataframe['mml_0_8'] = dataframe['mml_low'] + dataframe['mml_step'] * 4
        dataframe['mml_support_zone'] = (
            (dataframe['close'] <= dataframe['mml_-2_8'] * 1.01)  # cerca del extremo inferior
        )
        
        # Heikin Ashi
        ha = qtpylib.heikinashi(dataframe)
        dataframe['ha_open'] = ha['open']
        dataframe['ha_close'] = ha['close']
        dataframe['ha_color'] = (dataframe['ha_close'] > dataframe['ha_open']).astype('int')  # 1 si verde
        
        dataframe['ema_fast'] = ta.EMA(dataframe['close'], timeperiod=12)
        dataframe['ema_slow'] = ta.EMA(dataframe['close'], timeperiod=26)
        
        dataframe['atr'] = ta.ATR(dataframe['high'], dataframe['low'], dataframe['close'], timeperiod=14)
        
        # VWAP (Volume Weighted Average Price)
        dataframe['typical_price'] = (dataframe['high'] + dataframe['low'] + dataframe['close']) / 3
        dataframe['vwap'] = (dataframe['typical_price'] * dataframe['volume']).cumsum() / dataframe['volume'].cumsum()
        dataframe['vwap_up_close'] = dataframe['close'] < dataframe['vwap']
        dataframe['vwap_diff_pct'] = (dataframe['vwap'] - dataframe['close']) / dataframe['vwap'] * 100
        
        # OBV
        dataframe['obv'] = ta.OBV(dataframe['close'], dataframe['volume'])
        dataframe['obv_before'] = dataframe['obv'].shift(1)
        dataframe['obv_diff'] = dataframe['obv_before'] - dataframe['obv']
        dataframe['obv_growing'] = (dataframe['obv'] > dataframe['obv'].shift(1))
            
        runmode = self.config.get("runmode", "")
        
        dataframe['macdhist_rising'] = dataframe['macdhist'] > dataframe['macdhist'].shift(1)
        dataframe['macdhist_rising_confirm'] = dataframe['macdhist_rising'].rolling(
            macd_hist_rising_confirm
        ).sum() == macd_hist_rising_confirm

        dataframe['rsi_confirm'] = dataframe['rsi'] > rsi_entry_threshold

        
        rsi_mins = []
        price_mins = []
        rsi_mins_causal = []
        price_mins_causal = []
        
        rsi_maxs = []
        price_maxs = []
        rsi_maxs_causal = []
        price_maxs_causal = []
        dataframe['line_trend_bullish'] = np.nan
        dataframe['break_trend'] = False
        dataframe['break_trend_rsi'] = np.nan
        dataframe['price_maxs_backwards'] = np.nan
            
        # Si hay suficientes datos, calcular los extremos
        if len(dataframe) > window:
            
            # BEGIN - Detectar mínimos locales
            rsi_mins = argrelextrema(dataframe['rsi'].round(2).values, np.less_equal, order=window)[0]
            price_mins = argrelextrema(dataframe['close'].values, np.less_equal, order=window)[0]
        
            dataframe.loc[dataframe.index[rsi_mins], 'rsi_min_local'] = 1
            dataframe.loc[dataframe.index[price_mins], 'price_min_local'] = 1
            
            dataframe.loc[dataframe.index[rsi_mins], 'rsi_min'] = dataframe.loc[dataframe.index[rsi_mins], 'rsi']
            dataframe.loc[dataframe.index[price_mins], 'price_min'] = dataframe.loc[dataframe.index[rsi_mins], 'close']
            
            for i in range(window, len(dataframe)):
                if self.is_causal_minimum(dataframe['rsi'], i, window):
                    rsi_mins_causal.append(i)
                if self.is_causal_minimum(dataframe['close'], i, window):
                    price_mins_causal.append(i)
            
            # Marca visual en el dataframe (opcional)
            dataframe.loc[dataframe.index[rsi_mins_causal], 'rsi_min_causal'] = 1
            dataframe.loc[dataframe.index[price_mins_causal], 'price_min_causal'] = 1
            # END - Detectar mínimos locales
            
            
            # BEGIN - Detectar máximos locales
            rsi_maxs = argrelextrema(dataframe['rsi'].values, np.greater_equal, order=window)[0]
            price_maxs = argrelextrema(dataframe['close'].values, np.greater_equal, order=window)[0]
            
            dataframe['max_ohlc'] = dataframe[['open', 'close']].max(axis=1)
            price_maxs_trend = argrelextrema(dataframe['max_ohlc'].values, np.greater_equal, order=2)[0]
        
            dataframe.loc[dataframe.index[rsi_maxs], 'rsi_max_local'] = 1
            dataframe.loc[dataframe.index[price_maxs], 'price_max_local'] = 1
            
            dataframe.loc[dataframe.index[rsi_maxs], 'rsi_max'] = dataframe.loc[dataframe.index[rsi_maxs], 'rsi']
            dataframe.loc[dataframe.index[price_maxs], 'price_max'] = dataframe.loc[dataframe.index[price_maxs], 'close']
            
            # for i in range(window, len(dataframe)):
            #     if self.is_causal_maximum(dataframe['rsi'], i, window):
            #         rsi_maxs_causal.append(i)
            #     if self.is_causal_maximum(dataframe['close'], i, window):
            #         price_maxs_causal.append(i)
            
            # Marca visual en el dataframe (opcional)
            dataframe.loc[dataframe.index[rsi_maxs_causal], 'rsi_max_causal'] = 1
            dataframe.loc[dataframe.index[price_maxs_causal], 'price_max_causal'] = 1
            # END - Detectar máximos locales
            
            
            # ----------------------------
            # 🟢 Divergencias Alcistas
            bullish_divs = []
            bullish_divs_causal = []
            bullish_divs_hidden = []
            bullish_divs_causal_hidden = []
            dataframe['divergencia_rsi_alcista_rsi'] = np.nan
            dataframe['divergencia_rsi_alcista_precio'] = np.nan
            
            dataframe['divergencia_rsi_bajista_rsi'] = np.nan
            dataframe['divergencia_rsi_bajista_precio'] = np.nan
            
            dataframe['divergencia_rsi_alcista_oculta_rsi'] = np.nan
            dataframe['divergencia_rsi_alcista_oculta_precio'] = np.nan
            
            dataframe['divergencia_rsi_bajista_oculta_rsi'] = np.nan
            dataframe['divergencia_rsi_bajista_oculta_precio'] = np.nan
            
            dataframe['rsi_divergence_first'] = np.nan
            dataframe['rsi_divergence_second'] = np.nan
            
            # Para cada i2 causal, busca el i1 más cercano en rsi_mins (pasado confirmado)
            for i2 in rsi_mins_causal:
                # Buscar el último i1 en rsi_mins que sea anterior a i2
                i1_candidates = [i for i in rsi_mins if i < i2 - divergence_window]
                if not i1_candidates:
                    continue
                i1 = i1_candidates[-1]
                
                if i1 + window + 1 >= i2:
                    continue
            
                # Validación de divergencia
                rsi1, rsi2 = dataframe.loc[dataframe.index[[i1, i2]], 'rsi']
                price1, price2 = dataframe.loc[dataframe.index[[i1, i2]], 'close']
                macd1, macd2 = dataframe.loc[dataframe.index[[i1, i2]], 'macd']
                date1, date2 = dataframe.loc[dataframe.index[[i1, i2]], 'date']
                date1_datetime = date1
                date2_datetime = date2
                if isinstance(date1_datetime, str):
                    date1_datetime = pd.to_datetime(date1_datetime)
                if isinstance(date2_datetime, str):
                    date2_datetime = pd.to_datetime(date2_datetime)
                diff_minutes = (date2_datetime - date1_datetime).total_seconds() / 60
                
                dataframe.loc[dataframe.index[i2], 'rsi_div_diff'] = rsi2 - rsi1
                dataframe.loc[dataframe.index[i2], 'price_div_diff'] = price1 - price2
            
                price_below_boll_lb = dataframe.loc[dataframe.index[i2], 'price_below_boll_lb']
            
                if rsi2 < rsi1 and price2 > price1:  
                    bullish_divs_causal_hidden.append(i2)
            
                if rsi2 > rsi1 and price2 < price1: 
                    price_prob = 1
                    rsi_prob = 1
                    bullish_divs_causal.append(i2)
                    
            dataframe.loc[dataframe.index[bullish_divs_causal], 'bullish_divergence_causal'] = 1
            dataframe.loc[dataframe.index[bullish_divs_causal_hidden], 'bullish_divergence_causal_hidden'] = 1
            
            for i in range(1, len(rsi_mins)):
                i1 = rsi_mins[i - 1]
                i2 = rsi_mins[i]
                if i1 in price_mins and i2 in price_mins:
                    
                    # Evita usar divergencias que terminen fuera de la penúltima vela (solo en dry_run y live)
                    # if runmode in ("dry_run", "live") and i2 >= len(dataframe) - 2:
                    #     continue
                    
                    rsi1, rsi2 = dataframe.loc[dataframe.index[[i1, i2]], 'rsi']
                    price1, price2 = dataframe.loc[dataframe.index[[i1, i2]], 'close']
                    date1, date2 = dataframe.loc[dataframe.index[[i1, i2]], 'date']
                    macd1, macd2 = dataframe.loc[dataframe.index[[i1, i2]], 'macd']
                    
                    date1_datetime = date1
                    date2_datetime = date2
                    if isinstance(date1_datetime, str):
                        date1_datetime = pd.to_datetime(date1_datetime)
                    if isinstance(date2_datetime, str):
                        date2_datetime = pd.to_datetime(date2_datetime)
                    diff_minutes = (date2_datetime - date1_datetime).total_seconds() / 60
                    
                    if rsi2 < rsi1 and price2 > price1:
                        bullish_divs_hidden.append(i2)
                        
                        # y = mx + b -> y = pendiente * x + ordenada
                        pendiente_rsi = (rsi1 - rsi2) / (i1 - i2)
                        # b = y - mx -> ordenada = y - pendiente * x
                        ordenada_rsi = rsi2 - pendiente_rsi * i2
                        
                        pendiente_precio = (price1 - price2) / (i1 - i2)
                        ordenada_precio = price2 - pendiente_precio * i2
                        
                        for idx in range(i1, i2 + 1):
                            # y = mx + b -> y = pendiente * x + ordenada
                            y_rsi = pendiente_rsi * idx + ordenada_rsi
                            dataframe.loc[dataframe.index[idx], 'divergencia_rsi_alcista_oculta_rsi'] = y_rsi
                            y_precio = pendiente_precio * idx + ordenada_precio
                            dataframe.loc[dataframe.index[idx], 'divergencia_rsi_alcista_oculta_precio'] = y_precio
                        
                    if rsi2 > rsi1 and price2 < price1:

                        # Guardar info solo si hay divergencia
                        dataframe.loc[dataframe.index[i1], 'rsi_div1'] = rsi1
                        dataframe.loc[dataframe.index[i2], 'rsi_div2'] = rsi2
                        dataframe.loc[dataframe.index[i2], 'price_div1'] = price1
                        dataframe.loc[dataframe.index[i2], 'price_div2'] = price2
                        dataframe.loc[dataframe.index[i2], 'date_div1'] = date1
                        dataframe.loc[dataframe.index[i2], 'date_div2'] = date2
                        dataframe.loc[dataframe.index[i2], 'diff_div'] = diff_minutes
                        dataframe.loc[dataframe.index[i2], 'rsi_div_diff'] = rsi2 - rsi1
                        dataframe.loc[dataframe.index[i2], 'price_div_diff'] = price1 - price2
                        dataframe.loc[dataframe.index[i2], 'divergence_strength'] = (rsi2 - rsi1) / max(rsi1, 1)
                        dataframe.loc[dataframe.index[i2], 'divergence_strength_price'] = (price1 - price2) / max(price1, 1)
                        dataframe.loc[dataframe.index[i2], 'divergence_window'] = window
                        
                        # y = mx + b -> y = pendiente * x + ordenada
                        pendiente_rsi = (rsi1 - rsi2) / (i1 - i2)
                        # b = y - mx -> ordenada = y - pendiente * x
                        ordenada_rsi = rsi2 - pendiente_rsi * i2
                        
                        pendiente_precio = (price1 - price2) / (i1 - i2)
                        ordenada_precio = price2 - pendiente_precio * i2
                        
                        for idx in range(i1, i2 + 1):
                            # y = mx + b -> y = pendiente * x + ordenada
                            y_rsi = pendiente_rsi * idx + ordenada_rsi
                            dataframe.loc[dataframe.index[idx], 'divergencia_rsi_alcista_rsi'] = y_rsi
                            y_precio = pendiente_precio * idx + ordenada_precio
                            dataframe.loc[dataframe.index[idx], 'divergencia_rsi_alcista_precio'] = y_precio
                            
                        # maximos_en_rango = [idx for idx in price_maxs if i1 <= idx <= i2]
                        # if not maximos_en_rango:
                        #     maximos_en_rango = [max(range(i1, i2+1), key=lambda idx: dataframe.loc[dataframe.index[idx], 'close'])]
                            
                        maximos_en_rango = []
                        max_busqueda = 200
                        count = 0
                        ultimo_max = None
                        
                        # Indices de price_maxs que están en el rango ampliado
                        price_maxs_backwards = sorted(
                            [idx for idx in price_maxs_trend if idx <= i2 and idx >= max(i1 - max_busqueda, 0)],
                            reverse=True
                        )
                        
                        for idx in price_maxs_backwards:
                            precio_actual = dataframe.loc[dataframe.index[idx], 'max_ohlc']
                            if count <= 4:
                                # Siempre añade los dos primeros puntos
                                maximos_en_rango.append(idx)
                                ultimo_max = precio_actual
                            else:
                                if precio_actual >= ultimo_max:
                                    maximos_en_rango.append(idx)
                                    ultimo_max = precio_actual
                                else:
                                    break  # Corta solo después del segundo punto
                            count += 1
                        
                        maximos_en_rango = sorted(maximos_en_rango)
                        
                        for idx in maximos_en_rango:
                            dataframe.loc[dataframe.index[idx], 'price_maxs_backwards'] = dataframe.loc[dataframe.index[idx], 'max_ohlc']
                            
                        x_max = np.array(maximos_en_rango)
                        y_max = np.array([dataframe.loc[dataframe.index[idx], 'close'] for idx in maximos_en_rango])

                        # Inicializa los pesos a 1
                        pesos = np.ones_like(x_max, dtype=float)
                        if len(pesos) > 2:
                            pesos[0] = 4   # Doble peso al primer punto
                            pesos[-1] = 4  # Doble peso al último punto
                        
                        if len(x_max) > 1:
                            pendiente_superior, ordenada_superior = np.polyfit(x_max, y_max, 1, w=pesos)
                        else:
                            pendiente_superior = 0
                            ordenada_superior = y_max[0]
                            
                        if pendiente_superior < 0:
                            

                            # BEGIN - Subimos la ordenada para que pase siempre por el máximo que hay en la divergencia
                            # Busca el primer índice de máximo dentro de i1 a i2
                            primer_maximo_idx = next((idx for idx in range(i2, i1 - 1, -1) if idx in price_maxs_trend), None)

                            ultimo_idx_maximos = maximos_en_rango[0]
                            
                            if primer_maximo_idx is not None:
                                precio_maximo = dataframe.loc[dataframe.index[primer_maximo_idx], 'max_ohlc']
                                valor_linea_en_max = pendiente_superior * primer_maximo_idx + ordenada_superior
                                desplazamiento = precio_maximo - valor_linea_en_max
                                ordenada_superior_ajustada = ordenada_superior + desplazamiento
                            else:
                                # Si no hay máximo en el rango, puedes dejar la ordenada igual o manejarlo como prefieras
                                ordenada_superior_ajustada = ordenada_superior

                            # END - Subimos la ordenada para que pase siempre por el máximo que hay en la divergencia
                            
                            start_idx = max(i1 - 60, 0)
                            end_idx = min(i2 + 200, len(dataframe) - 1)
                            for idx in range(start_idx, end_idx + 1):
                                if idx < i1 and not np.isnan(dataframe.loc[dataframe.index[idx], 'divergencia_rsi_alcista_precio']):
                                    break
                                y_superior = pendiente_superior * idx + ordenada_superior_ajustada
                                
                                if np.isnan(dataframe.loc[dataframe.index[idx], 'line_trend_bullish']):
                                    if idx >= ultimo_idx_maximos:
                                        dataframe.loc[dataframe.index[idx], 'line_trend_bullish'] = y_superior
                                else:
                                    break
                                
                                if idx > i2 and ((dataframe.loc[dataframe.index[idx], 'close'] > y_superior and dataframe.loc[dataframe.index[idx], 'open'] > y_superior) or dataframe.loc[dataframe.index[idx], 'open'] > y_superior):
                                    dataframe.loc[dataframe.index[idx], 'break_trend'] = True
                                    dataframe.loc[dataframe.index[idx], 'break_trend_rsi'] = dataframe.loc[dataframe.index[idx], 'rsi']
                                    dataframe.loc[dataframe.index[idx], 'rsi_divergence_first'] = rsi1
                                    dataframe.loc[dataframe.index[idx], 'rsi_divergence_second'] = rsi2
                                    break

                        
                        bullish_divs.append(i2)
                        
                        now = datetime.now(timezone.utc)
                        if isinstance(date2, str):
                            date2 = pd.to_datetime(date2)
                    
                        #logger.info(f"date2 type: {type(date2)}, value: {date2}, tzinfo: {getattr(date2, 'tzinfo', None)} runmode: {runmode}")
                        #logger.info(f"Evaluando log: now={now}, date2={date2}, diferencia={(now - date2)}, runmode={runmode}")
                        if (now - date2 < timedelta(minutes=30)) and runmode in ("dry_run", "live"):
                            logger.info(f"--→[{metadata['pair']}][{now}][{i2}] Divergencia alcista detectada - "
                                f"[{date1} → {date2}] diff: {diff_minutes} "
                                f"RSI: {rsi1:.3f} → {rsi2:.3f}, "
                                f"Precio: {price1:.3f} → {price2:.3f}, "
                                f"Window: {window}  diff: {diff_minutes}")
            dataframe.loc[dataframe.index[bullish_divs], 'bullish_divergence'] = 1
            dataframe.loc[dataframe.index[bullish_divs_hidden], 'bullish_divergence_hidden'] = 1
        
            # print(f"---- price_mins_causal: {price_mins_causal}")
            print(f"---- rsi_mins_causal({len(rsi_mins_causal)}):")
            print(f"---- rsi_mins({len(rsi_mins)}): {rsi_mins}")
        
            # 🔴 Divergencias Bajistas
            bearish_divs = []
            bearish_divs_causal = []
            bearish_divs_hidden = []
            bearish_divs_causal_hidden = []
            
            for i2 in rsi_maxs_causal:
                # Buscar el último i1 en rsi_mins que sea anterior a i2
                i1_candidates = [i for i in rsi_maxs if i < i2 - divergence_window]
                if not i1_candidates:
                    continue
                i1 = i1_candidates[-1]
                
                if i1 + window + 1 >= i2:
                    continue
                
                print(f"i1: {i1} i2: {i2}")
            
                # Validación de divergencia
                rsi1, rsi2 = dataframe.loc[dataframe.index[[i1, i2]], 'rsi']
                price1, price2 = dataframe.loc[dataframe.index[[i1, i2]], 'close']
                macd1, macd2 = dataframe.loc[dataframe.index[[i1, i2]], 'macd']
                date1, date2 = dataframe.loc[dataframe.index[[i1, i2]], 'date']
                date1_datetime = date1
                date2_datetime = date2
                if isinstance(date1_datetime, str):
                    date1_datetime = pd.to_datetime(date1_datetime)
                if isinstance(date2_datetime, str):
                    date2_datetime = pd.to_datetime(date2_datetime)
                diff_minutes = (date2_datetime - date1_datetime).total_seconds() / 60
                
                dataframe.loc[dataframe.index[i2], 'rsi_div_bear_diff'] = rsi1 - rsi2
                dataframe.loc[dataframe.index[i2], 'price_div_bear_diff'] = price2 - price1
            
                if rsi2 < rsi1 and price2 > price1:  
                    bearish_divs_causal.append(i2)
                if rsi2 > rsi1 and price2 < price1:
                    bearish_divs_causal_hidden.append(i2)
                
                    
            dataframe.loc[dataframe.index[bearish_divs_causal], 'bearish_divergence_causal'] = 1
            dataframe.loc[dataframe.index[bearish_divs_causal_hidden], 'bearish_divergence_causal_hidden'] = 1
            
            for i in range(1, len(rsi_maxs)):
                i1 = rsi_maxs[i - 1]
                i2 = rsi_maxs[i]
                if i1 in price_maxs and i2 in price_maxs:
                    
                    rsi1, rsi2 = dataframe.loc[dataframe.index[[i1, i2]], 'rsi']
                    price1, price2 = dataframe.loc[dataframe.index[[i1, i2]], 'close']
                    date1, date2 = dataframe.loc[dataframe.index[[i1, i2]], 'date']
                    macd1, macd2 = dataframe.loc[dataframe.index[[i1, i2]], 'macd']
                    
                    date1_datetime = date1
                    date2_datetime = date2
                    if isinstance(date1_datetime, str):
                        date1_datetime = pd.to_datetime(date1_datetime)
                    if isinstance(date2_datetime, str):
                        date2_datetime = pd.to_datetime(date2_datetime)
                    diff_minutes = (date2_datetime - date1_datetime).total_seconds() / 60
                        
                    if rsi2 < rsi1 and price2 > price1:
                        bearish_divs.append(i2)
                        
                        # y = mx + b -> y = pendiente * x + ordenada
                        pendiente_rsi = (rsi1 - rsi2) / (i1 - i2)
                        # b = y - mx -> ordenada = y - pendiente * x
                        ordenada_rsi = rsi2 - pendiente_rsi * i2
                        
                        pendiente_precio = (price1 - price2) / (i1 - i2)
                        ordenada_precio = price2 - pendiente_precio * i2
                        
                        for idx in range(i1, i2 + 1):
                            # y = mx + b -> y = pendiente * x + ordenada
                            y_rsi = pendiente_rsi * idx + ordenada_rsi
                            dataframe.loc[dataframe.index[idx], 'divergencia_rsi_bajista_rsi'] = y_rsi
                            y_precio = pendiente_precio * idx + ordenada_precio
                            dataframe.loc[dataframe.index[idx], 'divergencia_rsi_bajista_precio'] = y_precio
                    if rsi2 > rsi1 and price2 < price1:
                        bearish_divs_hidden.append(i2)
                        
                        # y = mx + b -> y = pendiente * x + ordenada
                        pendiente_rsi = (rsi1 - rsi2) / (i1 - i2)
                        # b = y - mx -> ordenada = y - pendiente * x
                        ordenada_rsi = rsi2 - pendiente_rsi * i2
                        
                        pendiente_precio = (price1 - price2) / (i1 - i2)
                        ordenada_precio = price2 - pendiente_precio * i2
                        
                        for idx in range(i1, i2 + 1):
                            # y = mx + b -> y = pendiente * x + ordenada
                            y_rsi = pendiente_rsi * idx + ordenada_rsi
                            dataframe.loc[dataframe.index[idx], 'divergencia_rsi_bajista_oculta_rsi'] = y_rsi
                            y_precio = pendiente_precio * idx + ordenada_precio
                            dataframe.loc[dataframe.index[idx], 'divergencia_rsi_bajista_oculta_precio'] = y_precio
                        
                        now = datetime.now(timezone.utc)
                        if isinstance(date2, str):
                            date2 = pd.to_datetime(date2)
                        if (now - date2 < timedelta(minutes=30)) and runmode in ("dry_run", "live"):
                            logger.info(f"--→[{metadata['pair']}][{now}][{i2}] Divergencia bajista detectada - "
                                f"[{date1} → {date2}] diff: {diff_minutes} "
                                f"RSI: {rsi1:.3f} → {rsi2:.3f}, "
                                f"Precio: {price1:.3f} → {price2:.3f}, "
                                f"Window: {window}  diff: {diff_minutes}")
            dataframe.loc[dataframe.index[bearish_divs], 'bearish_divergence'] = 1
            dataframe.loc[dataframe.index[bearish_divs_hidden], 'bearish_divergence_hidden'] = 1


        if not self.is_hyperopt():
            print(f"Divergencias alcistas detectadas: {dataframe['bullish_divergence'].sum()}")
            print(f"Divergencias bajistas detectadas: {dataframe['bearish_divergence'].sum()}")
            print(f"Divergencias alcistas ocultas detectadas: {dataframe['bullish_divergence_hidden'].sum()}")
            print(f"Divergencias bajistas ocultas detectadas: {dataframe['bearish_divergence_hidden'].sum()}")
            print(f"----------------------------------------------------------------------------------------")
            print(f"Divergencias alcistas causales detectadas: {dataframe['bullish_divergence_causal'].sum()}")
            print(f"Divergencias bajistas causales detectadas: {dataframe['bearish_divergence_causal'].sum()}")
            print(f"Divergencias alcistas causales ocultas detectadas: {dataframe['bullish_divergence_causal_hidden'].sum()}")
            print(f"Divergencias bajistas causales ocultas detectadas: {dataframe['bearish_divergence_causal_hidden'].sum()}")
        # END - Detectar divergencias (usamos una ventana móvil de N barras para identificar picos/valles)
        
        # Cálculo del ATR para custom_stoploss
        dataframe['atr'] = ta.ATR(dataframe['high'], dataframe['low'], dataframe['close'], timeperiod=14)
        dataframe['atr_pct'] = dataframe['atr'] / dataframe['close'] * 100
        dataframe['volume_threshold'] = dataframe['volume'].rolling(window=50).mean() * 1.5
        dataframe['volume_ratio'] = dataframe['volume'] / dataframe['volume_threshold']
        
        # Calcula las pendientes (velocidad de cambio) de las medias móviles
        dataframe['ma_7_slope'] = dataframe['ma_7'].diff()
        dataframe['ma_25_slope'] = dataframe['ma_25'].diff()
        
        # Calcula el tiempo estimado para que se crucen
        dataframe['ma_diff'] = dataframe['ma_7'] - dataframe['ma_25']
        dataframe['ma_diff_next'] = dataframe['ma_diff'] + (dataframe['ma_7_slope'] - dataframe['ma_25_slope'])
        
        # Verifica si se tocarán en la próxima iteración
        dataframe['will_touch'] = (dataframe['ma_diff'] > 0) & (dataframe['ma_diff_next'] <= 0)
        dataframe['mid_price'] = (dataframe['close'] + dataframe['open']) / 2
        
        dataframe['price_cross_ma14'] = (
            (dataframe['mid_price'] > dataframe['ma_7']) & (dataframe['mid_price'].shift(1) <= dataframe['ma_7'].shift(1))
        )
        
        dataframe['bullish_divergence_recent'] = dataframe['bullish_divergence_causal'].shift(divergence_window).rolling(window=18, min_periods=1).max() == 1
        #dataframe['bullish_divergence_recent'] = dataframe['bullish_divergence'].shift(divergence_window).rolling(window=18, min_periods=1).max() == 1
        
        # BEGIN - Calculamos la línea de tendencia inmediata
        dataframe['cond_divup'] = ((dataframe['break_trend'] & ((dataframe['rsi_divergence_first'] <= 30) | (dataframe['rsi_divergence_second'] <= 30))))
        dataframe['cond_divup_strong'] = ((dataframe['break_trend'] & ((dataframe['rsi_divergence_first'] <= 30) & (dataframe['rsi_divergence_second'] <= 30))))
        dataframe['cond_bulldiv'] = (dataframe['bullish_divergence_causal'] == 1) 
        dataframe['cond_bulldiv_hidden'] = (dataframe['bullish_divergence_causal_hidden'] == 1) 
            
        # 1. Martillo (Hammer)
        dataframe['body'] = abs(dataframe['close'] - dataframe['open'])
        dataframe['upper_wick'] = dataframe['high'] - dataframe[['close', 'open']].max(axis=1)
        dataframe['lower_wick'] = dataframe[['close', 'open']].min(axis=1) - dataframe['low']
        dataframe['range'] = dataframe['high'] - dataframe['low']
        
        dataframe['is_hammer'] = (
            (dataframe['range'] > 0) &
            (dataframe['body'] / dataframe['range'] < 0.4) &
            (dataframe['lower_wick'] > 1.5 * dataframe['body']) &
            (dataframe['upper_wick'] < 1.0 * dataframe['body'])
        )
        
        # 2. Martillo Invertido
        dataframe['is_inverted_hammer'] = (
            (dataframe['range'] > 0) &
            (dataframe['body'] / dataframe['range'] < 0.4) &
            (dataframe['upper_wick'] > 1.5 * dataframe['body']) &
            (dataframe['lower_wick'] < 1.0 * dataframe['body'])
        )
        
        # 3. Bullish Engulfing
        dataframe['bullish_engulfing'] = (
            (dataframe['close'].shift(1) < dataframe['open'].shift(1)) &  # vela roja anterior
            (dataframe['close'] > dataframe['open']) &                    # vela verde actual
            (dataframe['open'] < dataframe['close'].shift(1)) &
            (dataframe['close'] > dataframe['open'].shift(1))
        )
        
        # 4. Piercing Pattern
        prev_body = abs(dataframe['close'].shift(1) - dataframe['open'].shift(1))
        dataframe['piercing_pattern'] = (
            (dataframe['close'].shift(1) < dataframe['open'].shift(1)) &  # vela roja anterior
            (dataframe['open'] < dataframe['low'].shift(1)) &             # abre por debajo del mínimo anterior
            (dataframe['close'] > (dataframe['open'].shift(1) - prev_body / 2)) &  # cierra por encima del 50%
            (dataframe['close'] < dataframe['open'].shift(1))             # pero no llega a engulfing total
        )
        
        # 5. Morning Star (3 velas)
        dataframe['morning_star'] = (
            (dataframe['close'].shift(2) < dataframe['open'].shift(2)) &  # vela 1 roja
            (abs(dataframe['close'].shift(1) - dataframe['open'].shift(1)) / 
             (dataframe['high'].shift(1) - dataframe['low'].shift(1) + 1e-9) < 0.3) &  # vela 2 pequeña
            (dataframe['close'] > dataframe['open']) &  # vela 3 verde
            (dataframe['close'] > (dataframe['close'].shift(2) + dataframe['open'].shift(2)) / 2)
        )
        
        # 6. Three White Soldiers
        dataframe['three_white_soldiers'] = (
            (dataframe['close'] > dataframe['open']) &
            (dataframe['close'].shift(1) > dataframe['open'].shift(1)) &
            (dataframe['close'].shift(2) > dataframe['open'].shift(2)) &
            (dataframe['close'] > dataframe['close'].shift(1)) &
            (dataframe['close'].shift(1) > dataframe['close'].shift(2))
        )
        
        # 7. Doji o Dragonfly Doji (cuerpo muy pequeño)
        dataframe['doji'] = (
            (dataframe['range'] > 0) &
            (abs(dataframe['close'] - dataframe['open']) / dataframe['range'] < 0.1)
        )



        dataframe['cond_bulldiv_5m'] = (dataframe['bullish_divergence_5m'] == 1) 
        dataframe['cond_bulldiv_hidden_5m'] = (dataframe['bullish_divergence_hidden_5m'] == 1) 
        
        div_window = 5  # margen de velas en 1m donde se acepta divergencia 5m
        
        dataframe['recent_5m_divergence'] = (
            dataframe['cond_bulldiv_5m'].rolling(window=div_window).max().fillna(0).astype(bool) |
            dataframe['cond_bulldiv_hidden_5m'].rolling(window=div_window).max().fillna(0).astype(bool)
        )
        
            
        bullish_cond = (
            (((dataframe['cond_bulldiv'] | dataframe['cond_bulldiv_hidden']) & 
            (dataframe['is_hammer'] | dataframe['bullish_engulfing'])) | 
            ((dataframe['cond_bulldiv'].shift(1) | dataframe['cond_bulldiv_hidden'].shift(1)) & 
            (dataframe['is_hammer'] | dataframe['bullish_engulfing']))) & 
            # (dataframe['cond_bulldiv'] | dataframe['cond_bulldiv_hidden']) & 
            # (dataframe['lower_wick'] > 2 * dataframe['body']) & 
            (dataframe['macdhist'] > dataframe['macdhist'].shift(1)) & 
            (dataframe['macd'] < 0) & (dataframe['macdsignal'] < 0) & 
            (dataframe['macdsignal'].shift(8) < 0) & 
            (dataframe['volume'] > 800) &
            (dataframe['volume_ratio'] > volume_ratio_threshold_2)
            & dataframe['macdhist_rising_confirm']
            & dataframe['rsi_confirm']
        )
        
        dataframe['bullish_cond'] = bullish_cond
        
        # Suma de bullish_cond en las 6 velas anteriores (excluyendo la actual)
        dataframe['bullish_cond_recent'] = dataframe['bullish_cond'].shift(1).rolling(window=divergence_window).sum()
        
        # Nueva condición: hubo al menos 1 bullish_cond en las últimas 6 velas
        dataframe['bullish_in_last'] = dataframe['bullish_cond_recent'] >= 1

        dataframe['price_ema_cross_up'] = (
            (dataframe['close'].shift(1) < dataframe['ema_fast'].shift(1)) &
            (dataframe['close'] > dataframe['ema_fast'])
        )
        dataframe['vol_up'] = dataframe['volume'] > dataframe['volume'].rolling(3).mean()
        dataframe['macd_cross_up'] = (
            (dataframe['macd'].shift(1) < dataframe['macdsignal'].shift(1)) &
            (dataframe['macd'] > dataframe['macdsignal'])
        )
        dataframe['break_struct'] = dataframe['close'] > dataframe['mml_+2_8']
        dataframe['ha_bullish'] = (dataframe['ha_color'] == 1) & (dataframe['ha_color'].shift(1) == 0)


        dataframe['price_ema_confirmed'] = (
            dataframe['bullish_in_last'] &
            dataframe['price_ema_cross_up']
        )
        
        return dataframe

    def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        
        pair = metadata.get("pair", "UNKNOWN_PAIR")
        required_columns = [
            'rsi', 'close', 'open', 'volume'
        ]
        for col in required_columns:
            if col not in dataframe.columns:
                # O puedes usar logger.warning si prefieres no imprimir
                if not self.is_hyperopt():
                    print(f"[{metadata.get('pair')}] Ignorado por falta de columna: {col}")
                    logger.info(f"[{metadata.get('pair')}] Ignorado por falta de columna: {col}")
                return dataframe  # Devuelve sin hacer nada
        
        params = self.get_pair_params(pair)
        if not self.is_hyperopt() and not params: 
            print(f"populate_buy_trend({pair}) params optimized not found")
            return dataframe
        
        volume_ratio_threshold_2 = self.volume_ratio_threshold_2.value
        atr_multiplier_stoploss = self.atr_multiplier_stoploss.value
        atr_multiplier_roi = self.atr_multiplier_roi.value

        # Si existen valores optimizados para este par, los usamos
        if params and not self.is_hyperopt():
            buy_params = params.get('params', {}).get('buy', {})
            sell_params = params.get('params', {}).get('sell', {})
            
            volume_ratio_threshold_2 = buy_params.get('volume_ratio_threshold_2', 0.2)
            atr_multiplier_stoploss = sell_params.get('atr_multiplier_stoploss', 0.8)
            atr_multiplier_roi = sell_params.get('atr_multiplier_roi', 2.0)
            
            rsi_entry_threshold = buy_params.get('rsi_entry_threshold', 50)
            macd_hist_rising_confirm = buy_params.get('macd_hist_rising_confirm', 2)
            bullish_candle_confirm_window = buy_params.get('bullish_candle_confirm_window', 3)
            divergence_strength_threshold = buy_params.get('divergence_strength_threshold', 0.1)
            divergence_confirmation_volume_ratio = buy_params.get('divergence_confirmation_volume_ratio', 1.2)
            sell_on_rsi_drop_threshold = sell_params.get('sell_on_rsi_drop_threshold', 50)
            take_profit_multiplier = sell_params.get('take_profit_multiplier', 2.0)
        
        # Condiciones de compra
        dataframe.loc[dataframe['bullish_cond'], 'buy'] = 1
        dataframe.loc[dataframe['bullish_cond'], 'enter_long'] = 1
        dataframe.loc[dataframe['cond_divup'], 'enter_tag'] = 'divup'
        dataframe.loc[dataframe['cond_divup_strong'], 'enter_tag'] = 'divup_strong'
        dataframe.loc[dataframe['cond_bulldiv'], 'enter_tag'] = 'bulldiv'
        dataframe.loc[dataframe['cond_bulldiv_hidden'], 'enter_tag'] = 'bulldiv_hidden'
        
        
        runmode = self.config.get("runmode", "unknown")
        now = datetime.now(timezone.utc)
        
        # Filtrar las filas que cumplen la condición
        bullish_rows = dataframe[dataframe['bullish_cond']]
        
        # Loguear cada fila que cumple la condición
        for idx, row in bullish_rows.iterrows():
            
            date_bullish = row.get('date', 'N/A')
            if isinstance(date_bullish, str):
                date_bullish = pd.to_datetime(date_bullish)
            if isinstance(now, str):
                now = pd.to_datetime(now)
        
            logger.info(
                f"-- [{pair}] BULLISH ENTRY [{idx}] - "
                f"rsi: {row['rsi']:.2f}, "
                f"change_pct: {row['change_pct']:.2f}, "
                f"volume: {row['volume']:.2f}, "
                f"volume_ratio: {row['volume_ratio']:.2f}, "
                f"bullish_divergence: {row['bullish_divergence']}, "
                f"price: {row.get('close', 'N/A')}, "
                f"time: {row.get('date', 'N/A')} "
                f"diff: {row.get('diff_div', 'N/A')}"
            )
        
        
        # Verifica que el índice sea de tipo `DatetimeIndex`
        if not isinstance(dataframe.index, pd.DatetimeIndex):
            dataframe.set_index(pd.to_datetime(dataframe['date']), inplace=True)
        
        # Almacena más detalles de la operación en custom_trade_info
        if not hasattr(self, 'custom_trade_info'):
            self.custom_trade_info = {}
    
        for index, row in dataframe[dataframe['buy'] == 1].iterrows():
            trade_time = index.to_pydatetime()
            
            # row.to_dict() te devuelve {columna: valor, ...}
            row_dict = row.to_dict()
        
            # Si quieres sustituir los NaN por 'No Data':
            row_dict = {col: (val if pd.notna(val) else 'No Data')
                        for col, val in row_dict.items()}
            
            for offset in [0, 1]:  # 0 minutos y 1 minuto
                time_key = trade_time + pd.Timedelta(minutes=offset)
                
                self.custom_trade_info[time_key] = row_dict.copy()
                #print(f"Almacenado datos para {time_key}: {self.custom_trade_info[time_key]}")
        
        return dataframe

    def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        
        params = self.get_pair_params(pair)

        # Si existen valores optimizados para este par, los usamos
        if params and not self.is_hyperopt():
            sell_params = params.get('params', {}).get('sell', {})
            atr_multiplier_stoploss = sell_params.get('atr_multiplier_stoploss', 0.8)
            
            sell_on_rsi_drop_threshold = sell_params.get('sell_on_rsi_drop_threshold', 50)
            take_profit_multiplier = sell_params.get('take_profit_multiplier', 2.0)
        
        dataframe['take_profit_price'] = dataframe['open'] + dataframe['atr'] * take_profit_multiplier

        print(f"take_profit_multiplier: {take_profit_multiplier}")

        dataframe.loc[
            (
                (dataframe['close'] > dataframe['take_profit_price']) |
                (dataframe['rsi'] < sell_on_rsi_drop_threshold)
            ),
            'sell'
        ] = 1
        # dataframe.loc[
        #     (
        #         # Detectamos cuando el RSI cae por debajo de 70 después de estar por encima
        #         (dataframe['rsi'].shift(1) > dataframe['rsi_sobrecompra']) & 
        #         (dataframe['rsi'] < dataframe['rsi_sobrecompra'])
        #     ) | 
        #     (dataframe['bearish_divergence'] == 1)
        #     , 'sell'] = 1
        dataframe.loc[(dataframe['bearish_divergence'] == 1), 'exit_tag'] = 'bearish_div'
        dataframe.loc[(dataframe['rsi'].shift(1) > dataframe['rsi_sobrecompra']) & 
                (dataframe['rsi'] < dataframe['rsi_sobrecompra']), 'exit_tag'] = 'overbought_drop'
        return dataframe
        
    def custom_stoploss(self, pair: str, trade, current_time: datetime, current_rate: float,
                        current_profit: float, **kwargs) -> float:
        """
        Stoploss dinámico basado en ATR, limitado a un máximo del 3% de pérdida.
        """
        try:
            dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
            if dataframe is None or dataframe.empty or 'atr' not in dataframe.columns:
                logger.warning(f"[{pair}] No se pudo aplicar custom_stoploss: sin ATR.")
                return 1
    
            params = self.get_pair_params(pair)
            atr_multiplier_stoploss = self.atr_multiplier_stoploss.value

            # Si existen valores optimizados para este par, los usamos
            if params and not self.is_hyperopt():
                sell_params = params.get('params', {}).get('sell', {})
                atr_multiplier_stoploss = sell_params.get('atr_multiplier_stoploss', 0.8)
                
                sell_on_rsi_drop_threshold = sell_params.get('sell_on_rsi_drop_threshold', 50)
                take_profit_multiplier = sell_params.get('take_profit_multiplier', 2.0)
    
            trade_open_time = trade.open_date_utc.replace(second=0, microsecond=0)
            # Asegura que el índice esté ordenado
            dataframe = dataframe.sort_index()
            
            # Usa asof() para encontrar el índice más cercano anterior o igual
            trade_open_time = dataframe.index.asof(trade_open_time)
            
            if pd.isna(trade_open_time):
                logger.warning(f"[{pair}] No se encontró un índice válido para {trade_open_time}.")
                return 1
    
            entry_price = trade.open_rate
            atr_at_entry = dataframe.loc[trade_open_time, 'atr']
            atr_multiplier = atr_multiplier_stoploss
    
            # Calculamos el SL por ATR
            atr_stoploss_price = entry_price - (atr_at_entry * atr_multiplier)
    
            # Calculamos el SL máximo permitido (1.5%)
            max_stoploss_price = entry_price * 0.995  # 1.5% por debajo del entry
    
            # Tomamos el más conservador (más alto)
            stoploss_price = max(atr_stoploss_price, max_stoploss_price)
    
            if current_rate <= stoploss_price:
                return 0.01  # Ejecutar venta inmediata
            return 1  # Mantener
    
        except Exception as e:
            logger.error(f"[{pair}] Error en custom_stoploss: {e}")
            return 1
        
    def custom_exit(self, pair: str, trade, current_time: datetime, current_rate: float,
                current_profit: float, **kwargs) -> bool:
        """
        ROI dinámico personalizado por par, basado en ATR y archivo optimizado.
        """
        try:
            dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
            if dataframe is None or dataframe.empty or 'atr' not in dataframe.columns:
                return False
    
            params = self.get_pair_params(pair)
            roi_dict = {"0": 0.11}  # ROI por defecto
    
            if params and not self.is_hyperopt():
                roi = params.get('params', {}).get('roi', {})
                if isinstance(roi, dict):
                    roi_dict = roi
    
            # Calcular cuántos minutos lleva abierta la operación
            delta_min = int((current_time - trade.open_date_utc).total_seconds() // 60)
    
            # Cerrar si lleva más de 60 minutos
            if delta_min >= 60:
                return True
    
            # Encontrar el ROI más apropiado según el tiempo transcurrido
            selected_roi = 0.0
            for t in sorted(map(int, roi_dict.keys()), reverse=True):
                if delta_min >= t:
                    selected_roi = roi_dict[str(t)]
                    break
    
            # Obtener precio de entrada
            entry_price = trade.open_rate
    
            # Obtener comisión total por la operación completa (entrada + salida)
            fee = self.config.get('fee', 0)  # valor por defecto: 0.1%
            total_fee = fee * 2
    
            # ROI objetivo ajustado al fee
            roi_price = entry_price * (1 + selected_roi + total_fee)
    
            # Si el precio actual ha alcanzado o superado el ROI objetivo, salir
            if current_rate >= roi_price:
                return True
    
            return False
    
        except Exception as e:
            logger.error(f"[{pair}] Error en custom_exit con ROI dinámico: {e}")
            return False

    sections_printed = False

    def print_sections(self, indicador, sections_min, sections_max, sections_step) :
        """
        Divide los tramos de RSI de 100→90, 90→80, ..., 10→0
        y calcula porcentaje de operaciones ganadoras/perdedoras.
        """
        total_rows = len(self.custom_trade_info)
        print(f"Total rows en custom_trade_info: {total_rows}")
        
        sorted_items = sorted(self.custom_trade_info.items(), key=lambda x: x[0])

        custom_trade_info_aux = {}
        ultima_fecha_agregada = None
        
        for fecha, info in sorted_items:
            # print(f"****** trade_info date: {fecha} close: {info.get('close')}")
            if info and info.get("profit") is not None:
                if (ultima_fecha_agregada is None or (fecha - ultima_fecha_agregada).total_seconds() > 60):
                    custom_trade_info_aux[fecha] = info
                    ultima_fecha_agregada = fecha
        
        
        total_rows_aux = len(custom_trade_info_aux)
        print(f"Total rows en custom_trade_info_aux: {total_rows_aux}")
        if isinstance(sections_min, bool) and isinstance(sections_max, bool):
            total = total_rows_aux
            wins_true = 0
            wins_false = 0
            loss_true = 0
            loss_false = 0
            for trade_time, info in custom_trade_info_aux.items():
                val = info.get(indicador)
                is_win = info.get('is_win', False)
                if val: 
                    if is_win: 
                        wins_true += 1
                    else: 
                        loss_true += 1
                else: 
                    if is_win: 
                        wins_false += 1
                    else: 
                        loss_false += 1
                    
            total_true = wins_true + loss_true
            total_false = wins_false + loss_false
            if total > 0:
                pct_true = wins_true / total_true * 100
                pct_false = wins_false / total_false * 100
            else:
                pct_true = pct_false = 0.0
    
            print(f"--- Estadísticas booleanas para '{indicador}' ---")
            print(f"True : {wins_true}/{total_true} = {pct_true:.1f}%")
            print(f"False: {wins_false}/{total_false} = {pct_false:.1f}%")
            self.sections_printed = True
            return  # ¡terminas aquí!
        
        sections = []
        current = sections_max
        while current > sections_min:
            lower = max(current - sections_step, sections_min)
            sections.append((current, lower))
            current -= sections_step
            
        data = []
        for trade_time, info in custom_trade_info_aux.items():
            rsi_val = info.get(indicador, 0)
            # Asumimos que 'is_win' se guardó en custom_trade_info
            is_win = info.get('is_win', False)
            data.append((rsi_val, is_win))
        print(f"--- Estadísticas por tramo ({indicador}) ---")
        for hi, lo in sections:
            if isinstance(rsi_val, str):
                continue
            bin_data = [win for (rsi_val, win) in data if lo <= rsi_val < hi]
            total = len(bin_data)
            wins = sum(bin_data)
            if total > 0:
                win_pct = wins / total * 100
                loss_pct = 100 - win_pct
            else:
                win_pct = loss_pct = 0.0
            print(f"{indicador} {hi}-{lo}: {win_pct:.1f}% wins ({wins}/{total}), {loss_pct:.1f}% losses")
        # Marcar como impreso para no repetir
        self.sections_printed = True
        
    def fmt(self, value, decimals=2, default='n/a'):
        try:
            return f"{value:.{decimals}f}"
        except (TypeError, ValueError):
            return default

        
    def confirm_trade_exit(self, pair: str, trade, order_type: str, amount: float,
                      rate: float, time_in_force: str, exit_reason: str,
                      current_time: datetime, **kwargs) -> bool:
        
        logger.info(f"confirm_trade_exit[{pair}]")
        self.trades_count += 1
        
        trade_time = trade.open_date
        trade_info = self.custom_trade_info.get(trade_time, {})
        beneficio_float = trade.calc_profit_ratio(rate) * 100
        is_win = beneficio_float > 0
        if trade_time in self.custom_trade_info:
            self.custom_trade_info[trade_time]['is_win'] = is_win
            self.custom_trade_info[trade_time]['profit'] = beneficio_float
            self.custom_trade_info[trade_time]['rate'] = rate
        
        stats_time = datetime(2025, 6, 17, 2, 0, 0, tzinfo=timezone.utc)
        if not self.is_hyperopt() and current_time >= stats_time and not self.sections_printed: 
            self.print_sections('score', 0, 100, 10)
            
        utc = pytz.utc
        madrid = pytz.timezone('Europe/Madrid')
        open_date_spain = trade.open_date.replace(tzinfo=utc).astimezone(madrid)
        current_time_spain = current_time.replace(tzinfo=utc).astimezone(madrid)
        
        open_date_spain_str = str(open_date_spain).replace(':00+01:00', '')
        current_time_spain_str = str(current_time_spain).replace(':00+01:00', '')
        open_date_spain_str = str(open_date_spain_str).replace(':00+02:00', '')
        current_time_spain_str = str(current_time_spain_str).replace(':00+02:00', '')
        
        beneficio = f"{beneficio_float:.2f}%".rjust(6)
        if not self.is_hyperopt():
            
            params = self.get_pair_params(pair)

            volume_ratio_threshold_2 = self.volume_ratio_threshold_2.value
            atr_multiplier_stoploss = self.atr_multiplier_stoploss.value
            atr_multiplier_roi = self.atr_multiplier_roi.value

            
            trades_count_string = f"{self.trades_count}".rjust(4)
                
            RED = '\033[91m'
            RESET = '\033[0m'
            
            color = RED if beneficio_float < 0 else ''
            reset = RESET if beneficio_float < 0 else ''

            # Si existen valores optimizados para este par, los usamos
            if params:
                buy_params = params.get('params', {}).get('buy', {})
                sell_params = params.get('params', {}).get('sell', {})
                
                volume_ratio_threshold_2 = buy_params.get('volume_ratio_threshold_2', 0.2)
                atr_multiplier_stoploss = sell_params.get('atr_multiplier_stoploss', 0.8)
                atr_multiplier_roi = sell_params.get('atr_multiplier_roi', 2.0)
            
            macd_1h_direccion = "BEAR"
            if float(trade_info.get('macd_1h')) > float(trade_info.get('macd_ant_1h')):
                macd_1h_direccion = "BULL"
            macdsignal_1h_direccion = "BEAR"
            if float(trade_info.get('macdsignal_1h')) > float(trade_info.get('macdsignal_ant_1h')):
                macdsignal_1h_direccion = "BULL"
            macdhist_1h_direccion = "BEAR"
            if float(trade_info.get('macdhist_1h')) > float(trade_info.get('macdhist_ant_1h')):
                macdhist_1h_direccion = "BULL"
            
            debug_string = (
                f"[{trades_count_string}]{color}[{pair}]"
                f"[{open_date_spain_str} - {current_time_spain_str.split(' ')[1]}]"
                f"[{beneficio}]"
                f"[{exit_reason[:4].ljust(4)}]"
                f"[RSI: {self.fmt(trade_info.get('rsi', 'n/a'), decimals=2)} 5m: {self.fmt(trade_info.get('rsi_5m_5m', 'n/a'), decimals=2)} 1h: {self.fmt(trade_info.get('rsi_1h', 'n/a'), decimals=2)}] "
                f"[pct 1d: {self.fmt(trade_info.get('pct_1d'), decimals=2)} prev: {self.fmt(trade_info.get('pct_prev_1d'), decimals=2)}]"
                f"[change: {self.fmt(trade_info.get('change_pct'), decimals=2)}]"
                f"vl: {trade_info.get('volume')} "
                f"currency_pct_green: {self.fmt(trade_info.get('currency_pct_green_1h'), decimals=2)} "
                f"currency_pct_avg: {self.fmt(trade_info.get('currency_pct_average_1h'), decimals=2)} "
                f"score: {self.fmt(trade_info.get('score'))} "
                # f"usdc_d: {self.fmt(trade_info.get('dominance'), decimals=2)} 1h: {self.fmt(trade_info.get('dominance_1h'), decimals=2)} EMA(14) 1h: {self.fmt(trade_info.get('dominance_ema_14_1h'), decimals=2)} "
                # f"adx: {trade_info.get('adx')} di+: {trade_info.get('di_plus')} di-: {trade_info.get('di_minus')} "
                # f"th: {self.fmt(trade_info.get('volume_threshold'), decimals=0)} "
                # f"rt: {self.fmt(trade_info.get('volume_ratio'), decimals=3)} "
                # f"divRsi: {self.fmt(trade_info.get('rsi_div1'), decimals=2)}->{self.fmt(trade_info.get('rsi_div2'), decimals=2)} (diff: {self.fmt(trade_info.get('rsi_div_diff'), decimals=2)}) "
                #f"progression: {self.fmt(trade_info.get('rsi_div2_prev6'), decimals=2)}->{self.fmt(trade_info.get('rsi_div2_prev5'), decimals=2)}->{self.fmt(trade_info.get('rsi_div2_prev4'), decimals=2)}->{self.fmt(trade_info.get('rsi_div2_prev3'), decimals=2)}->{self.fmt(trade_info.get('rsi_div2_prev2'), decimals=2)}->{self.fmt(trade_info.get('rsi_div2_prev1'), decimals=2)}->{self.fmt(trade_info.get('rsi_div2'), decimals=2)} diff last_period: {self.fmt(trade_info.get('rsi_div2_diff'), decimals=2)} diff1: {self.fmt(trade_info.get('rsi_div2_diff1'), decimals=2)} diff2: {self.fmt(trade_info.get('rsi_div2_diff2'), decimals=2)} diff3: {self.fmt(trade_info.get('rsi_div2_diff3'), decimals=2)} diff4: {self.fmt(trade_info.get('rsi_div2_diff4'), decimals=2)} diff5: {self.fmt(trade_info.get('rsi_div2_diff5'), decimals=2)} diff6: {self.fmt(trade_info.get('rsi_div2_diff6'), decimals=2)} ratio: {self.fmt(trade_info.get('rsi_div2_ratio_ruido'), decimals=2)} amplit: {self.fmt(trade_info.get('rsi_div2_amplitud'), decimals=2)} "
                # f"divPrice: {self.fmt(trade_info.get('price_div1'), decimals=4)}->{self.fmt(trade_info.get('price_div2'), decimals=4)} (diff: {self.fmt(trade_info.get('price_div_diff'), decimals=4)}) "
                # f"divDate: {trade_info.get('date_div1')}->{trade_info.get('date_div2')} diff: {trade_info.get('diff_div')} "
                # f"str: {self.fmt(trade_info.get('divergence_strength'), decimals=2)} "
                # f"strPrice: {self.fmt(trade_info.get('divergence_strength_price'), decimals=4)} "
                #f"price_prob: {self.fmt(trade_info['price_prob'], decimals=2)} rsi_prob: {self.fmt(trade_info['rsi_prob'], decimals=2)} "
                #f"cruceMacd: {trade_info.get('cruce_macd')} bd: {trade_info['bullish_divergence_causal']} "
                #f"macd: {self.fmt(trade_info.get('macd'), decimals=8)} macdsignal: {self.fmt(trade_info.get('macdsignal'), decimals=8)} "
                #f"bblow: {self.fmt(trade_info.get('boll_lb'), decimals=4)} price_bellow: {(trade_info.get('price_below_boll_lb'))} "
                #f"div_macd: {trade_info.get('divergence_macd')} "
                f"bc: {trade_info.get('bullish_cond')} "
                #f"vwap: {self.fmt(trade_info.get('vwap'), decimals=4)} vwap_up_close: {(trade_info.get('vwap_up_close'))} pct: {self.fmt(trade_info.get('vwap_diff_pct'), decimals=4)} "
                #f"obv: {self.fmt(trade_info.get('obv'), decimals=4)} obv before: {self.fmt(trade_info.get('obv_before'), decimals=4)} obv_growing: {(trade_info.get('obv_growing'))} diff:{self.fmt(trade_info.get('obv_diff'), decimals=4)}  "
                #f"haColor: {trade_info.get('ha_color')} mml_support_zone: {(trade_info.get('mml_support_zone'))} "
                #f"ema fast: {self.fmt(trade_info.get('ema_fast'), decimals=4)} slow: {self.fmt(trade_info.get('ema_slow'), decimals=4)} "
                #f"date: {trade_info.get('date')} "
                #f"atr: {self.fmt(trade_info.get('atr'), decimals=6)}  "
                f"[{macd_1h_direccion}]macd: {self.fmt(trade_info.get('macd_ant_1h'), decimals=6)}->{self.fmt(trade_info.get('macd_1h'), decimals=6)} [{macdsignal_1h_direccion}]signal: {self.fmt(trade_info.get('macdsignal_ant_1h'), decimals=6)}->{self.fmt(trade_info.get('macdsignal'), decimals=6)} [{macdhist_1h_direccion}]hist: {self.fmt(trade_info.get('macdhist_ant_1h'), decimals=6)}->{self.fmt(trade_info.get('macdhist_1h'), decimals=6)} "
                f"{reset}"
            )
            
            logger.info(debug_string)
            print(debug_string)
        
        # Llama al método original para no romper la lógica base
        return super().confirm_trade_exit(pair, trade, order_type, amount, rate, time_in_force, exit_reason, current_time, **kwargs)
