<?php
/**
 * Polymarket Bot Dashboard
 * Lee el archivo trades.json generado por el bot de Python y lo muestra visualmente.
 */

// ── Ruta al directorio del bot (fuera de public_html) ────────────────────────
// Ajustar si la estructura del servidor es diferente
define('BOT_DIR', dirname(__DIR__) . '/trading_bot');

// CORS ya no necesario (todo en Apache :8080), pero lo dejamos por si acaso
header('Access-Control-Allow-Origin: *');

// Endpoint: sirve archivos de trade_logs/
if (isset($_GET['trade_log'])) {
    $file = basename($_GET['trade_log']);
    $path = BOT_DIR . '/trade_logs/' . $file;
    if ($file && str_ends_with($file, '.json') && file_exists($path)) {
        header('Content-Type: application/json');
        header('Cache-Control: no-cache');
        readfile($path);
    } else {
        http_response_code(404);
        echo json_encode(['error' => 'not found']);
    }
    exit;
}

// Endpoint: sirve trades_analizador.json
if (isset($_GET['analyzer_trades'])) {
    $path = BOT_DIR . '/analizador_datos/trades_analizador.json';
    header('Content-Type: application/json');
    header('Cache-Control: no-cache');
    if (file_exists($path)) {
        readfile($path);
    } else {
        echo json_encode([]);
    }
    exit;
}

// Endpoint: sirve un archivo de market_logs/ + eventos de salida del trade log asociado
if (isset($_GET['market_log'])) {
    $file = basename($_GET['market_log']);
    $path = BOT_DIR . '/market_logs/' . $file;
    if ($file && str_ends_with($file, '.json') && file_exists($path)) {
        header('Content-Type: application/json');
        header('Cache-Control: no-cache');
        $data = json_decode(file_get_contents($path), true);
        // Buscar trade log con el mismo slug para obtener eventos de salida reales
        $slug = $data['slug'] ?? '';
        $tradeDir = BOT_DIR . '/trade_logs/';
        $tradeEvents = null;
        $tradeSnapCount = 0;
        if ($slug && is_dir($tradeDir)) {
            foreach (scandir($tradeDir) as $tf) {
                if (!str_ends_with($tf, '.json')) continue;
                $td = @json_decode(file_get_contents($tradeDir . $tf), true);
                if (!$td || ($td['trade']['slug'] ?? '') !== $slug) continue;
                $tradeEvents    = $td['events'] ?? [];
                $tradeSnapCount = count($td['snapshots'] ?? []);
                break;
            }
        }
        // Inyectar en la respuesta si encontramos el trade log
        if ($tradeEvents !== null) {
            $data['trade_events']     = $tradeEvents;
            $data['trade_snap_count'] = $tradeSnapCount;
        }
        echo json_encode($data);
    } else {
        http_response_code(404);
        echo json_encode(['error' => 'not found']);
    }
    exit;
}

// Endpoint: lista todos los market_logs con metadata (con caché en disco)
if (isset($_GET['market_logs_list'])) {
    header('Content-Type: application/json');
    $dir       = BOT_DIR . '/market_logs/';
    $cacheFile = BOT_DIR . '/market_logs_index.json';

    // Servir desde caché solo si tiene menos de 60 segundos
    $cacheTTL = 60; 
    if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < $cacheTTL)) {
        readfile($cacheFile);
        exit;
    }

    // Reconstruir índice (solo ocurre cuando hay archivos nuevos)
    $result = [];
    if (is_dir($dir)) {
        foreach (scandir($dir) as $fname) {
            if (!str_ends_with($fname, '.json')) continue;
            $data = json_decode(file_get_contents($dir . $fname), true);
            if (!$data) continue;
            $decisions = [];
            $enter_side = null;
            $factor = 1.0;
            $factor_dir = '';
            foreach ($data['evaluations'] ?? [] as $ev) {
                $d = $ev['decision'] ?? '';
                $decisions[$d] = ($decisions[$d] ?? 0) + 1;
                if ($d === 'ENTERED' && $enter_side === null) {
                    $enter_side = $ev['side'] ?? null;
                }
                if (($ev['factor'] ?? 1.0) != 1.0 && $factor == 1.0) {
                    $factor    = $ev['factor'];
                    $factor_dir = $ev['direction'] ?? '';
                }
            }
            $result[] = [
                'fname'      => $fname,
                'asset'      => $data['asset']    ?? 'BTC',
                'slug'       => $data['slug']      ?? '',
                'end_date'   => $data['end_date']  ?? '',
                'outcome'    => $data['outcome'],
                'n_evals'    => count($data['evaluations'] ?? []),
                'decisions'  => $decisions,
                'enter_side' => $enter_side,
                'factor'     => $factor,
                'factor_dir' => $factor_dir,
            ];
        }
    }
    usort($result, fn($a, $b) => strcmp($b['end_date'], $a['end_date']));
    $json = json_encode($result);
    file_put_contents($cacheFile, $json);
    echo $json;
    exit;
}

// Endpoint: sirve volatility_history.json desde fuera de public_html
if (isset($_GET['volatility_history'])) {
    $path = BOT_DIR . '/volatility_history.json';
    header('Content-Type: application/json');
    echo file_exists($path) ? file_get_contents($path) : '[]';
    exit;
}

$logFile = BOT_DIR . '/trades.json';
define('POLY_WALLET',  '0xE5e9C1e602b4a234d7F22Ee0b94b170fd75FCAaA');
define('POLY_CACHE',   'polymarket_cache.json');
define('POLY_CACHE_TTL', 300); // 5 minutos

// ── Endpoint: datos de Polymarket (con cache) ─────────────────────────────────
if (isset($_GET['polymarket'])) {
    header('Content-Type: application/json');

    // Servir cache si es reciente
    if (file_exists(POLY_CACHE)) {
        $cached = json_decode(file_get_contents(POLY_CACHE), true);
        if ($cached && isset($cached['_ts']) && (time() - $cached['_ts']) < POLY_CACHE_TTL) {
            echo json_encode($cached);
            exit;
        }
    }

    // Paginar activity
    $allActivity = [];
    $offset = 0;
    $limit  = 500;
    do {
        $url = "https://data-api.polymarket.com/activity?user=" . POLY_WALLET
             . "&limit=$limit&offset=$offset";
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_TIMEOUT, 15);
        $res = curl_exec($ch);
        curl_close($ch);
        $batch = $res ? json_decode($res, true) : [];
        if (!$batch) break;
        $allActivity = array_merge($allActivity, $batch);
        $offset += $limit;
    } while (count($batch) === $limit);

    // Calcular stats
    $trades  = array_filter($allActivity, fn($x) => $x['type'] === 'TRADE' && $x['side'] === 'BUY');
    $redeems = array_filter($allActivity, fn($x) => $x['type'] === 'REDEEM');

    $totalSpent    = array_sum(array_column(array_values($trades),  'usdcSize'));
    $totalRedeemed = array_sum(array_column(array_values($redeems), 'usdcSize'));

    // Daily breakdown
    $daily = [];
    foreach ($trades as $t) {
        $day = date('Y-m-d', $t['timestamp']);
        if (!isset($daily[$day])) $daily[$day] = ['spent' => 0, 'redeemed' => 0, 'n' => 0];
        $daily[$day]['spent'] += $t['usdcSize'];
        $daily[$day]['n']++;
    }
    foreach ($redeems as $r) {
        $day = date('Y-m-d', $r['timestamp']);
        if (!isset($daily[$day])) $daily[$day] = ['spent' => 0, 'redeemed' => 0, 'n' => 0];
        $daily[$day]['redeemed'] += $r['usdcSize'];
    }
    ksort($daily);

    // Últimos 30 trades para la tabla
    $recent = array_slice(array_values($allActivity), 0, 30);

    $result = [
        '_ts'           => time(),
        'total_spent'   => round($totalSpent,    2),
        'total_redeemed'=> round($totalRedeemed, 2),
        'net_pnl'       => round($totalRedeemed - $totalSpent, 2),
        'n_trades'      => count($trades),
        'n_redeems'     => count($redeems),
        'daily'         => $daily,
        'recent'        => $recent,
    ];

    file_put_contents(POLY_CACHE, json_encode($result));
    echo json_encode($result);
    exit;
}

// ── Endpoint: Portfolio y Cash (posiciones abiertas + saldo USDC en Polygon) ──
if (isset($_GET['poly_accounts'])) {
    header('Content-Type: application/json');

    $cache = 'poly_accounts_cache.json';
    if (file_exists($cache)) {
        $cached = json_decode(file_get_contents($cache), true);
        if ($cached && isset($cached['_ts']) && (time() - $cached['_ts']) < 60) {
            echo json_encode($cached);
            exit;
        }
    }

    // Posiciones abiertas
    $ch = curl_init("https://data-api.polymarket.com/positions?user=" . POLY_WALLET . "&sizeThreshold=.01");
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_TIMEOUT, 10);
    $res = curl_exec($ch);
    curl_close($ch);
    $positions = ($res ? json_decode($res, true) : null) ?: [];

    // Obtener proxy wallet (Polymarket usa un contrato proxy para cada usuario)
    $proxyWallet = null;
    foreach ($positions as $p) {
        if (!empty($p['proxyWallet'])) { $proxyWallet = $p['proxyWallet']; break; }
    }

    // Separar posiciones activas (mercado no expirado, curPrice > 0) de las expiradas
    $today = date('Y-m-d');
    $activePositions  = [];
    $expiredPositions = [];
    foreach ($positions as $p) {
        $curPrice = (float)($p['curPrice'] ?? 0);
        $endDate  = $p['endDate'] ?? '';
        if ($curPrice > 0 && $endDate >= $today) {
            $activePositions[] = $p;
        } else {
            $expiredPositions[] = $p;
        }
    }

    // Valor del portfolio: solo posiciones activas (size × curPrice)
    $portfolioValue = 0;
    foreach ($activePositions as $p) {
        $portfolioValue += (float)($p['size'] ?? 0) * (float)($p['curPrice'] ?? 0);
    }

    // Inversión inicial total en posiciones expiradas aún no limpiadas (dinero ya perdido)
    $expiredInitial = array_sum(array_map(fn($p) => (float)($p['initialValue'] ?? 0), $expiredPositions));

    $result = [
        '_ts'              => time(),
        'portfolio_value'  => round($portfolioValue, 2),
        'cash_usdc'        => null, // el USDC está en el contrato del exchange, no accesible sin auth
        'n_positions'      => count($positions),
        'n_active'         => count($activePositions),
        'n_expired'        => count($expiredPositions),
        'expired_initial'  => round($expiredInitial, 2),
        'positions'        => array_slice($activePositions, 0, 30),
    ];
    file_put_contents($cache, json_encode($result));
    echo json_encode($result);
    exit;
}

