import os

# ================== THREADS (DEBE ir antes de cualquier import numérico) ==================
os.environ["OPENBLAS_NUM_THREADS"] = "1"
os.environ["OMP_NUM_THREADS"] = "1"
os.environ["MKL_NUM_THREADS"] = "1"
os.environ["NUMEXPR_NUM_THREADS"] = "1"

import time
import json
import csv
import requests
import ccxt
import pandas as pd
from ta.momentum import RSIIndicator
from ta.trend import EMAIndicator, ADXIndicator
from ta.volatility import AverageTrueRange
from dotenv import load_dotenv
from datetime import datetime
from config import (
    SYMBOL, TIMEFRAME, LEVERAGE, RISK_PERCENT, STOP_LOSS_PERCENT,
    EMA_FAST, EMA_SLOW, RSI_PERIOD, HEARTBEAT_INTERVAL,
)

# ================== CONSTANTES OPTIMIZADAS (backtest 14d) ==================
TP_MULTIPLIER = 2.0              # Optimizado v2: R:R 2.0:1 — más alcanzable, sube WR de 39→44%
MIN_ADX = 18                     # Relajado: ADX 25 bloqueaba todo en mercado lateral
LONG_RSI_MIN = 38                # Ligeramente ampliado
LONG_RSI_MAX = 58                # Ligeramente ampliado
SHORT_RSI_MIN = 48               # Ligeramente ampliado
LONG_VOL_MIN = 0.0008            # Relajado: 0.0012 era muy exigente para 5m
SHORT_VOL_MIN = 0.0012           # Relajado: shorts también necesitan oportunidad
ANTI_FLIP_FLOP_S = 900           # 15 min — flip-flops causan muchos SL
COOLDOWN_SL1_S = 300             # Cooldown 1 SL: 5 min
COOLDOWN_SL2_S = 3600            # Cooldown 2 SL: 1 hora
COOLDOWN_SL3_S = 10800           # Cooldown 3+ SL: 3 horas
COOLDOWN_TP_S = 120              # Cooldown post-TP: 2 min
BLOCK_HOUR_START = 21            # Ventana operativa: 10-21 UTC (11 horas)
BLOCK_HOUR_END = 10              # Ampliado: hora 10 y 19-20 tenían WR positivo
TOXIC_HOURS = {10, 11, 12, 15, 16, 17}  # Horas con WR < 30% en backtest 60d
MAX_DAILY_LOSSES = 3             # Máximo 3 SL por día → parar
MAX_DAILY_TRADES = 5             # Máximo 5 trades por día
ATR_MULTIPLIER_SL = 1.5          # SL dinámico basado en ATR
ATR_MULTIPLIER_TP = 3.5          # TP dinámico basado en ATR
USE_DYNAMIC_SLTP = True          # Usar SL/TP basado en ATR
ENABLE_SHORTS = False            # Shorts deshabilitados: 25% WR, PNL negativo en backtest
SHORT_ADX_MIN = 28               # Si se habilitan, exigir ADX alto (tendencia clara)
SHORT_CONFIRMATIONS_MIN = 2      # Shorts necesitan 2 de 3 confirmaciones (más estricto)

load_dotenv()
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
STATE_FILE = os.path.join(BASE_DIR, "state.json")
TRADES_FILE = os.path.join(BASE_DIR, "trades_ethusdt.csv")
REPORT_FILE = os.path.join(BASE_DIR, "last_report.json")

exchange = ccxt.binance({
    "apiKey": os.getenv("BINANCE_FUTURES_KEY"),
    "secret": os.getenv("BINANCE_FUTURES_SECRET"),
    "enableRateLimit": True,
    "options": {"defaultType": "future"},
})

exchange.set_leverage(LEVERAGE, SYMBOL)
try:
    exchange.set_margin_mode("isolated", SYMBOL)
except Exception:
    pass


# ================== UTILS ==================
def telegram(msg):
    url = f"https://api.telegram.org/bot{os.getenv('TELEGRAM_TOKEN')}/sendMessage"
    requests.post(url, data={"chat_id": os.getenv("TELEGRAM_CHAT_ID"), "text": msg})


def init_csv():
    if not os.path.exists(TRADES_FILE):
        with open(TRADES_FILE, "w", newline="") as f:
            writer = csv.writer(f)
            writer.writerow([
                "timestamp", "symbol", "side",
                "entry", "exit", "qty", "pnl_pct",
                "pnl_usdt", "result", "sl", "tp", "rr",
            ])