// ── Endpoint AJAX para obtener datos frescos sin recargar la página ──────────
if (isset($_GET['ajax'])) {
    header('Content-Type: application/json');
    $data = ['trades' => [], 'btc_price' => null, 'klines' => [], 'eth_price' => null, 'eth_klines' => [], 'sol_price' => null, 'sol_klines' => [], 'bankroll' => null, 'base_bankroll' => null, 'total_pnl' => null];

    if (file_exists($logFile)) {
        $data['trades'] = json_decode(file_get_contents($logFile), true);
    }

    // 1. Ticker de precio (PRIORIDAD: Chainlink desde el bot, FALLBACK: Promedio REST)
    $btcPrice = null;
    $currentStrike = null;
    $priceFile = 'latest_btc_price.json';

    if (file_exists($priceFile)) {
        $priceData = json_decode(file_get_contents($priceFile), true);
        // Si el precio tiene menos de 15 segundos, lo usamos para ser consistente con el bot
        if ($priceData && isset($priceData['price']) && (time() - $priceData['ts'] < 15)) {
            $btcPrice = (float)$priceData['price'];
            $currentStrike = isset($priceData['strike']) ? (float)$priceData['strike'] : null;
        }
        if ($priceData) {
            if (isset($priceData['bankroll']))          $data['bankroll']          = (float)$priceData['bankroll'];
            if (isset($priceData['base_bankroll']))     $data['base_bankroll']     = (float)$priceData['base_bankroll'];
            if (isset($priceData['total_pnl']))         $data['total_pnl']         = (float)$priceData['total_pnl'];
            // Nuevas métricas del bot
            if (isset($priceData['rsi']))               $data['bot_rsi']           = (float)$priceData['rsi'];
            if (isset($priceData['rsi_signal']))        $data['bot_rsi_signal']    = $priceData['rsi_signal'];
            if (isset($priceData['use_rsi_filter']))    $data['use_rsi_filter']    = (bool)$priceData['use_rsi_filter'];
            if (isset($priceData['rsi_up_min']))        $data['rsi_up_min']        = (float)$priceData['rsi_up_min'];
            if (isset($priceData['rsi_down_max']))      $data['rsi_down_max']      = (float)$priceData['rsi_down_max'];
            if (isset($priceData['use_dynamic_kelly'])) $data['use_dynamic_kelly'] = (bool)$priceData['use_dynamic_kelly'];
            if (isset($priceData['dynamic_kelly_mult'])) $data['dynamic_kelly_mult'] = (float)$priceData['dynamic_kelly_mult'];
            if (isset($priceData['dynamic_kelly_conf'])) $data['dynamic_kelly_conf'] = (float)$priceData['dynamic_kelly_conf'];
            if (isset($priceData['use_clob_volume']))   $data['use_clob_volume']   = (bool)$priceData['use_clob_volume'];
        }
    }

    $prices = [];
    if ($btcPrice === null) {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_TIMEOUT, 2);

        // Fuente A: Bitstamp
    curl_setopt($ch, CURLOPT_URL, "https://www.bitstamp.net/api/v2/ticker/btcusd/");
    $res = curl_exec($ch);
    if ($res) {
        $json = json_decode($res, true);
        if (isset($json['last'])) $prices[] = (float)$json['last'];
    }

    // Fuente B: Kraken
    curl_setopt($ch, CURLOPT_URL, "https://api.kraken.com/0/public/Ticker?pair=XBTUSD");
    $res = curl_exec($ch);
    if ($res) {
        $json = json_decode($res, true);
        if (isset($json['result']['XXBTZUSD']['c'][0])) $prices[] = (float)$json['result']['XXBTZUSD']['c'][0];
    }

        curl_close($ch);

        $btcPrice = count($prices) > 0 ? array_sum($prices) / count($prices) : null;
    }

    $data['btc_price'] = $btcPrice;
    $data['current_strike'] = $currentStrike;

    // 2. Klines para indicadores (Síncrono: Pedimos datos reales en cada petición)
    $ch = curl_init("https://www.bitstamp.net/api/v2/ohlc/btcusd/?step=180&limit=35");
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_TIMEOUT, 5);
    $res = curl_exec($ch);
    if ($res) {
        $json = json_decode($res, true);
        $data['klines'] = $json['data']['ohlc'] ?? [];
    }
    curl_close($ch);

    // 3. ETH price (Bitstamp + Kraken)
    $ethCh = curl_init();
    curl_setopt($ethCh, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ethCh, CURLOPT_TIMEOUT, 2);
    $ethPrices = [];

    curl_setopt($ethCh, CURLOPT_URL, "https://www.bitstamp.net/api/v2/ticker/ethusd/");
    $res = curl_exec($ethCh);
    if ($res) { $j = json_decode($res, true); if (isset($j['last'])) $ethPrices[] = (float)$j['last']; }

    curl_setopt($ethCh, CURLOPT_URL, "https://api.kraken.com/0/public/Ticker?pair=ETHUSD");
    $res = curl_exec($ethCh);
    if ($res) { $j = json_decode($res, true); if (isset($j['result']['XETHZUSD']['c'][0])) $ethPrices[] = (float)$j['result']['XETHZUSD']['c'][0]; }

    $data['eth_price'] = count($ethPrices) > 0 ? array_sum($ethPrices) / count($ethPrices) : null;
    curl_close($ethCh);

    // 4. ETH klines (Bitstamp 3m candles)
    $ethKlinesCh = curl_init("https://www.bitstamp.net/api/v2/ohlc/ethusd/?step=180&limit=35");
    curl_setopt($ethKlinesCh, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ethKlinesCh, CURLOPT_TIMEOUT, 5);
    $res = curl_exec($ethKlinesCh);
    if ($res) { $j = json_decode($res, true); $data['eth_klines'] = $j['data']['ohlc'] ?? []; }
    curl_close($ethKlinesCh);

    // 5. SOL price (Bitstamp + Kraken)
    $solCh = curl_init();
    curl_setopt($solCh, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($solCh, CURLOPT_TIMEOUT, 2);
    $solPrices = [];

    curl_setopt($solCh, CURLOPT_URL, "https://www.bitstamp.net/api/v2/ticker/solusd/");
    $res = curl_exec($solCh);
    if ($res) { $j = json_decode($res, true); if (isset($j['last'])) $solPrices[] = (float)$j['last']; }

    curl_setopt($solCh, CURLOPT_URL, "https://api.kraken.com/0/public/Ticker?pair=SOLUSD");
    $res = curl_exec($solCh);
    if ($res) { $j = json_decode($res, true); if (isset($j['result']['SOLUSD']['c'][0])) $solPrices[] = (float)$j['result']['SOLUSD']['c'][0]; }

    $data['sol_price'] = count($solPrices) > 0 ? array_sum($solPrices) / count($solPrices) : null;
    curl_close($solCh);

    // 6. SOL klines (Bitstamp 3m candles)
    $solKlinesCh = curl_init("https://www.bitstamp.net/api/v2/ohlc/solusd/?step=180&limit=35");
    curl_setopt($solKlinesCh, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($solKlinesCh, CURLOPT_TIMEOUT, 5);
    $res = curl_exec($solKlinesCh);
    if ($res) { $j = json_decode($res, true); $data['sol_klines'] = $j['data']['ohlc'] ?? []; }
    curl_close($solKlinesCh);

    // 7. XRP price (Bitstamp + Kraken)
    $xrpCh = curl_init();
    curl_setopt($xrpCh, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($xrpCh, CURLOPT_TIMEOUT, 2);
    $xrpPrices = [];

    curl_setopt($xrpCh, CURLOPT_URL, "https://www.bitstamp.net/api/v2/ticker/xrpusd/");
    $res = curl_exec($xrpCh);
    if ($res) { $j = json_decode($res, true); if (isset($j['last'])) $xrpPrices[] = (float)$j['last']; }

    curl_setopt($xrpCh, CURLOPT_URL, "https://api.kraken.com/0/public/Ticker?pair=XRPUSD");
    $res = curl_exec($xrpCh);
    if ($res) { $j = json_decode($res, true); if (isset($j['result']['XXRPZUSD']['c'][0])) $xrpPrices[] = (float)$j['result']['XXRPZUSD']['c'][0]; }

    $data['xrp_price'] = count($xrpPrices) > 0 ? array_sum($xrpPrices) / count($xrpPrices) : null;
    curl_close($xrpCh);

    // 8. XRP klines (Bitstamp 3m candles)
    $xrpKlinesCh = curl_init("https://www.bitstamp.net/api/v2/ohlc/xrpusd/?step=180&limit=35");
    curl_setopt($xrpKlinesCh, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($xrpKlinesCh, CURLOPT_TIMEOUT, 5);
    $res = curl_exec($xrpKlinesCh);
    if ($res) { $j = json_decode($res, true); $data['xrp_klines'] = $j['data']['ohlc'] ?? []; }
    curl_close($xrpKlinesCh);

    echo json_encode($data);
    exit;
}
?>
<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Bot Dashboard | BTC / ETH / SOL Polymarket</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
    <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600&display=swap" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-annotation@3"></script>
    <style>
        body { background-color: #f8f9fa; color: #212529; font-family: 'Outfit', sans-serif; letter-spacing: -0.01em; }
        .card { background-color: #ffffff; border: 1px solid #dee2e6; border-radius: 12px; }
        .stat-card { padding: 1.5rem; }
        .stat-label { font-size: 0.75rem; color: #6c757d; text-transform: uppercase; font-weight: 700; letter-spacing: 0.05em; }
        .stat-value { font-size: 1.8rem; font-weight: 700; margin-top: 0.5rem; }
        h2, h3, .h2, .h3 { font-weight: 700; letter-spacing: -0.02em; }
        .text-win { color: #198754; }
        .text-loss { color: #dc3545; }
        .border-open { border-left: 4px solid #0d6efd !important; }
        .font-mono { font-family: 'IBM Plex Mono', monospace; font-weight: 500; }
        .table { color: #212529; border-color: #dee2e6; }
        .badge-win { background-color: #d1e7dd; color: #0f5132; border: 1px solid #badbcc; }
        .badge-loss { background-color: #f8d7da; color: #842029; border: 1px solid #f5c2c7; }
        .badge-open { background-color: #cfe2ff; color: #084298; border: 1px solid #b6d4fe; }
        .badge-emergency { background-color: #fff3cd; color: #664d03; border: 1px solid #ffecb5; }
        .hover-link:hover { text-decoration: underline !important; color: #0d6efd !important; }
        .chart-container { height: 350px; position: relative; }
        .indicator-pill { padding: 4px 10px; border-radius: 20px; font-size: 0.75rem; font-weight: 700; text-transform: uppercase; margin-right: 5px; }
        .bg-neutral { background-color: #e9ecef; color: #6c757d; }
        .macd-bar { display: inline-block; width: 6px; margin: 0 1px; vertical-align: bottom; border-radius: 2px; }
        .progress { height: 8px; border-radius: 10px; background-color: #e9ecef; }
        .signal-meter { font-size: 0.7rem; font-weight: bold; margin-bottom: 2px; display: block; }
        .card-active-header { border-bottom: 1px solid #f0f0f0; padding-bottom: 10px; margin-bottom: 15px; }
        .info-box { background: #fbfbfb; border: 1px solid #eee; border-radius: 8px; padding: 10px; }
        .gold-trade { border: 2px solid #FFD700 !important; background: linear-gradient(to right, #ffffff, #fffdf0) !important; position: relative; }
        .gold-trade::after { content: "✨ GOLD ENTRY"; position: absolute; top: -10px; right: 20px; background: #FFD700; color: #000; font-size: 0.6rem; font-weight: 800; padding: 2px 8px; border-radius: 10px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
    </style>
</head>
<body>
    <div class="container-fluid px-4 py-4">
        <div class="d-flex justify-content-between align-items-center mb-4">
            <h2 class="fw-bold m-0">⚡ BTC / ETH / SOL / XRP Scanner Dashboard</h2>
            <div id="status-tag" class="small text-muted">Sincronizando...</div>
        </div>

        <!-- BANKROLL STATS ROW -->
        <div class="row g-3 mb-4">
            <div class="col-md-3">
                <div class="card stat-card shadow-sm h-100">
                    <div class="stat-label">💼 Bankroll Efectivo</div>
                    <div id="bankroll-value" class="stat-value text-primary font-mono">$ ---.--</div>
                    <div id="bankroll-detail" class="small text-muted mt-1"></div>
                </div>
            </div>
            <div class="col-md-3">
                <div class="card stat-card shadow-sm h-100">
                    <div class="stat-label">📈 Reinversión Activa</div>
                    <div id="reinvest-value" class="stat-value font-mono text-success">$ ---.--</div>
                    <div id="reinvest-pct" class="small text-muted mt-1"></div>
                </div>
            </div>
            <div class="col-md-3">
                <div class="card stat-card shadow-sm h-100">
                    <div class="stat-label">💰 P&L Bot (Tiempo Real)</div>
                    <div id="live-pnl-value" class="stat-value font-mono">$ ---.--</div>
                    <div id="live-pnl-detail" class="small text-muted mt-1"></div>
                </div>
            </div>
            <div class="col-md-3">
                <div class="card stat-card shadow-sm h-100">
                    <div class="stat-label">⚡ Kelly Max Bet</div>
                    <div id="kelly-max-value" class="stat-value font-mono text-warning">$ ---.--</div>
                    <div id="kelly-max-detail" class="small text-muted mt-1"></div>
                </div>
            </div>
        </div>

        <!-- LIVE REAL-TIME CHART -->
        <div class="row g-4 mb-5">
            <div class="col-md-9">
                <div class="card p-4 border-open shadow-sm h-100" style="min-height: 500px;">
                    <div class="d-flex justify-content-between align-items-center mb-4">
                        <div class="stat-label text-primary">📈 Monitor de Precio vs Strike</div>
                        <div class="text-end">
                            <div id="live-btc-display" class="h3 fw-bold m-0 text-dark font-mono">$ --,---.--</div>
                            <div id="live-btc-diff" class="small font-mono"></div>
                        </div>
                    </div>
                    <div class="chart-container h-100">
                        <canvas id="liveChart"></canvas>
                    </div>
                </div>
            </div>
            <div class="col-md-3">
                <div class="card p-4 shadow-sm h-100">
                    <div class="mb-4"><center>
                        <div class="stat-label mb-2">⏳ Tiempo al Cierre</div>
                        <div id="global-timer" class="h2 fw-bold m-0 font-mono text-secondary text-center mt-2">--:--</div>
                    </div>
                    
                    <hr class="my-3">
                    <center>
                    <div class="stat-label mb-3">🛠 Indicadores Técnicos</div>
                    
                    <div id="indicator-panel" class="text-center py-4">
                        <div class="spinner-border text-light-emphasis" role="status"></div>
                    </div>

                    <div class="mt-auto">
                        <div class="stat-label small mb-2">Señal Combinada</div>
                        <div id="signal-progress-container">
                            <div class="signal-meter text-muted d-flex justify-content-between">
                                <span>DOWN</span><span id="signal-pct">50%</span><span>UP</span>
                            </div>
                            <div class="progress"><div id="signal-bar" class="progress-bar bg-primary" style="width: 50%"></div></div>
                        </div>
                    </div>
                </div>
            </div>
        </div>

        <!-- FEATURES ACTIVAS DEL BOT -->
        <div class="row mb-3">
            <div class="col-12">
                <div class="card p-2 shadow-sm" style="border-left: 4px solid #0d6efd; background:#f8f9fa;">
                    <div id="bot-features-row" class="d-flex align-items-center flex-wrap gap-2 px-2" style="min-height:32px;">
                        <span class="text-muted small">Cargando configuración del bot...</span>
                    </div>
                </div>
            </div>
        </div>

        <!-- ETH 15m MARKET PANEL -->
        <div class="row g-4 mb-4">
            <div class="col-12">
                <div class="card p-3 shadow-sm" style="border-left: 4px solid #627EEA;">
                    <div class="d-flex align-items-center gap-5 flex-wrap">
                        <!-- ETH Price -->
                        <div style="min-width:130px;">
                            <div class="stat-label" style="font-size:0.65rem; color:#627EEA; letter-spacing:0.06em;">⟠ ETHEREUM / USD</div>
                            <div id="eth-price-display" class="h4 fw-bold m-0 font-mono">$ ----.--</div>
                            <div id="eth-strike-diff" class="small font-mono text-muted"></div>
                        </div>

                        <!-- ETH Momentum -->
                        <div class="border-start ps-4">
                            <div class="stat-label small">Momentum 15m</div>
                            <div id="eth-momentum-display" class="fw-bold font-mono" style="font-size:1.05rem;">—</div>
                            <div id="eth-candle-count" class="text-muted" style="font-size:0.72rem;"></div>
                        </div>

                        <!-- ETH MACD -->
                        <div class="border-start ps-4">
                            <div class="stat-label small">MACD (5/13/5)</div>
                            <div id="eth-macd-bars" class="d-flex align-items-end" style="height:32px; gap:2px; margin-top:4px;"></div>
                        </div>

                        <!-- ETH RSI -->
                        <div class="border-start ps-4">
                            <div class="stat-label small">RSI (7)</div>
                            <div id="eth-rsi-display" class="font-mono mt-1" style="font-size:1rem;">—</div>
                        </div>

                        <!-- ETH Signal bar -->
                        <div class="border-start ps-4">
                            <div class="stat-label small mb-1">Señal</div>
                            <div class="d-flex justify-content-between" style="font-size:0.65rem; color:#6c757d; font-weight:700; width:130px;">
                                <span>DOWN</span><span id="eth-signal-pct">50%</span><span>UP</span>
                            </div>
                            <div class="progress" style="width:130px; height:8px; border-radius:10px; background:#e9ecef;">
                                <div id="eth-signal-bar" class="progress-bar bg-secondary" style="width:50%; border-radius:10px;"></div>
                            </div>
                            <div id="eth-signal-label" class="text-muted mt-1" style="font-size:0.7rem;"></div>
                        </div>

                        <!-- ETH Volatility -->
                        <div class="border-start ps-4">
                            <div class="stat-label small mb-1">Volatilidad (3m)</div>
                            <div class="d-flex flex-column gap-1" style="font-size:0.72rem; font-family:monospace;">
                                <span id="eth-vol-atr"   style="color:#0dcaf0;">ATR ---</span>
                                <span id="eth-vol-bb"    style="color:#d63384;">BB  ---</span>
                                <span id="eth-vol-spike" style="color:#ffc107;">SPK ---</span>
                            </div>
                        </div>

                        <!-- ETH Active Trade status (right-aligned) -->
                        <div class="border-start ps-4 ms-auto text-end">
                            <div class="stat-label small mb-1">Posición ETH</div>
                            <div id="eth-trade-status" class="text-muted small">Sin posición activa</div>
                        </div>
                    </div>
                </div>
            </div>
        </div>

        <!-- SOL 15m MARKET PANEL -->
        <div class="row g-4 mb-4">
            <div class="col-12">
                <div class="card p-3 shadow-sm" style="border-left: 4px solid #9945FF;">
                    <div class="d-flex align-items-center gap-5 flex-wrap">
                        <!-- SOL Price -->
                        <div style="min-width:130px;">
                            <div class="stat-label" style="font-size:0.65rem; color:#9945FF; letter-spacing:0.06em;">◎ SOLANA / USD</div>
                            <div id="sol-price-display" class="h4 fw-bold m-0 font-mono">$ ---.--</div>
                            <div id="sol-strike-diff" class="small font-mono text-muted"></div>
                        </div>

                        <!-- SOL Momentum -->
                        <div class="border-start ps-4">
                            <div class="stat-label small">Momentum 15m</div>
                            <div id="sol-momentum-display" class="fw-bold font-mono" style="font-size:1.05rem;">—</div>
                            <div id="sol-candle-count" class="text-muted" style="font-size:0.72rem;"></div>
                        </div>

                        <!-- SOL MACD -->
                        <div class="border-start ps-4">
                            <div class="stat-label small">MACD (5/13/5)</div>
                            <div id="sol-macd-bars" class="d-flex align-items-end" style="height:32px; gap:2px; margin-top:4px;"></div>
                        </div>

                        <!-- SOL RSI -->
                        <div class="border-start ps-4">
                            <div class="stat-label small">RSI (7)</div>
                            <div id="sol-rsi-display" class="font-mono mt-1" style="font-size:1rem;">—</div>
                        </div>

                        <!-- SOL Signal bar -->
                        <div class="border-start ps-4">
                            <div class="stat-label small mb-1">Señal</div>
                            <div class="d-flex justify-content-between" style="font-size:0.65rem; color:#6c757d; font-weight:700; width:130px;">
                                <span>DOWN</span><span id="sol-signal-pct">50%</span><span>UP</span>
                            </div>
                            <div class="progress" style="width:130px; height:8px; border-radius:10px; background:#e9ecef;">
                                <div id="sol-signal-bar" class="progress-bar bg-secondary" style="width:50%; border-radius:10px;"></div>
                            </div>
                            <div id="sol-signal-label" class="text-muted mt-1" style="font-size:0.7rem;"></div>
                        </div>

                        <!-- SOL Volatility -->
                        <div class="border-start ps-4">
                            <div class="stat-label small mb-1">Volatilidad (3m)</div>
                            <div class="d-flex flex-column gap-1" style="font-size:0.72rem; font-family:monospace;">
                                <span id="sol-vol-atr"  style="color:#0dcaf0;">ATR ---</span>
                                <span id="sol-vol-bb"   style="color:#d63384;">BB  ---</span>
                                <span id="sol-vol-spike" style="color:#ffc107;">SPK ---</span>
                            </div>
                        </div>

                        <!-- SOL Active Trade status (right-aligned) -->
                        <div class="border-start ps-4 ms-auto text-end">
                            <div class="stat-label small mb-1">Posición SOL</div>
                            <div id="sol-trade-status" class="text-muted small">Sin posición activa</div>
                        </div>
                    </div>
                </div>
            </div>
        </div>

        <!-- XRP 15m MARKET PANEL -->
        <div class="row g-4 mb-4">
            <div class="col-12">
                <div class="card p-3 shadow-sm" style="border-left: 4px solid #00AAE4;">
                    <div class="d-flex align-items-center gap-5 flex-wrap">
                        <!-- XRP Price -->
                        <div style="min-width:130px;">
                            <div class="stat-label" style="font-size:0.65rem; color:#00AAE4; letter-spacing:0.06em;">✕ XRP / USD</div>
                            <div id="xrp-price-display" class="h4 fw-bold m-0 font-mono">$ -.----</div>
                            <div id="xrp-strike-diff" class="small font-mono text-muted"></div>
                        </div>

                        <!-- XRP Momentum -->
                        <div class="border-start ps-4">
                            <div class="stat-label small">Momentum 15m</div>
                            <div id="xrp-momentum-display" class="fw-bold font-mono" style="font-size:1.05rem;">—</div>
                            <div id="xrp-candle-count" class="text-muted" style="font-size:0.72rem;"></div>
                        </div>

                        <!-- XRP MACD -->
                        <div class="border-start ps-4">
                            <div class="stat-label small">MACD (5/13/5)</div>
                            <div id="xrp-macd-bars" class="d-flex align-items-end" style="height:32px; gap:2px; margin-top:4px;"></div>
                        </div>

                        <!-- XRP RSI -->
                        <div class="border-start ps-4">
                            <div class="stat-label small">RSI (7)</div>
                            <div id="xrp-rsi-display" class="font-mono mt-1" style="font-size:1rem;">—</div>
                        </div>

                        <!-- XRP Signal bar -->
                        <div class="border-start ps-4">
                            <div class="stat-label small mb-1">Señal</div>
                            <div class="d-flex justify-content-between" style="font-size:0.65rem; color:#6c757d; font-weight:700; width:130px;">
                                <span>DOWN</span><span id="xrp-signal-pct">50%</span><span>UP</span>
                            </div>
                            <div class="progress" style="width:130px; height:8px; border-radius:10px; background:#e9ecef;">
                                <div id="xrp-signal-bar" class="progress-bar bg-secondary" style="width:50%; border-radius:10px;"></div>
                            </div>
                            <div id="xrp-signal-label" class="text-muted mt-1" style="font-size:0.7rem;"></div>
                        </div>

                        <!-- XRP Volatility -->
                        <div class="border-start ps-4">
                            <div class="stat-label small mb-1">Volatilidad (3m)</div>
                            <div class="d-flex flex-column gap-1" style="font-size:0.72rem; font-family:monospace;">
                                <span id="xrp-vol-atr"   style="color:#0dcaf0;">ATR ---</span>
                                <span id="xrp-vol-bb"    style="color:#d63384;">BB  ---</span>
                                <span id="xrp-vol-spike" style="color:#ffc107;">SPK ---</span>
                            </div>
                        </div>

                        <!-- XRP Active Trade status (right-aligned) -->
                        <div class="border-start ps-4 ms-auto text-end">
                            <div class="stat-label small mb-1">Posición XRP</div>
                            <div id="xrp-trade-status" class="text-muted small">Sin posición activa</div>
                        </div>
                    </div>
                </div>
            </div>
        </div>

        <!-- VOLATILITY CHARTS SECTION -->
        <div class="row g-4 mb-5">
            <div class="col-md-4">
                <div class="card p-3 shadow-sm h-100">
                    <div class="stat-label mb-2 text-info">Ruido (ATR %)</div>
                    <div style="height: 180px;"><canvas id="atrChart"></canvas></div>
                </div>
            </div>
            <div class="col-md-4">
                <div class="card p-3 shadow-sm h-100">
                    <div class="stat-label mb-2" style="color: #d63384;">Expansión (Bollinger %)</div>
                    <div style="height: 180px;"><canvas id="bbChart"></canvas></div>
                </div>
            </div>
            <div class="col-md-4">
                <div class="card p-3 shadow-sm h-100">
                    <div class="stat-label mb-2 text-warning">Violencia (Max Spike %)</div>
                    <div style="height: 180px;"><canvas id="spikeChart"></canvas></div>
                </div>
            </div>
        </div>

        <!-- Active Trades Section -->
        <div id="active-trades-section" class="mb-5">
            <div class="stat-label mb-3 text-primary">● Estado de Operación</div>
            <div id="active-trades-list"></div>
            <div id="window-status-msg"></div>
        </div>

        <!-- Chart Section -->
        <div class="card p-4 mb-5 shadow-sm">
            <div class="d-flex justify-content-between align-items-center mb-4">
                <div class="d-flex align-items-center gap-3">
                    <div class="stat-label">💰 Curva de Equidad (P&L Histórico)</div>
                    <div class="btn-group btn-group-sm" role="group">
                        <button type="button" class="btn btn-outline-secondary range-btn" onclick="setRange('4h')">4H</button>
                        <button type="button" class="btn btn-outline-secondary range-btn" onclick="setRange('12h')">12H</button>
                        <button type="button" class="btn btn-outline-secondary range-btn" onclick="setRange('1d')">1D</button>
                        <button type="button" class="btn btn-outline-secondary range-btn" onclick="setRange('1w')">1W</button>
                        <button type="button" class="btn btn-outline-secondary range-btn active" onclick="setRange('all')">TODO</button>
                    </div>
                </div>
                <div class="d-flex gap-4">
                    <div class="text-end">
                        <div class="stat-label" style="font-size: 0.65rem;">P&L Neto Total</div>
                        <div id="total-pnl" class="fw-bold" style="font-size: 1.1rem;">$0.00</div>
                    </div>
                    <div class="text-end border-start ps-4">
                        <div class="stat-label" style="font-size: 0.65rem;">Win Rate</div>
                        <div id="win-rate" class="fw-bold text-info" style="font-size: 1.1rem;">0%</div>
                    </div>
                    <div class="text-end border-start ps-4">
                        <div class="stat-label" style="font-size: 0.65rem;">Ratio W/L</div>
                        <div id="wl-ratio" class="fw-bold" style="font-size: 1.1rem;">0 / 0</div>
                    </div>
                    <div class="text-end border-start ps-4 text-primary">
                        <div class="stat-label" style="font-size: 0.65rem;">Pendiente Redeem</div>
                        <div id="pending-redeem" class="fw-bold" style="font-size: 1.1rem;">$0.00</div>
                    </div>
                </div>
            </div>
            <div class="chart-container" style="height: 200px;">
                <canvas id="equityChart"></canvas>
            </div>
        </div>

        <!-- UP vs DOWN WIN/LOSS SECTION -->
        <div class="card p-4 mb-5 shadow-sm">
            <div class="d-flex justify-content-between align-items-center mb-4">
                <div class="stat-label">⬆️⬇️ UP vs DOWN — Resultados</div>
                <div id="side-wr-stats" class="small text-muted font-mono"></div>
            </div>
            <div class="row g-4">
                <div class="col-md-4">
                    <div class="stat-label small mb-2 text-muted">WIN / LOSS por Dirección</div>
                    <div style="height:220px;"><canvas id="sideWinLossChart"></canvas></div>
                </div>
                <div class="col-md-4">
                    <div class="stat-label small mb-2 text-muted">Win Rate % por Activo × Dirección</div>
                    <div style="height:220px;"><canvas id="sideAssetWRChart"></canvas></div>
                </div>
                <div class="col-md-4">
                    <div class="stat-label small mb-2 text-muted">P&L acumulado por Dirección</div>
                    <div style="height:220px;"><canvas id="sidePnlChart"></canvas></div>
                </div>
            </div>
        </div>

        <!-- Table Section -->
        <!-- MOMENTUM ANALYSIS SECTION -->
        <div class="card p-4 mb-5 shadow-sm">
            <div class="d-flex justify-content-between align-items-center mb-4">
                <div class="stat-label">📊 Análisis de Momentum (Entry)</div>
                <div id="momentum-stats" class="small text-muted font-mono"></div>
            </div>
            <div class="row g-4">
                <div class="col-md-7">
                    <div class="stat-label small mb-2 text-muted">Momentum vs P&L por Trade</div>
                    <div style="height:220px;"><canvas id="momentumScatterChart"></canvas></div>
                </div>
                <div class="col-md-5">
                    <div class="stat-label small mb-2 text-muted">Win Rate por Rango de Momentum</div>
                    <div style="height:220px;"><canvas id="momentumWRChart"></canvas></div>
                </div>
            </div>
        </div>

        <!-- MACD SIGNAL DISTRIBUTION CHART -->
        <div class="card p-4 mb-5 shadow-sm">
            <div class="d-flex justify-content-between align-items-center mb-4">
                <div class="stat-label">📶 Señales MACD al Entry — Win Rate por Tipo</div>
                <div id="macd-signal-stats" class="small text-muted font-mono"></div>
            </div>
            <div class="row g-4">
                <div class="col-md-7">
                    <div class="stat-label small mb-2 text-muted">WIN vs LOSS por señal</div>
                    <div style="height:220px;"><canvas id="macdSignalBarChart"></canvas></div>
                </div>
                <div class="col-md-5">
                    <div class="stat-label small mb-2 text-muted">Distribución de señales usadas</div>
                    <div style="height:220px;"><canvas id="macdSignalDonutChart"></canvas></div>
                </div>
            </div>
        </div>

        <!-- P&L POR MONEDA -->
        <div class="card p-4 mb-5 shadow-sm">
            <div class="d-flex justify-content-between align-items-center mb-4">
                <div class="stat-label">🪙 P&L por Moneda</div>
                <div id="pnl-asset-stats" class="small text-muted font-mono"></div>
            </div>
            <div class="row g-4">
                <div class="col-md-8">
                    <div class="stat-label small mb-2 text-muted">P&L acumulado por moneda</div>
                    <div style="height:240px;"><canvas id="pnlAssetBarChart"></canvas></div>
                </div>
                <div class="col-md-4">
                    <div class="stat-label small mb-2 text-muted">Win Rate por moneda</div>
                    <div style="height:240px;"><canvas id="pnlAssetWRChart"></canvas></div>
                </div>
            </div>
        </div>

        <!-- RENDIMIENTO POR HORA -->
        <div class="card p-4 mb-5 shadow-sm">
            <div class="d-flex justify-content-between align-items-center mb-4">
                <div class="stat-label">🕐 Rendimiento por Hora del Día (UTC)</div>
                <div id="hourly-stats" class="small text-muted font-mono"></div>
            </div>
            <div style="height:260px;"><canvas id="hourlyPnlChart"></canvas></div>
        </div>

        <!-- WR HISTÓRICO POR DÍA -->
        <div class="card p-4 mb-5 shadow-sm">
            <div class="d-flex flex-wrap justify-content-between align-items-center gap-3 mb-4">
                <div class="stat-label">📈 Win Rate Histórico (por día)</div>
                <div class="d-flex align-items-center gap-2 flex-wrap">
                    <div class="btn-group btn-group-sm" role="group">
                        <button class="btn btn-outline-secondary wr-range-btn active" onclick="setWRRange(7)">7 días</button>
                        <button class="btn btn-outline-secondary wr-range-btn" onclick="setWRRange(30)">30 días</button>
                        <button class="btn btn-outline-secondary wr-range-btn" onclick="setWRRange(0)">Todo</button>
                    </div>
                    <input type="date" id="wr-from" class="form-control form-control-sm" style="width:140px;" onchange="applyWRDates()">
                    <span class="text-muted small">→</span>
                    <input type="date" id="wr-to"   class="form-control form-control-sm" style="width:140px;" onchange="applyWRDates()">
                </div>
                <div id="wr-summary" class="small text-muted font-mono"></div>
            </div>
            <div style="height:280px;"><canvas id="dailyWRChart"></canvas></div>
        </div>

        <!-- POLYMARKET API PORTFOLIO PANEL -->
        <div class="card p-4 mb-5 shadow-sm" id="poly-panel" style="border-top: 3px solid #6c3df4;">

            <!-- Header: title + date range controls -->
            <div class="d-flex flex-wrap justify-content-between align-items-center gap-3 mb-4">
                <div class="stat-label" style="color:#6c3df4;">🟣 Portfolio Real — Polymarket API</div>

                <div class="d-flex align-items-center gap-2 flex-wrap">
                    <!-- Quick-select buttons -->
                    <div class="btn-group btn-group-sm" role="group">
                        <button class="btn btn-outline-secondary poly-range-btn" onclick="setPolyRange(7)"   >7 días</button>
                        <button class="btn btn-outline-secondary poly-range-btn" onclick="setPolyRange(30)"  >30 días</button>
                        <button class="btn btn-outline-secondary poly-range-btn" onclick="setPolyRange(90)"  >90 días</button>
                        <button class="btn btn-outline-secondary poly-range-btn active" onclick="setPolyRange(0)">Todo</button>
                    </div>
                    <!-- Date inputs -->
                    <input type="date" id="poly-from" class="form-control form-control-sm" style="width:140px;" onchange="applyPolyDates()">
                    <span class="text-muted small">→</span>
                    <input type="date" id="poly-to"   class="form-control form-control-sm" style="width:140px;" onchange="applyPolyDates()">
                    <div id="poly-status" class="small text-muted ms-2">Cargando...</div>
                </div>
            </div>

            <!-- Row 1: Activity stats (date-filtered) -->
            <div class="row g-3 mb-3">
                <div class="col-md-3">
                    <div class="card stat-card h-100" style="background:#faf8ff; border-color:#d5c7fa;">
                        <div class="stat-label">💸 Total Gastado</div>
                        <div id="poly-spent" class="stat-value font-mono text-danger">$ ---</div>
                        <div id="poly-n-trades" class="small text-muted mt-1"></div>
                    </div>
                </div>
                <div class="col-md-3">
                    <div class="card stat-card h-100" style="background:#faf8ff; border-color:#d5c7fa;">
                        <div class="stat-label">💰 Total Redeemed</div>
                        <div id="poly-redeemed" class="stat-value font-mono text-success">$ ---</div>
                        <div id="poly-n-redeems" class="small text-muted mt-1"></div>
                    </div>
                </div>
                <div class="col-md-3">
                    <div class="card stat-card h-100" style="background:#faf8ff; border-color:#d5c7fa;">
                        <div class="stat-label">📊 Net P&L Polymarket</div>
                        <div id="poly-net-pnl" class="stat-value font-mono">$ ---</div>
                        <div id="poly-pnl-detail" class="small text-muted mt-1"></div>
                    </div>
                </div>
                <div class="col-md-3">
                    <div class="card stat-card h-100" style="background:#fff8e1; border-color:#ffe082;">
                        <div class="stat-label">⚖️ Bot vs Polymarket</div>
                        <div id="poly-vs-bot" class="stat-value font-mono text-muted">$ ---</div>
                        <div id="poly-vs-detail" class="small text-muted mt-1"></div>
                    </div>
                </div>
            </div>

            <!-- Row 2: Live wallet state (always full, no date filter) -->
            <div class="row g-3 mb-4">
                <div class="col-md-3">
                    <div class="card stat-card h-100" style="background:#f0fff4; border-color:#b2dfdb;">
                        <div class="stat-label">💼 Portfolio (posiciones abiertas)</div>
                        <div id="poly-portfolio" class="stat-value font-mono text-primary">$ ---</div>
                        <div id="poly-n-positions" class="small text-muted mt-1"></div>
                    </div>
                </div>
                <div class="col-md-3">
                    <div class="card stat-card h-100" style="background:#f0fff4; border-color:#b2dfdb;">
                        <div class="stat-label">💵 Cash / Bankroll</div>
                        <div id="poly-cash" class="stat-value font-mono text-success">$ ---</div>
                        <div id="poly-cash-detail" class="small text-muted mt-1">Bankroll del bot (proxy)</div>
                    </div>
                </div>
                <div class="col-md-3">
                    <div class="card stat-card h-100" style="background:#f0fff4; border-color:#b2dfdb;">
                        <div class="stat-label">🏦 Capital Total Estimado</div>
                        <div id="poly-total-wallet" class="stat-value font-mono">$ ---</div>
                        <div id="poly-wallet-detail" class="small text-muted mt-1">Portfolio + Bankroll</div>
                    </div>
                </div>
                <div class="col-md-3">
                    <div class="card stat-card h-100" style="background:#f8f8f8; border-color:#dee2e6;">
                        <div class="stat-label" style="font-size:0.65rem;">🔄 Última actualización</div>
                        <div id="poly-accounts-ts" class="stat-value font-mono text-muted" style="font-size:1rem; margin-top:0.75rem;">---</div>
                        <div class="small text-muted mt-1"><button class="btn btn-sm btn-outline-secondary py-0 px-2" onclick="fetchPolyAccounts(true)" style="font-size:0.7rem;">↻ Actualizar</button></div>
                    </div>
                </div>
            </div>

            <!-- Daily P&L chart + Recent activity table -->
            <div class="row g-4">
                <div class="col-md-5">
                    <div class="stat-label small mb-2 text-muted">P&L Diario (Redeemed − Gastado) — rango seleccionado</div>
                    <div style="height:220px;"><canvas id="polyDailyChart"></canvas></div>
                </div>
                <div class="col-md-7">
                    <div class="stat-label small mb-2 text-muted">Transacciones — rango seleccionado</div>
                    <div style="max-height:220px; overflow-y:auto;">
                        <table class="table table-sm table-hover" style="font-size:0.78rem;">
                            <thead class="sticky-top bg-white">
                                <tr>
                                    <th class="border-0">Fecha (VN)</th>
                                    <th class="border-0">Tipo</th>
                                    <th class="border-0">Mercado</th>
                                    <th class="border-0 text-end">USDC</th>
                                </tr>
                            </thead>
                            <tbody id="poly-activity-body"></tbody>
                        </table>
                    </div>
                </div>
            </div>
        </div>

        <!-- MARKET LOGS (NEAR-MISSES) TABLE -->
        <div class="card p-4 shadow-sm mb-4">
            <div class="d-flex align-items-center gap-3 mb-3">
                <div class="stat-label mb-0">Near-Misses — Evaluaciones de Mercado</div>
                <button class="btn btn-sm btn-outline-secondary py-0 px-2" onclick="fetchMarketLogs()" style="font-size:0.75rem;">↺ Actualizar</button>
            </div>
            <div class="table-responsive">
                <table class="table table-hover table-sm mb-0">
                    <thead>
                        <tr>
                            <th class="border-0">Asset</th>
                            <th class="border-0">Cierre (VN)</th>
                            <th class="border-0 text-center">Outcome</th>
                            <th class="border-0 text-center">Evals</th>
                            <th class="border-0">Decisiones</th>
                            <th class="border-0 text-center">Chart</th>
                        </tr>
                    </thead>
                    <tbody id="market-logs-table-body">
                        <tr><td colspan="6" class="text-muted text-center py-3">Cargando...</td></tr>
                    </tbody>
                </table>
            </div>
            <div id="market-logs-pagination" class="mt-3 d-flex justify-content-center gap-2 align-items-center"></div>
        </div>

        <!-- TRADES TABLE -->
        <div class="card p-4 shadow-sm">
            <div class="stat-label mb-4">Últimas Posiciones</div>
            <div class="table-responsive">
                <table class="table table-hover">
                    <thead>
                        <tr>
                            <th class="border-0">Fecha</th>
                            <th class="border-0">Asset</th>
                            <th class="border-0">Side</th>
                            <th class="border-0 text-center">Strike</th>
                            <th class="border-0 text-center">Costo</th>
                            <th class="border-0 text-center">Estado</th>
                            <th class="border-0 text-center">Señal MACD</th>
                            <th class="border-0 text-center">Momentum</th>
                            <th class="border-0 text-center">Vol (Entry)</th>
                            <th class="border-0 text-center">CLOB</th>
                            <th class="border-0 text-center">Factor</th>
                            <th class="border-0 text-end">Profit</th>
                        </tr>
                    </thead>
                    <tbody id="trades-table-body" class="border-top-0">
                        <!-- Se inyecta vía JS -->
                    </tbody>
                </table>
            </div>
            <div id="trades-pagination" class="mt-3 d-flex justify-content-center gap-2 align-items-center"></div>
        </div>

        <!-- ANALYZER TRADES -->
        <div class="card p-4 shadow-sm mt-4">
            <div class="d-flex justify-content-between align-items-center mb-3">
                <div class="stat-label">Analizador — Trades Simulados</div>
                <div class="d-flex align-items-center gap-3">
                    <span id="analyzer-summary" class="small text-muted"></span>
                    <button class="btn btn-sm btn-outline-secondary" onclick="fetchAnalyzerTrades()">↺ Actualizar</button>
                </div>
            </div>
            <div class="table-responsive">
                <table class="table table-hover">
                    <thead>
                        <tr>
                            <th class="border-0">Fecha</th>
                            <th class="border-0">Asset</th>
                            <th class="border-0">Side</th>
                            <th class="border-0 text-center">Strike</th>
                            <th class="border-0 text-center">Costo</th>
                            <th class="border-0 text-center">Estado</th>
                            <th class="border-0 text-center">Señal MACD</th>
                            <th class="border-0 text-center">Momentum</th>
                            <th class="border-0 text-center">Vol (Entry)</th>
                            <th class="border-0 text-center">CLOB</th>
                            <th class="border-0 text-center">Factor</th>
                            <th class="border-0 text-center">Salida</th>
                            <th class="border-0 text-end">Profit</th>
                            <th class="border-0 text-center">Gráf.</th>
                        </tr>
                    </thead>
                    <tbody id="analyzer-table-body" class="border-top-0">
                        <tr><td colspan="14" class="text-muted text-center py-3">Ejecuta el analizador para ver trades</td></tr>
                    </tbody>
                </table>
            </div>
            <div id="analyzer-pagination" class="mt-3 d-flex justify-content-center gap-2 align-items-center"></div>
        </div>
    </div>

    <script>
        // URL dinámica — funciona tanto en localhost:8080 como en el servidor de producción
        const API = window.location.pathname;

        // Chart.js Plugin para la línea vertical en hover
        const hoverLinePlugin = {
            id: 'hoverLine',
            afterDraw(chart, args, options) {
                if (chart.tooltip && chart.tooltip.active && chart.tooltip.active.length) {
                    const ctx = chart.ctx;
                    ctx.save();
                    const x = chart.tooltip.active[0].element.x;
                    const topY = chart.scales.y.top;
                    const bottomY = chart.scales.y.bottom;

                    // Dibujar la línea vertical
                    ctx.beginPath();
                    ctx.moveTo(x, topY);
                    ctx.lineTo(x, bottomY);
                    ctx.lineWidth = (options && options.lineWidth) || 1;
                    ctx.strokeStyle = (options && options.lineColor) || '#adb5bd'; 
                    ctx.stroke();
                    ctx.restore();
                }
            }
        };
        Chart.register(hoverLinePlugin); // Registrar el plugin

        let equityChart = null;
        let liveChart = null;
        let volCharts = { atr: null, bb: null, spike: null };
        let sideWinLossChart = null;
        let sideAssetWRChart = null;
        let sidePnlChart     = null;
        let momentumScatterChart = null;
        let momentumWRChart = null;
        let macdSignalBarChart = null;
        let macdSignalDonutChart = null;
        let pnlAssetBarChart = null;
        let pnlAssetWRChart = null;
        let hourlyPnlChart = null;
        let dailyWRChart   = null;
        let wrRangeDays    = 7;
        let wrFromDate     = null;
        let wrToDate       = null;
        let _wrAllResolved = [];
        let priceHistory = [];
        let labelHistory = [];
        let unixHistory = [];
        let macdHistory = [];
        let globalTargetDate = null;
        let lastResolvedCount = -1;
        let selectedRange = 'all';
        let lastRange = 'all';
        let lastData = null;

        // Paginación
        let tradesPage = 0;
        let marketLogsPage = 0;
        const PAGE_SIZE = 20;

        function setRange(range) {
            selectedRange = range;
            document.querySelectorAll('.range-btn').forEach(btn => {
                const text = btn.innerText.toLowerCase();
                const isMatch = (range === 'all' && text === 'todo') || (text === range.toLowerCase());
                btn.classList.toggle('active', isMatch);
            });
            if (lastData) renderDashboard(lastData);
        }

        // Configuración sincronizada con el bot de Python
        const ATR_THRESHOLDS = {
            "BTC": 0.080,
            "ETH": 0.100,
            "SOL": 0.140,
            "XRP": 0.065
        };
        const MAX_BANDWIDTH_PCT = 1.2; // 1.2%
        const MAX_CANDLE_SPIKE_PCT = 0.70;

        const MAX_HISTORY = 180; // 3 minutos de historia con scroll fluido de 1s

        async function fetchData() {
            try {
                const res = await fetch(`${API}?ajax=1`);
                const result = await res.json();
                renderDashboard(result);
                await updateVolatilityCharts();
                document.getElementById('status-tag').innerText = 'Ult. Sync (VN): ' + new Date().toLocaleTimeString('es-ES', {timeZone: 'Asia/Ho_Chi_Minh', hour12: false});
            } catch (e) {
                console.error("Error en fetchData:", e);
            }
        }

        // Helper genérico para renderizar botones de paginación
        function renderPaginationUI(id, totalPages, currentPage, onPageChange) {
            const el = document.getElementById(id);
            if (!el) return;
            if (totalPages <= 1) { el.innerHTML = ''; return; }

            // Sanitizar el ID para usarlo como nombre de función (evitar guiones que rompen el onclick)
            const fnName = id.replace(/-/g, '_') + 'Go';

            el.innerHTML = `
                <button class="btn btn-sm btn-outline-primary" ${currentPage === 0 ? 'disabled' : ''} onclick="${fnName}(${currentPage - 1})">« Anterior</button>
                <span class="small text-muted font-mono">Página ${currentPage + 1} de ${totalPages}</span>
                <button class="btn btn-sm btn-outline-primary" ${currentPage >= totalPages - 1 ? 'disabled' : ''} onclick="${fnName}(${currentPage + 1})">Siguiente »</button>
            `;
            window[fnName] = onPageChange;
        }

        function renderBotFeatures(data) {
            const el = document.getElementById('bot-features-row');
            if (!el) return;

            const rsiOn   = data.use_rsi_filter;
            const kellOn  = data.use_dynamic_kelly;
            const clobOn  = data.use_clob_volume;
            const rsiVal  = data.bot_rsi   !== undefined ? data.bot_rsi.toFixed(1)  : '—';
            const rsiSig  = data.bot_rsi_signal || '—';
            const kellMul = data.dynamic_kelly_mult !== undefined ? data.dynamic_kelly_mult.toFixed(2) : '1.30';
            const kellCon = data.dynamic_kelly_conf !== undefined ? (data.dynamic_kelly_conf * 100).toFixed(0) : '80';

            const rsiColor  = rsiSig === 'UP' ? '#198754' : rsiSig === 'DOWN' ? '#dc3545' : '#6c757d';
            const onBadge  = `background:#19875420; color:#198754; border:1px solid #19875440;`;
            const offBadge = `background:#e9ecef; color:#6c757d; border:1px solid #dee2e6;`;

            el.innerHTML = `
                <span class="stat-label me-3" style="font-size:0.65rem;">⚙ FILTROS ACTIVOS</span>
                <span class="indicator-pill me-2" style="${rsiOn ? onBadge : offBadge}">
                    RSI(7) ${rsiOn ? '✓' : '✗'} — BTC: <b style="color:${rsiColor}">${rsiVal}</b> [${rsiSig}]
                </span>
                <span class="indicator-pill me-2" style="${kellOn ? onBadge : offBadge}">
                    Kelly Dinámico ${kellOn ? '✓' : '✗'} — ×${kellMul} si conf ≥${kellCon}%
                </span>
                <span class="indicator-pill me-2" style="${clobOn ? onBadge : offBadge}">
                    CLOB Libro ${clobOn ? '✓' : '✗'}
                </span>
            `;
        }

        function renderBankroll(data) {
            const bankroll    = data.bankroll;
            const base        = data.base_bankroll;
            const totalPnl    = data.total_pnl;
            if (bankroll === null || bankroll === undefined) return;

            const reinvested  = bankroll - base;
            const reinvestPct = base > 0 ? (reinvested / base * 100) : 0;
            const multiplier  = base > 0 ? (bankroll / base) : 1;

            // Bankroll efectivo
            document.getElementById('bankroll-value').innerText = `$${bankroll.toFixed(2)}`;
            document.getElementById('bankroll-detail').innerText =
                `base $${base.toFixed(0)} × ${multiplier.toFixed(2)}x`;

            // Reinversión
            const reinEl = document.getElementById('reinvest-value');
            reinEl.innerText = `$${reinvested.toFixed(2)}`;
            reinEl.className = `stat-value font-mono ${reinvested >= 0 ? 'text-success' : 'text-muted'}`;
            document.getElementById('reinvest-pct').innerText =
                `${reinvestPct.toFixed(1)}% sobre base`;

            // P&L del bot (en tiempo real desde el archivo del bot)
            const pnlEl = document.getElementById('live-pnl-value');
            if (totalPnl !== null && totalPnl !== undefined) {
                pnlEl.innerText = `${totalPnl >= 0 ? '+' : ''}$${totalPnl.toFixed(2)}`;
                pnlEl.className = `stat-value font-mono ${totalPnl >= 0 ? 'text-win' : 'text-loss'}`;
                const avgTrade = data.trades && data.trades.filter(t => t.resolved).length > 0
                    ? totalPnl / data.trades.filter(t => t.resolved).length : 0;
                document.getElementById('live-pnl-detail').innerText =
                    `avg $${avgTrade >= 0 ? '+' : ''}${avgTrade.toFixed(3)}/trade`;
            }

            // Kelly Max estimado con bankroll actual
            // KELLY_FRACTION=0.25, KELLY_MAX_BET=35, prob típico ~0.65, entry ~0.52
            const kellyEst = Math.min(35, bankroll * 0.25 * 0.52);
            document.getElementById('kelly-max-value').innerText = `$${kellyEst.toFixed(2)}`;
            document.getElementById('kelly-max-detail').innerText =
                `cap $35 · fracción 25%`;
        }

        function renderDashboard(data) {
            lastData = data;
            const trades = data.trades;
            const btcPrice = data.btc_price;
            const ethPrice = data.eth_price;
            const solPrice = data.sol_price;
            const klines = data.klines;
            const ethKlines = data.eth_klines;
            const solKlines = data.sol_klines;
            const xrpKlines = data.xrp_klines;
            const xrpPrice  = data.xrp_price || null;

            renderBankroll(data);
            renderBotFeatures(data);

            // Procesar Indicadores BTC
            let tech = null;
            if (klines && klines.length > 20) {
                tech = calculateIndicators(klines);
                renderIndicators(tech, klines);
            }

            // Procesar Indicadores ETH
            if (ethKlines && ethKlines.length > 20) {
                const ethTech = calculateIndicators(ethKlines);
                const ethVol  = calculateVolatility(ethKlines);
                renderEthPanel(ethTech, ethPrice, trades, ethVol);
            } else if (ethPrice) {
                renderEthPanel(null, ethPrice, trades, null);
            }

            // Procesar Indicadores SOL
            if (solKlines && solKlines.length > 20) {
                const solTech = calculateIndicators(solKlines);
                const solVol  = calculateVolatility(solKlines);
                renderSolPanel(solTech, solPrice, trades, solVol);
            } else if (solPrice) {
                renderSolPanel(null, solPrice, trades, null);
            }

            // Procesar Indicadores XRP
            if (xrpKlines && xrpKlines.length > 20) {
                const xrpTech = calculateIndicators(xrpKlines);
                const xrpVol  = calculateVolatility(xrpKlines);
                renderXrpPanel(xrpTech, xrpPrice, trades, xrpVol);
            } else if (xrpPrice) {
                renderXrpPanel(null, xrpPrice, trades, null);
            }

            const currentMacd = tech ? tech.macdHist[tech.macdHist.length - 1] : 0;
            if (btcPrice) {
                // Obtener el trade BTC más reciente activo
                const activeTrade = trades.filter(t => !t.resolved && (t.asset === 'BTC' || !t.asset)).pop();

                document.getElementById('live-btc-display').innerText = `BTC $${btcPrice.toLocaleString(undefined, {minimumFractionDigits: 2})}`;

                const diffEl = document.getElementById('live-btc-diff');
                updateLiveBTCInfo(btcPrice, activeTrade, diffEl);
                updateLiveChart(btcPrice, trades, currentMacd, data.current_strike);
            }

            if (!trades || trades.length === 0) return;

            // Trades activos por asset
            const btcActive = trades.filter(t => !t.resolved && (t.asset === 'BTC' || !t.asset)).pop();
            const ethActive = trades.filter(t => !t.resolved && t.asset === 'ETH').pop();
            const solActive = trades.filter(t => !t.resolved && t.asset === 'SOL').pop();
            const xrpActive = trades.filter(t => !t.resolved && t.asset === 'XRP').pop();
            renderActiveTrades(btcActive, btcPrice, data.current_strike, ethActive, ethPrice, solActive, solPrice, xrpActive, xrpPrice);

            // Actualizar fecha objetivo para el cronómetro global (usa el trade activo más próximo a vencer)
            const anyActive = btcActive || ethActive || solActive || xrpActive;
            if (anyActive && anyActive.end_date) {
                globalTargetDate = new Date(anyActive.end_date);
            } else {
                globalTargetDate = null;
            }

            // Stats
            const resolved = trades.filter(t => t.resolved);
            
            // Filtrado dinámico por rango de tiempo para el gráfico y stats
            const now = new Date();
            const filteredResolved = resolved.filter(t => {
                if (selectedRange === 'all') return true;
                const tradeDate = new Date(t.end_date);
                const diffMs = now - tradeDate;
                const hours = { '4h': 4, '12h': 12, '1d': 24, '1w': 168 }[selectedRange];
                return diffMs <= hours * 3600 * 1000;
            });

            // Sort filteredResolved by end_date in ascending order for chart display
            filteredResolved.sort((a, b) => new Date(a.end_date) - new Date(b.end_date));
            
            // Actualizar etiquetas de stats basadas en el rango
            const totalPnl = filteredResolved.reduce((acc, t) => acc + t.pnl, 0);
            const wins = filteredResolved.filter(t => t.outcome === 'WIN' || t.outcome === 'TAKE PROFIT' || t.pnl > 0).length;
            const losses = filteredResolved.length - wins;
            const wr = filteredResolved.length > 0 ? (wins / filteredResolved.length) * 100 : 0;
            
            // El "Pendiente Redeem" siempre se calcula sobre el total histórico (es dinero real en la wallet)
            const pendingRedeem = resolved.filter(t => t.outcome === 'WIN' && t.real).reduce((acc, t) => acc + (t.contracts * 1.0), 0);

            document.getElementById('pending-redeem').innerText = `$${pendingRedeem.toFixed(2)}`;
            document.getElementById('total-pnl').innerText = `$${totalPnl.toFixed(2)}`;
            document.getElementById('total-pnl').className = `fw-bold ${totalPnl >= 0 ? 'text-win' : 'text-loss'}`;
            document.getElementById('win-rate').innerText = `${wr.toFixed(1)}%`;
            document.getElementById('wl-ratio').innerText = `${wins}W / ${losses}L`;

            // Solo recrear el gráfico si cambió la cantidad de trades filtrados o el rango
            // O si la página actual cambió
            if (filteredResolved.length !== lastResolvedCount || selectedRange !== lastRange || data._forceTradesRender) {
                lastResolvedCount = filteredResolved.length;
                lastRange = selectedRange;
                renderHistoryChart(filteredResolved);
                renderTradesTable(trades);
                renderSideWRCharts(resolved);
                renderMomentumCharts(resolved);
                renderMacdSignalCharts(resolved);
                renderPnlByAsset(resolved);
                renderHourlyChart(resolved);
                _wrAllResolved = resolved;
                renderDailyWRChart(resolved);
            }
        }

        function updateLiveBTCInfo(btcPrice, activeTrade, diffEl) {
            if (activeTrade) {
                const usdDiff = btcPrice - activeTrade.strike;
                const pctDiff = (usdDiff / activeTrade.strike) * 100;
                const isWinning = (activeTrade.side === 'Up' && btcPrice > activeTrade.strike) || (activeTrade.side === 'Down' && btcPrice < activeTrade.strike);
                const statusColor = isWinning ? 'text-win' : 'text-loss';
                const sign = usdDiff >= 0 ? '+' : '';
                diffEl.innerText = `${sign}${usdDiff.toFixed(2)} USD (${sign}${pctDiff.toFixed(3)}%)`;
                diffEl.className = `small font-mono fw-bold ${statusColor}`;
            } else {
                diffEl.innerText = '';
            }
        }

        function renderHourlyChart(resolved) {
            if (!resolved || resolved.length === 0) return;

            const hours = {};
            for (let h = 0; h < 24; h++) hours[h] = { pnl: 0, wins: 0, total: 0 };

            for (const t of resolved) {
                const ed = t.end_date || '';
                if (!ed) continue;
                try {
                    const h = new Date(ed).getUTCHours();
                    hours[h].pnl   += t.pnl || 0;
                    hours[h].total++;
                    if (t.outcome === 'WIN' || t.outcome === 'TAKE PROFIT') hours[h].wins++;
                } catch(e) {}
            }

            const labels  = Array.from({length:24}, (_, h) => h.toString().padStart(2,'0') + 'h');
            const pnlData = Array.from({length:24}, (_, h) => parseFloat((hours[h].pnl).toFixed(2)));
            const wrData  = Array.from({length:24}, (_, h) => hours[h].total > 0 ? parseFloat((hours[h].wins / hours[h].total * 100).toFixed(1)) : null);

            const bestH  = pnlData.reduce((b, v, i) => v > pnlData[b] ? i : b, 0);
            const worstH = pnlData.reduce((b, v, i) => v < pnlData[b] ? i : b, 0);
            document.getElementById('hourly-stats').textContent =
                `Mejor: ${labels[bestH]} ($${pnlData[bestH] >= 0 ? '+' : ''}${pnlData[bestH].toFixed(2)})  |  Peor: ${labels[worstH]} ($${pnlData[worstH].toFixed(2)})`;

            const ctx = document.getElementById('hourlyPnlChart').getContext('2d');
            if (hourlyPnlChart) hourlyPnlChart.destroy();
            hourlyPnlChart = new Chart(ctx, {
                type: 'bar',
                data: {
                    labels,
                    datasets: [
                        {
                            label: 'P&L ($)',
                            data: pnlData,
                            backgroundColor: pnlData.map(v => v >= 0 ? 'rgba(25,135,84,0.75)' : 'rgba(220,53,69,0.75)'),
                            borderColor:     pnlData.map(v => v >= 0 ? '#198754' : '#dc3545'),
                            borderWidth: 1,
                            borderRadius: 4,
                            yAxisID: 'yPnl',
                            order: 2,
                        },
                        {
                            label: 'Win Rate (%)',
                            data: wrData,
                            type: 'line',
                            borderColor: 'rgba(255,193,7,0.9)',
                            backgroundColor: 'rgba(255,193,7,0.15)',
                            pointBackgroundColor: wrData.map(v => v === null ? 'transparent' : v >= 55 ? '#198754' : v <= 45 ? '#dc3545' : '#ffc107'),
                            pointRadius: 4,
                            borderWidth: 2,
                            tension: 0.3,
                            yAxisID: 'yWr',
                            order: 1,
                            spanGaps: true,
                        }
                    ]
                },
                options: {
                    responsive: true,
                    maintainAspectRatio: false,
                    interaction: { mode: 'index', intersect: false },
                    plugins: {
                        legend: { labels: { font: { size: 11 } } },
                        tooltip: {
                            callbacks: {
                                afterBody: (items) => {
                                    const h = items[0].dataIndex;
                                    const d = hours[h];
                                    return d.total > 0 ? [`Trades: ${d.total}  W:${d.wins} L:${d.total - d.wins}`] : ['Sin trades'];
                                }
                            }
                        }
                    },
                    scales: {
                        yPnl: {
                            type: 'linear', position: 'left',
                            grid: { color: 'rgba(0,0,0,0.06)' },
                            ticks: { callback: v => (v >= 0 ? '+' : '') + '$' + v.toFixed(0) }
                        },
                        yWr: {
                            type: 'linear', position: 'right',
                            min: 0, max: 100,
                            grid: { drawOnChartArea: false },
                            ticks: { callback: v => v + '%' }
                        },
                        x: { grid: { display: false } }
                    }
                }
            });
        }

        function setWRRange(days) {
            wrRangeDays = days;
            wrFromDate  = null;
            wrToDate    = null;
            document.getElementById('wr-from').value = '';
            document.getElementById('wr-to').value   = '';
            document.querySelectorAll('.wr-range-btn').forEach(b => {
                const t = b.innerText.trim();
                b.classList.toggle('active',
                    (days === 7  && t === '7 días')  ||
                    (days === 30 && t === '30 días') ||
                    (days === 0  && t === 'Todo'));
            });
            renderDailyWRChart(_wrAllResolved);
        }

        function applyWRDates() {
            wrFromDate  = document.getElementById('wr-from').value || null;
            wrToDate    = document.getElementById('wr-to').value   || null;
            wrRangeDays = 0;
            document.querySelectorAll('.wr-range-btn').forEach(b => b.classList.remove('active'));
            renderDailyWRChart(_wrAllResolved);
        }

        function renderDailyWRChart(resolved) {
            if (!resolved || !resolved.length) return;

            // Filtrar por rango
            const now = new Date();
            const trades = resolved.filter(t => {
                if (!t.end_date) return false;
                const d = new Date(t.end_date);
                if (wrFromDate && d < new Date(wrFromDate)) return false;
                if (wrToDate   && d > new Date(wrToDate + 'T23:59:59')) return false;
                if (!wrFromDate && !wrToDate && wrRangeDays > 0) {
                    return (now - d) <= wrRangeDays * 86400 * 1000;
                }
                return true;
            });

            if (!trades.length) return;

            // Agrupar por día UTC
            const byDay = {};
            for (const t of trades) {
                const day = t.end_date.slice(0, 10);
                if (!byDay[day]) byDay[day] = { wins: 0, total: 0, pnl: 0 };
                byDay[day].total++;
                byDay[day].pnl += t.pnl || 0;
                if (t.outcome === 'WIN' || t.outcome === 'TAKE PROFIT' || t.pnl > 0) byDay[day].wins++;
            }

            const days    = Object.keys(byDay).sort();
            const wrData  = days.map(d => parseFloat((byDay[d].wins / byDay[d].total * 100).toFixed(1)));
            const nData   = days.map(d => byDay[d].total);
            const pnlData = days.map(d => parseFloat(byDay[d].pnl.toFixed(2)));

            // WR acumulado
            let cumWins = 0, cumTotal = 0;
            const cumWR = days.map(d => {
                cumWins  += byDay[d].wins;
                cumTotal += byDay[d].total;
                return parseFloat((cumWins / cumTotal * 100).toFixed(1));
            });

            // Labels en formato corto VN (GMT+7)
            const labels = days.map(d => {
                const dt = new Date(d + 'T12:00:00Z');
                return dt.toLocaleDateString('es-ES', { timeZone: 'Asia/Ho_Chi_Minh', month: 'short', day: 'numeric' });
            });

            // Resumen
            const totalT  = trades.length;
            const totalW  = trades.filter(t => t.outcome === 'WIN' || t.outcome === 'TAKE PROFIT' || t.pnl > 0).length;
            const globalWR = (totalW / totalT * 100).toFixed(1);
            const totalPnl = trades.reduce((s, t) => s + (t.pnl || 0), 0);
            const pnlColor = totalPnl >= 0 ? '#198754' : '#dc3545';
            document.getElementById('wr-summary').innerHTML =
                `${totalT} trades &nbsp;|&nbsp; WR global <strong>${globalWR}%</strong> &nbsp;|&nbsp; PnL <strong style="color:${pnlColor}">${totalPnl >= 0 ? '+' : ''}$${totalPnl.toFixed(2)}</strong>`;

            const ctx = document.getElementById('dailyWRChart').getContext('2d');
            if (dailyWRChart) dailyWRChart.destroy();
            dailyWRChart = new Chart(ctx, {
                data: {
                    labels,
                    datasets: [
                        {
                            type: 'bar',
                            label: 'Trades',
                            data: nData,
                            backgroundColor: 'rgba(108,117,125,0.25)',
                            borderColor: 'rgba(108,117,125,0.5)',
                            borderWidth: 1,
                            borderRadius: 4,
                            yAxisID: 'yN',
                            order: 3,
                        },
                        {
                            type: 'line',
                            label: 'WR diario (%)',
                            data: wrData,
                            borderColor: '#ffc107',
                            backgroundColor: 'rgba(255,193,7,0.12)',
                            pointBackgroundColor: wrData.map(v => v >= 65 ? '#198754' : v <= 45 ? '#dc3545' : '#ffc107'),
                            pointRadius: 5,
                            pointHoverRadius: 7,
                            borderWidth: 2,
                            tension: 0.3,
                            yAxisID: 'yWR',
                            order: 1,
                        },
                        {
                            type: 'line',
                            label: 'WR acumulado (%)',
                            data: cumWR,
                            borderColor: 'rgba(13,110,253,0.8)',
                            backgroundColor: 'transparent',
                            pointRadius: 3,
                            borderWidth: 2,
                            borderDash: [5, 3],
                            tension: 0.3,
                            yAxisID: 'yWR',
                            order: 2,
                        }
                    ]
                },
                options: {
                    responsive: true,
                    maintainAspectRatio: false,
                    interaction: { mode: 'index', intersect: false },
                    plugins: {
                        legend: { labels: { font: { size: 11 } } },
                        tooltip: {
                            callbacks: {
                                afterBody: items => {
                                    const i   = items[0].dataIndex;
                                    const day = days[i];
                                    const d   = byDay[day];
                                    return [`W: ${d.wins}  L: ${d.total - d.wins}  PnL: ${d.pnl >= 0 ? '+' : ''}$${d.pnl.toFixed(2)}`];
                                }
                            }
                        },
                        annotation: {
                            annotations: {
                                line65: {
                                    type: 'line',
                                    yMin: 65, yMax: 65,
                                    yScaleID: 'yWR',
                                    borderColor: 'rgba(25,135,84,0.4)',
                                    borderWidth: 1,
                                    borderDash: [4, 4],
                                    label: { content: '65%', display: true, position: 'end', font: { size: 10 }, color: '#198754', backgroundColor: 'transparent' }
                                }
                            }
                        }
                    },
                    scales: {
                        yWR: {
                            type: 'linear', position: 'left',
                            min: 0, max: 100,
                            ticks: { callback: v => v + '%', font: { size: 11 } },
                            grid: { color: 'rgba(0,0,0,0.06)' }
                        },
                        yN: {
                            type: 'linear', position: 'right',
                            min: 0,
                            ticks: { stepSize: 1, font: { size: 10 } },
                            grid: { drawOnChartArea: false }
                        },
                        x: { grid: { display: false }, ticks: { font: { size: 11 } } }
                    }
                }
            });
        }

        function renderHistoryChart(resolved) {
            const history = [0];
            const chartLabels = ["Inicio"];
            let rolling = 0;

            let useFullDateFormat = false;
            if (resolved.length > 0) {
                const firstTradeDate = new Date(resolved[0].end_date);
                const lastTradeDate = new Date(resolved[resolved.length - 1].end_date);
                const timeDiff = Math.abs(lastTradeDate.getTime() - firstTradeDate.getTime());
                const oneDay = 24 * 60 * 60 * 1000; // milliseconds in one day
                if (timeDiff > oneDay) {
                    useFullDateFormat = true;
                }
            }

            resolved.forEach(t => { 
                rolling += t.pnl; 
                history.push(rolling); 
                const tradeDate = new Date(t.end_date);
                let formattedTime;
                if (useFullDateFormat) {
                    formattedTime = tradeDate.toLocaleDateString('es-ES', {timeZone: 'Asia/Ho_Chi_Minh', day: '2-digit', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit', hour12: false});
                } else {
                    formattedTime = tradeDate.toLocaleTimeString('es-ES', {timeZone: 'Asia/Ho_Chi_Minh', hour: '2-digit', minute: '2-digit', hour12: false});
                }
                chartLabels.push(formattedTime);
            });

            if (equityChart) equityChart.destroy();
            const ctx = document.getElementById('equityChart').getContext('2d');
            equityChart = new Chart(ctx, {
                type: 'line',
                data: {
                    labels: chartLabels,
                    datasets: [{
                        data: history,
                        borderColor: '#198754',
                        backgroundColor: 'rgba(25, 135, 84, 0.1)',
                        fill: true, tension: 0.6, borderWidth: 3, pointRadius: 2
                    }]
                },
                options: {
                    responsive: true,
                    maintainAspectRatio: false,
                    plugins: {
                        legend: { display: false },
                        hoverLine: { // Opciones para nuestro plugin
                            lineWidth: 1,
                            lineColor: '#adb5bd'
                        },
                        tooltip: {
                            backgroundColor: 'rgba(33, 37, 41, 0.95)',
                            titleFont: { size: 17, family: 'Outfit', weight: '700' },
                            bodyFont: { size: 17, family: 'Outfit' },
                            padding: 12,
                            cornerRadius: 10,
                            displayColors: false
                        }
                    },
                    interaction: { mode: 'index', intersect: false }, // Activar tooltip en hover sobre el eje X
                    scales: { x: { display: true, ticks: { color: '#6c757d', maxRotation: 0, autoSkip: true, maxTicksLimit: 10 }, grid: { drawOnChartArea: false } }, y: { grid: { color: '#e9ecef' } } }
                }
            });
        }

        function renderSideWRCharts(resolved) {
            if (!resolved || resolved.length === 0) return;
            const isWin = t => t.outcome === 'WIN' || t.outcome === 'TAKE PROFIT' || t.outcome === 'MARKET';
            const assets  = ['BTC', 'ETH', 'SOL', 'XRP'];
            const sides   = ['Up', 'Down'];
            const assetColors = { BTC: '#f7931a', ETH: '#627eea', SOL: '#9945ff', XRP: '#00aae4' };

            // ── Stats summary ──────────────────────────────────────────────────
            const upTrades   = resolved.filter(t => t.side === 'Up');
            const downTrades = resolved.filter(t => t.side === 'Down');
            const upWins     = upTrades.filter(isWin).length;
            const downWins   = downTrades.filter(isWin).length;
            const upWR       = upTrades.length   ? (upWins   / upTrades.length   * 100).toFixed(1) : '–';
            const downWR     = downTrades.length ? (downWins / downTrades.length * 100).toFixed(1) : '–';
            document.getElementById('side-wr-stats').innerText =
                `↑ UP ${upWins}W/${upTrades.length - upWins}L (${upWR}%)  |  ↓ DOWN ${downWins}W/${downTrades.length - downWins}L (${downWR}%)`;

            // ── Chart 1: Grouped bar — WIN/LOSS per direction ──────────────────
            const ctx1 = document.getElementById('sideWinLossChart').getContext('2d');
            if (sideWinLossChart) sideWinLossChart.destroy();
            sideWinLossChart = new Chart(ctx1, {
                type: 'bar',
                data: {
                    labels: ['↑ UP', '↓ DOWN'],
                    datasets: [
                        {
                            label: 'WIN',
                            data: [upWins, downWins],
                            backgroundColor: 'rgba(25,135,84,0.82)',
                            borderRadius: 5,
                            borderWidth: 0
                        },
                        {
                            label: 'LOSS',
                            data: [upTrades.length - upWins, downTrades.length - downWins],
                            backgroundColor: 'rgba(220,53,69,0.72)',
                            borderRadius: 5,
                            borderWidth: 0
                        }
                    ]
                },
                options: {
                    responsive: true, maintainAspectRatio: false,
                    plugins: {
                        legend: { labels: { font: { size: 11 }, boxWidth: 12 } },
                        tooltip: {
                            callbacks: {
                                afterBody: (items) => {
                                    const idx  = items[0].dataIndex;
                                    const list = idx === 0 ? upTrades : downTrades;
                                    const w    = idx === 0 ? upWins : downWins;
                                    const wr   = list.length ? (w / list.length * 100).toFixed(1) : '–';
                                    const pnl  = list.reduce((a, t) => a + (t.pnl || 0), 0).toFixed(2);
                                    return [`WR: ${wr}%  |  PnL: $${pnl}`];
                                }
                            }
                        }
                    },
                    scales: {
                        x: { ticks: { font: { size: 13, weight: '600' } }, grid: { display: false } },
                        y: { ticks: { font: { size: 10 }, stepSize: 1 }, grid: { color: '#f0f0f0' },
                             title: { display: true, text: 'Trades', font: { size: 10 } } }
                    }
                }
            });

            // ── Chart 2: Win Rate per asset × direction ────────────────────────
            const labels2 = [];
            const wrData2 = [];
            const bgColors2 = [];
            for (const side of sides) {
                for (const asset of assets) {
                    const grp  = resolved.filter(t => t.side === side && (t.asset||'').toUpperCase() === asset);
                    if (!grp.length) continue;
                    const wr2 = grp.filter(isWin).length / grp.length * 100;
                    labels2.push(`${asset} ${side === 'Up' ? '↑' : '↓'}`);
                    wrData2.push(parseFloat(wr2.toFixed(1)));
                    const alpha = side === 'Up' ? 'cc' : '88';
                    bgColors2.push((assetColors[asset] || '#999') + alpha);
                }
            }
            const ctx2 = document.getElementById('sideAssetWRChart').getContext('2d');
            if (sideAssetWRChart) sideAssetWRChart.destroy();
            sideAssetWRChart = new Chart(ctx2, {
                type: 'bar',
                data: {
                    labels: labels2,
                    datasets: [{
                        label: 'Win Rate %',
                        data: wrData2,
                        backgroundColor: bgColors2,
                        borderRadius: 5,
                        borderWidth: 0
                    }]
                },
                options: {
                    responsive: true, maintainAspectRatio: false,
                    plugins: {
                        legend: { display: false },
                        tooltip: {
                            callbacks: {
                                label: ctx => {
                                    const parts = ctx.label.split(' ');
                                    const ast   = parts[0];
                                    const sid   = ctx.label.includes('↑') ? 'Up' : 'Down';
                                    const grp   = resolved.filter(t => t.side === sid && (t.asset||'').toUpperCase() === ast);
                                    const w     = grp.filter(isWin).length;
                                    return `${ctx.formattedValue}%  (${w}W/${grp.length - w}L)`;
                                }
                            }
                        },
                        annotation: {}
                    },
                    scales: {
                        x: { ticks: { font: { size: 9 } }, grid: { display: false } },
                        y: {
                            min: 0, max: 100,
                            ticks: { font: { size: 9 }, callback: v => v + '%' },
                            grid: { color: '#f0f0f0' }
                        }
                    }
                },
                plugins: [{
                    id: 'refLine50',
                    afterDraw(chart) {
                        const { ctx, chartArea, scales } = chart;
                        if (!scales.y) return;
                        const y = scales.y.getPixelForValue(50);
                        ctx.save();
                        ctx.setLineDash([4, 4]);
                        ctx.strokeStyle = 'rgba(108,117,125,0.5)';
                        ctx.lineWidth = 1;
                        ctx.beginPath();
                        ctx.moveTo(chartArea.left, y);
                        ctx.lineTo(chartArea.right, y);
                        ctx.stroke();
                        ctx.restore();
                    }
                }]
            });

            // ── Chart 3: PnL acumulado per direction ──────────────────────────
            const upPnl   = upTrades.reduce((a, t) => a + (t.pnl || 0), 0);
            const downPnl = downTrades.reduce((a, t) => a + (t.pnl || 0), 0);
            const pnlByAssetSide = [];
            const pnlLabels3     = [];
            const pnlColors3     = [];
            for (const side of sides) {
                for (const asset of assets) {
                    const grp = resolved.filter(t => t.side === side && (t.asset||'').toUpperCase() === asset);
                    if (!grp.length) continue;
                    const pnl3 = grp.reduce((a, t) => a + (t.pnl || 0), 0);
                    pnlLabels3.push(`${asset} ${side === 'Up' ? '↑' : '↓'}`);
                    pnlByAssetSide.push(parseFloat(pnl3.toFixed(2)));
                    pnlColors3.push(pnl3 >= 0 ? (assetColors[asset] || '#999') + 'bb' : 'rgba(220,53,69,0.7)');
                }
            }
            const ctx3 = document.getElementById('sidePnlChart').getContext('2d');
            if (sidePnlChart) sidePnlChart.destroy();
            sidePnlChart = new Chart(ctx3, {
                type: 'bar',
                data: {
                    labels: pnlLabels3,
                    datasets: [{
                        label: 'P&L $',
                        data: pnlByAssetSide,
                        backgroundColor: pnlColors3,
                        borderRadius: 5,
                        borderWidth: 0
                    }]
                },
                options: {
                    responsive: true, maintainAspectRatio: false,
                    plugins: {
                        legend: { display: false },
                        tooltip: {
                            callbacks: {
                                label: ctx => `$${ctx.formattedValue}`
                            }
                        }
                    },
                    scales: {
                        x: { ticks: { font: { size: 9 } }, grid: { display: false } },
                        y: {
                            ticks: { font: { size: 9 }, callback: v => `$${v}` },
                            grid: { color: '#f0f0f0' }
                        }
                    }
                },
                plugins: [{
                    id: 'refLine0',
                    afterDraw(chart) {
                        const { ctx, chartArea, scales } = chart;
                        if (!scales.y) return;
                        const y = scales.y.getPixelForValue(0);
                        ctx.save();
                        ctx.strokeStyle = 'rgba(108,117,125,0.6)';
                        ctx.lineWidth = 1;
                        ctx.beginPath();
                        ctx.moveTo(chartArea.left, y);
                        ctx.lineTo(chartArea.right, y);
                        ctx.stroke();
                        ctx.restore();
                    }
                }]
            });
        }

        function renderMomentumCharts(resolved) {
            if (!resolved || resolved.length === 0) return;

            const isWin = t => t.outcome === 'WIN' || t.outcome === 'TAKE PROFIT';

            // ── Scatter: 4 datasets — Up/Down × WIN/LOSS ──────────────────────
            // ● círculo = Up  |  ▲ triángulo = Down
            // verde = WIN     |  rojo = LOSS
            const mkPoint = t => ({ x: t.momentum || 0, y: t.pnl, side: t.side });
            const upWin   = resolved.filter(t =>  isWin(t) && t.side === 'Up').map(mkPoint);
            const upLoss  = resolved.filter(t => !isWin(t) && t.side === 'Up'   && t.outcome === 'LOSS').map(mkPoint);
            const downWin = resolved.filter(t =>  isWin(t) && t.side === 'Down').map(mkPoint);
            const downLoss= resolved.filter(t => !isWin(t) && t.side === 'Down' && t.outcome === 'LOSS').map(mkPoint);

            const totalWins = upWin.length + downWin.length;
            const totalLoss = upLoss.length + downLoss.length;
            const avgMom = resolved.reduce((a,t) => a + (t.momentum||0), 0) / (resolved.length || 1);
            document.getElementById('momentum-stats').innerText =
                `${resolved.length} trades | avg momentum: ${avgMom >= 0 ? '+' : ''}${avgMom.toFixed(3)}% | ` +
                `Up ${upWin.length}W/${upLoss.length}L  Down ${downWin.length}W/${downLoss.length}L`;

            const scatterCtx = document.getElementById('momentumScatterChart').getContext('2d');
            if (momentumScatterChart) momentumScatterChart.destroy();
            momentumScatterChart = new Chart(scatterCtx, {
                type: 'scatter',
                data: {
                    datasets: [
                        { label: '● Up WIN',   data: upWin,   pointStyle: 'circle',   pointRadius: 5, pointHoverRadius: 8, backgroundColor: 'rgba(25,135,84,0.75)',  borderColor: 'rgba(25,135,84,0.9)',  borderWidth: 1 },
                        { label: '● Up LOSS',  data: upLoss,  pointStyle: 'circle',   pointRadius: 5, pointHoverRadius: 8, backgroundColor: 'rgba(220,53,69,0.6)',   borderColor: 'rgba(220,53,69,0.85)', borderWidth: 1 },
                        { label: '▲ Down WIN', data: downWin, pointStyle: 'triangle', pointRadius: 6, pointHoverRadius: 9, backgroundColor: 'rgba(25,135,84,0.45)',  borderColor: 'rgba(25,135,84,0.7)',  borderWidth: 1 },
                        { label: '▲ Down LOSS',data: downLoss,pointStyle: 'triangle', pointRadius: 6, pointHoverRadius: 9, backgroundColor: 'rgba(220,53,69,0.35)',  borderColor: 'rgba(220,53,69,0.6)',  borderWidth: 1 }
                    ]
                },
                options: {
                    responsive: true, maintainAspectRatio: false,
                    plugins: {
                        legend: { labels: { font: { size: 10 }, boxWidth: 12, usePointStyle: true } },
                        tooltip: {
                            callbacks: {
                                label: ctx => {
                                    const d = ctx.raw;
                                    const sign = d.x >= 0 ? '+' : '';
                                    return `${ctx.dataset.label.slice(2)}  mom: ${sign}${d.x.toFixed(3)}%  P&L: $${d.y >= 0 ? '+' : ''}${d.y.toFixed(2)}`;
                                }
                            }
                        }
                    },
                    scales: {
                        x: {
                            title: { display: true, text: 'Momentum % al entrar', font: { size: 10 }, color: '#6c757d' },
                            grid: { color: '#f0f0f0' },
                            ticks: { font: { size: 9 }, color: '#999', callback: v => `${v >= 0 ? '+' : ''}${v.toFixed(2)}%` }
                        },
                        y: {
                            title: { display: true, text: 'P&L ($)', font: { size: 10 }, color: '#6c757d' },
                            grid: { color: '#f0f0f0' },
                            ticks: { font: { size: 9 }, color: '#999', callback: v => `$${v >= 0 ? '+' : ''}${v.toFixed(0)}` }
                        }
                    }
                }
            });

            // ── Bar chart agrupado: WR% por rango — Up (azul) vs Down (naranja) ─
            const buckets = [
                { label: '< -0.3%',      lo: -99,  hi: -0.3 },
                { label: '-0.3 → -0.1%', lo: -0.3, hi: -0.1 },
                { label: '-0.1 → +0.1%', lo: -0.1, hi:  0.1 },
                { label: '+0.1 → +0.3%', lo:  0.1, hi:  0.3 },
                { label: '> +0.3%',      lo:  0.3, hi:  99  }
            ];

            const wrLabels = [], upWRData = [], downWRData = [], upCounts = [], downCounts = [];
            buckets.forEach(b => {
                const inRange = t => { const m = t.momentum || 0; return m >= b.lo && m < b.hi; };
                const upBt   = resolved.filter(t => t.side === 'Up'   && inRange(t));
                const downBt = resolved.filter(t => t.side === 'Down' && inRange(t));
                const calcWR = arr => arr.length ? parseFloat(((arr.filter(isWin).length / arr.length) * 100).toFixed(1)) : null;
                wrLabels.push(b.label);
                upWRData.push(calcWR(upBt));
                downWRData.push(calcWR(downBt));
                upCounts.push(upBt.length);
                downCounts.push(downBt.length);
            });

            const wrCtx = document.getElementById('momentumWRChart').getContext('2d');
            if (momentumWRChart) momentumWRChart.destroy();
            momentumWRChart = new Chart(wrCtx, {
                type: 'bar',
                data: {
                    labels: wrLabels,
                    datasets: [
                        {
                            label: '▲ Up WR%',
                            data: upWRData,
                            backgroundColor: upWRData.map(v => v === null ? '#eee' : v >= 55 ? 'rgba(25,135,84,0.8)' : v < 45 ? 'rgba(220,53,69,0.7)' : 'rgba(255,193,7,0.8)'),
                            borderRadius: 4, borderWidth: 0
                        },
                        {
                            label: '▼ Down WR%',
                            data: downWRData,
                            backgroundColor: downWRData.map(v => v === null ? '#eee' : v >= 55 ? 'rgba(25,135,84,0.4)' : v < 45 ? 'rgba(220,53,69,0.35)' : 'rgba(255,193,7,0.4)'),
                            borderRadius: 4, borderWidth: 1,
                            borderColor: downWRData.map(v => v === null ? '#ccc' : v >= 55 ? 'rgba(25,135,84,0.7)' : v < 45 ? 'rgba(220,53,69,0.6)' : 'rgba(255,193,7,0.7)')
                        }
                    ]
                },
                options: {
                    responsive: true, maintainAspectRatio: false, indexAxis: 'y',
                    plugins: {
                        legend: { labels: { font: { size: 10 }, boxWidth: 12 } },
                        tooltip: {
                            callbacks: {
                                label: ctx => {
                                    const counts = ctx.datasetIndex === 0 ? upCounts : downCounts;
                                    const n = counts[ctx.dataIndex];
                                    return ctx.raw === null ? 'Sin datos' : `WR: ${ctx.raw}%  (${n} trades)`;
                                }
                            }
                        }
                    },
                    scales: {
                        x: {
                            min: 0, max: 100,
                            ticks: { font: { size: 9 }, color: '#999', callback: v => v + '%' },
                            grid: { color: '#f0f0f0' }
                        },
                        y: { ticks: { font: { size: 10 }, color: '#555' }, grid: { display: false } }
                    }
                }
            });
        }

        function renderMacdSignalCharts(resolved) {
            if (!resolved || resolved.length === 0) return;
            resolved = resolved.filter(t => t.macd_signal && t.macd_signal.trim() !== '');
            if (resolved.length === 0) return;
            const isWin = t => t.outcome === 'WIN' || t.outcome === 'TAKE PROFIT';
            const signalOrder = ['BULL CROSS', 'BULL REVERSAL', 'BULLISH', 'BEAR CROSS', 'BEAR REVERSAL', 'BEARISH'];
            const sigBgColors = {
                'BULL CROSS':    '#198754',
                'BULL REVERSAL': '#0dcaf0',
                'BULLISH':       '#90ee90',
                'BEAR CROSS':    '#dc3545',
                'BEAR REVERSAL': '#fd7e14',
                'BEARISH':       '#6f42c1',
            };

            // Bucket trades by signal
            const bySignal = {};
            resolved.forEach(t => {
                const s = (t.macd_signal || 'SIN SEÑAL').toUpperCase();
                if (!bySignal[s]) bySignal[s] = { win: 0, loss: 0 };
                if (isWin(t)) bySignal[s].win++;
                else bySignal[s].loss++;
            });

            const usedSignals = signalOrder.filter(s => bySignal[s]);
            const otherKeys   = Object.keys(bySignal).filter(s => !signalOrder.includes(s));
            const allSignals  = [...usedSignals, ...otherKeys];

            const winData  = allSignals.map(s => bySignal[s].win);
            const lossData = allSignals.map(s => bySignal[s].loss);
            const totalPerSig = allSignals.map(s => bySignal[s].win + bySignal[s].loss);
            const barColors = allSignals.map(s => sigBgColors[s] || '#6c757d');

            document.getElementById('macd-signal-stats').innerText =
                `${resolved.length} trades con señal registrada`;

            // Bar chart: WIN vs LOSS per signal
            const barCtx = document.getElementById('macdSignalBarChart').getContext('2d');
            if (macdSignalBarChart) macdSignalBarChart.destroy();
            macdSignalBarChart = new Chart(barCtx, {
                type: 'bar',
                data: {
                    labels: allSignals,
                    datasets: [
                        { label: 'WIN',  data: winData,  backgroundColor: 'rgba(25,135,84,0.8)',  borderRadius: 4, borderWidth: 0 },
                        { label: 'LOSS', data: lossData, backgroundColor: 'rgba(220,53,69,0.7)',  borderRadius: 4, borderWidth: 0 }
                    ]
                },
                options: {
                    responsive: true, maintainAspectRatio: false,
                    plugins: {
                        legend: { labels: { font: { size: 10 }, boxWidth: 12 } },
                        tooltip: {
                            callbacks: {
                                afterLabel: ctx => {
                                    const total = totalPerSig[ctx.dataIndex];
                                    const wr = total ? (winData[ctx.dataIndex] / total * 100).toFixed(0) : 0;
                                    return `WR: ${wr}%  (${total} trades)`;
                                }
                            }
                        }
                    },
                    scales: {
                        x: { ticks: { font: { size: 9 }, color: '#555' }, grid: { display: false } },
                        y: { ticks: { font: { size: 9 }, color: '#999', stepSize: 1 }, grid: { color: '#f0f0f0' } }
                    }
                }
            });

            // Donut: distribution of signals used
            const donutCtx = document.getElementById('macdSignalDonutChart').getContext('2d');
            if (macdSignalDonutChart) macdSignalDonutChart.destroy();
            macdSignalDonutChart = new Chart(donutCtx, {
                type: 'doughnut',
                data: {
                    labels: allSignals,
                    datasets: [{
                        data: totalPerSig,
                        backgroundColor: barColors.map(c => c + 'cc'),
                        borderColor: barColors,
                        borderWidth: 2
                    }]
                },
                options: {
                    responsive: true, maintainAspectRatio: false,
                    plugins: {
                        legend: { position: 'right', labels: { font: { size: 10 }, boxWidth: 12 } },
                        tooltip: {
                            callbacks: {
                                label: ctx => {
                                    const total = ctx.dataset.data.reduce((a,b) => a+b, 0);
                                    const pct = (ctx.raw / total * 100).toFixed(0);
                                    return `${ctx.label}: ${ctx.raw} (${pct}%)`;
                                }
                            }
                        }
                    }
                }
            });
        }

        function renderPnlByAsset(resolved) {
            if (!resolved || resolved.length === 0) return;

            const assets = ['BTC', 'ETH', 'SOL', 'XRP'];
            const colors = {
                BTC: { bg: 'rgba(247,147,26,0.75)',  border: '#f7931a' },
                ETH: { bg: 'rgba(98,126,234,0.75)',   border: '#627eea' },
                SOL: { bg: 'rgba(153,69,255,0.75)',   border: '#9945ff' },
                XRP: { bg: 'rgba(0,170,228,0.75)',    border: '#00aae4' },
            };

            const stats = {};
            for (const a of assets) {
                stats[a] = { pnl: 0, wins: 0, losses: 0, n: 0, invested: 0 };
            }

            for (const t of resolved) {
                const a = (t.asset || 'BTC').toUpperCase();
                if (!stats[a]) stats[a] = { pnl: 0, wins: 0, losses: 0, n: 0, invested: 0 };
                stats[a].pnl      += t.pnl;
                stats[a].invested += t.position;
                stats[a].n++;
                if (t.outcome === 'WIN' || t.outcome === 'TAKE PROFIT') stats[a].wins++;
                else stats[a].losses++;
            }

            // Summary text
            const totalPnl = Object.values(stats).reduce((s, v) => s + v.pnl, 0);
            const best = assets.reduce((b, a) => stats[a].pnl > stats[b].pnl ? a : b, 'BTC');
            document.getElementById('pnl-asset-stats').textContent =
                `Total: ${totalPnl >= 0 ? '+' : ''}$${totalPnl.toFixed(2)}  |  Mejor: ${best}`;

            // Bar chart: P&L acumulado por asset
            const barCtx = document.getElementById('pnlAssetBarChart').getContext('2d');
            if (pnlAssetBarChart) pnlAssetBarChart.destroy();
            pnlAssetBarChart = new Chart(barCtx, {
                type: 'bar',
                data: {
                    labels: assets,
                    datasets: [{
                        label: 'P&L ($)',
                        data: assets.map(a => parseFloat(stats[a].pnl.toFixed(2))),
                        backgroundColor: assets.map(a => stats[a].pnl >= 0 ? colors[a].bg : 'rgba(220,53,69,0.7)'),
                        borderColor:     assets.map(a => stats[a].pnl >= 0 ? colors[a].border : '#dc3545'),
                        borderWidth: 2,
                        borderRadius: 6,
                    }, {
                        label: 'Invertido ($)',
                        data: assets.map(a => parseFloat(stats[a].invested.toFixed(2))),
                        backgroundColor: assets.map(a => colors[a].bg.replace('0.75', '0.2')),
                        borderColor:     assets.map(a => colors[a].border),
                        borderWidth: 1,
                        borderRadius: 6,
                        borderDash: [4,2],
                    }]
                },
                options: {
                    responsive: true, maintainAspectRatio: false,
                    plugins: {
                        legend: { labels: { font: { size: 11 } } },
                        tooltip: {
                            callbacks: {
                                afterLabel: (ctx) => {
                                    const a = assets[ctx.dataIndex];
                                    const s = stats[a];
                                    const wr = s.n > 0 ? (s.wins / s.n * 100).toFixed(1) : '0.0';
                                    return `Trades: ${s.n}  |  WR: ${wr}%  |  W:${s.wins} L:${s.losses}`;
                                }
                            }
                        }
                    },
                    scales: {
                        y: {
                            grid: { color: 'rgba(255,255,255,0.05)' },
                            ticks: { callback: v => '$' + v.toFixed(0) }
                        },
                        x: { grid: { display: false } }
                    }
                }
            });

            // Horizontal bar: Win Rate por asset
            const wrCtx = document.getElementById('pnlAssetWRChart').getContext('2d');
            if (pnlAssetWRChart) pnlAssetWRChart.destroy();
            pnlAssetWRChart = new Chart(wrCtx, {
                type: 'bar',
                data: {
                    labels: assets,
                    datasets: [{
                        label: 'Win Rate %',
                        data: assets.map(a => stats[a].n > 0 ? parseFloat((stats[a].wins / stats[a].n * 100).toFixed(1)) : 0),
                        backgroundColor: assets.map(a => {
                            const wr = stats[a].n > 0 ? stats[a].wins / stats[a].n : 0;
                            return wr >= 0.55 ? colors[a].bg : 'rgba(220,53,69,0.65)';
                        }),
                        borderColor: assets.map(a => {
                            const wr = stats[a].n > 0 ? stats[a].wins / stats[a].n : 0;
                            return wr >= 0.55 ? colors[a].border : '#dc3545';
                        }),
                        borderWidth: 2,
                        borderRadius: 6,
                    }]
                },
                options: {
                    indexAxis: 'y',
                    responsive: true, maintainAspectRatio: false,
                    plugins: {
                        legend: { display: false },
                        tooltip: {
                            callbacks: {
                                label: (ctx) => {
                                    const a = assets[ctx.dataIndex];
                                    const s = stats[a];
                                    return ` ${ctx.raw}%  (${s.wins}W / ${s.losses}L  de ${s.n} trades)`;
                                }
                            }
                        }
                    },
                    scales: {
                        x: {
                            min: 0, max: 100,
                            grid: { color: 'rgba(255,255,255,0.05)' },
                            ticks: { callback: v => v + '%' }
                        },
                        y: { grid: { display: false } }
                    },
                    plugins2: {
                        annotation: {
                            annotations: {
                                breakeven: {
                                    type: 'line', xMin: 50, xMax: 50,
                                    borderColor: 'rgba(255,255,255,0.3)',
                                    borderWidth: 1, borderDash: [4,4],
                                }
                            }
                        }
                    }
                }
            });
        }

        function renderTradesTable(trades) {
            const formatVN = (iso) => iso ? new Date(iso).toLocaleString('es-ES', {timeZone: 'Asia/Ho_Chi_Minh', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false}) : '---';
            
            // Ordenar trades por fecha de cierre (vencimiento) de forma descendente
            const sortedTrades = [...trades].sort((a, b) => {
                return new Date(b.end_date) - new Date(a.end_date);
            });

            const totalPages = Math.ceil(sortedTrades.length / PAGE_SIZE);
            if (tradesPage >= totalPages && totalPages > 0) tradesPage = totalPages - 1;

            const start = tradesPage * PAGE_SIZE;
            const pageTrades = sortedTrades.slice(start, start + PAGE_SIZE);

            const tableBody = document.getElementById('trades-table-body');
            tableBody.innerHTML = pageTrades.map(t => {
                const pnlColor = t.pnl >= 0 ? 'text-win' : 'text-loss';
                const outcomeText = t.outcome || 'OPEN';
                const roiPct = t.position > 0 ? (t.pnl / t.position * 100).toFixed(1) : '0.0';

                const entryT = formatVN(t.entry_time);
                const exitT = formatVN(t.exit_time);
                const asset = t.asset || 'BTC';
                const assetBadge = asset === 'ETH'
                    ? `<span class="badge" style="background:#627EEA;color:white;font-size:0.7rem;">⟠ ETH</span>`
                    : asset === 'SOL'
                    ? `<span class="badge" style="background:#9945FF;color:white;font-size:0.7rem;">◎ SOL</span>`
                    : asset === 'XRP'
                    ? `<span class="badge" style="background:#00AAE4;color:white;font-size:0.7rem;">✕ XRP</span>`
                    : `<span class="badge" style="background:#F7931A;color:white;font-size:0.7rem;">₿ BTC</span>`;
                const assetLabel = asset === 'ETH' ? 'ETH' : asset === 'SOL' ? 'SOL' : asset === 'XRP' ? 'XRP' : 'BTC';
                const statsInfo = `ENTRADA: ${entryT} | SALIDA: ${exitT} | MOMENTUM: ${(t.momentum || 0).toFixed(4)}% | CONF: ${((t.confidence || 0) * 100).toFixed(0)}% | ${assetLabel} ENT: $${(t.entry_btc || 0).toLocaleString()} | ${assetLabel} SAL: $${(t.exit_btc || 0).toLocaleString()} | KELLY: $${(t.kelly_size || 0).toFixed(2)} | ORDER: ${t.order_id || 'N/A'}`;

                let outcomeClass = 'badge-open';

                if (t.resolved) {
                    if (outcomeText === 'WIN' || outcomeText === 'TAKE PROFIT') outcomeClass = 'badge-win';
                    else if (outcomeText === 'LOSS') outcomeClass = 'badge-loss';
                    else outcomeClass = 'badge-emergency';
                }

                // Columna momentum: valor + barra visual + confianza
                const mom = t.momentum || 0;
                const conf = (t.confidence || 0) * 100;
                const momColor = mom > 0.1 ? '#198754' : mom < -0.1 ? '#dc3545' : '#6c757d';
                const momBg = mom > 0.1 ? '#d1e7dd' : mom < -0.1 ? '#f8d7da' : '#e9ecef';
                const momSign = mom >= 0 ? '+' : '';
                const momAbsPct = Math.min(Math.abs(mom) / 0.5 * 100, 100); // escala: 0.5% = 100%
                const momInfo = `<div style="font-size:0.68rem; line-height:1.4;">
                    <span style="color:${momColor}; font-weight:700; font-family:monospace;">${momSign}${mom.toFixed(3)}%</span>
                    <div style="background:#e9ecef; border-radius:3px; height:4px; margin:2px 0; overflow:hidden;">
                        <div style="background:${momColor}; width:${momAbsPct}%; height:4px; border-radius:3px;"></div>
                    </div>
                    <span style="color:#6c757d;">conf ${conf.toFixed(0)}%</span>
                </div>`;

                // Badge señal MACD
                const sig = (t.macd_signal || '').toUpperCase();
                const sigColors = {
                    'BULL CROSS':     { bg: '#198754', text: 'white' },
                    'BULL REVERSAL':  { bg: '#0dcaf0', text: '#000'  },
                    'BULLISH':        { bg: '#90ee90', text: '#000'  },
                    'BEAR CROSS':     { bg: '#dc3545', text: 'white' },
                    'BEAR REVERSAL':  { bg: '#fd7e14', text: 'white' },
                    'BEARISH':        { bg: '#6f42c1', text: 'white' },
                };
                const sc = sigColors[sig] || { bg: '#6c757d', text: 'white' };
                const sigBadge = sig
                    ? `<span class="badge" style="background:${sc.bg};color:${sc.text};font-size:0.65rem;white-space:normal;text-align:center;max-width:90px;">${sig}</span>`
                    : '<span class="text-muted small">—</span>';

                // Formatear info de volatilidad si existe
                const volInfo = t.vol_atr ? `<div style="font-size:0.65rem; line-height:1.1;">ATR: ${t.vol_atr.toFixed(2)}%<br>BB: ${t.vol_bb.toFixed(2)}%<br>SPK: ${t.vol_spike.toFixed(2)}%</div>` : '<span class="text-muted">---</span>';

                // Badge CLOB
                const clobSig = (t.clob_signal || '').toUpperCase();
                const clobBadge = clobSig === 'BUY_PRESSURE'
                    ? `<span class="badge" style="background:#198754;color:white;font-size:0.62rem;">↑ BUY</span>`
                    : clobSig === 'SELL_PRESSURE'
                    ? `<span class="badge" style="background:#dc3545;color:white;font-size:0.62rem;">↓ SELL</span>`
                    : `<span class="text-muted small">—</span>`;

                // Badge Factor Bayesiano
                const tFact = t.factor || 1.0;
                const tDirR = (t.direction || '').toUpperCase();
                let factorBadgeR = '<span class="text-muted small">×1.00</span>';
                if (Math.abs(tFact - 1.0) > 0.01) {
                    const fc = tFact > 1.0 ? '#198754' : '#dc3545';
                    factorBadgeR = `<span class="badge" style="background:${fc};color:white;font-size:0.62rem;" title="${asset} ${tDirR}">${tFact > 1.0 ? '↑' : '↓'}×${tFact.toFixed(2)}</span>`;
                }

                const fechaVN = t.end_date ? new Date(t.end_date).toLocaleString('es-ES', {timeZone: 'Asia/Ho_Chi_Minh', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', hour12: false}) : '---';
                
                const goldClass = t.is_gold_minute ? 'style="background: #fffdf0; border-left: 4px solid #FFD700;"' : '';

                return `
                    <tr ${goldClass}>
                        <td>
                            <a href="https://polymarket.com/event/${t.slug}" target="_blank" class="text-decoration-none text-dark hover-link" title="Ver mercado en Polymarket">${t.is_gold_minute ? '✨ ':''}${fechaVN} VN</a>
                        </td>
                        <td>${assetBadge}</td>
                        <td><span class="badge ${t.side === 'Up' ? 'bg-success' : 'bg-danger'}">${t.side}</span></td>
                        <td class="text-center text-secondary small">$${t.strike.toLocaleString()}</td>
                        <td class="text-center">$${t.position.toFixed(2)}</td>
                        <td class="text-center">
                            <span class="badge ${outcomeClass}" title="${statsInfo}" style="cursor:help;">${outcomeText}</span>
                        </td>
                        <td class="text-center">${sigBadge}</td>
                        <td class="text-center">${momInfo}</td>
                        <td class="text-center text-secondary">
                            ${volInfo}
                        </td>
                        <td class="text-center">${clobBadge}</td>
                        <td class="text-center">${factorBadgeR}</td>
                        <td class="text-end ${pnlColor} fw-bold">
                            ${t.pnl >= 0 ? '+' : ''}$${t.pnl.toFixed(2)}
                            <span class="text-muted fw-normal small" style="font-size: 0.75rem;">(${t.pnl >= 0 ? '+' : ''}${roiPct}%)</span>
                        </td>
                        <td class="text-center">
                            ${t.log_file ? `<button class="btn btn-outline-secondary btn-sm py-0 px-1" style="font-size:0.7rem;" onclick="openTradeChart('${t.log_file.split('/').pop()}')">📊</button>` : '<span class="text-muted" style="font-size:0.7rem;">—</span>'}
                        </td>
                    </tr>
                `;
            }).join('');

            renderPaginationUI('trades-pagination', totalPages, tradesPage, (p) => {
                tradesPage = p;
                renderTradesTable(trades);
            });

            prefetchTradeLogs(trades); // precargar logs en background mientras el usuario lee la tabla
        }

        function renderEthPanel(ethTech, ethPrice, trades, ethVol) {
            // Precio ETH
            const priceEl = document.getElementById('eth-price-display');
            if (ethPrice) {
                priceEl.innerText = `$${ethPrice.toLocaleString(undefined, {minimumFractionDigits: 2})}`;
            }

            // Trade ETH activo (para diff vs strike)
            const ethActive = trades ? trades.filter(t => !t.resolved && t.asset === 'ETH').pop() : null;
            const diffEl = document.getElementById('eth-strike-diff');
            if (ethActive && ethPrice) {
                const usdDiff = ethPrice - ethActive.strike;
                const pctDiff = (usdDiff / ethActive.strike) * 100;
                const isWin = (ethActive.side === 'Up' && ethPrice > ethActive.strike) || (ethActive.side === 'Down' && ethPrice < ethActive.strike);
                diffEl.innerText = `${usdDiff >= 0 ? '+' : ''}${usdDiff.toFixed(2)} (${pctDiff >= 0 ? '+' : ''}${pctDiff.toFixed(3)}%)`;
                diffEl.className = `small font-mono fw-bold ${isWin ? 'text-win' : 'text-loss'}`;
            } else {
                diffEl.innerText = '';
            }

            // Indicadores técnicos ETH
            if (!ethTech) return;

            // Momentum
            const momEl = document.getElementById('eth-momentum-display');
            const momColor = ethTech.changePct >= 0 ? '#198754' : '#dc3545';
            momEl.innerHTML = `<span style="color:${momColor};">${ethTech.changePct >= 0 ? '↑' : '↓'} ${Math.abs(ethTech.changePct).toFixed(3)}%</span>`;
            document.getElementById('eth-candle-count').innerText = `${ethTech.up}↑ / ${ethTech.down}↓ velas`;

            // MACD bars
            const macdContainer = document.getElementById('eth-macd-bars');
            macdContainer.innerHTML = ethTech.macdHist.map(h => {
                const hPx = Math.min(Math.abs(h) * 5, 28);
                const bColor = h >= 0 ? '#198754' : '#dc3545';
                return `<div style="width:7px; height:${Math.max(hPx,2)}px; background:${bColor}; border-radius:2px; opacity:0.8;"></div>`;
            }).join('');

            // RSI ETH
            const ethRsiEl = document.getElementById('eth-rsi-display');
            if (ethRsiEl && ethTech.rsi !== undefined) {
                const rc = ethTech.rsi >= 55 ? '#198754' : ethTech.rsi <= 45 ? '#dc3545' : '#6c757d';
                const rl = ethTech.rsi >= 55 ? 'BULL' : ethTech.rsi <= 45 ? 'BEAR' : 'NEUTRAL';
                ethRsiEl.innerHTML = `<span style="color:${rc}; font-weight:bold;">${ethTech.rsi.toFixed(1)}</span>
                    <span class="indicator-pill ms-1" style="background:${rc}20; color:${rc}; border:1px solid ${rc}40; font-size:0.65rem;">${rl}</span>`;
            }

            // Signal bar
            const signal = Math.max(10, Math.min(90, ethTech.signal));
            const signalBar = document.getElementById('eth-signal-bar');
            signalBar.style.width = signal + '%';
            signalBar.className = `progress-bar ${signal > 50 ? 'bg-success' : 'bg-danger'}`;
            signalBar.style.borderRadius = '10px';
            document.getElementById('eth-signal-pct').innerText = signal.toFixed(0) + '%';
            document.getElementById('eth-signal-label').innerText = signal > 55 ? '● BULLISH' : signal < 45 ? '● BEARISH' : '◌ NEUTRAL';
            document.getElementById('eth-signal-label').style.color = signal > 55 ? '#198754' : signal < 45 ? '#dc3545' : '#6c757d';

            // Volatilidad ETH
            if (ethVol) {
                const limit = ATR_THRESHOLDS["ETH"];
                const atrOk = ethVol.atrPct <= limit;
                const bbOk  = ethVol.bbBandwidth <= MAX_BANDWIDTH_PCT;
                const spkOk = ethVol.maxSpike <= MAX_CANDLE_SPIKE_PCT;

                document.getElementById('eth-vol-atr').innerHTML   = `ATR <span style="color:${atrOk ? '#0dcaf0':'#dc3545'}">${ethVol.atrPct.toFixed(2)}%</span> <small class="text-muted">(${limit})</small>`;
                document.getElementById('eth-vol-bb').innerHTML    = `BB  <span style="color:${bbOk ? '#d63384':'#dc3545'}">${ethVol.bbBandwidth.toFixed(2)}%</span>`;
                document.getElementById('eth-vol-spike').innerHTML = `SPK <span style="color:${spkOk ? '#ffc107':'#dc3545'}">${ethVol.maxSpike.toFixed(2)}%</span>`;
            }

            // Trade status en el panel
            const tradeStatusEl = document.getElementById('eth-trade-status');
            if (ethActive) {
                const isWin = ethPrice && ((ethActive.side === 'Up' && ethPrice > ethActive.strike) || (ethActive.side === 'Down' && ethPrice < ethActive.strike));
                tradeStatusEl.innerHTML = `
                    <span class="badge ${ethActive.side === 'Up' ? 'bg-success' : 'bg-danger'} me-1">${ethActive.side}</span>
                    <span class="fw-bold ${isWin ? 'text-win' : 'text-loss'}">${isWin ? '✓ ITM' : '✗ OTM'}</span>
                    <br><span class="font-mono" style="font-size:0.75rem;">Strike $${ethActive.strike.toLocaleString()}</span>`;
            } else {
                tradeStatusEl.innerHTML = '<span class="text-muted small">Sin posición activa</span>';
            }
        }

        function calculateVolatility(klines) {
            if (!klines || klines.length < 15) return null;
            // ATR% (14-period)
            let trSum = 0;
            for (let i = 1; i <= 14; i++) {
                const idx = klines.length - 14 - 1 + i;
                if (idx < 1) continue;
                const h = parseFloat(klines[idx].high);
                const l = parseFloat(klines[idx].low);
                const pc = parseFloat(klines[idx - 1].close);
                trSum += Math.max(h - l, Math.abs(h - pc), Math.abs(l - pc));
            }
            const midPrice = parseFloat(klines[klines.length - 1].close);
            const atrPct = midPrice > 0 ? ((trSum / 14) / midPrice) * 100 : 0;
            // Bollinger bandwidth (20-period, 2 std dev)
            const closes = klines.slice(-20).map(k => parseFloat(k.close));
            const mean = closes.reduce((a, b) => a + b) / closes.length;
            const std = Math.sqrt(closes.reduce((a, b) => a + (b - mean) ** 2, 0) / closes.length);
            const bbBandwidth = mean > 0 ? (4 * std / mean) * 100 : 0;
            // Max spike% (biggest wick range in last 5 candles)
            const last5 = klines.slice(-5);
            const maxSpike = Math.max(...last5.map(k => {
                const h = parseFloat(k.high), l = parseFloat(k.low);
                const mid = (h + l) / 2;
                return mid > 0 ? ((h - l) / mid) * 100 : 0;
            }));
            return { atrPct, bbBandwidth, maxSpike };
        }

        function renderSolPanel(solTech, solPrice, trades, solVol) {
            const priceEl = document.getElementById('sol-price-display');
            if (solPrice) {
                priceEl.innerText = `$${solPrice.toLocaleString(undefined, {minimumFractionDigits: 2})}`;
            }

            const solActive = trades ? trades.filter(t => !t.resolved && t.asset === 'SOL').pop() : null;
            const diffEl = document.getElementById('sol-strike-diff');
            if (solActive && solPrice) {
                const usdDiff = solPrice - solActive.strike;
                const pctDiff = (usdDiff / solActive.strike) * 100;
                const isWin = (solActive.side === 'Up' && solPrice > solActive.strike) || (solActive.side === 'Down' && solPrice < solActive.strike);
                diffEl.innerText = `${usdDiff >= 0 ? '+' : ''}${usdDiff.toFixed(2)} (${pctDiff >= 0 ? '+' : ''}${pctDiff.toFixed(3)}%)`;
                diffEl.className = `small font-mono fw-bold ${isWin ? 'text-win' : 'text-loss'}`;
            } else {
                diffEl.innerText = '';
            }

            if (solVol) {
                const limit = ATR_THRESHOLDS["SOL"];
                const atrOk = solVol.atrPct <= limit;
                const bbOk  = solVol.bbBandwidth <= MAX_BANDWIDTH_PCT;
                const spkOk = solVol.maxSpike <= MAX_CANDLE_SPIKE_PCT;

                document.getElementById('sol-vol-atr').innerHTML   = `ATR <span style="color:${atrOk ? '#0dcaf0':'#dc3545'}">${solVol.atrPct.toFixed(2)}%</span> <small class="text-muted">(${limit})</small>`;
                document.getElementById('sol-vol-bb').innerHTML    = `BB  <span style="color:${bbOk ? '#d63384':'#dc3545'}">${solVol.bbBandwidth.toFixed(2)}%</span>`;
                document.getElementById('sol-vol-spike').innerHTML = `SPK <span style="color:${spkOk ? '#ffc107':'#dc3545'}">${solVol.maxSpike.toFixed(2)}%</span>`;
            }

            const tradeStatusEl = document.getElementById('sol-trade-status');
            if (solActive) {
                const isWin = solPrice && ((solActive.side === 'Up' && solPrice > solActive.strike) || (solActive.side === 'Down' && solPrice < solActive.strike));
                tradeStatusEl.innerHTML = `
                    <span class="badge ${solActive.side === 'Up' ? 'bg-success' : 'bg-danger'} me-1">${solActive.side}</span>
                    <span class="fw-bold ${isWin ? 'text-win' : 'text-loss'}">${isWin ? '✓ ITM' : '✗ OTM'}</span>
                    <br><span class="font-mono" style="font-size:0.75rem;">Strike $${solActive.strike.toLocaleString()}</span>`;
            } else {
                tradeStatusEl.innerHTML = '<span class="text-muted small">Sin posición activa</span>';
            }

            if (!solTech) return;

            const momEl = document.getElementById('sol-momentum-display');
            const momColor = solTech.changePct >= 0 ? '#198754' : '#dc3545';
            momEl.innerHTML = `<span style="color:${momColor};">${solTech.changePct >= 0 ? '↑' : '↓'} ${Math.abs(solTech.changePct).toFixed(3)}%</span>`;
            document.getElementById('sol-candle-count').innerText = `${solTech.up}↑ / ${solTech.down}↓ velas`;

            const macdContainer = document.getElementById('sol-macd-bars');
            macdContainer.innerHTML = solTech.macdHist.map(h => {
                const hPx = Math.min(Math.abs(h) * 5, 28);
                const bColor = h >= 0 ? '#198754' : '#dc3545';
                return `<div style="width:7px; height:${Math.max(hPx, 2)}px; background:${bColor}; border-radius:2px; opacity:0.8;"></div>`;
            }).join('');

            // RSI SOL
            const solRsiEl = document.getElementById('sol-rsi-display');
            if (solRsiEl && solTech.rsi !== undefined) {
                const rc = solTech.rsi >= 55 ? '#198754' : solTech.rsi <= 45 ? '#dc3545' : '#6c757d';
                const rl = solTech.rsi >= 55 ? 'BULL' : solTech.rsi <= 45 ? 'BEAR' : 'NEUTRAL';
                solRsiEl.innerHTML = `<span style="color:${rc}; font-weight:bold;">${solTech.rsi.toFixed(1)}</span>
                    <span class="indicator-pill ms-1" style="background:${rc}20; color:${rc}; border:1px solid ${rc}40; font-size:0.65rem;">${rl}</span>`;
            }

            const signal = Math.max(10, Math.min(90, solTech.signal));
            const signalBar = document.getElementById('sol-signal-bar');
            signalBar.style.width = signal + '%';
            signalBar.className = `progress-bar ${signal > 50 ? 'bg-success' : 'bg-danger'}`;
            signalBar.style.borderRadius = '10px';
            document.getElementById('sol-signal-pct').innerText = signal.toFixed(0) + '%';
            document.getElementById('sol-signal-label').innerText = signal > 55 ? '● BULLISH' : signal < 45 ? '● BEARISH' : '◌ NEUTRAL';
            document.getElementById('sol-signal-label').style.color = signal > 55 ? '#198754' : signal < 45 ? '#dc3545' : '#6c757d';
        }

        function renderXrpPanel(xrpTech, xrpPrice, trades, xrpVol) {
            const priceEl = document.getElementById('xrp-price-display');
            if (xrpPrice) {
                priceEl.innerText = `$${xrpPrice.toLocaleString(undefined, {minimumFractionDigits: 4})}`;
            }

            const xrpActive = trades ? trades.filter(t => !t.resolved && t.asset === 'XRP').pop() : null;
            const diffEl = document.getElementById('xrp-strike-diff');
            if (xrpActive && xrpPrice) {
                const usdDiff = xrpPrice - xrpActive.strike;
                const pctDiff = (usdDiff / xrpActive.strike) * 100;
                const isWin = (xrpActive.side === 'Up' && xrpPrice > xrpActive.strike) || (xrpActive.side === 'Down' && xrpPrice < xrpActive.strike);
                diffEl.innerText = `${usdDiff >= 0 ? '+' : ''}${usdDiff.toFixed(4)} (${pctDiff >= 0 ? '+' : ''}${pctDiff.toFixed(3)}%)`;
                diffEl.className = `small font-mono fw-bold ${isWin ? 'text-win' : 'text-loss'}`;
            } else {
                diffEl.innerText = '';
            }

            if (xrpVol) {
                const limit = ATR_THRESHOLDS["XRP"];
                const atrOk = xrpVol.atrPct <= limit;
                const bbOk  = xrpVol.bbBandwidth <= MAX_BANDWIDTH_PCT;
                const spkOk = xrpVol.maxSpike <= MAX_CANDLE_SPIKE_PCT;

                document.getElementById('xrp-vol-atr').innerHTML   = `ATR <span style="color:${atrOk ? '#0dcaf0':'#dc3545'}">${xrpVol.atrPct.toFixed(2)}%</span> <small class="text-muted">(${limit})</small>`;
                document.getElementById('xrp-vol-bb').innerHTML    = `BB  <span style="color:${bbOk ? '#d63384':'#dc3545'}">${xrpVol.bbBandwidth.toFixed(2)}%</span>`;
                document.getElementById('xrp-vol-spike').innerHTML = `SPK <span style="color:${spkOk ? '#ffc107':'#dc3545'}">${xrpVol.maxSpike.toFixed(2)}%</span>`;
            }

            const tradeStatusEl = document.getElementById('xrp-trade-status');
            if (xrpActive) {
                const isWin = xrpPrice && ((xrpActive.side === 'Up' && xrpPrice > xrpActive.strike) || (xrpActive.side === 'Down' && xrpPrice < xrpActive.strike));
                tradeStatusEl.innerHTML = `
                    <span class="badge ${xrpActive.side === 'Up' ? 'bg-success' : 'bg-danger'} me-1">${xrpActive.side}</span>
                    <span class="fw-bold ${isWin ? 'text-win' : 'text-loss'}">${isWin ? '✓ ITM' : '✗ OTM'}</span>
                    <br><span class="font-mono" style="font-size:0.75rem;">Strike $${xrpActive.strike.toLocaleString(undefined, {minimumFractionDigits: 4})}</span>`;
            } else {
                tradeStatusEl.innerHTML = '<span class="text-muted small">Sin posición activa</span>';
            }

            if (!xrpTech) return;

            const momEl = document.getElementById('xrp-momentum-display');
            const momColor = xrpTech.changePct >= 0 ? '#198754' : '#dc3545';
            momEl.innerHTML = `<span style="color:${momColor};">${xrpTech.changePct >= 0 ? '↑' : '↓'} ${Math.abs(xrpTech.changePct).toFixed(3)}%</span>`;
            document.getElementById('xrp-candle-count').innerText = `${xrpTech.up}↑ / ${xrpTech.down}↓ velas`;

            const macdContainer = document.getElementById('xrp-macd-bars');
            macdContainer.innerHTML = xrpTech.macdHist.map(h => {
                const hPx = Math.min(Math.abs(h) * 5, 28);
                const bColor = h >= 0 ? '#198754' : '#dc3545';
                return `<div style="width:7px; height:${Math.max(hPx, 2)}px; background:${bColor}; border-radius:2px; opacity:0.8;"></div>`;
            }).join('');

            const xrpRsiEl = document.getElementById('xrp-rsi-display');
            if (xrpRsiEl && xrpTech.rsi !== undefined) {
                const rc = xrpTech.rsi >= 55 ? '#198754' : xrpTech.rsi <= 45 ? '#dc3545' : '#6c757d';
                const rl = xrpTech.rsi >= 55 ? 'BULL' : xrpTech.rsi <= 45 ? 'BEAR' : 'NEUTRAL';
                xrpRsiEl.innerHTML = `<span style="color:${rc}; font-weight:bold;">${xrpTech.rsi.toFixed(1)}</span>
                    <span class="indicator-pill ms-1" style="background:${rc}20; color:${rc}; border:1px solid ${rc}40; font-size:0.65rem;">${rl}</span>`;
            }

            const signal = Math.max(10, Math.min(90, xrpTech.signal));
            const signalBar = document.getElementById('xrp-signal-bar');
            signalBar.style.width = signal + '%';
            signalBar.className = `progress-bar ${signal > 50 ? 'bg-success' : 'bg-danger'}`;
            signalBar.style.borderRadius = '10px';
            document.getElementById('xrp-signal-pct').innerText = signal.toFixed(0) + '%';
            document.getElementById('xrp-signal-label').innerText = signal > 55 ? '● BULLISH' : signal < 45 ? '● BEARISH' : '◌ NEUTRAL';
            document.getElementById('xrp-signal-label').style.color = signal > 55 ? '#198754' : signal < 45 ? '#dc3545' : '#6c757d';
        }

        function buildTradeCard(t, spotPrice, labelName, accentColor, borderClass) {
            const diff = spotPrice ? ((spotPrice - t.strike) / t.strike * 100) : 0;
            const usdDiff = spotPrice ? (spotPrice - t.strike) : 0;
            const isWinning = spotPrice ? ((t.side === 'Up' && spotPrice > t.strike) || (t.side === 'Down' && spotPrice < t.strike)) : false;
            const statusColor = isWinning ? 'text-win' : 'text-loss';
            const vence = t.end_date ? new Date(t.end_date).toLocaleTimeString('es-ES', {timeZone: 'Asia/Ho_Chi_Minh', hour12: false}) : '--:--';
            const livePnl = spotPrice ? (isWinning ? (t.contracts - t.position) : -t.position) : 0;
            const liveReturnPct = spotPrice ? (livePnl / t.position * 100) : 0;
            const goldClass = t.is_gold_minute ? 'gold-trade' : '';

            return `
                <div class="card p-4 mb-3 shadow-sm ${goldClass}" style="border-left: 4px solid ${accentColor};">
                    <div class="card-active-header d-flex justify-content-between align-items-center">
                        <div>
                            <span class="badge ${t.side === 'Up' ? 'bg-success' : 'bg-danger'} p-2 me-2">${t.side.toUpperCase()} POSITION</span>
                            ${(() => { const s=(t.macd_signal||'').toUpperCase(); const sc={'BULL CROSS':{bg:'#198754',tx:'white'},'BULL REVERSAL':{bg:'#0dcaf0',tx:'#000'},'BULLISH':{bg:'#90ee90',tx:'#000'},'BEAR CROSS':{bg:'#dc3545',tx:'white'},'BEAR REVERSAL':{bg:'#fd7e14',tx:'white'},'BEARISH':{bg:'#6f42c1',tx:'white'}}[s]||{bg:'#6c757d',tx:'white'}; return s ? `<span class="badge me-2" style="background:${sc.bg};color:${sc.tx};font-size:0.7rem;">${s}</span>` : ''; })()}
                            <span class="text-muted fw-bold">${t.question ? t.question.split(' - ')[0] + ' - 15 Minutes' : labelName + ' Up or Down - 15 Minutes'}</span>
                        </div>
                        <div class="text-end">
                            <span class="text-danger small fw-bold me-2">${t.real ? '● LIVE TRADING' : '[PAPER]'}</span>
                            <div class="h4 m-0 font-mono text-dark countdown-item" data-expire="${t.end_date}">--:--</div>
                            <div class="small text-muted font-mono" style="font-size:0.6rem">CIERRE: ${vence} VN</div>
                        </div>
                    </div>
                    <div class="text-center mb-4 py-3 bg-light rounded-3 border">
                        <div class="stat-label" style="color:${accentColor};">PRECIO STRIKE (${labelName})</div>
                        <div class="h1 fw-bold m-0 font-mono text-dark">$${t.strike.toLocaleString(undefined, {minimumFractionDigits: 2})}</div>
                    </div>
                    <div class="row align-items-center">
                        <div class="col-md-3">
                            <div class="stat-label">Inversión</div>
                            <div class="h5 fw-bold m-0 font-mono">$${t.position.toFixed(2)} <small class="text-muted">(@ ${(t.entry_price*100).toFixed(1)}¢)</small></div>
                        </div>
                        <div class="col-md-5 text-center border-start border-end">
                            <div class="info-box border-0 bg-transparent">
                                <div class="${statusColor} h4 fw-bold m-0">${isWinning ? '✓ IN THE MONEY' : '✗ OUT OF MONEY'}</div>
                                <div class="${statusColor} fw-bold font-mono">${livePnl >= 0 ? '+' : ''}$${livePnl.toFixed(2)} (${liveReturnPct.toFixed(1)}%)</div>
                            </div>
                        </div>
                        <div class="col-md-4 text-end">
                            <div class="stat-label">${labelName} Actual</div>
                            <div class="font-mono h4 fw-bold m-0">$${spotPrice ? spotPrice.toLocaleString() : '...'}</div>
                            <div class="small font-mono text-muted">${usdDiff >= 0 ? '+' : ''}${usdDiff.toFixed(2)} USD (${diff >= 0 ? '+' : ''}${diff.toFixed(3)}%)</div>
                        </div>
                    </div>
                </div>`;
        }

        function renderActiveTrades(btcTrade, btcPrice, serverStrike, ethTrade, ethPrice, solTrade, solPrice, xrpTrade, xrpPrice) {
            const activeList = document.getElementById('active-trades-list');
            let html = '';

            if (btcTrade) {
                html += buildTradeCard(btcTrade, btcPrice, 'Bitcoin', '#0d6efd', 'border-open');
            } else if (serverStrike) {
                html += `
                    <div class="card p-4 mb-3 shadow-sm border-start border-4 border-info">
                        <div class="text-center py-2">
                            <div class="stat-label text-primary">Strike BTC Mercado Actual</div>
                            <div class="h2 fw-bold m-0 font-mono text-dark">$${serverStrike.toLocaleString(undefined, {minimumFractionDigits: 2})}</div>
                        </div>
                    </div>`;
            }

            if (ethTrade) {
                html += buildTradeCard(ethTrade, ethPrice, 'Ethereum', '#627EEA', '');
            }

            if (solTrade) {
                html += buildTradeCard(solTrade, solPrice, 'Solana', '#9945FF', '');
            }

            if (xrpTrade) {
                html += buildTradeCard(xrpTrade, xrpPrice, 'XRP', '#00AAE4', '');
            }

            activeList.innerHTML = html;
        }

        // --- Cálculos Matemáticos del Bot ---
        function ema(values, period) {
            if (values.length < period) return [];
            const k = 2 / (period + 1);
            let emaArr = [values.slice(0, period).reduce((a, b) => a + b) / period];
            for (let i = period; i < values.length; i++) {
                emaArr.push(values[i] * k + emaArr[emaArr.length - 1] * (1 - k));
            }
            return emaArr;
        }

        function calculateRsi(closes, period = 7) {
            if (closes.length < period + 1) return 50;
            const gains = [], losses = [];
            for (let i = 1; i < closes.length; i++) {
                const diff = closes[i] - closes[i - 1];
                gains.push(Math.max(diff, 0));
                losses.push(Math.max(-diff, 0));
            }
            const avgGain = gains.slice(-period).reduce((a, b) => a + b, 0) / period;
            const avgLoss = losses.slice(-period).reduce((a, b) => a + b, 0) / period;
            if (avgLoss === 0) return 100;
            const rs = avgGain / avgLoss;
            return 100 - (100 / (1 + rs));
        }

        function calculateIndicators(klines) {
            const closes = klines.map(k => parseFloat(k.close));
            const volumes = klines.map(k => parseFloat(k.volume));

            // MACD (5/13/5)
            const emaFast = ema(closes, 5);
            const emaSlow = ema(closes, 13);
            const offset = 13 - 5;
            const macdLine = emaFast.slice(offset).map((f, i) => f - emaSlow[i]);
            const signalLine = ema(macdLine, 5);
            const macdAligned = macdLine.slice(macdLine.length - signalLine.length);
            const histogram = macdAligned.map((m, i) => m - signalLine[i]);

            // Momentum (últimas 5 velas)
            const last5 = klines.slice(-5);
            const c = last5.map(k => parseFloat(k.close));
            const v = last5.map(k => parseFloat(k.volume));
            const changePct = (c[4] - c[0]) / c[0];

            let up = 0, down = 0;
            for(let i=1; i<c.length; i++) {
                if (c[i] > c[i-1]) up++; else if (c[i] < c[i-1]) down++;
            }

            // RSI (7 períodos)
            const rsiValue = calculateRsi(closes, 7);

            const macdHistNow = histogram[histogram.length - 1];
            const signalPct = 50 + (changePct * 20000);

            return {
                macdHist: histogram.slice(-5),
                changePct: changePct * 100,
                up, down,
                volInc: v[4] > v[3],
                signal: signalPct,
                isBull: macdHistNow > 0,
                rsi: rsiValue
            };
        }

        function renderIndicators(tech, klines) {
            const panel = document.getElementById('indicator-panel');
            const color = tech.changePct >= 0 ? 'text-win' : 'text-loss';
            
            // Render MACD histogram bars
            const macdBars = tech.macdHist.map(h => {
                const hPx = Math.min(Math.abs(h) * 5, 30);
                const bColor = h >= 0 ? '#198754' : '#dc3545';
                return `<div class="macd-bar" style="height:${hPx}px; background-color:${bColor}"></div>`;
            }).join('');

            const rsiColor = tech.rsi >= 55 ? '#198754' : tech.rsi <= 45 ? '#dc3545' : '#6c757d';
            const rsiLabel = tech.rsi >= 55 ? 'BULL' : tech.rsi <= 45 ? 'BEAR' : 'NEUTRAL';

            panel.innerHTML = `
                <div class="mb-4">
                    <div class="stat-label small">Momentum 15m</div>
                    <div class="h3 fw-bold ${color} font-mono m-0">${tech.changePct >= 0 ? '↑' : '↓'} ${Math.abs(tech.changePct).toFixed(3)}%</div>
                    <div class="text-muted small">${tech.up}↑ / ${tech.down}↓ velas</div>
                </div>

                <div class="mb-4">
                    <div class="stat-label small mb-2">MACD Hist (5/13/5)</div>
                    <div class="d-flex align-items-end justify-content-center" style="height:35px">
                        ${macdBars}
                    </div>
                </div>

                <div class="mb-3">
                    <div class="stat-label small mb-1">RSI (7)</div>
                    <div class="d-flex align-items-center gap-2">
                        <div class="fw-bold font-mono" style="color:${rsiColor}; font-size:1.1rem;">${tech.rsi.toFixed(1)}</div>
                        <span class="indicator-pill" style="background:${rsiColor}20; color:${rsiColor}; border:1px solid ${rsiColor}40;">${rsiLabel}</span>
                        <div class="progress flex-grow-1" style="height:6px;">
                            <div class="progress-bar" style="width:${tech.rsi}%; background:${rsiColor}; border-radius:10px;"></div>
                        </div>
                    </div>
                </div>
            <div class="mb-2">
                <div class="stat-label small">Volatilidad BTC</div>
                <div id="btc-vol-stats-panel" class="small font-mono text-muted" style="font-size:0.75rem;">ATR calculando...</div>
            </div>

                <div class="d-flex justify-content-center">
                    <span class="indicator-pill ${tech.changePct > 0.03 ? 'badge-win' : 'bg-neutral'}">Consist</span>
                    <span class="indicator-pill ${tech.volInc ? 'badge-win' : 'bg-neutral'}">Vol ↑</span>
                    <span class="indicator-pill" style="background:${rsiColor}20; color:${rsiColor};">RSI ${tech.rsi.toFixed(0)}</span>
                </div>
            `;

        // Panel de volatilidad BTC dinámico
        const btcVol = calculateVolatility(klines);
        if (btcVol) {
            const limit = ATR_THRESHOLDS["BTC"];
            const atrOk = btcVol.atrPct <= limit;
            const bbOk  = btcVol.bbBandwidth <= MAX_BANDWIDTH_PCT;
            const spkOk = btcVol.maxSpike <= MAX_CANDLE_SPIKE_PCT;
            
            document.getElementById('btc-vol-stats-panel').innerHTML = `
                ATR <span style="color:${atrOk ? '#0dcaf0':'#dc3545'}">${btcVol.atrPct.toFixed(3)}%</span> | 
                BB <span style="color:${bbOk ? '#d63384':'#dc3545'}">${btcVol.bbBandwidth.toFixed(2)}%</span> | 
                SPK <span style="color:${spkOk ? '#ffc107':'#dc3545'}">${btcVol.maxSpike.toFixed(2)}%</span>`;
        }

            // Actualizar barra de señal
            const signal = Math.max(10, Math.min(90, tech.signal));
            document.getElementById('signal-bar').style.width = signal + '%';
            document.getElementById('signal-bar').className = `progress-bar ${signal > 50 ? 'bg-success' : 'bg-danger'}`;
            document.getElementById('signal-pct').innerText = signal.toFixed(0) + '%';
        }

        function updateLiveChart(currentPrice, trades, currentMacd, serverStrike) {
            const now = new Date();
            const timeLabel = now.toLocaleTimeString('es-ES', {timeZone: 'Asia/Ho_Chi_Minh', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false});
            
            // Inicialización: Pre-llenar historial para evitar que el gráfico comience desde cero y salte
            if (priceHistory.length === 0) {
                priceHistory = new Array(MAX_HISTORY).fill(currentPrice);
                labelHistory = new Array(MAX_HISTORY).fill("");
                unixHistory = new Array(MAX_HISTORY).fill(now.getTime());
                macdHistory = new Array(MAX_HISTORY).fill(0);
            }

            priceHistory.push(currentPrice);
            labelHistory.push(timeLabel);
            unixHistory.push(now.getTime());
            macdHistory.push(currentMacd);
            
            if (priceHistory.length > MAX_HISTORY) {
                priceHistory.shift();
                labelHistory.shift();
                unixHistory.shift();
                macdHistory.shift();
            }

            // Generar colores dinámicos para las barras del MACD (Verde = positivo, Rojo = negativo)
            const macdBgColors = macdHistory.map(v => v >= 0 ? 'rgba(25, 135, 84, 0.3)' : 'rgba(220, 53, 69, 0.3)');
            const macdBorderColors = macdHistory.map(v => v >= 0 ? 'rgba(25, 135, 84, 0.5)' : 'rgba(220, 53, 69, 0.5)');

            // Buscar el trade más reciente (el último del array)
            const activeTrade = trades.filter(t => !t.resolved).pop();
            
            // Prioridad: Strike de trade activo > Strike del bot > null
            // Usamos un array de longitud fija siempre para evitar "saltos" en Chart.js
            const strikeValue = activeTrade ? activeTrade.strike : (serverStrike || null);
            const strikeData = new Array(priceHistory.length).fill(strikeValue);

            // Marcadores para trades finalizados
            let winMarkers = new Array(priceHistory.length).fill(null);
            let lossMarkers = new Array(priceHistory.length).fill(null);

            trades.forEach(t => {
                if (t.resolved && t.end_date) {
                    const endTs = new Date(t.end_date).getTime();
                    let bestIdx = -1;
                    let minDiff = 12000; // Tolerancia de 12 segundos para alinear con el punto del gráfico
                    
                    unixHistory.forEach((ts, idx) => {
                        const diff = Math.abs(ts - endTs);
                        if (diff < minDiff) {
                            minDiff = diff;
                            bestIdx = idx;
                        }
                    });

                    if (bestIdx !== -1) {
                        const val = t.exit_btc || priceHistory[bestIdx];
                        if (t.outcome === 'WIN') winMarkers[bestIdx] = val;
                        else lossMarkers[bestIdx] = val;
                    }
                }
            });

            const ctx = document.getElementById('liveChart').getContext('2d');
            
            if (!liveChart) {
                liveChart = new Chart(ctx, {
                    type: 'line',
                    data: {
                        labels: labelHistory,
                        datasets: [
                            {
                                label: 'MACD Hist',
                                data: macdHistory,
                                type: 'bar',
                                backgroundColor: macdBgColors,
                                borderColor: macdBorderColors,
                                borderWidth: 1,
                                yAxisID: 'y1',
                                z: 1
                            },
                            {
                                label: 'BTC Price',
                                data: priceHistory,
                                borderColor: '#212529',
                                borderWidth: 3,
                                pointRadius: 0,
                                tension: 0.6,
                                yAxisID: 'y',
                                z: 10
                            },
                            {
                                label: 'Strike',
                                data: strikeData,
                                borderColor: '#fd7e14',
                                borderDash: [5, 5],
                                borderWidth: 2,
                                pointRadius: 0,
                                fill: false,
                                animations: false, // Strike fijo, sin saltos verticales
                                yAxisID: 'y',
                                z: 5
                            },
                            {
                                label: 'WIN',
                                data: winMarkers,
                                backgroundColor: '#198754',
                                borderColor: '#ffffff',
                                borderWidth: 2,
                                pointRadius: 8,
                                animations: false, // Los puntos de WIN no deben saltar
                                yAxisID: 'y',
                                showLine: false
                            },
                            {
                                label: 'LOSS',
                                data: lossMarkers,
                                backgroundColor: '#dc3545',
                                borderColor: '#ffffff',
                                borderWidth: 2,
                                pointRadius: 8,
                                animations: false, // Los puntos de LOSS no deben saltar
                                yAxisID: 'y',
                                showLine: false
                            }
                        ]
                    },
                    options: {
                        responsive: true,
                        maintainAspectRatio: false,
                        interaction: { mode: 'index', intersect: false },
                        animation: { duration: 1000, easing: 'linear' }, // Animación líquida de 1s
                        plugins: {
                            legend: { display: false },
                            hoverLine: {
                                lineWidth: 1,
                                lineColor: '#dee2e6'
                            },
                            tooltip: {
                                backgroundColor: 'rgba(33, 37, 41, 0.95)',
                                titleFont: { size: 17, family: 'Outfit', weight: '700' },
                                bodyFont: { size: 17, family: 'Outfit' },
                                padding: 12,
                                cornerRadius: 10,
                                displayColors: false
                            }
                        },
                        scales: {
                            x: { grid: { display: false }, ticks: { color: '#6c757d', maxRotation: 0, autoSkip: true, maxTicksLimit: 10 } },
                            y: {
                                type: 'linear',
                                display: true,
                                position: 'right', // Precio a la derecha (estilo TradingView)
                                grid: { color: '#e9ecef' },
                                ticks: { color: '#6c757d' },
                                grace: '10%'
                            },
                            y1: {
                                type: 'linear',
                                display: false, // Mantenerlo invisible para no ensuciar, pero se usa para escalar el MACD
                                position: 'left',
                                grid: { display: false },
                                // Centrar el MACD para que no tape el precio
                                suggestedMin: -100,
                                suggestedMax: 100
                            }
                        }
                    }
                });
            } else {
                liveChart.data.labels = labelHistory;
                liveChart.data.datasets[0].data = macdHistory;
                liveChart.data.datasets[0].backgroundColor = macdBgColors;
                liveChart.data.datasets[0].borderColor = macdBorderColors;
                liveChart.data.datasets[1].data = priceHistory;
                liveChart.data.datasets[2].data = strikeData;
                liveChart.data.datasets[3].data = winMarkers;
                liveChart.data.datasets[4].data = lossMarkers;
                
                // Cambiar color de la línea de precio según si va ganando o perdiendo
                if (activeTrade) {
                    const winning = (activeTrade.side === 'Up' && currentPrice > strikeValue) || 
                                    (activeTrade.side === 'Down' && currentPrice < strikeValue);
                    liveChart.data.datasets[1].borderColor = winning ? '#198754' : '#dc3545';
                } else {
                    liveChart.data.datasets[1].borderColor = '#777';
                }
                
                liveChart.update();
            }
        }

        function createVolChart(id, label, color, limitLabel) {
            const ctx = document.getElementById(id).getContext('2d');
            return new Chart(ctx, {
                type: 'line',
                data: {
                    labels: [],
                    datasets: [
                        { 
                            label: label, 
                            data: [], 
                            borderColor: color, 
                            backgroundColor: color.replace('rgb', 'rgba').replace(')', ', 0.1)'), 
                            borderWidth: 1.5, 
                            fill: true, 
                            tension: 0.4, 
                            pointRadius: 0,
                            pointHitRadius: 10
                        },
                        { label: limitLabel, data: [], borderColor: 'rgba(220, 53, 69, 0.5)', borderDash: [4, 4], borderWidth: 1, fill: false, pointRadius: 0 }
                    ]
                },
                options: {
                    responsive: true,
                    maintainAspectRatio: false,
                    animation: false,
                    plugins: { legend: { display: false } },
                    scales: {
                        x: { display: false },
                        y: { 
                            grid: { color: '#f0f0f0' },
                            ticks: { font: { size: 9 }, color: '#999' }
                        }
                    }
                }
            });
        }

        async function updateVolatilityCharts() {
            try {
                const response = await fetch('?volatility_history=1&_=' + new Date().getTime());
                const data = await response.json();
                if (!data || !data.length) return;

                // Inicializar si no existen
                if (!volCharts.atr) {
                    volCharts.atr = createVolChart('atrChart', 'ATR %', 'rgb(13, 202, 240)', 'Límite');
                    volCharts.bb = createVolChart('bbChart', 'BB %', 'rgb(214, 51, 132)', 'Límite');
                    volCharts.spike = createVolChart('spikeChart', 'Spike %', 'rgb(255, 193, 7)', 'Límite');
                }

                const labels = data.map(d => new Date(d.ts * 1000).toLocaleTimeString());
                
                // Actualizar ATR
                volCharts.atr.data.labels = labels;
                volCharts.atr.data.datasets[0].data = data.map(d => d.atr_pct);
                volCharts.atr.data.datasets[1].data = Array(labels.length).fill(data[0].atr_limit);
                
                // Cambiar color a rojo si supera el límite
                const lastAtr = data[data.length-1].atr_pct;
                const atrColor = lastAtr > data[0].atr_limit ? 'rgb(220, 53, 69)' : 'rgb(13, 202, 240)';
                volCharts.atr.data.datasets[0].borderColor = atrColor;
                volCharts.atr.data.datasets[0].backgroundColor = atrColor.replace('rgb', 'rgba').replace(')', ', 0.1)');
                volCharts.atr.update();

                // Actualizar BB
                volCharts.bb.data.labels = labels;
                volCharts.bb.data.datasets[0].data = data.map(d => d.bandwidth);
                volCharts.bb.data.datasets[1].data = Array(labels.length).fill(data[0].bb_limit);
                const lastBb = data[data.length-1].bandwidth;
                const bbColor = lastBb > data[0].bb_limit ? 'rgb(220, 53, 69)' : 'rgb(214, 51, 132)';
                volCharts.bb.data.datasets[0].borderColor = bbColor;
                volCharts.bb.data.datasets[0].backgroundColor = bbColor.replace('rgb', 'rgba').replace(')', ', 0.1)');
                volCharts.bb.update();

                // Actualizar Spike
                volCharts.spike.data.labels = labels;
                volCharts.spike.data.datasets[0].data = data.map(d => d.max_spike);
                volCharts.spike.data.datasets[1].data = Array(labels.length).fill(data[0].spike_limit);
                const lastSpike = data[data.length-1].max_spike;
                const spikeColor = lastSpike > data[0].spike_limit ? 'rgb(220, 53, 69)' : 'rgb(255, 193, 7)';
                volCharts.spike.data.datasets[0].borderColor = spikeColor;
                volCharts.spike.data.datasets[0].backgroundColor = spikeColor.replace('rgb', 'rgba').replace(')', ', 0.1)');
                volCharts.spike.update();
            } catch (e) {}
        }

        // Función que corre cada 1 segundo para la adrenalina pura
        function runTickers() {
            const now = new Date();
            
            // 1. Cronómetro Global (Stat Card)
            const timerEl = document.getElementById('global-timer');
            let target = globalTargetDate;
            
            // Si no hay trade activo, mostramos countdown al siguiente intervalo de 15m
            if (!target) {
                target = new Date(now);
                target.setMinutes(Math.ceil((now.getMinutes() + 0.1) / 15) * 15, 0, 0);
                timerEl.className = "h2 fw-bold m-0 font-mono text-secondary";
            } else {
                const diff = target - now;
                // Si queda menos de 2 minutos, ponemos el cronómetro en rojo
                timerEl.className = (diff < 120000) ? "h2 fw-bold m-0 font-mono text-loss" : "h2 fw-bold m-0 font-mono text-primary";
            }

            const diff = target - now;
            if (diff > 0) {
                const m = Math.floor(diff / 60000);
                const s = Math.floor((diff % 60000) / 1000);
                timerEl.innerText = `${m}:${s.toString().padStart(2, '0')}`;
            } else {
                timerEl.innerText = "0:00";
            }

            // 2. Estado de Ventana (cuando no hay trades activos)
            const windowMsgEl = document.getElementById('window-status-msg');
            if (!globalTargetDate) {
                const ENTRY_DELAY_SEC = 10;
                const ENTRY_WINDOW_SEC = 150;

                // Cálculo exacto de segundos transcurridos en el bloque de 15 min
                const elapsedSec = (now.getMinutes() % 15) * 60 + now.getSeconds();
                
                const m = Math.floor(elapsedSec / 60);
                const s = elapsedSec % 60;
                const timeStr = `${m}m${s.toString().padStart(2, '0')}s`;

                if (elapsedSec < ENTRY_DELAY_SEC) {
                    windowMsgEl.innerHTML = `<div class="info-box text-center text-warning fw-bold">⏳ Estabilizando mercado (esperando ${ENTRY_DELAY_SEC - elapsedSec}s)...</div>`;
                } else if (elapsedSec > ENTRY_WINDOW_SEC) {
                    windowMsgEl.innerHTML = `<div class="info-box text-center text-muted">⏸ Fuera de ventana de entrada (${timeStr})</div>`;
                } else {
                    windowMsgEl.innerHTML = `<div class="info-box text-center text-success fw-bold">⌛ Buscando señal... Ventana abierta (${timeStr})</div>`;
                }
            } else {
                windowMsgEl.innerHTML = '';
            }

            // 3. Cronómetros individuales en las tarjetas de posición
            document.querySelectorAll('.countdown-item').forEach(el => {
                const expireStr = el.getAttribute('data-expire');
                if (!expireStr) return;
                const expireDate = new Date(expireStr);
                const d = expireDate - now;
                if (d > 0) {
                    const m = Math.floor(d / 60000);
                    const s = Math.floor((d % 60000) / 1000);
                    el.innerText = `${m}:${s.toString().padStart(2, '0')}`;
                    el.className = (d < 120000) ? "h4 m-0 font-mono text-loss fw-bold" : "h4 m-0 font-mono text-dark";
                } else {
                    el.innerText = "EXPIRADO";
                }
            });
        }

        // ── Polymarket Portfolio Panel ────────────────────────────────────────────
        let polyDailyChart = null;
        let polyRawData    = null;   // datos completos sin filtrar
        let polyFromDate   = null;   // 'YYYY-MM-DD' o null
        let polyToDate     = null;

        // Establece rango rápido (días = 0 → todo)
        function setPolyRange(days) {
            document.querySelectorAll('.poly-range-btn').forEach(b => b.classList.remove('active'));
            event.target.classList.add('active');
            if (days === 0) {
                polyFromDate = null;
                polyToDate   = null;
                document.getElementById('poly-from').value = '';
                document.getElementById('poly-to').value   = '';
            } else {
                const to   = new Date();
                const from = new Date(to - days * 86400000);
                polyToDate   = to.toISOString().slice(0, 10);
                polyFromDate = from.toISOString().slice(0, 10);
                document.getElementById('poly-from').value = polyFromDate;
                document.getElementById('poly-to').value   = polyToDate;
            }
            if (polyRawData) renderPolymarket(polyRawData);
        }

        function applyPolyDates() {
            polyFromDate = document.getElementById('poly-from').value || null;
            polyToDate   = document.getElementById('poly-to').value   || null;
            document.querySelectorAll('.poly-range-btn').forEach(b => b.classList.remove('active'));
            if (polyRawData) renderPolymarket(polyRawData);
        }

        async function fetchPolymarket() {
            try {
                document.getElementById('poly-status').innerText = 'Consultando API...';
                const res  = await fetch(`${API}?polymarket=1`);
                const data = await res.json();
                polyRawData = data;
                renderPolymarket(data);
            } catch (e) {
                document.getElementById('poly-status').innerText = 'Error al cargar datos';
                console.error('Polymarket fetch error:', e);
            }
        }

        async function fetchPolyAccounts(force = false) {
            try {
                const url  = `${API}?poly_accounts=1` + (force ? '&force=1' : '');
                const res  = await fetch(url);
                const data = await res.json();
                renderPolyAccounts(data);
            } catch (e) {
                console.error('PolyAccounts error:', e);
            }
        }

        function renderPolyAccounts(data) {
            const portfolio  = data.portfolio_value || 0;
            const nActive    = data.n_active   ?? 0;
            const nExpired   = data.n_expired  ?? 0;
            const expiredAmt = data.expired_initial || 0;

            // Portfolio: posiciones activas (market still open, curPrice > 0)
            document.getElementById('poly-portfolio').innerText   = `$${portfolio.toFixed(2)}`;
            document.getElementById('poly-n-positions').innerText =
                `${nActive} activa${nActive !== 1 ? 's' : ''} · ${nExpired} por limpiar`;

            // Cash: el USDC del usuario está en el contrato del exchange de Polymarket
            // No es legible sin autenticación. Usamos el bankroll del bot como proxy.
            const botBankroll = lastData && lastData.bankroll != null ? lastData.bankroll : null;
            const cashEl = document.getElementById('poly-cash');
            if (botBankroll !== null) {
                cashEl.innerText = `$${botBankroll.toFixed(2)}`;
                document.getElementById('poly-cash-detail').innerText = 'Bankroll del bot (proxy)';
            } else {
                cashEl.innerText = '---';
                document.getElementById('poly-cash-detail').innerText = 'No disponible sin auth';
            }

            // Total wallet
            const total    = portfolio + (botBankroll || 0);
            const totalEl  = document.getElementById('poly-total-wallet');
            totalEl.innerText = botBankroll !== null ? `$${total.toFixed(2)}` : '---';
            document.getElementById('poly-wallet-detail').innerText =
                nExpired > 0 ? `Portfolio + Bankroll  ·  ⚠️ $${expiredAmt.toFixed(2)} invertido en ${nExpired} pos. expiradas`
                             : 'Portfolio + Bankroll';

            const ts = data._ts ? new Date(data._ts * 1000).toLocaleTimeString('es-ES', {timeZone: 'Asia/Ho_Chi_Minh', hour12: false}) : '?';
            document.getElementById('poly-accounts-ts').innerText = ts + ' VN';
        }

        function renderPolymarket(data) {
            const ts = data._ts ? new Date(data._ts * 1000).toLocaleTimeString('es-ES', {timeZone: 'Asia/Ho_Chi_Minh', hour12: false}) : '?';
            document.getElementById('poly-status').innerText = `Cache: ${ts} VN (5min)`;

            // Filtrar daily por rango de fechas
            const allDaily  = data.daily || {};
            const daily = {};
            Object.keys(allDaily).sort().forEach(day => {
                if (polyFromDate && day < polyFromDate) return;
                if (polyToDate   && day > polyToDate)   return;
                daily[day] = allDaily[day];
            });

            // Recalcular totales desde daily filtrado
            let spent = 0, redeemed = 0, nTrades = 0, nRedeems = 0;
            Object.values(daily).forEach(d => {
                spent    += d.spent;
                redeemed += d.redeemed;
                nTrades  += d.n;
            });
            // Contar redeems aproximados (daily no tiene n_redeems; usar total ratio)
            const redeemRatio = (data.n_redeems || 0) / Math.max(data.n_trades || 1, 1);
            nRedeems = Math.round(nTrades * redeemRatio);

            const netPnl = redeemed - spent;

            document.getElementById('poly-spent').innerText     = `$${spent.toFixed(2)}`;
            document.getElementById('poly-redeemed').innerText  = `$${redeemed.toFixed(2)}`;
            document.getElementById('poly-n-trades').innerText  = `${nTrades} trades (BUY)`;
            document.getElementById('poly-n-redeems').innerText = `~${nRedeems} redeems`;

            const pnlEl = document.getElementById('poly-net-pnl');
            pnlEl.innerText = `${netPnl >= 0 ? '+' : ''}$${netPnl.toFixed(2)}`;
            pnlEl.className = `stat-value font-mono ${netPnl >= 0 ? 'text-win' : 'text-loss'}`;
            document.getElementById('poly-pnl-detail').innerText =
                `ROI: ${spent > 0 ? (netPnl / spent * 100).toFixed(1) : 0}%`;

            // Comparación vs bot
            const vsEl  = document.getElementById('poly-vs-bot');
            const detEl = document.getElementById('poly-vs-detail');
            if (lastData && lastData.total_pnl !== null && lastData.total_pnl !== undefined) {
                const botPnl = lastData.total_pnl;
                const diff   = netPnl - botPnl;
                vsEl.innerText  = `${diff >= 0 ? '+' : ''}$${diff.toFixed(2)}`;
                vsEl.className  = `stat-value font-mono ${Math.abs(diff) < 5 ? 'text-muted' : diff >= 0 ? 'text-win' : 'text-loss'}`;
                detEl.innerText = `Bot: ${botPnl >= 0 ? '+' : ''}$${botPnl.toFixed(2)} · Poly: ${netPnl >= 0 ? '+' : ''}$${netPnl.toFixed(2)}`;
            } else {
                vsEl.innerText  = '---';
                detEl.innerText = 'Bot no reporta P&L aún';
            }

            // Daily P&L bar chart
            const days   = Object.keys(daily).sort();
            const pnlArr = days.map(d => parseFloat((daily[d].redeemed - daily[d].spent).toFixed(2)));
            const colors = pnlArr.map(v => v >= 0 ? 'rgba(25,135,84,0.75)' : 'rgba(220,53,69,0.7)');

            if (polyDailyChart) polyDailyChart.destroy();
            const ctx = document.getElementById('polyDailyChart').getContext('2d');
            polyDailyChart = new Chart(ctx, {
                type: 'bar',
                data: {
                    labels: days.map(d => d.slice(5)),
                    datasets: [{ label: 'P&L diario', data: pnlArr, backgroundColor: colors, borderRadius: 4, borderWidth: 0 }]
                },
                options: {
                    responsive: true, maintainAspectRatio: false,
                    plugins: {
                        legend: { display: false },
                        tooltip: {
                            callbacks: {
                                label: ctx => {
                                    const d = daily[days[ctx.dataIndex]];
                                    return [`P&L: ${ctx.raw >= 0 ? '+' : ''}$${ctx.raw}`,
                                            `Gastado: $${d.spent.toFixed(2)}`,
                                            `Redeemed: $${d.redeemed.toFixed(2)}`,
                                            `Trades: ${d.n}`];
                                }
                            }
                        }
                    },
                    scales: {
                        x: { ticks: { font: { size: 9 }, color: '#999' }, grid: { display: false } },
                        y: { ticks: { font: { size: 9 }, color: '#999', callback: v => `$${v}` }, grid: { color: '#f0f0f0' } }
                    }
                }
            });

            // Activity table — filtrar recent por rango de fechas
            const fmt = ts => new Date(ts * 1000).toLocaleString('es-ES', {
                timeZone: 'Asia/Ho_Chi_Minh', month: 'short', day: 'numeric',
                hour: '2-digit', minute: '2-digit', hour12: false
            });
            const fromTs = polyFromDate ? new Date(polyFromDate).getTime() / 1000 : 0;
            const toTs   = polyToDate   ? (new Date(polyToDate).getTime() / 1000 + 86400) : Infinity;
            const filtered = (data.recent || []).filter(item => item.timestamp >= fromTs && item.timestamp <= toTs);

            const tbody = document.getElementById('poly-activity-body');
            tbody.innerHTML = filtered.map(item => {
                const isRedeem = item.type === 'REDEEM';
                const isBuy    = item.type === 'TRADE' && item.side === 'BUY';
                const badgeCls = isRedeem ? 'badge-win' : (isBuy ? 'badge-open' : 'bg-neutral');
                const typeStr  = isRedeem ? 'REDEEM' : (isBuy ? 'BUY' : item.type);
                const sign     = isRedeem ? '+' : '-';
                const usdcAmt  = parseFloat(item.usdcSize || 0).toFixed(2);
                const title    = (item.title || item.market || '').slice(0, 40);
                return `<tr>
                    <td class="font-mono" style="white-space:nowrap;">${fmt(item.timestamp)}</td>
                    <td><span class="badge ${badgeCls}" style="font-size:0.65rem;">${typeStr}</span></td>
                    <td class="text-muted" style="max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="${item.title || ''}">${title}</td>
                    <td class="text-end font-mono ${isRedeem ? 'text-win' : 'text-danger'} fw-bold">${sign}$${usdcAmt}</td>
                </tr>`;
            }).join('') || '<tr><td colspan="4" class="text-center text-muted py-3">Sin transacciones en el rango</td></tr>';
        }

        setInterval(runTickers, 1000);
        let _fetchInterval = setInterval(fetchData, 1000); // Frecuencia de 1s para efecto cine

        // Pausar polling mientras el modal de gráfico está abierto
        window.addEventListener('DOMContentLoaded', () => {
            const _chartModalEl = document.getElementById('tradeChartModal');
            if (_chartModalEl) {
                _chartModalEl.addEventListener('show.bs.modal',   () => { clearInterval(_fetchInterval); });
                _chartModalEl.addEventListener('hidden.bs.modal', () => { _fetchInterval = setInterval(fetchData, 1000); });
            }
            const _meModalEl = document.getElementById('marketEvalModal');
            if (_meModalEl) {
                _meModalEl.addEventListener('show.bs.modal',   () => { clearInterval(_fetchInterval); });
                _meModalEl.addEventListener('hidden.bs.modal', () => { _fetchInterval = setInterval(fetchData, 1000); });
            }
        });

        // ── Analizador ────────────────────────────────────────────────────────
        let analyzerPage = 0;

        async function fetchAnalyzerTrades() {
            try {
                const res = await fetch(`${API}?analyzer_trades=1&_=${Date.now()}`);
                const trades = await res.json();
                renderAnalyzerTable(trades);
            } catch(e) {
                console.error('fetchAnalyzerTrades:', e);
            }
        }

        function renderAnalyzerTable(trades) {
            if (!trades || !trades.length) return;

            const sorted = [...trades].sort((a, b) => new Date(b.end_date) - new Date(a.end_date));
            const PAGE = 20;
            const totalPages = Math.ceil(sorted.length / PAGE);
            if (analyzerPage >= totalPages) analyzerPage = totalPages - 1;
            const page = sorted.slice(analyzerPage * PAGE, (analyzerPage + 1) * PAGE);

            const formatVN = iso => iso ? new Date(iso).toLocaleString('es-ES', {timeZone: 'Asia/Ho_Chi_Minh', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', hour12: false}) : '---';

            const wins  = trades.filter(t => t.outcome === 'WIN' || t.outcome === 'TAKE PROFIT').length;
            const pnl   = trades.reduce((s, t) => s + (t.pnl || 0), 0);
            const wr    = trades.length ? (wins / trades.length * 100).toFixed(1) : '0.0';
            document.getElementById('analyzer-summary').innerHTML =
                `${trades.length} trades &nbsp;|&nbsp; WR ${wr}% &nbsp;|&nbsp; PnL <strong class="${pnl >= 0 ? 'text-win' : 'text-loss'}">${pnl >= 0 ? '+' : ''}$${pnl.toFixed(2)}</strong>`;

            // Mantener referencia para que openAnalyzerChart pueda acceder sin serializar JSON
            window._analyzerPageTrades = page;

            const tbody = document.getElementById('analyzer-table-body');
            tbody.innerHTML = page.map((t, rowIdx) => {
                const pnlColor = t.pnl >= 0 ? 'text-win' : 'text-loss';
                const roiPct   = t.position > 0 ? (t.pnl / t.position * 100).toFixed(1) : '0.0';
                const asset    = t.asset || 'BTC';
                const assetBadge = asset === 'ETH'
                    ? `<span class="badge" style="background:#627EEA;color:white;font-size:0.7rem;">⟠ ETH</span>`
                    : asset === 'SOL'
                    ? `<span class="badge" style="background:#9945FF;color:white;font-size:0.7rem;">◎ SOL</span>`
                    : asset === 'XRP'
                    ? `<span class="badge" style="background:#00AAE4;color:white;font-size:0.7rem;">✕ XRP</span>`
                    : `<span class="badge" style="background:#F7931A;color:white;font-size:0.7rem;">₿ BTC</span>`;

                const outcomeText = t.outcome || '—';
                let outcomeClass = 'badge-open';
                if (outcomeText === 'WIN' || outcomeText === 'TAKE PROFIT') outcomeClass = 'badge-win';
                else if (outcomeText === 'LOSS') outcomeClass = 'badge-loss';
                else if (outcomeText.startsWith('STOP')) outcomeClass = 'badge-emergency';

                const exitBadge = t.exit_type === 'TP'
                    ? `<span class="badge" style="background:#198754;color:white;font-size:0.65rem;">TP</span>`
                    : t.exit_type === 'SL'
                    ? `<span class="badge" style="background:#dc3545;color:white;font-size:0.65rem;">SL</span>`
                    : `<span class="badge bg-secondary" style="font-size:0.65rem;">Mercado</span>`;

                const mom = t.momentum || 0;
                const momColor = mom > 0.1 ? '#198754' : mom < -0.1 ? '#dc3545' : '#6c757d';
                const momSign  = mom >= 0 ? '+' : '';
                const momAbsPct = Math.min(Math.abs(mom) / 0.5 * 100, 100);
                const momInfo = `<div style="font-size:0.68rem;line-height:1.4;">
                    <span style="color:${momColor};font-weight:700;font-family:monospace;">${momSign}${mom.toFixed(3)}%</span>
                    <div style="background:#e9ecef;border-radius:3px;height:4px;margin:2px 0;overflow:hidden;">
                        <div style="background:${momColor};width:${momAbsPct}%;height:4px;border-radius:3px;"></div>
                    </div>
                </div>`;

                const sig = (t.macd_signal || '').toUpperCase();
                const sigColors = {
                    'BULL CROSS': {bg:'#198754',text:'white'}, 'BULL REVERSAL': {bg:'#0dcaf0',text:'#000'},
                    'BULLISH':    {bg:'#90ee90',text:'#000'},  'BEAR CROSS':    {bg:'#dc3545',text:'white'},
                    'BEAR REVERSAL': {bg:'#fd7e14',text:'white'}, 'BEARISH':    {bg:'#f8d7da',text:'#842029'},
                };
                const sc = sigColors[sig];
                const sigBadge = sc
                    ? `<span class="badge" style="background:${sc.bg};color:${sc.text};font-size:0.65rem;">${sig}</span>`
                    : '<span class="text-muted small">—</span>';

                const volInfo = t.vol_atr
                    ? `<div style="font-size:0.65rem;line-height:1.1;">ATR: ${t.vol_atr.toFixed(2)}%<br>BB: ${t.vol_bb.toFixed(2)}%<br>SPK: ${t.vol_spike.toFixed(2)}%</div>`
                    : '<span class="text-muted">—</span>';

                const clobSigA = (t.clob_signal || '').toUpperCase();
                const clobBadgeA = clobSigA === 'BUY_PRESSURE'
                    ? `<span class="badge" style="background:#198754;color:white;font-size:0.62rem;">↑ BUY</span>`
                    : clobSigA === 'SELL_PRESSURE'
                    ? `<span class="badge" style="background:#dc3545;color:white;font-size:0.62rem;">↓ SELL</span>`
                    : `<span class="text-muted small">—</span>`;

                const tFactor = t.factor || 1.0;
                const tDir    = (t.direction || '').toUpperCase();
                let factorPill = '<span class="text-muted small">×1.00</span>';
                if (Math.abs(tFactor - 1.0) > 0.01) {
                    const fc = tFactor > 1.0 ? '#198754' : '#dc3545';
                    const arrow = tFactor > 1.0 ? '↑' : '↓';
                    factorPill = `<span class="badge" style="background:${fc};color:white;font-size:0.65rem;" title="${t.asset} ${tDir}">${arrow}×${tFactor.toFixed(2)}</span>`;
                }

                const fechaVN = formatVN(t.end_date);
                const statsInfo = `ENTRADA: ${formatVN(t.entry_time)} | MOM: ${(t.momentum||0).toFixed(4)}% | KELLY: $${(t.kelly_size||0).toFixed(2)}`;

                const acBtn = t.market_log_fname
                    ? `<button class="btn btn-outline-secondary btn-sm py-0 px-1" style="font-size:0.7rem;"
                         onclick="openAnalyzerChart(window._analyzerPageTrades[${rowIdx}].market_log_fname, window._analyzerPageTrades[${rowIdx}])">📊</button>`
                    : '<span class="text-muted" style="font-size:0.7rem;">—</span>';

                return `<tr>
                    <td><span class="text-dark" title="${statsInfo}">${fechaVN} VN</span></td>
                    <td>${assetBadge}</td>
                    <td><span class="badge ${t.side === 'Up' ? 'bg-success' : 'bg-danger'}">${t.side}</span></td>
                    <td class="text-center text-secondary small">${t.strike ? '$' + t.strike.toLocaleString() : '—'}</td>
                    <td class="text-center">$${(t.position||0).toFixed(2)}</td>
                    <td class="text-center"><span class="badge ${outcomeClass}" title="${statsInfo}">${outcomeText}</span></td>
                    <td class="text-center">${sigBadge}</td>
                    <td class="text-center">${momInfo}</td>
                    <td class="text-center">${volInfo}</td>
                    <td class="text-center">${clobBadgeA}</td>
                    <td class="text-center">${factorPill}</td>
                    <td class="text-center">${exitBadge}</td>
                    <td class="text-end ${pnlColor} fw-bold">
                        ${t.pnl >= 0 ? '+' : ''}$${(t.pnl||0).toFixed(2)}
                        <span class="text-muted fw-normal small">(${t.pnl >= 0 ? '+' : ''}${roiPct}%)</span>
                    </td>
                    <td class="text-center">${acBtn}</td>
                </tr>`;
            }).join('');

            renderPaginationUI('analyzer-pagination', totalPages, analyzerPage, (p) => {
                analyzerPage = p;
                renderAnalyzerTable(trades);
            });
        }

        fetchData();
        fetchPolymarket();    // actividad histórica (cache 5min)
        fetchPolyAccounts();  // portfolio + cash (cache 60s)
        fetchAnalyzerTrades();
    </script>

    <!-- Modal Analyzer Comparison Chart -->
    <div class="modal fade" id="analyzerChartModal" tabindex="-1">
        <div class="modal-dialog modal-xl modal-dialog-centered">
            <div class="modal-content" style="background:#fff; border:1px solid #dee2e6;">
                <div class="modal-header border-bottom pb-2">
                    <div>
                        <h5 class="modal-title mb-0" id="acTitle">Comparación Simulado vs Real</h5>
                        <div id="acMeta" class="small text-muted mt-1"></div>
                    </div>
                    <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
                </div>
                <div class="modal-body pt-2">
                    <div id="acKpis" class="d-flex gap-3 flex-wrap mb-3" style="font-size:0.8rem;"></div>
                    <div style="height:270px; position:relative;">
                        <canvas id="acPriceChart"></canvas>
                    </div>
                    <div style="height:100px; position:relative; margin-top:8px;">
                        <canvas id="acMacdChart"></canvas>
                    </div>
                    <div class="mt-3 d-flex flex-wrap gap-3" style="font-size:0.7rem; color:#495057;">
                        <span><span style="display:inline-block;width:14px;height:3px;background:#0dcaf0;vertical-align:middle;margin-right:3px;"></span>Entrada SIM</span>
                        <span><span style="display:inline-block;width:14px;height:3px;background:#198754;vertical-align:middle;margin-right:3px;"></span>TP SIM / TP real</span>
                        <span><span style="display:inline-block;width:14px;height:3px;background:#dc3545;vertical-align:middle;margin-right:3px;"></span>SL SIM / SL real</span>
                        <span><span style="display:inline-block;width:14px;height:3px;background:#fd7e14;vertical-align:middle;margin-right:3px;border-style:dashed;"></span>Entrada BOT real</span>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <!-- Modal Market Eval Chart (Near-Misses) -->
    <div class="modal fade" id="marketEvalModal" tabindex="-1">
        <div class="modal-dialog modal-xl modal-dialog-centered">
            <div class="modal-content" style="background:#fff; border:1px solid #dee2e6;">
                <div class="modal-header border-bottom pb-2">
                    <div>
                        <h5 class="modal-title mb-0" id="marketEvalTitle">Near-Miss</h5>
                        <div id="marketEvalMeta" class="small text-muted mt-1"></div>
                    </div>
                    <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
                </div>
                <div class="modal-body pt-2">
                    <div id="marketEvalKpis" class="d-flex gap-4 flex-wrap mb-3" style="font-size:0.82rem;"></div>
                    <div style="height:240px; position:relative;">
                        <canvas id="meTokenChart"></canvas>
                    </div>
                    <div style="height:100px; position:relative; margin-top:8px;">
                        <canvas id="meMacdChart"></canvas>
                    </div>
                    <div style="height:100px; position:relative; margin-top:8px;">
                        <canvas id="meMomChart"></canvas>
                    </div>
                    <!-- Leyenda de decisiones -->
                    <div class="mt-3 d-flex flex-wrap gap-2" style="font-size:0.7rem;">
                        <span><span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:#198754;"></span> ENTERED</span>
                        <span><span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:#fd7e14;"></span> MACD/RSI/Momentum</span>
                        <span><span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:#dc3545;"></span> Vol/Spread/Price/Kelly</span>
                        <span><span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:#6f42c1;"></span> Slots/Corr</span>
                        <span><span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:#adb5bd;"></span> Ventana/Hora/Ya en trade</span>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <!-- Modal Trade Chart -->
    <div class="modal fade" id="tradeChartModal" tabindex="-1">
        <div class="modal-dialog modal-xl modal-dialog-centered">
            <div class="modal-content" style="background:#fff; border:1px solid #dee2e6;">
                <div class="modal-header border-bottom pb-2">
                    <div>
                        <h5 class="modal-title mb-0" id="tradeChartTitle">Trayecto del Trade</h5>
                        <div id="tradeChartMeta" class="small text-muted mt-1"></div>
                    </div>
                    <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
                </div>
                <div class="modal-body pt-2">
                    <!-- KPIs -->
                    <div id="tradeChartKpis" class="d-flex gap-4 flex-wrap mb-3" style="font-size:0.82rem;"></div>
                    <!-- Token price + events -->
                    <div style="height:260px; position:relative;">
                        <canvas id="tradeTokenChart"></canvas>
                    </div>
                    <!-- MACD histogram -->
                    <div style="height:110px; position:relative; margin-top:8px;">
                        <canvas id="tradeMacdChart"></canvas>
                    </div>
                    <!-- Momentum + RSI -->
                    <div style="height:110px; position:relative; margin-top:8px;">
                        <canvas id="tradeMomChart"></canvas>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <script>
    let _tcTokenChart = null, _tcMacdChart = null, _tcMomChart = null;
    let _meTokenChart = null, _meMacdChart = null, _meMomChart = null;

    // Cache en memoria: filename → data (precargado en background)
    const _tradeLogCache = {};
    const _marketLogCache = {};

    // ── Market Logs (Near-Misses) ─────────────────────────────────────────────
    function fetchMarketLogs() {
        fetch(`${API}?market_logs_list=1&_=${Date.now()}`, { cache: 'no-store' })
            .then(r => r.ok ? r.json() : [])
            .then(data => renderMarketLogsTable(data))
            .catch(() => {
                document.getElementById('market-logs-table-body').innerHTML =
                    '<tr><td colspan="6" class="text-danger text-center py-2">Error cargando evaluaciones</td></tr>';
            });
    }

    function renderMarketLogsTable(logs) {
        const tbody = document.getElementById('market-logs-table-body');
        if (!logs || !logs.length) {
            tbody.innerHTML = '<tr><td colspan="6" class="text-muted text-center py-3">Sin evaluaciones registradas aún</td></tr>';
            return;
        }

        const totalPages = Math.ceil(logs.length / PAGE_SIZE);
        if (marketLogsPage >= totalPages && totalPages > 0) marketLogsPage = totalPages - 1;
        
        const start = marketLogsPage * PAGE_SIZE;
        const pageLogs = logs.slice(start, start + PAGE_SIZE);

        const assetColors = {BTC:'#F7931A', ETH:'#627EEA', SOL:'#9945FF', XRP:'#00AAE4'};
        const assetIcons  = {BTC:'₿', ETH:'⟠', SOL:'◎', XRP:'✕'};
        tbody.innerHTML = pageLogs.map(m => {
            const ac = assetColors[m.asset] || '#aaa';
            const ai = assetIcons[m.asset]  || '●';
            const fechaVN = m.end_date
                ? new Date(m.end_date).toLocaleString('es-ES', {timeZone:'Asia/Ho_Chi_Minh', month:'short', day:'numeric', hour:'2-digit', minute:'2-digit', hour12:false})
                : '---';

            const outcomeHtml = m.outcome === 'UP'
                ? '<span class="badge bg-success" style="font-size:0.7rem;">UP ✓</span>'
                : m.outcome === 'DOWN'
                ? '<span class="badge bg-danger" style="font-size:0.7rem;">DOWN ✗</span>'
                : '<span class="badge bg-secondary" style="font-size:0.7rem;">—</span>';

            const decisions = m.decisions || {};
            const dParts = [];
            if (decisions['ENTERED']) {
                const side = (m.enter_side || '').toLowerCase();
                const sideBadge = side === 'up'
                    ? `<span class="badge ms-1" style="font-size:0.6rem;background:#0d6efd;">↑ UP</span>`
                    : side === 'down'
                    ? `<span class="badge ms-1" style="font-size:0.6rem;background:#dc3545;">↓ DOWN</span>`
                    : '';
                dParts.push(`<span class="badge bg-success" style="font-size:0.6rem;">ENTER ×${decisions['ENTERED']}</span>${sideBadge}`);
            }
            const blocked = Object.entries(decisions).filter(([k]) => k.startsWith('BLOCKED')).reduce((s,[,v]) => s+v, 0);
            const topBlock = Object.entries(decisions).filter(([k]) => k.startsWith('BLOCKED')).sort((a,b) => b[1]-a[1])[0];
            if (blocked) {
                const reason = topBlock ? ` <span style="font-size:0.6rem;color:#6c757d;">(${topBlock[0].replace('BLOCKED_','')})</span>` : '';
                dParts.push(`<span class="badge bg-secondary" style="font-size:0.6rem;">BLOCKED ×${blocked}</span>${reason}`);
            }
            if (m.factor && Math.abs(m.factor - 1.0) > 0.01) {
                const fc = m.factor > 1.0 ? '#198754' : '#dc3545';
                const fd = m.factor_dir ? m.factor_dir.toUpperCase() : '';
                const arrow = m.factor > 1.0 ? '↑' : '↓';
                dParts.push(`<span class="badge ms-1" style="font-size:0.6rem;background:${fc};opacity:0.85;" title="Factor Bayesiano ${m.asset} ${fd}">${arrow}×${m.factor.toFixed(2)}</span>`);
            }

            return `<tr>
                <td><span class="badge" style="background:${ac};color:white;font-size:0.7rem;">${ai} ${m.asset}</span></td>
                <td class="small">
                    <a href="https://polymarket.com/event/${m.slug}" target="_blank" class="text-decoration-none text-dark hover-link" title="Ver mercado en Polymarket">${fechaVN} VN</a>
                </td>
                <td class="text-center">${outcomeHtml}</td>
                <td class="text-center small">${m.n_evals}</td>
                <td class="small">${dParts.join(' ') || '<span class="text-muted">—</span>'}</td>
                <td class="text-center">
                    <button class="btn btn-outline-secondary btn-sm py-0 px-1" style="font-size:0.7rem;" onclick="openMarketEvalChart('${m.fname}')">📊</button>
                </td>
            </tr>`;
        }).join('');

        renderPaginationUI('market-logs-pagination', totalPages, marketLogsPage, (p) => {
            marketLogsPage = p;
            renderMarketLogsTable(logs);
        });
    }

    function openMarketEvalChart(fname) {
        const modalEl = document.getElementById('marketEvalModal');
        const modal   = bootstrap.Modal.getOrCreateInstance(modalEl);
        document.getElementById('marketEvalTitle').textContent = '⏳ Cargando...';
        document.getElementById('marketEvalMeta').textContent = '';
        document.getElementById('marketEvalKpis').innerHTML = '';

        // Render only after modal is fully visible so canvas has real dimensions.
        const doRender = (data) => {
            const go = () => { try { renderMarketEvalChart(data); } catch(e) { document.getElementById('marketEvalTitle').textContent = '❌ JS: ' + e.message; console.error(e); } };
            if (modalEl.classList.contains('show')) {
                go();
            } else {
                const handler = () => { modalEl.removeEventListener('shown.bs.modal', handler); go(); };
                modalEl.addEventListener('shown.bs.modal', handler);
            }
        };

        modal.show();

        if (_marketLogCache[fname]) {
            doRender(_marketLogCache[fname]);
            return;
        }
        fetch(`?market_log=${encodeURIComponent(fname)}&_=${Date.now()}`, { cache: 'no-store' })
            .then(r => { if (!r.ok) throw new Error('Log no encontrado: ' + fname); return r.json(); })
            .then(data => { _marketLogCache[fname] = data; doRender(data); })
            .catch(e => { document.getElementById('marketEvalTitle').textContent = '❌ ' + e.message; });
    }

    function renderMarketEvalChart(data) {
        const evals = data.evaluations || [];
        const asset = data.asset || 'BTC';
        const assetColors = {BTC:'#F7931A', ETH:'#627EEA', SOL:'#9945FF', XRP:'#00AAE4'};
        const ac = assetColors[asset] || '#aaa';

        // Título
        const outcomeHtml = data.outcome === 'UP'
            ? '<span class="badge bg-success ms-1">UP ✓</span>'
            : data.outcome === 'DOWN'
            ? '<span class="badge bg-danger ms-1">DOWN ✗</span>'
            : '<span class="badge bg-secondary ms-1">Abierto</span>';
        document.getElementById('marketEvalTitle').innerHTML =
            `<span style="color:${ac};">●</span> ${asset} Near-Miss ${outcomeHtml}`;
        const endDt = data.end_date
            ? new Date(data.end_date).toLocaleString('es-ES', {timeZone:'Asia/Ho_Chi_Minh', hour12:false})
            : '';
        document.getElementById('marketEvalMeta').textContent =
            `Cierre: ${endDt} VN  |  ${evals.length} evaluaciones  |  Strike: $${(data.strike||0).toLocaleString()}`;

        // KPIs
        const entered   = evals.filter(e => e.decision === 'ENTERED').length;
        const blocked   = evals.filter(e => (e.decision||'').startsWith('BLOCKED')).length;
        const blockTypes = [...new Set(evals.filter(e => (e.decision||'').startsWith('BLOCKED')).map(e => e.decision.replace('BLOCKED_','')))];
        const avgConf   = evals.length ? (evals.reduce((s,e) => s+(e.confidence||0),0)/evals.length*100).toFixed(0) : 0;
        const firstEval = evals[0] || {};
        // Factor bayesiano: tomamos el primer eval con factor != 1.0 (o el primero disponible)
        const factorEval  = evals.find(e => e.factor && Math.abs(e.factor - 1.0) > 0.01) || firstEval;
        const factorVal   = factorEval.factor || 1.0;
        const factorDir   = (factorEval.direction || '').toUpperCase();
        const factorColor = factorVal > 1.0 ? '#198754' : factorVal < 0.99 ? '#dc3545' : '#6c757d';
        const factorBg    = factorVal > 1.0 ? '#d1f0da' : factorVal < 0.99 ? '#f8d7da' : '#f1f3f5';
        const factorBdr   = factorVal > 1.0 ? '#a3d9b1' : factorVal < 0.99 ? '#f5c2c7' : '#dee2e6';
        const factorLabel = factorDir ? `×${factorVal.toFixed(2)} ${factorDir}` : `×${factorVal.toFixed(2)}`;
        document.getElementById('marketEvalKpis').innerHTML = `
            <div class="px-3 py-1 rounded" style="background:#f1f3f5;border:1px solid #dee2e6;">
                <div class="text-muted" style="font-size:0.7rem;">Outcome</div>
                <div style="font-weight:700;">${data.outcome || '—'}</div>
            </div>
            <div class="px-3 py-1 rounded" style="background:#f1f3f5;border:1px solid #dee2e6;">
                <div class="text-muted" style="font-size:0.7rem;">Entradas</div>
                <div style="color:#198754;font-weight:700;font-family:monospace;">${entered}</div>
            </div>
            <div class="px-3 py-1 rounded" style="background:#f1f3f5;border:1px solid #dee2e6;">
                <div class="text-muted" style="font-size:0.7rem;">Bloqueados</div>
                <div style="font-family:monospace;font-weight:600;">${blocked}</div>
            </div>
            <div class="px-3 py-1 rounded" style="background:#f1f3f5;border:1px solid #dee2e6;">
                <div class="text-muted" style="font-size:0.7rem;">Razones top</div>
                <div style="font-family:monospace;font-size:0.68rem;color:#495057;">${blockTypes.slice(0,3).join(', ')||'—'}</div>
            </div>
            <div class="px-3 py-1 rounded" style="background:#f1f3f5;border:1px solid #dee2e6;">
                <div class="text-muted" style="font-size:0.7rem;">Conf. media</div>
                <div style="font-family:monospace;color:#0077b6;font-weight:600;">${avgConf}%</div>
            </div>
            <div class="px-3 py-1 rounded" style="background:${factorBg};border:1px solid ${factorBdr};">
                <div class="text-muted" style="font-size:0.7rem;">Factor Bayesiano</div>
                <div style="font-family:monospace;font-weight:700;color:${factorColor};">${factorLabel}</div>
            </div>
            <div class="px-3 py-1 rounded" style="background:#fff3cd;border:1px solid #ffeeba;">
                <div class="text-muted" style="font-size:0.7rem;">Límite ATR</div>
                <div style="font-family:monospace;font-weight:600;color:#856404;">${(firstEval.atr_limit || ATR_THRESHOLDS[asset] || 0.08).toFixed(3)}%</div>
            </div>`;

        // Datos para los charts
        const labels    = evals.map(e => {
            if (!e.ts) return `${e.elapsed_sec}s`;
            const d = new Date(e.ts);
            return d.toLocaleTimeString('es-ES', {timeZone: 'Asia/Ho_Chi_Minh', hour12: false});
        });
        // price_yes: precio BID del token YES (UP) — siempre se grafica la cara UP
        const prices    = evals.map(e => e.price_yes || e.entry_price || 0);
        const macds     = evals.map(e => e.macd_hist   || 0);
        const macdLines = evals.map(e => e.macd_line   || 0);
        const moms      = evals.map(e => e.momentum   || 0);
        const confs     = evals.map(e => (e.confidence || 0) * 100);
        const rsis      = evals.map(e => e.rsi || 50);

        // Color de cada punto según decisión
        const decisionColor = d => {
            if (!d || d === 'NO_SIGNAL') return '#6c757d';
            if (d === 'ENTERED')         return '#198754';
            if (d === 'BLOCKED_SLOTS_FULL' || d.includes('CORR')) return '#6f42c1';
            if (d.includes('DISABLED') || d.includes('PRICE_HIGH') || d.includes('LATE_SCALP')) return '#e83e8c';
            if (d.includes('MACD') || d.includes('FLAT') || d.includes('ONLY_UP')) return '#fd7e14';
            if (d.includes('RSI')  || d.includes('MOMENTUM'))     return '#e67e00';
            if (d.includes('VOL')  || d.includes('SPREAD') || d.includes('PRICE') || d.includes('KELLY')) return '#dc3545';
            return '#adb5bd'; // WINDOW / HOURS / ALREADY_IN / BTC_CONFIRM
        };
        const ptColors = evals.map(e => decisionColor(e.decision));
        const ptRadii  = evals.map(e => e.decision === 'ENTERED' ? 7 : 4);

        const chartDefaults = {
            responsive: true, maintainAspectRatio: false, animation: false,
            plugins: { legend: { labels: { color:'#495057', font:{size:10} } } },
            scales: { x: { ticks: { color:'#6c757d', font:{size:9}, maxTicksLimit:12 }, grid: { color:'rgba(0,0,0,0.06)' } } }
        };

        // Anotaciones: entrada + salida
        const meAnnotations = {};
        const tradeEvColors = {
            ENTRY:'#0dcaf0', TAKE_PROFIT:'#198754', STOP_LOSS:'#dc3545',
            MARKET_WIN:'#198754', MARKET_LOSS:'#dc3545'
        };

        // Línea vertical en el punto de entrada (primer eval con decision ENTERED)
        const entryIdx = evals.findIndex(e => e.decision === 'ENTERED');
        if (entryIdx >= 0) {
            const entryP = (evals[entryIdx].price_yes || evals[entryIdx].entry_price || 0);
            meAnnotations.entry = {
                type: 'line', xMin: entryIdx, xMax: entryIdx,
                borderColor: '#0dcaf0', borderWidth: 2, borderDash: [4, 3],
                label: { display: true, content: `Entrada @ ${(entryP * 100).toFixed(0)}¢`,
                         position: 'start',
                         color: '#fff', backgroundColor: '#0099bb',
                         font: { size: 10, weight: 'bold' }, padding: 4 }
            };
        }

        // Punto de salida real: usar eventos del trade log si están disponibles
        const tradeEvts = data.trade_events || [];
        const EXIT_TYPES = ['TAKE_PROFIT', 'STOP_LOSS', 'MARKET_WIN', 'MARKET_LOSS'];
        const exitTradeEv = tradeEvts.find(ev => EXIT_TYPES.includes(ev.type));

        if (exitTradeEv) {
            // El trade log usa elapsed_sec desde entry_time del trade.
            // Aproximamos el índice en evals buscando el eval más cercano en tiempo.
            const entryEval = evals[entryIdx] || evals[0];
            const entryTs   = entryEval ? new Date(entryEval.ts || 0).getTime() : 0;
            const exitTs    = entryTs + exitTradeEv.t * 1000;
            let exitIdx = evals.length - 1;
            if (entryTs > 0) {
                let best = Infinity;
                evals.forEach((e, i) => {
                    if (!e.ts) return;
                    const diff = Math.abs(new Date(e.ts).getTime() - exitTs);
                    if (diff < best) { best = diff; exitIdx = i; }
                });
            }
            const exitColor = (exitTradeEv.type === 'TAKE_PROFIT' || exitTradeEv.type === 'MARKET_WIN')
                              ? '#198754' : '#dc3545';
            const exitLabel = exitTradeEv.label || exitTradeEv.type.replace('_', ' ');
            meAnnotations.exit = {
                type: 'line', xMin: exitIdx, xMax: exitIdx,
                borderColor: exitColor, borderWidth: 2.5, borderDash: [5, 3],
                label: { display: true, content: exitLabel, position: 'center',
                         color: '#fff', backgroundColor: exitColor,
                         font: { size: 10, weight: 'bold' }, padding: 4 }
            };
        } else if (data.outcome && evals.length > 0) {
            // Fallback: resolución de mercado si no hay trade log
            const outcomeColor = data.outcome === 'UP' ? '#198754' : '#dc3545';
            const outcomeLabel = data.outcome === 'UP' ? '✓ Cierra UP' : '✗ Cierra DOWN';
            meAnnotations.resolution = {
                type: 'line', xMin: evals.length - 1, xMax: evals.length - 1,
                borderColor: outcomeColor, borderWidth: 2.5, borderDash: [5, 3],
                label: { display: true, content: outcomeLabel, position: 'center',
                         color: '#fff', backgroundColor: outcomeColor,
                         font: { size: 10, weight: 'bold' }, padding: 4 }
            };
        }

        // Chart 1: Precio token UP (YES) — siempre el token de la cara UP
        if (_meTokenChart) _meTokenChart.destroy();
        _meTokenChart = new Chart(document.getElementById('meTokenChart').getContext('2d'), {
            type: 'line',
            data: {
                labels,
                datasets: [
                    { label: 'Precio UP (YES token)', data: prices,
                      borderColor: ac, backgroundColor: ac+'22', borderWidth: 2,
                      pointRadius: ptRadii, pointBackgroundColor: ptColors, tension: 0.2, fill: true },
                    { label: 'TP 75¢', data: evals.length ? evals.map(() => 0.75) : [],
                      borderColor: '#19875455', borderWidth: 1, pointRadius: 0, borderDash: [4,3] },
                    { label: '50¢', data: evals.length ? evals.map(() => 0.50) : [],
                      borderColor: '#adb5bd55', borderWidth: 1, pointRadius: 0, borderDash: [2,4] },
                ]
            },
            options: { ...chartDefaults,
                plugins: { ...chartDefaults.plugins, annotation: { annotations: meAnnotations } },
                scales: { ...chartDefaults.scales,
                    y: { ticks: { color:'#6c757d', callback: v => (v*100).toFixed(0)+'¢' },
                         grid: { color:'rgba(0,0,0,0.06)' } }
                }
            }
        });

        // Chart 2: MACD
        const macdPos = macds.map(v => v >= 0 ? v : 0);
        const macdNeg = macds.map(v => v <  0 ? v : 0);
        const allMacd = [...macds, ...macdLines].filter(v => isFinite(v));
        const macdMin = allMacd.length ? Math.min(...allMacd) : -1;
        const macdMax = allMacd.length ? Math.max(...allMacd) :  1;
        const macdPad = Math.max(Math.abs(macdMax - macdMin) * 0.2, 0.001);
        if (_meMacdChart) _meMacdChart.destroy();
        _meMacdChart = new Chart(document.getElementById('meMacdChart').getContext('2d'), {
            type: 'bar',
            data: {
                labels,
                datasets: [
                    { label:'Hist+', data:macdPos, type:'bar', backgroundColor:'#198754BB', borderColor:'#198754', borderWidth:1, order:3 },
                    { label:'Hist-', data:macdNeg, type:'bar', backgroundColor:'#dc3545BB', borderColor:'#dc3545', borderWidth:1, order:3 },
                    { label:'MACD',  data:macdLines, type:'line', borderColor:'#0077b6', borderWidth:1.5, pointRadius:0, tension:0.3, order:1 },
                ]
            },
            options: { ...chartDefaults,
                plugins: { ...chartDefaults.plugins,
                    annotation: { annotations: { zero:{ type:'line', yMin:0, yMax:0, borderColor:'#adb5bd', borderWidth:1 }, ...meAnnotations } }
                },
                scales: { ...chartDefaults.scales,
                    y: { min: macdMin-macdPad, max: macdMax+macdPad,
                         ticks: { color:'#6c757d', font:{size:9} }, grid: { color:'rgba(0,0,0,0.06)' } }
                }
            }
        });

        // Chart 3: Momentum + Confianza + RSI
        if (_meMomChart) _meMomChart.destroy();
        _meMomChart = new Chart(document.getElementById('meMomChart').getContext('2d'), {
            type: 'line',
            data: {
                labels,
                datasets: [
                    { label:'Momentum %', data:moms,  borderColor:'#e67e00', borderWidth:1.5, pointRadius:1, tension:0.3, yAxisID:'yMom' },
                    { label:'Conf %',     data:confs, borderColor:'#0dcaf0', borderWidth:1.5, pointRadius:1, tension:0.3, yAxisID:'yMom', borderDash:[3,2] },
                    { label:'RSI',        data:rsis,  borderColor:'#0077b6', borderWidth:1.5, pointRadius:1, tension:0.3, yAxisID:'yRsi' },
                ]
            },
            options: { ...chartDefaults,
                plugins: { ...chartDefaults.plugins, annotation: { annotations: meAnnotations } },
                scales: { ...chartDefaults.scales,
                    yMom: { type:'linear', position:'left',
                            ticks:{ color:'#e67e00', font:{size:9}, callback: v => v.toFixed(1)+'%' },
                            grid:{ color:'rgba(0,0,0,0.06)' } },
                    yRsi: { type:'linear', position:'right', min:0, max:100,
                            ticks:{ color:'#0077b6', font:{size:9}, callback: v => v+'' },
                            grid:{ drawOnChartArea:false } }
                }
            }
        });
    }

    function prefetchTradeLogs(trades) {
        trades.forEach(t => {
            if (!t.log_file) return;
            const fname = t.log_file.split('/').pop();
            if (_tradeLogCache[fname]) return; // ya cargado
            fetch(`${API}?trade_log=${encodeURIComponent(fname)}&_=${Date.now()}`, { cache: 'no-store' })
                .then(r => r.ok ? r.json() : null)
                .then(data => { if (data) _tradeLogCache[fname] = data; })
                .catch(() => {});
        });
    }

    function openTradeChart(logFile) {
        const modal = bootstrap.Modal.getOrCreateInstance(document.getElementById('tradeChartModal'));
        modal.show();
        document.getElementById('tradeChartTitle').textContent = '⏳ Cargando...';
        document.getElementById('tradeChartMeta').textContent = '';
        document.getElementById('tradeChartKpis').innerHTML = '';

        const cached = _tradeLogCache[logFile];
        if (cached) {
            // Soporte para caché viejo (solo trade_data) y nuevo ({trade_data, mlog_data})
            const td = cached.trade_data || cached;
            const ml = cached.mlog_data  || null;
            renderTradeChart(td, ml);
            return;
        }

        fetch(`${API}?trade_log=${encodeURIComponent(logFile)}&_=${Date.now()}`, { cache: 'no-store' })
            .then(r => { if (!r.ok) throw new Error('Log no encontrado: ' + logFile); return r.json(); })
            .then(tradeData => {
                // Derivar el nombre del market log desde el trade
                const t = tradeData.trade || {};
                const mlfname = t.market_log_fname ||
                    (t.asset && t.slug ? `${t.asset}_${t.slug.slice(-16)}.json` : null);
                const mlogPromise = mlfname
                    ? fetch(`${API}?market_log=${encodeURIComponent(mlfname)}&_=${Date.now()}`, { cache: 'no-store' })
                        .then(r => r.ok ? r.json() : null).catch(() => null)
                    : Promise.resolve(null);
                return mlogPromise.then(mlogData => {
                    _tradeLogCache[logFile] = { trade_data: tradeData, mlog_data: mlogData };
                    renderTradeChart(tradeData, mlogData);
                });
            })
            .catch(e => { document.getElementById('tradeChartTitle').textContent = '❌ ' + e.message; });
    }

    function renderTradeChart(data, mlogData) {
        const t = data.trade;
        const snaps = data.snapshots || [];
        const events = data.events || [];

        // Prepend evaluaciones del market log anteriores a la entrada
        const entryElapsed = t.elapsed_sec || 0;
        const preSnaps = mlogData ? (mlogData.evaluations || [])
            .filter(ev => (ev.elapsed_sec || 0) < entryElapsed && ev.entry_price > 0)
            .map(ev => ({
                t:         (ev.elapsed_sec || 0) - entryElapsed,  // negativo: antes de entrar
                bid:       ev.price_yes || ev.entry_price || 0,
                ask:       ev.entry_price || 0,
                macd_hist: ev.macd_hist  || 0,
                macd_line: ev.macd_line  || 0,
                sig_line:  null,
                momentum:  ev.momentum   || 0,
                rsi:       ev.rsi        || 50,
            })) : [];
        const allSnaps = [...preSnaps, ...snaps];

        // Título y meta
        const asset = t.asset || 'BTC';
        const assetColors = {BTC:'#F7931A', ETH:'#627EEA', SOL:'#9945FF', XRP:'#00AAE4'};
        const ac = assetColors[asset] || '#aaa';
        document.getElementById('tradeChartTitle').innerHTML =
            `<span style="color:${ac};">●</span> ${asset} — ${t.side} @ ${(t.entry_price*100).toFixed(0)}¢  <span class="badge ${t.outcome==='WIN'||t.outcome==='TAKE PROFIT'?'bg-success':t.outcome==='LOSS'?'bg-danger':'bg-warning text-dark'}">${t.outcome||'OPEN'}</span>`;
        const entryDt = t.entry_time ? new Date(t.entry_time).toLocaleString('es-ES',{timeZone:'Asia/Ho_Chi_Minh',hour12:false}) : '';
        document.getElementById('tradeChartMeta').textContent =
            `Entrada: ${entryDt} VN  |  Strike: $${(t.strike||0).toLocaleString()}  |  Inversión: $${(t.position||0).toFixed(2)}`;

        // KPIs
        const pnlColor = (t.pnl||0) >= 0 ? '#198754' : '#dc3545';
        const roi = t.position > 0 ? (t.pnl/t.position*100).toFixed(1) : '0.0';
        const tcFactor = t.factor || 1.0;
        const tcDir    = (t.direction || '').toUpperCase();
        const tcFColor = tcFactor > 1.0 ? '#198754' : tcFactor < 0.99 ? '#dc3545' : '#6c757d';
        const tcFBg    = tcFactor > 1.0 ? '#d1f0da' : tcFactor < 0.99 ? '#f8d7da' : '#f1f3f5';
        const tcFBdr   = tcFactor > 1.0 ? '#a3d9b1' : tcFactor < 0.99 ? '#f5c2c7' : '#dee2e6';
        const tcFLabel = tcDir ? `×${tcFactor.toFixed(2)} ${tcDir}` : `×${tcFactor.toFixed(2)}`;
        document.getElementById('tradeChartKpis').innerHTML = `
            <div class="px-3 py-1 rounded" style="background:#f1f3f5; border:1px solid #dee2e6;">
                <div class="text-muted" style="font-size:0.7rem;">P&L</div>
                <div style="color:${pnlColor}; font-weight:700; font-family:monospace;">${(t.pnl||0)>=0?'+':''}$${(t.pnl||0).toFixed(2)} (${roi}%)</div>
            </div>
            <div class="px-3 py-1 rounded" style="background:#f1f3f5; border:1px solid #dee2e6;">
                <div class="text-muted" style="font-size:0.7rem;">Momentum</div>
                <div style="font-family:monospace; color:#e67e00; font-weight:600;">${(t.momentum||0)>=0?'+':''}${(t.momentum||0).toFixed(3)}%</div>
            </div>
            <div class="px-3 py-1 rounded" style="background:#f1f3f5; border:1px solid #dee2e6;">
                <div class="text-muted" style="font-size:0.7rem;">Confianza</div>
                <div style="font-family:monospace; color:#0077b6; font-weight:600;">${((t.confidence||0)*100).toFixed(0)}%</div>
            </div>
            <div class="px-3 py-1 rounded" style="background:#f1f3f5; border:1px solid #dee2e6;">
                <div class="text-muted" style="font-size:0.7rem;">MACD</div>
                <div style="font-family:monospace; color:#495057; font-weight:600;">${t.macd_signal||'—'}</div>
            </div>
            <div class="px-3 py-1 rounded" style="background:${tcFBg}; border:1px solid ${tcFBdr};">
                <div class="text-muted" style="font-size:0.7rem;">Factor Bayesiano</div>
                <div style="font-family:monospace; font-weight:700; color:${tcFColor};">${tcFLabel}</div>
            </div>
            <div class="px-3 py-1 rounded" style="background:#f1f3f5; border:1px solid #dee2e6;">
                <div class="text-muted" style="font-size:0.7rem;">ATR / BB / SPK</div>
                <div style="font-family:monospace; color:#495057; font-size:0.75rem;">${(t.vol_atr||0).toFixed(2)}% / ${(t.vol_bb||0).toFixed(2)}% / ${(t.vol_spike||0).toFixed(2)}%</div>
            </div>`;

        // Preparar datos de los charts (allSnaps = pre-entrada + post-entrada)
        const isDown = t.side === 'Down';
        const _entryTs = t.entry_time ? new Date(t.entry_time).getTime() : null;
        const labels = allSnaps.map(s => {
            if (!_entryTs) return `${s.t}s`;
            const d = new Date(_entryTs + s.t * 1000);
            return d.toLocaleTimeString('es-ES', {timeZone: 'Asia/Ho_Chi_Minh', hour12: false});
        });
        const bids   = allSnaps.map(s => isDown ? 1 - (s.ask || s.bid) : s.bid);
        const asks   = allSnaps.map(s => isDown ? 1 - s.bid             : s.ask);
        const macds  = allSnaps.map(s => s.macd_hist);
        const moms   = allSnaps.map(s => s.momentum);
        const rsis   = allSnaps.map(s => s.rsi);

        // Anotaciones de eventos como líneas verticales
        const eventColors = {
            ENTRY:'#0dcaf0', TAKE_PROFIT:'#198754', STOP_LOSS:'#dc3545',
            MARKET_WIN:'#198754', MARKET_LOSS:'#dc3545'
        };
        const annotations = {};
        // Línea vertical en el punto de entrada (t=0)
        const entrySnapIdx = allSnaps.findIndex(s => s.t >= 0);
        if (entrySnapIdx >= 0) {
            annotations['entry_line'] = {
                type: 'line', xMin: entrySnapIdx, xMax: entrySnapIdx,
                borderColor: '#0dcaf0', borderWidth: 2, borderDash: [5, 3],
                label: { display: true, content: 'Entrada', position: 'start',
                         color: '#fff', backgroundColor: '#0dcaf080',
                         font: { size: 9 }, padding: 2 }
            };
        }
        events.forEach((ev, i) => {
            const snapIdx = allSnaps.findIndex(s => s.t >= ev.t);
            if (snapIdx < 0 && allSnaps.length === 0) return;
            const xIdx = snapIdx >= 0 ? snapIdx : allSnaps.length - 1;
            annotations[`ev${i}`] = {
                type: 'line', xMin: xIdx, xMax: xIdx,
                borderColor: eventColors[ev.type] || '#aaa',
                borderWidth: 2, borderDash: [4,3],
                label: { display: true, content: ev.label || ev.type,
                         position: i % 2 === 0 ? 'start' : 'end',
                         color: '#fff', backgroundColor: eventColors[ev.type] || '#555',
                         font: { size: 10 }, padding: 3 }
            };
        });

        const chartDefaults = {
            responsive: true, maintainAspectRatio: false,
            animation: false,
            plugins: { legend: { labels: { color: '#495057', font: { size: 10 } } } },
            scales: {
                x: { ticks: { color: '#6c757d', font: { size: 9 }, maxTicksLimit: 10 },
                     grid: { color: 'rgba(0,0,0,0.06)' } }
            }
        };

        // ── Chart 1: Token price ──────────────────────────────────────────────
        if (_tcTokenChart) _tcTokenChart.destroy();
        _tcTokenChart = new Chart(document.getElementById('tradeTokenChart').getContext('2d'), {
            type: 'line',
            data: {
                labels,
                datasets: [
                    { label: 'Bid', data: bids, borderColor: ac, backgroundColor: ac+'22',
                      borderWidth: 2, pointRadius: 2, tension: 0.3, fill: true },
                    { label: 'Ask', data: asks, borderColor: '#6c757d', borderWidth: 1,
                      pointRadius: 0, tension: 0.3, borderDash: [3,2] },
                    { label: 'Entrada', data: allSnaps.map(() => isDown ? 1 - t.entry_price       : t.entry_price),
                      borderColor: '#0dcaf088', borderWidth: 1, pointRadius: 0, borderDash: [6,3] },
                    { label: 'TP',      data: allSnaps.map(() => isDown ? 1 - (t.target_tp||0.80) : (t.target_tp||0.80)),
                      borderColor: '#19875466', borderWidth: 1, pointRadius: 0, borderDash: [2,4] },
                ]
            },
            options: { ...chartDefaults,
                plugins: { ...chartDefaults.plugins, annotation: { annotations } },
                scales: { ...chartDefaults.scales,
                    y: { min: 0, max: 1, ticks: { color: '#6c757d', callback: v => (v*100).toFixed(0)+'¢' },
                         grid: { color: 'rgba(0,0,0,0.06)' } }
                }
            }
        });

        // ── Chart 2: MACD histogram + MACD line + Signal line ────────────────
        const macdLines = allSnaps.map(s => s.macd_line);
        const sigLines  = allSnaps.map(s => s.sig_line);
        const macdPos   = macds.map(v => v >= 0 ? v : 0);
        const macdNeg   = macds.map(v => v <  0 ? v : 0);
        const allMacd   = [...macds, ...macdLines, ...sigLines].filter(v => isFinite(v));
        const macdMin   = allMacd.length ? Math.min(...allMacd) : -1;
        const macdMax   = allMacd.length ? Math.max(...allMacd) :  1;
        const macdPad   = Math.max(Math.abs(macdMax - macdMin) * 0.2, 0.001);
        if (_tcMacdChart) _tcMacdChart.destroy();
        _tcMacdChart = new Chart(document.getElementById('tradeMacdChart').getContext('2d'), {
            type: 'bar',
            data: {
                labels,
                datasets: [
                    { label: 'Hist+', data: macdPos, type: 'bar',
                      backgroundColor: '#198754BB', borderColor: '#198754', borderWidth: 1, order: 3 },
                    { label: 'Hist-', data: macdNeg, type: 'bar',
                      backgroundColor: '#dc3545BB', borderColor: '#dc3545', borderWidth: 1, order: 3 },
                    { label: 'MACD', data: macdLines, type: 'line',
                      borderColor: '#0077b6', borderWidth: 1.5, pointRadius: 0, tension: 0.3, order: 1 },
                    { label: 'Signal', data: sigLines, type: 'line',
                      borderColor: '#e67e00', borderWidth: 1.5, pointRadius: 0, tension: 0.3, borderDash: [4,2], order: 1 },
                ]
            },
            options: { ...chartDefaults,
                plugins: { ...chartDefaults.plugins,
                    annotation: { annotations: {
                        zero: { type:'line', yMin:0, yMax:0, borderColor:'#adb5bd', borderWidth:1 }
                    }}
                },
                scales: { ...chartDefaults.scales,
                    y: { min: macdMin - macdPad, max: macdMax + macdPad,
                         ticks: { color: '#6c757d', font: { size: 9 } },
                         grid: { color: 'rgba(0,0,0,0.06)' } }
                }
            }
        });

        // ── Chart 3: Momentum + RSI ───────────────────────────────────────────
        if (_tcMomChart) _tcMomChart.destroy();
        _tcMomChart = new Chart(document.getElementById('tradeMomChart').getContext('2d'), {
            type: 'line',
            data: {
                labels,
                datasets: [
                    { label: 'Momentum %', data: moms, borderColor: '#e67e00', borderWidth: 1.5,
                      pointRadius: 1, tension: 0.3, yAxisID: 'yMom' },
                    { label: 'RSI', data: rsis, borderColor: '#0077b6', borderWidth: 1.5,
                      pointRadius: 1, tension: 0.3, yAxisID: 'yRsi' },
                ]
            },
            options: { ...chartDefaults,
                scales: { ...chartDefaults.scales,
                    yMom: { type:'linear', position:'left',
                            ticks: { color:'#e67e00', font:{size:9}, callback: v => v.toFixed(2)+'%' },
                            grid: { color:'rgba(0,0,0,0.06)' } },
                    yRsi: { type:'linear', position:'right',  min:0, max:100,
                            ticks: { color:'#0077b6', font:{size:9}, callback: v => v+'%' },
                            grid: { drawOnChartArea:false } }
                }
            }
        });
    }

    fetchMarketLogs();

    // ── Analyzer Comparison Chart ──────────────────────────────────────────────
    let _acPriceChart = null, _acMacdChart = null;
    const _acCache = {};

    function openAnalyzerChart(fname, trade) {
        const modalEl = document.getElementById('analyzerChartModal');
        const modal   = bootstrap.Modal.getOrCreateInstance(modalEl);
        document.getElementById('acTitle').textContent = '⏳ Cargando...';
        document.getElementById('acMeta').textContent  = '';
        document.getElementById('acKpis').innerHTML    = '';

        const doRender = (data) => {
            const go = () => {
                try { renderAnalyzerChart(data, trade); }
                catch(e) { document.getElementById('acTitle').textContent = '❌ ' + e.message; console.error(e); }
            };
            if (modalEl.classList.contains('show')) go();
            else { const h = () => { modalEl.removeEventListener('shown.bs.modal', h); go(); }; modalEl.addEventListener('shown.bs.modal', h); }
        };

        modal.show();
        if (_acCache[fname]) { doRender(_acCache[fname]); return; }

        fetch(`${API}?market_log=${encodeURIComponent(fname)}&_=${Date.now()}`, { cache: 'no-store' })
            .then(r => r.ok ? r.json() : null)
            .then(data => {
                if (!data) { document.getElementById('acTitle').textContent = '❌ No se encontró el archivo'; return; }
                _acCache[fname] = data;
                doRender(data);
            })
            .catch(e => { document.getElementById('acTitle').textContent = '❌ ' + e.message; });
    }

    function renderAnalyzerChart(data, trade) {
        if (_acPriceChart) { _acPriceChart.destroy(); _acPriceChart = null; }
        if (_acMacdChart)  { _acMacdChart.destroy();  _acMacdChart  = null; }

        const evals  = data.evaluations || [];
        const asset  = data.asset || 'BTC';
        const assetC = {BTC:'#F7931A', ETH:'#627EEA', SOL:'#9945FF', XRP:'#00AAE4'}[asset] || '#aaa';
        const assetI = {BTC:'₿', ETH:'⟠', SOL:'◎', XRP:'✕'}[asset] || '●';

        // Título
        const mktOutBadge = data.outcome === 'UP'
            ? '<span class="badge bg-success ms-1" style="font-size:0.7rem;">UP ✓</span>'
            : data.outcome === 'DOWN'
            ? '<span class="badge bg-danger ms-1" style="font-size:0.7rem;">DOWN ✗</span>'
            : '';
        document.getElementById('acTitle').innerHTML =
            `<span style="color:${assetC};">${assetI}</span> ${asset} — Simulado vs Real ${mktOutBadge}`;
        const endDt = data.end_date
            ? new Date(data.end_date).toLocaleString('es-ES', {timeZone:'Asia/Ho_Chi_Minh', hour12:false})
            : '';
        document.getElementById('acMeta').textContent =
            `Cierre: ${endDt} VN  |  Strike: $${(data.strike||0).toLocaleString()}  |  ${evals.length} evals`;

        // KPIs: comparar simulado vs real
        const simOutcome  = trade.outcome || '—';
        const simPnl      = trade.pnl || 0;
        const simEntry    = trade.sim_entry_price || trade.entry_price || 0;
        const simExitFill = trade.sim_exit_price_filled || 0;
        const simExitType = trade.exit_type || 'MARKET';
        const simOutColor = simPnl >= 0 ? '#198754' : '#dc3545';

        const tradeEvts   = data.trade_events || [];
        const EXIT_TYPES  = ['TAKE_PROFIT', 'STOP_LOSS', 'MARKET_WIN', 'MARKET_LOSS'];
        const realExitEv  = tradeEvts.find(ev => EXIT_TYPES.includes(ev.type));
        const realEntryEv = tradeEvts.find(ev => ev.type === 'ENTRY');

        const realOutcome  = realExitEv ? realExitEv.type.replace('_', ' ') : '(sin trade real)';
        const realOutColor = (realExitEv && (realExitEv.type === 'TAKE_PROFIT' || realExitEv.type === 'MARKET_WIN')) ? '#198754' : '#dc3545';
        const realEntryPrc = realEntryEv ? realEntryEv.price || 0 : 0;
        const realExitPrc  = realExitEv  ? realExitEv.price  || 0 : 0;

        const kpiBox = (label, val, color='#212529', bg='#f8f9fa', bdr='#dee2e6') =>
            `<div class="px-3 py-1 rounded" style="background:${bg};border:1px solid ${bdr};min-width:90px;">
                <div class="text-muted" style="font-size:0.65rem;">${label}</div>
                <div style="font-weight:700;color:${color};font-family:monospace;">${val}</div>
             </div>`;

        const acFactor  = trade.factor || 1.0;
        const acDir     = (trade.direction || '').toUpperCase();
        const acFColor  = acFactor > 1.0 ? '#198754' : acFactor < 0.99 ? '#dc3545' : '#6c757d';
        const acFBg     = acFactor > 1.0 ? '#d1f0da' : acFactor < 0.99 ? '#f8d7da' : '#f8f9fa';
        const acFBdr    = acFactor > 1.0 ? '#a3d9b1' : acFactor < 0.99 ? '#f5c2c7' : '#dee2e6';
        const acFLabel  = acDir ? `×${acFactor.toFixed(2)} ${acDir}` : `×${acFactor.toFixed(2)}`;

        document.getElementById('acKpis').innerHTML =
            `<div class="me-2 fw-semibold" style="color:#0077b6;align-self:center;">SIM</div>` +
            kpiBox('Outcome', simOutcome, simOutColor) +
            kpiBox('Entrada', `${(simEntry*100).toFixed(0)}¢`) +
            kpiBox('Salida', `${(simExitFill*100).toFixed(0)}¢`) +
            kpiBox('PnL', `${simPnl>=0?'+':''}$${simPnl.toFixed(2)}`, simOutColor) +
            kpiBox('Factor', acFLabel, acFColor, acFBg, acFBdr) +
            `<div class="mx-2" style="border-left:2px solid #dee2e6;"></div>` +
            `<div class="me-2 fw-semibold" style="color:#fd7e14;align-self:center;">BOT</div>` +
            (realEntryEv
                ? kpiBox('Outcome', realOutcome, realOutColor) +
                  kpiBox('Entrada', `${(realEntryPrc*100).toFixed(0)}¢`) +
                  kpiBox('Salida',  `${(realExitPrc*100).toFixed(0)}¢`)
                : `<div class="text-muted small" style="align-self:center;">Sin trade real para este mercado</div>`);

        // Series de datos
        const labels = evals.map(e => {
            if (!e.ts) return `${Math.round(e.elapsed_sec||0)}s`;
            return new Date(e.ts).toLocaleTimeString('es-ES', {timeZone:'Asia/Ho_Chi_Minh', hour12:false});
        });
        const prices = evals.map(e => e.price_yes || e.entry_price || 0);
        const macds  = evals.map(e => e.macd_hist || 0);

        // Helper: índice del eval más próximo a elapsed_sec dado
        const evalIdxFor = (elapsed) => {
            if (!evals.length) return 0;
            let best = 0, bestD = Infinity;
            evals.forEach((e, i) => { const d = Math.abs((e.elapsed_sec||0) - elapsed); if (d < bestD) { bestD = d; best = i; } });
            return best;
        };

        // Anotaciones
        const annotations = {};
        const simEntryElapsed = trade.sim_entry_elapsed || 0;
        const simExitElapsed  = trade.sim_exit_elapsed  || 900;
        const targetTp        = trade.target_tp || 0.80;
        const slThreshold     = trade.sl_threshold || 0;
        const simEntryIdx     = evalIdxFor(simEntryElapsed);
        const simExitIdx      = evalIdxFor(simExitElapsed);

        // Línea TP
        annotations.tpLine = {
            type: 'line', yMin: targetTp, yMax: targetTp,
            borderColor: 'rgba(25,135,84,0.7)', borderWidth: 1.5, borderDash: [6,4],
            label: { display: true, content: `TP ${(targetTp*100).toFixed(0)}¢`, position: '1%',
                     color: '#198754', backgroundColor: 'transparent', font:{size:9} }
        };

        // Línea SL threshold (solo si es visible)
        if (slThreshold > 0.05) {
            annotations.slLine = {
                type: 'line', yMin: slThreshold, yMax: slThreshold,
                borderColor: 'rgba(220,53,69,0.5)', borderWidth: 1, borderDash: [4,4],
                label: { display: true, content: `SL ${(slThreshold*100).toFixed(0)}¢`, position: '1%',
                         color: '#dc3545', backgroundColor: 'transparent', font:{size:9} }
            };
        }

        // Entrada simulada
        annotations.simEntry = {
            type: 'line', xMin: simEntryIdx, xMax: simEntryIdx,
            borderColor: '#0dcaf0', borderWidth: 2, borderDash: [5,3],
            label: { display: true, content: `SIM ENT ${(simEntry*100).toFixed(0)}¢`,
                     position: 'start', color: '#fff', backgroundColor: '#0099bb',
                     font:{size:9,weight:'bold'}, padding:3 }
        };

        // Salida simulada
        const exitColor = simExitType === 'TP' ? '#198754' : simExitType === 'SL' ? '#dc3545' : '#6c757d';
        const exitLabel = simExitType === 'TP' ? 'SIM TP' : simExitType === 'SL' ? 'SIM SL' : 'SIM FIN';
        annotations.simExit = {
            type: 'line', xMin: simExitIdx, xMax: simExitIdx,
            borderColor: exitColor, borderWidth: 2, borderDash: [5,3],
            label: { display: true, content: `${exitLabel} ${(simExitFill*100).toFixed(0)}¢`,
                     position: 'end', color: '#fff', backgroundColor: exitColor,
                     font:{size:9,weight:'bold'}, padding:3 }
        };

        // Entrada real del bot
        if (realEntryEv) {
            const firstEval = evals[0] || {};
            const baseTs    = firstEval.ts ? new Date(firstEval.ts).getTime() : 0;
            const entEvalIdx = evals.findIndex(e => e.decision === 'ENTERED');
            const refEval    = entEvalIdx >= 0 ? evals[entEvalIdx] : firstEval;
            const refTs      = refEval.ts ? new Date(refEval.ts).getTime() : baseTs;
            const realEntTs  = refTs + (realEntryEv.t || 0) * 1000;
            let rEntIdx = 0, rBest = Infinity;
            evals.forEach((e, i) => { if (e.ts) { const d = Math.abs(new Date(e.ts).getTime() - realEntTs); if (d < rBest) { rBest = d; rEntIdx = i; } } });
            annotations.realEntry = {
                type: 'line', xMin: rEntIdx, xMax: rEntIdx,
                borderColor: '#fd7e14', borderWidth: 2,
                label: { display: true, content: `BOT ENT ${(realEntryPrc*100).toFixed(0)}¢`,
                         position: 'start', color: '#fff', backgroundColor: '#e06c00',
                         font:{size:9,weight:'bold'}, padding:3 }
            };

            // Salida real del bot
            if (realExitEv) {
                const realExitTs = refTs + (realExitEv.t || 0) * 1000;
                let rExitIdx = 0, rBest2 = Infinity;
                evals.forEach((e, i) => { if (e.ts) { const d = Math.abs(new Date(e.ts).getTime() - realExitTs); if (d < rBest2) { rBest2 = d; rExitIdx = i; } } });
                const rExitCol = (realExitEv.type === 'TAKE_PROFIT' || realExitEv.type === 'MARKET_WIN') ? '#198754' : '#dc3545';
                const rExitLbl = realExitEv.type === 'TAKE_PROFIT' ? 'BOT TP' : realExitEv.type === 'STOP_LOSS' ? 'BOT SL' : realExitEv.type === 'MARKET_WIN' ? 'BOT WIN' : 'BOT LOSS';
                annotations.realExit = {
                    type: 'line', xMin: rExitIdx, xMax: rExitIdx,
                    borderColor: rExitCol, borderWidth: 2,
                    label: { display: true, content: `${rExitLbl} ${(realExitPrc*100).toFixed(0)}¢`,
                             position: 'end', color: '#fff', backgroundColor: rExitCol,
                             font:{size:9,weight:'bold'}, padding:3 }
                };
            }
        }

        // Colores de puntos según decisión
        const ptColors = evals.map(e => {
            const d = e.decision || '';
            if (d === 'ENTERED') return '#198754';
            if (d.startsWith('BLOCKED')) return '#dc3545';
            return '#adb5bd';
        });

        const scaleDefaults = { ticks:{color:'#6c757d',font:{size:9}}, grid:{color:'rgba(0,0,0,0.06)'} };

        _acPriceChart = new Chart(document.getElementById('acPriceChart').getContext('2d'), {
            type: 'line',
            data: {
                labels,
                datasets: [{
                    label: 'Precio token (BID)',
                    data: prices,
                    borderColor: '#0077b6', borderWidth: 1.5,
                    pointBackgroundColor: ptColors, pointRadius: ptColors.map(c => c === '#198754' ? 6 : 2),
                    fill: false, tension: 0.15
                }]
            },
            options: {
                responsive: true, maintainAspectRatio: false, animation: false,
                plugins: {
                    legend: { display: false },
                    annotation: { annotations }
                },
                scales: {
                    x: { ...scaleDefaults, ticks:{...scaleDefaults.ticks, maxTicksLimit:15} },
                    y: { ...scaleDefaults, min:0, max:1,
                         ticks:{...scaleDefaults.ticks, callback: v => (v*100).toFixed(0)+'¢'} }
                }
            }
        });

        _acMacdChart = new Chart(document.getElementById('acMacdChart').getContext('2d'), {
            type: 'bar',
            data: {
                labels,
                datasets: [{
                    label: 'MACD Hist',
                    data: macds,
                    backgroundColor: macds.map(v => v >= 0 ? 'rgba(25,135,84,0.6)' : 'rgba(220,53,69,0.6)'),
                    borderWidth: 0
                }]
            },
            options: {
                responsive: true, maintainAspectRatio: false, animation: false,
                plugins: { legend:{display:false} },
                scales: {
                    x: { ...scaleDefaults, ticks:{...scaleDefaults.ticks, maxTicksLimit:10} },
                    y: { ...scaleDefaults }
                }
            }
        });
    }

    fetchMarketLogs();
    </script>
</body>
</html>