def get_position():
    positions = exchange.fetch_positions([SYMBOL])
    for p in positions:
        if abs(float(p["contracts"])) > 0:
            return {
                "side": "long" if float(p["contracts"]) > 0 else "short",
                "qty": abs(float(p["contracts"])),
                "entry": float(p["entryPrice"]),
            }
    return None


def load_state():
    if not os.path.exists(STATE_FILE):
        return {
            "position": False,
            "side": None,
            "entry_price": 0,
            "quantity": 0,
        }
    with open(STATE_FILE, "r") as f:
        return json.load(f)


def save_state(state):
    with open(STATE_FILE, "w") as f:
        json.dump(state, f, indent=2)


# ================= REPORTE =================
def hourly_report():
    try:
        now = time.time()
        last = 0
        if os.path.exists(REPORT_FILE):
            with open(REPORT_FILE) as f:
                last = json.load(f).get("last", 0)

        if now - last < HEARTBEAT_INTERVAL:
            return

        ohlcv = exchange.fetch_ohlcv(SYMBOL, TIMEFRAME, limit=100)
        df = pd.DataFrame(ohlcv, columns=["t", "o", "h", "l", "c", "v"])
        last_price = df["c"].iloc[-1]

        state = load_state()

        if state["position"]:
            entry = state["entry_price"]
            side = state["side"]
            qty = state.get("quantity", 0)
            entry_time = state.get("entry_time", 0)
            hold_time = int(now - entry_time) if entry_time else 0

            if side == "long":
                pnl_pct = ((last_price - entry) / entry) * 100
            else:
                pnl_pct = ((entry - last_price) / entry) * 100

            telegram(
                f"💓 Bot FUTURES activo\n"
                f"📍 EN POSICION {side.upper()}\n"
                f"📥 Entrada: {entry:.2f}\n"
                f"📤 Precio: {last_price:.2f}\n"
                f"📦 Qty: {qty}\n"
                f"⏱ Tiempo: {hold_time} s\n"
                f"⚙ Leverage: {LEVERAGE}x\n"
                f"📈 PNL aprox: {pnl_pct:.2f}%"
            )
        else:
            telegram(
                f"💓 Bot FUTURES activo\n"
                f"⏳ Sin posicion\n"
                f"📤 Precio: {last_price:.2f}"
            )

        with open(REPORT_FILE, "w") as f:
            json.dump({"last": now}, f)
    except Exception as e:
        try:
            telegram(f"⚠ Error en hourly_report: {e}")
        except Exception:
            pass


# ================== HELPERS ==================
def count_daily_trades():
    """Cuenta trades y SLs del día actual desde el CSV."""
    today = datetime.utcnow().strftime("%Y-%m-%d")
    daily_trades = 0
    daily_losses = 0
    if os.path.exists(TRADES_FILE):
        try:
            with open(TRADES_FILE, "r") as f:
                reader = csv.DictReader(f)
                for row in reader:
                    if row.get("timestamp", "").startswith(today):
                        daily_trades += 1
                        if "SL" in row.get("result", ""):
                            daily_losses += 1
        except Exception:
            pass
    return daily_trades, daily_losses


def compute_atr(df, period=14):
    """Calcula ATR para SL/TP dinámico."""
    atr = AverageTrueRange(df["h"], df["l"], df["c"], window=period)
    return atr.average_true_range().iloc[-1]


def confirm_volume_spike(df, lookback=20, threshold=1.3):
    """Confirma que el volumen actual es superior al promedio."""
    avg_vol = df["v"].tail(lookback).mean()
    current_vol = df["v"].iloc[-1]
    return current_vol > avg_vol * threshold


def confirm_candle_strength(df, side):
    """
    Confirma que la última vela cerrada apoya la dirección del trade.
    Para LONG: vela alcista (close > open) con cuerpo significativo.
    Para SHORT: vela bajista (close < open) con cuerpo significativo.
    """
    last_candle = df.iloc[-2]  # Penúltima vela (la cerrada)
    body = abs(last_candle["c"] - last_candle["o"])
    total_range = last_candle["h"] - last_candle["l"]

    if total_range == 0:
        return False

    body_ratio = body / total_range

    if side == "long":
        return last_candle["c"] > last_candle["o"] and body_ratio > 0.4
    else:
        return last_candle["c"] < last_candle["o"] and body_ratio > 0.4


# ================== BOT ==================
def run_bot():
    start = time.time()
    init_csv()

    while time.time() - start < 55:
        try:
            hourly_report()

            # 1 Fetch OHLCV
            ohlcv = exchange.fetch_ohlcv(SYMBOL, TIMEFRAME, limit=300)
            df = pd.DataFrame(ohlcv, columns=["t", "o", "h", "l", "c", "v"])
            close = df["c"]

            # 2 Indicadores
            rsi = RSIIndicator(close, RSI_PERIOD).rsi().iloc[-1]
            ema_fast = EMAIndicator(close, EMA_FAST).ema_indicator().iloc[-1]
            ema_slow = EMAIndicator(close, EMA_SLOW).ema_indicator().iloc[-1]
            last_price = close.iloc[-1]

            # 3 Estado
            state = load_state()
            in_position = state["position"]
            last_result = state.get("last_result")
            last_exit_time = state.get("last_exit_time", 0)

            # 4 FILTRO DE REGIMEN (rango de precio)
            range_lookback = 24
            market_range = (
                df["h"].tail(range_lookback).max()
                - df["l"].tail(range_lookback).min()
            )
            if market_range < last_price * 0.002:
                continue

            # Filtro anti-lateralidad (separación entre EMAs)
            ema_gap = abs(ema_fast - ema_slow) / last_price
            if ema_gap < 0.0005:
                continue

            # Filtro ADX
            adx = ADXIndicator(df["h"], df["l"], close, window=14).adx().iloc[-1]
            if adx < MIN_ADX:
                continue

            # ================== ENTRADA ==================
            if not in_position:
                # Filtro horario
                current_hour = datetime.utcnow().hour
                if current_hour >= BLOCK_HOUR_START or current_hour < BLOCK_HOUR_END:
                    continue
                if current_hour in TOXIC_HOURS:
                    continue

                # Límite diario de trades y pérdidas
                daily_trades, daily_losses = count_daily_trades()
                if daily_trades >= MAX_DAILY_TRADES:
                    continue
                if daily_losses >= MAX_DAILY_LOSSES:
                    continue

                # Cooldowns por SL consecutivos
                consecutive_sl = state.get("consecutive_sl", 0)
                elapsed = time.time() - last_exit_time

                if consecutive_sl >= 3 and elapsed < COOLDOWN_SL3_S:
                    continue
                if consecutive_sl == 2 and elapsed < COOLDOWN_SL2_S:
                    continue
                if last_result == "SL" and elapsed < COOLDOWN_SL1_S:
                    continue
                if last_result == "TP" and elapsed < COOLDOWN_TP_S:
                    continue

                # Anti-flip-flop
                last_side = state.get("side")
                time_since_exit = elapsed if last_exit_time else 999999

                balance = exchange.fetch_balance()["USDT"]["free"]
                qty = (balance * RISK_PERCENT * LEVERAGE) / last_price
                qty = float(exchange.amount_to_precision(SYMBOL, qty))

                if qty * last_price < 20:
                    time.sleep(2)
                    continue

                ema200_series = EMAIndicator(close, 200).ema_indicator()
                ema_200 = ema200_series.iloc[-1]

                # Pendiente EMA50: detecta tendencia bajista de mediano plazo
                ema50_series = EMAIndicator(close, EMA_SLOW).ema_indicator()
                ema50_now = ema50_series.iloc[-1]
                ema50_12ago = ema50_series.iloc[-13]  # hace 12 velas (1h en 5m)
                ema50_falling = bool(ema50_now < ema50_12ago)  # EMA50 bajando

                # Pendiente EMA200: detecta tendencia bajista de largo plazo
                ema200_24ago = ema200_series.iloc[-25]  # hace 24 velas (2h en 5m)
                ema200_falling = bool(ema_200 < ema200_24ago)

                # Filtro drawdown: no entrar si precio cayó >3% desde máx reciente
                high_48 = df["h"].iloc[-49:-1].max()  # máx últimas 48 velas (4h)
                drawdown_pct = (high_48 - last_price) / high_48
                if drawdown_pct > 0.03:
                    time.sleep(2)
                    continue

                # ATR para SL/TP dinámico
                atr = compute_atr(df)

                lookback_vol = 12
                rango_volatilidad = (
                    df["h"].tail(lookback_vol).max()
                    - df["l"].tail(lookback_vol).min()
                )

                # ================= LONG =================
                filtro_rsi = bool(LONG_RSI_MIN <= rsi <= LONG_RSI_MAX)
                filtro_ema = bool(ema_fast > ema_slow * 1.0002)
                filtro_tendencia = bool(last_price > ema_200 * 1.0005)
                filtro_volatilidad = bool(rango_volatilidad > last_price * LONG_VOL_MIN)
                filtro_momentum_macro = bool(not ema50_falling)
                filtro_tendencia_macro = bool(not ema200_falling)

                # Confirmaciones blandas (2 de 3 necesarias)
                has_volume = bool(confirm_volume_spike(df, threshold=1.2))
                has_candle = bool(confirm_candle_strength(df, "long"))
                adx_confirm = bool(adx > 25)
                confirmaciones = sum([has_volume, has_candle, adx_confirm])
                filtro_confirmacion = bool(confirmaciones >= 2)

                # Filtro momentum inmediato: no todas las últimas 3 cerradas bajistas
                last3 = df.iloc[-4:-1]
                all_bearish = bool((last3["c"] < last3["o"]).all())
                if all_bearish:
                    time.sleep(2)
                    continue

                flip_long = bool(
                    last_side == "short"
                    and time_since_exit < ANTI_FLIP_FLOP_S
                )

                if (filtro_rsi and filtro_ema and filtro_tendencia
                        and filtro_volatilidad and filtro_confirmacion
                        and filtro_momentum_macro
                        and filtro_tendencia_macro
                        and not flip_long):
                    try:
                        exchange.set_margin_mode("isolated", SYMBOL)
                    except Exception:
                        pass

                    exchange.create_market_buy_order(SYMBOL, qty)
                    entry = last_price

                    # SL/TP dinámico basado en ATR
                    if USE_DYNAMIC_SLTP:
                        sl = entry - (atr * ATR_MULTIPLIER_SL)
                        tp = entry + (atr * ATR_MULTIPLIER_TP)
                    else:
                        sl = entry * (1 - STOP_LOSS_PERCENT)
                        risk = entry - sl
                        tp = entry + (risk * TP_MULTIPLIER)

                    # Asegurar SL mínimo del 0.3% y máximo del 1.0%
                    min_sl = entry * (1 - 0.010)
                    max_sl = entry * (1 - 0.003)
                    sl = max(min_sl, min(sl, max_sl))

                    risk = entry - sl
                    if not USE_DYNAMIC_SLTP:
                        tp = entry + (risk * TP_MULTIPLIER)

                    exchange.create_order(
                        SYMBOL, "TAKE_PROFIT_MARKET", "sell", qty,
                        params={
                            "stopPrice": exchange.price_to_precision(SYMBOL, tp),
                            "closePosition": True,
                        },
                    )
                    exchange.create_order(
                        SYMBOL, "STOP_MARKET", "sell", qty,
                        params={
                            "stopPrice": exchange.price_to_precision(SYMBOL, sl),
                            "closePosition": True,
                        },
                    )

                    rr = round(abs(tp - entry) / abs(entry - sl), 2) if risk else 0

                    telegram(
                        f"🟢 LONG {SYMBOL}\n"
                        f"Entrada: {entry:.2f}\n"
                        f"TP: {tp:.2f} | SL: {sl:.2f}\n"
                        f"R:R {rr} | ATR: {atr:.2f}\n"
                        f"ADX: {adx:.1f} | RSI: {rsi:.1f}\n"
                        f"Trades hoy: {daily_trades + 1}/{MAX_DAILY_TRADES}"
                    )

                    save_state({
                        "position": True,
                        "side": "long",
                        "entry_price": entry,
                        "quantity": qty,
                        "sl_price": sl,
                        "tp_price": tp,
                        "entry_time": time.time(),
                        "last_result": state.get("last_result"),
                        "last_exit_time": state.get("last_exit_time", 0),
                        "consecutive_sl": state.get("consecutive_sl", 0),
                    })
                    in_position = True

                # ================= SHORT =================
                if not in_position and ENABLE_SHORTS:
                    filtro_rsi_short = bool(rsi >= SHORT_RSI_MIN)
                    filtro_ema_short = bool(ema_fast < ema_slow * 0.9995)
                    filtro_tendencia_short = bool(last_price < ema_200 * 0.999)
                    filtro_volatilidad_short = bool(rango_volatilidad > last_price * SHORT_VOL_MIN)
                    filtro_adx_short = bool(adx >= SHORT_ADX_MIN)
                    # Confirmaciones blandas para short (2 de 3)
                    has_volume_s = bool(confirm_volume_spike(df, threshold=1.2))
                    has_candle_s = bool(confirm_candle_strength(df, "short"))
                    adx_confirm_s = bool(adx > 30)
                    confirmaciones_s = sum([has_volume_s, has_candle_s, adx_confirm_s])
                    filtro_confirmacion_short = bool(confirmaciones_s >= SHORT_CONFIRMATIONS_MIN)

                    flip_short = bool(
                        last_side == "long"
                        and time_since_exit < ANTI_FLIP_FLOP_S
                    )

                    if (filtro_rsi_short and filtro_ema_short
                            and filtro_tendencia_short
                            and filtro_volatilidad_short
                            and filtro_adx_short
                            and filtro_confirmacion_short
                            and not flip_short):
                        try:
                            exchange.set_margin_mode("isolated", SYMBOL)
                        except Exception:
                            pass

                        exchange.create_market_sell_order(SYMBOL, qty)
                        entry = last_price

                        # SL/TP dinámico basado en ATR
                        if USE_DYNAMIC_SLTP:
                            sl = entry + (atr * ATR_MULTIPLIER_SL)
                            tp = entry - (atr * ATR_MULTIPLIER_TP)
                        else:
                            sl = entry * (1 + STOP_LOSS_PERCENT)
                            risk = sl - entry
                            tp = entry - (risk * TP_MULTIPLIER)

                        # Asegurar SL mínimo del 0.3% y máximo del 1.0%
                        min_sl = entry * (1 + 0.003)
                        max_sl = entry * (1 + 0.010)
                        sl = min(max_sl, max(sl, min_sl))

                        risk = sl - entry
                        if not USE_DYNAMIC_SLTP:
                            tp = entry - (risk * TP_MULTIPLIER)

                        exchange.create_order(
                            SYMBOL, "TAKE_PROFIT_MARKET", "buy", qty,
                            params={
                                "stopPrice": exchange.price_to_precision(SYMBOL, tp),
                                "closePosition": True,
                            },
                        )
                        exchange.create_order(
                            SYMBOL, "STOP_MARKET", "buy", qty,
                            params={
                                "stopPrice": exchange.price_to_precision(SYMBOL, sl),
                                "closePosition": True,
                            },
                        )

                        rr = round(abs(entry - tp) / abs(sl - entry), 2) if risk else 0

                        telegram(
                            f"🔴 SHORT {SYMBOL}\n"
                            f"Entrada: {entry:.2f}\n"
                            f"TP: {tp:.2f} | SL: {sl:.2f}\n"
                            f"R:R {rr} | ATR: {atr:.2f}\n"
                            f"ADX: {adx:.1f} | RSI: {rsi:.1f}\n"
                            f"Trades hoy: {daily_trades + 1}/{MAX_DAILY_TRADES}"
                        )

                        save_state({
                            "position": True,
                            "side": "short",
                            "entry_price": entry,
                            "quantity": qty,
                            "sl_price": sl,
                            "tp_price": tp,
                            "entry_time": time.time(),
                            "last_result": state.get("last_result"),
                            "last_exit_time": state.get("last_exit_time", 0),
                            "consecutive_sl": state.get("consecutive_sl", 0),
                        })
                        in_position = True

            # ================== SALIDA ==================
            if in_position:
                positions = exchange.fetch_positions([SYMBOL])

                binance_qty = 0
                for p in positions:
                    if SYMBOL in p.get("symbol", ""):
                        binance_qty = abs(float(p.get("contracts", 0)))

                now = time.time()
                entry_time = state.get("entry_time", 0)
                MIN_HOLD_TIME = 10  # Mínimo 10 seg antes de detectar cierre

                if binance_qty == 0 and now - entry_time > MIN_HOLD_TIME:
                    entry = state["entry_price"]
                    side = state["side"]
                    qty = state.get("quantity", 0)
                    sl = state.get("sl_price", 0)
                    tp = state.get("tp_price", 0)
                    timestamp = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")

                    # Obtener precio REAL de ejecución desde Binance
                    exit_price = last_price
                    real_pnl_usdt = None
                    try:
                        symbol_id = SYMBOL.replace("/", "")
                        recent_trades = exchange.fapiPrivate_get_usertrades({
                            "symbol": symbol_id,
                            "limit": 10,
                        })
                        if recent_trades:
                            close_trades = []
                            for t in reversed(recent_trades):
                                is_close = (
                                    (side == "long" and t.get("side") == "SELL")
                                    or (side == "short" and t.get("side") == "BUY")
                                )
                                if is_close:
                                    close_trades.append(t)
                                elif close_trades:
                                    break

                            if close_trades:
                                total_qty_fill = sum(
                                    float(t["qty"]) for t in close_trades
                                )
                                exit_price = sum(
                                    float(t["price"]) * float(t["qty"])
                                    for t in close_trades
                                ) / total_qty_fill
                                real_pnl_usdt = sum(
                                    float(t.get("realizedPnl", 0))
                                    for t in close_trades
                                )
                    except Exception:
                        pass

                    # Calcular PNL
                    if side == "long":
                        if sl == 0:
                            sl = entry * (1 - STOP_LOSS_PERCENT)
                        if tp == 0:
                            risk = entry - sl
                            tp = entry + (risk * TP_MULTIPLIER)
                        pnl_pct = ((exit_price - entry) / entry) * 100
                    else:
                        if sl == 0:
                            sl = entry * (1 + STOP_LOSS_PERCENT)
                        if tp == 0:
                            risk = sl - entry
                            tp = entry - (risk * TP_MULTIPLIER)
                        pnl_pct = ((entry - exit_price) / entry) * 100

                    # PNL en USDT
                    if real_pnl_usdt is not None:
                        pnl_usdt = real_pnl_usdt
                    else:
                        if side == "long":
                            pnl_usdt = (exit_price - entry) * qty
                        else:
                            pnl_usdt = (entry - exit_price) * qty
                        fees = (entry * qty + exit_price * qty) * 0.0004
                        pnl_usdt -= fees

                    result = "TP 🎯" if pnl_pct > 0 else "SL 🛑"

                    # Tiempo en posición
                    hold_secs = int(now - entry_time) if entry_time else 0
                    hold_min = hold_secs // 60

                    telegram(
                        f"{result} {SYMBOL}\n"
                        f"📥 Entrada: {entry:.2f}\n"
                        f"📤 Salida real: {exit_price:.2f}\n"
                        f"📦 Qty: {qty}\n"
                        f"📈 Resultado: {pnl_pct:.2f}%\n"
                        f"💵 PNL: {pnl_usdt:.2f} USDT\n"
                        f"⏱ Hold: {hold_min} min"
                    )

                    # Guardar trade en CSV
                    risk_value = abs(entry - sl)
                    reward_value = abs(tp - entry)
                    rr = round(reward_value / risk_value, 2) if risk_value else 0

                    with open(TRADES_FILE, "a", newline="") as f:
                        writer = csv.writer(f)
                        writer.writerow([
                            timestamp, SYMBOL, side, entry, exit_price, qty,
                            f"{pnl_pct:.2f}", f"{pnl_usdt:.2f}",
                            result, sl, tp, rr,
                        ])

                    new_consecutive_sl = (
                        0 if pnl_pct > 0
                        else state.get("consecutive_sl", 0) + 1
                    )

                    save_state({
                        "position": False,
                        "side": side,
                        "entry_price": 0,
                        "quantity": 0,
                        "sl_price": 0,
                        "tp_price": 0,
                        "entry_time": 0,
                        "last_result": "SL" if pnl_pct <= 0 else "TP",
                        "last_exit_time": time.time(),
                        "consecutive_sl": new_consecutive_sl,
                    })
                    in_position = False

                    # Si acumula 3+ SL consecutivos, notificar
                    if new_consecutive_sl >= 3:
                        telegram(
                            f"⚠️ {new_consecutive_sl} SL consecutivos.\n"
                            f"Bot en cooldown de {COOLDOWN_SL3_S // 60} min."
                        )

        except Exception as e:
            telegram(f"⚠ Error bot futuros: {e}")

        time.sleep(8)  # 8 seg entre ciclos (5m TF no necesita polling rápido)


if __name__ == "__main__":
    run_bot()
