Spaces:
Running
Running
<html lang="es"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Bitcoin Price Predictor</title> | |
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script> | |
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> | |
<script src="https://cdn.jsdelivr.net/npm/luxon@2.0.2"></script> | |
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-luxon@1.0.0"></script> | |
<script src="https://accounts.google.com/gsi/client" async defer></script> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<style> | |
* { | |
box-sizing: border-box; | |
margin: 0; | |
padding: 0; | |
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
} | |
body { | |
background-color: #f5f7fa; | |
color: #333; | |
line-height: 1.6; | |
} | |
.container { | |
width: 100%; | |
max-width: 1200px; | |
margin: 0 auto; | |
padding: 20px; | |
} | |
/* Header styles */ | |
header { | |
display: flex; | |
justify-content: space-between; | |
align-items: center; | |
margin-bottom: 30px; | |
flex-wrap: wrap; | |
} | |
.header-title { | |
display: flex; | |
align-items: center; | |
font-size: 28px; | |
font-weight: bold; | |
color: #2d3748; | |
} | |
.header-title i { | |
color: #f7931a; | |
margin-right: 10px; | |
} | |
.header-controls { | |
display: flex; | |
align-items: center; | |
} | |
.refresh-btn { | |
background-color: #3182ce; | |
color: white; | |
border: none; | |
padding: 8px 16px; | |
border-radius: 6px; | |
cursor: pointer; | |
display: flex; | |
align-items: center; | |
margin-right: 15px; | |
transition: background-color 0.3s; | |
} | |
.refresh-btn:hover { | |
background-color: #2c5282; | |
} | |
.refresh-btn i { | |
margin-right: 5px; | |
} | |
.last-updated { | |
font-size: 14px; | |
color: #718096; | |
} | |
.header-subtitle { | |
width: 100%; | |
margin-top: 10px; | |
color: #4a5568; | |
font-size: 14px; | |
} | |
/* Login section */ | |
.login-section { | |
display: flex; | |
justify-content: flex-end; | |
margin-bottom: 20px; | |
gap: 10px; | |
} | |
.user-info { | |
display: flex; | |
align-items: center; | |
} | |
.user-avatar { | |
width: 32px; | |
height: 32px; | |
border-radius: 50%; | |
margin-right: 10px; | |
} | |
.user-name { | |
font-size: 14px; | |
margin-right: 10px; | |
} | |
.logout-btn { | |
background: none; | |
border: none; | |
color: #3182ce; | |
cursor: pointer; | |
font-size: 14px; | |
} | |
.continue-anon { | |
background: none; | |
border: none; | |
color: #718096; | |
cursor: pointer; | |
font-size: 14px; | |
text-decoration: underline; | |
} | |
/* Card grid */ | |
.card-grid { | |
display: flex; | |
flex-wrap: wrap; | |
gap: 20px; | |
margin-bottom: 30px; | |
} | |
.card { | |
flex: 1; | |
min-width: 250px; | |
background: white; | |
border-radius: 10px; | |
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); | |
padding: 20px; | |
transition: transform 0.3s, box-shadow 0.3s; | |
} | |
.card:hover { | |
transform: translateY(-5px); | |
box-shadow: 0 10px 15px rgba(0, 0, 0, 0.1); | |
} | |
.card-header { | |
display: flex; | |
justify-content: space-between; | |
align-items: center; | |
margin-bottom: 15px; | |
} | |
.card-title { | |
font-size: 18px; | |
font-weight: 600; | |
color: #2d3748; | |
} | |
.card-badge { | |
font-size: 12px; | |
padding: 3px 8px; | |
border-radius: 20px; | |
background-color: #e2e8f0; | |
color: #4a5568; | |
} | |
.card-content { | |
display: flex; | |
flex-direction: column; | |
} | |
.price-large { | |
font-size: 28px; | |
font-weight: 700; | |
color: #2d3748; | |
} | |
.price-change { | |
display: flex; | |
align-items: center; | |
font-size: 14px; | |
margin-left: 10px; | |
} | |
.price-up { | |
color: #38a169; | |
} | |
.price-down { | |
color: #e53e3e; | |
} | |
.card-stats { | |
display: flex; | |
font-size: 13px; | |
color: #718096; | |
margin-top: 10px; | |
} | |
.card-stats span { | |
margin: 0 5px; | |
} | |
/* Timeframe tabs */ | |
.timeframe-tabs { | |
display: flex; | |
margin-bottom: 15px; | |
border-bottom: 1px solid #e2e8f0; | |
} | |
.timeframe-tab { | |
padding: 10px 20px; | |
cursor: pointer; | |
border-bottom: 2px solid transparent; | |
font-size: 14px; | |
font-weight: 500; | |
color: #718096; | |
} | |
.timeframe-tab.active { | |
color: #3182ce; | |
border-bottom-color: #3182ce; | |
} | |
/* Main chart */ | |
.chart-container { | |
background: white; | |
border-radius: 10px; | |
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); | |
padding: 20px; | |
margin-bottom: 30px; | |
} | |
.chart-header { | |
display: flex; | |
justify-content: space-between; | |
align-items: center; | |
margin-bottom: 20px; | |
} | |
.chart-title { | |
font-size: 20px; | |
font-weight: 600; | |
color: #2d3748; | |
} | |
.chart-wrapper { | |
height: 400px; | |
position: relative; | |
} | |
/* Prediction methods */ | |
.methods-grid { | |
display: flex; | |
flex-wrap: wrap; | |
gap: 20px; | |
margin-bottom: 30px; | |
} | |
.method-card { | |
flex: 1; | |
min-width: 250px; | |
background: white; | |
border-radius: 10px; | |
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); | |
padding: 20px; | |
} | |
.method-header { | |
display: flex; | |
justify-content: space-between; | |
align-items: center; | |
margin-bottom: 15px; | |
} | |
.method-title { | |
font-size: 16px; | |
font-weight: 600; | |
color: #2d3748; | |
} | |
.method-badge { | |
font-size: 11px; | |
padding: 3px 8px; | |
border-radius: 20px; | |
} | |
.method-content { | |
margin-bottom: 15px; | |
} | |
.method-row { | |
display: flex; | |
justify-content: space-between; | |
font-size: 14px; | |
color: #4a5568; | |
margin-bottom: 8px; | |
} | |
.method-value { | |
font-weight: 500; | |
} | |
.method-footer { | |
font-size: 12px; | |
color: #718096; | |
display: flex; | |
align-items: center; | |
} | |
.method-footer i { | |
margin-right: 5px; | |
} | |
/* History table */ | |
.history-container { | |
background: white; | |
border-radius: 10px; | |
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); | |
padding: 20px; | |
margin-bottom: 30px; | |
} | |
.history-header { | |
display: flex; | |
justify-content: space-between; | |
align-items: center; | |
margin-bottom: 20px; | |
} | |
.history-title { | |
font-size: 20px; | |
font-weight: 600; | |
color: #2d3748; | |
} | |
.export-btn { | |
background-color: #e2e8f0; | |
color: #4a5568; | |
border: none; | |
padding: 6px 12px; | |
border-radius: 6px; | |
cursor: pointer; | |
display: flex; | |
align-items: center; | |
font-size: 14px; | |
transition: background-color 0.3s; | |
} | |
.export-btn:hover { | |
background-color: #cbd5e0; | |
} | |
.export-btn i { | |
margin-right: 5px; | |
} | |
.history-table { | |
width: 100%; | |
border-collapse: collapse; | |
} | |
.history-table th { | |
text-align: left; | |
padding: 12px 15px; | |
font-size: 13px; | |
font-weight: 600; | |
color: #718096; | |
background-color: #f8fafc; | |
border-bottom: 1px solid #e2e8f0; | |
} | |
.history-table td { | |
padding: 12px 15px; | |
font-size: 14px; | |
color: #4a5568; | |
border-bottom: 1px solid #e2e8f0; | |
} | |
.history-table tr:nth-child(even) { | |
background-color: #f8fafc; | |
} | |
/* Settings */ | |
.settings-container { | |
background: white; | |
border-radius: 10px; | |
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); | |
padding: 20px; | |
} | |
.settings-title { | |
font-size: 20px; | |
font-weight: 600; | |
color: #2d3748; | |
margin-bottom: 20px; | |
} | |
.settings-grid { | |
display: flex; | |
flex-wrap: wrap; | |
gap: 30px; | |
} | |
.settings-section { | |
flex: 1; | |
min-width: 300px; | |
} | |
.section-title { | |
font-size: 16px; | |
font-weight: 600; | |
color: #2d3748; | |
margin-bottom: 15px; | |
} | |
.auto-refresh { | |
display: flex; | |
align-items: center; | |
margin-bottom: 15px; | |
} | |
.auto-refresh input { | |
margin-right: 10px; | |
} | |
.next-refresh { | |
font-size: 14px; | |
color: #718096; | |
} | |
.weight-control { | |
margin-bottom: 10px; | |
display: flex; | |
align-items: center; | |
} | |
.weight-label { | |
width: 150px; | |
font-size: 14px; | |
} | |
.weight-slider { | |
flex: 1; | |
margin: 0 10px; | |
} | |
.weight-value { | |
width: 40px; | |
text-align: right; | |
font-size: 14px; | |
} | |
/* Colors for badges */ | |
.badge-blue { | |
background-color: #ebf8ff; | |
color: #3182ce; | |
} | |
.badge-purple { | |
background-color: #faf5ff; | |
color: #805ad5; | |
} | |
.badge-red { | |
background-color: #fff5f5; | |
color: #e53e3e; | |
} | |
.badge-green { | |
background-color: #f0fff4; | |
color: #38a169; | |
} | |
.badge-yellow { | |
background-color: #fffff0; | |
color: #d69e2e; | |
} | |
.badge-indigo { | |
background-color: #ebf4ff; | |
color: #5c6ac4; | |
} | |
.badge-pink { | |
background-color: #fff5f7; | |
color: #d53f8c; | |
} | |
/* Tooltip */ | |
.tooltip { | |
position: relative; | |
display: inline-block; | |
} | |
.tooltip .tooltiptext { | |
visibility: hidden; | |
width: 200px; | |
background-color: #333; | |
color: #fff; | |
text-align: center; | |
border-radius: 6px; | |
padding: 5px; | |
position: absolute; | |
z-index: 1; | |
bottom: 125%; | |
left: 50%; | |
margin-left: -100px; | |
opacity: 0; | |
transition: opacity 0.3s; | |
font-size: 12px; | |
} | |
.tooltip:hover .tooltiptext { | |
visibility: visible; | |
opacity: 1; | |
} | |
/* Responsive adjustments */ | |
@media (max-width: 768px) { | |
.card, .method-card { | |
min-width: 100%; | |
} | |
.header-title { | |
font-size: 24px; | |
margin-bottom: 10px; | |
} | |
.header-controls { | |
width: 100%; | |
justify-content: space-between; | |
} | |
.chart-wrapper { | |
height: 300px; | |
} | |
} | |
</style> | |
</head> | |
<body> | |
<div class="container"> | |
<!-- Login Section --> | |
<div class="login-section" id="loginSection"> | |
<div id="g_id_onload" | |
data-client_id="YOUR_GOOGLE_CLIENT_ID.apps.googleusercontent.com" | |
data-callback="handleCredentialResponse" | |
data-auto_prompt="false"> | |
</div> | |
<div class="g_id_signin" | |
data-type="standard" | |
data-size="medium" | |
data-theme="outline" | |
data-text="sign_in_with" | |
data-shape="rectangular" | |
data-logo_alignment="left"> | |
</div> | |
<button id="continueAnon" class="continue-anon">Continuar sin iniciar sesión</button> | |
</div> | |
<header> | |
<div class="header-title"> | |
<i class="fab fa-bitcoin"></i> Bitcoin Price Predictor | |
</div> | |
<div class="header-controls"> | |
<button id="refreshBtn" class="refresh-btn"> | |
<i class="fas fa-sync-alt"></i> Actualizar | |
</button> | |
<span id="lastUpdated" class="last-updated"></span> | |
</div> | |
<div class="header-subtitle"> | |
Predicciones combinadas de múltiples modelos para los próximos periodos | |
</div> | |
</header> | |
<div class="card-grid"> | |
<!-- Current Price Card --> | |
<div class="card"> | |
<div class="card-header"> | |
<div class="card-title">Precio Actual</div> | |
<span class="card-badge">En tiempo real</span> | |
</div> | |
<div class="card-content"> | |
<div style="display: flex; align-items: flex-end;"> | |
<span id="currentPrice" class="price-large">$--</span> | |
<span id="priceChange" class="price-change"> | |
<span class="change-arrow"></span> | |
<span class="change-percent"></span> | |
</span> | |
</div> | |
<div class="card-stats"> | |
<span id="marketCap">Capitalización: $--</span> | |
<span>|</span> | |
<span id="volume">Volumen: $--</span> | |
</div> | |
</div> | |
</div> | |
<!-- Combined Prediction Card --> | |
<div class="card"> | |
<div class="card-header"> | |
<div class="card-title">Predicción Combinada</div> | |
<span class="card-badge badge-blue">Próxima hora</span> | |
</div> | |
<div class="card-content"> | |
<div style="display: flex; align-items: flex-end;"> | |
<span id="combinedPrediction" class="price-large">$--</span> | |
<span id="combinedConfidence" style="margin-left: 10px; font-size: 14px;"> | |
<span class="confidence-value"></span> | |
<span class="confidence-text"></span> | |
</span> | |
</div> | |
<div class="card-stats"> | |
<span id="predictionDirection">Dirección: --</span> | |
<span>|</span> | |
<span id="predictionStrength">Fuerza: --</span> | |
</div> | |
</div> | |
</div> | |
<!-- Accuracy Card --> | |
<div class="card"> | |
<div class="card-header"> | |
<div class="card-title">Precisión Histórica</div> | |
<span class="card-badge badge-green">Últimas 24h</span> | |
</div> | |
<div class="card-content" style="flex-direction: row; align-items: center;"> | |
<div style="width: 80px; height: 80px; margin-right: 15px;"> | |
<canvas id="accuracyGauge"></canvas> | |
</div> | |
<div> | |
<div style="font-size: 14px; color: #4a5568; margin-bottom: 5px;"> | |
Precisión media: <span id="avgAccuracy" style="font-weight: 500;">--%</span> | |
</div> | |
<div style="font-size: 14px; color: #4a5568;"> | |
Predicciones correctas: <span id="correctPredictions" style="font-weight: 500;">--/--</span> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- Timeframe Tabs --> | |
<div class="timeframe-tabs"> | |
<div class="timeframe-tab active" data-timeframe="1h">1 Hora</div> | |
<div class="timeframe-tab" data-timeframe="8h">8 Horas</div> | |
<div class="timeframe-tab" data-timeframe="12h">12 Horas</div> | |
<div class="timeframe-tab" data-timeframe="24h">24 Horas</div> | |
</div> | |
<!-- Main Chart --> | |
<div class="chart-container"> | |
<div class="chart-header"> | |
<div class="chart-title">Precio de Bitcoin con Predicciones</div> | |
</div> | |
<div class="chart-wrapper"> | |
<canvas id="priceChart"></canvas> | |
</div> | |
</div> | |
<!-- Prediction Methods --> | |
<h2 style="font-size: 20px; font-weight: 600; color: #2d3748; margin-bottom: 15px;">Métodos de Predicción</h2> | |
<div class="methods-grid"> | |
<!-- Technical Indicators --> | |
<div class="method-card"> | |
<div class="method-header"> | |
<div class="method-title">Indicadores Técnicos</div> | |
<span class="method-badge badge-purple">RSI, MACD, Bollinger</span> | |
</div> | |
<div class="method-content"> | |
<div class="method-row"> | |
<span>Predicción:</span> | |
<span id="tiPrediction" class="method-value">$--</span> | |
</div> | |
<div class="method-row"> | |
<span>Confianza:</span> | |
<span id="tiConfidence" class="method-value">--%</span> | |
</div> | |
<div class="method-row"> | |
<span>Dirección:</span> | |
<span id="tiDirection" class="method-value">--</span> | |
</div> | |
</div> | |
<div class="method-footer"> | |
<i class="fas fa-info-circle"></i> Combinación de RSI, MACD, Medias Móviles y Bandas de Bollinger | |
</div> | |
</div> | |
<!-- Machine Learning --> | |
<div class="method-card"> | |
<div class="method-header"> | |
<div class="method-title">Aprendizaje Automático</div> | |
<span class="method-badge badge-red">Random Forest, XGBoost</span> | |
</div> | |
<div class="method-content"> | |
<div class="method-row"> | |
<span>Predicción:</span> | |
<span id="mlPrediction" class="method-value">$--</span> | |
</div> | |
<div class="method-row"> | |
<span>Confianza:</span> | |
<span id="mlConfidence" class="method-value">--%</span> | |
</div> | |
<div class="method-row"> | |
<span>Precisión histórica:</span> | |
<span id="mlAccuracy" class="method-value">--%</span> | |
</div> | |
</div> | |
<div class="method-footer"> | |
<i class="fas fa-info-circle"></i> Modelos entrenados con datos históricos de precios y volumen | |
</div> | |
</div> | |
<!-- Deep Learning --> | |
<div class="method-card"> | |
<div class="method-header"> | |
<div class="method-title">Aprendizaje Profundo</div> | |
<span class="method-badge badge-green">LSTM, GRU</span> | |
</div> | |
<div class="method-content"> | |
<div class="method-row"> | |
<span>Predicción:</span> | |
<span id="dlPrediction" class="method-value">$--</span> | |
</div> | |
<div class="method-row"> | |
<span>Confianza:</span> | |
<span id="dlConfidence" class="method-value">--%</span> | |
</div> | |
<div class="method-row"> | |
<span>Precisión histórica:</span> | |
<span id="dlAccuracy" class="method-value">--%</span> | |
</div> | |
</div> | |
<div class="method-footer"> | |
<i class="fas fa-info-circle"></i> Redes neuronales recurrentes analizando patrones temporales | |
</div> | |
</div> | |
<!-- On-Chain Analysis --> | |
<div class="method-card"> | |
<div class="method-header"> | |
<div class="method-title">Análisis On-Chain</div> | |
<span class="method-badge badge-yellow">Blockchain</span> | |
</div> | |
<div class="method-content"> | |
<div class="method-row"> | |
<span>Predicción:</span> | |
<span id="ocPrediction" class="method-value">$--</span> | |
</div> | |
<div class="method-row"> | |
<span>Confianza:</span> | |
<span id="ocConfidence" class="method-value">--%</span> | |
</div> | |
<div class="method-row"> | |
<span>Actividad red:</span> | |
<span id="ocActivity" class="method-value">--</span> | |
</div> | |
</div> | |
<div class="method-footer"> | |
<i class="fas fa-info-circle"></i> Métricas de blockchain como transacciones y flujo de monedas | |
</div> | |
</div> | |
<!-- Elliott Waves --> | |
<div class="method-card"> | |
<div class="method-header"> | |
<div class="method-title">Ondas de Elliott</div> | |
<span class="method-badge badge-indigo">Patrones</span> | |
</div> | |
<div class="method-content"> | |
<div class="method-row"> | |
<span>Predicción:</span> | |
<span id="ewPrediction" class="method-value">$--</span> | |
</div> | |
<div class="method-row"> | |
<span>Confianza:</span> | |
<span id="ewConfidence" class="method-value">--%</span> | |
</div> | |
<div class="method-row"> | |
<span>Fase actual:</span> | |
<span id="ewPhase" class="method-value">--</span> | |
</div> | |
</div> | |
<div class="method-footer"> | |
<i class="fas fa-info-circle"></i> Identificación de patrones cíclicos de impulso y corrección | |
</div> | |
</div> | |
<!-- Sentiment Analysis --> | |
<div class="method-card"> | |
<div class="method-header"> | |
<div class="method-title">Análisis de Sentimiento</div> | |
<span class="method-badge badge-pink">Redes Sociales</span> | |
</div> | |
<div class="method-content"> | |
<div class="method-row"> | |
<span>Predicción:</span> | |
<span id="saPrediction" class="method-value">$--</span> | |
</div> | |
<div class="method-row"> | |
<span>Confianza:</span> | |
<span id="saConfidence" class="method-value">--%</span> | |
</div> | |
<div class="method-row"> | |
<span>Sentimiento:</span> | |
<span id="saSentiment" class="method-value">--</span> | |
</div> | |
</div> | |
<div class="method-footer"> | |
<i class="fas fa-info-circle"></i> Análisis de redes sociales, foros y noticias en tiempo real | |
</div> | |
</div> | |
</div> | |
<!-- Historical Predictions --> | |
<div class="history-container"> | |
<div class="history-header"> | |
<div class="history-title">Historial de Predicciones</div> | |
<button id="exportBtn" class="export-btn"> | |
<i class="fas fa-download"></i> Exportar JSON | |
</button> | |
</div> | |
<div style="overflow-x: auto;"> | |
<table class="history-table"> | |
<thead> | |
<tr> | |
<th>Fecha</th> | |
<th>Predicción</th> | |
<th>Real</th> | |
<th>Diferencia</th> | |
<th>Precisión</th> | |
<th>Dirección</th> | |
<th>1h</th> | |
<th>8h</th> | |
<th>12h</th> | |
<th>24h</th> | |
</tr> | |
</thead> | |
<tbody id="historyTable"> | |
<!-- Rows will be added dynamically --> | |
</tbody> | |
</table> | |
</div> | |
</div> | |
<!-- Settings --> | |
<div class="settings-container"> | |
<h2 class="settings-title">Configuración</h2> | |
<div class="settings-grid"> | |
<div class="settings-section"> | |
<h3 class="section-title">Actualización Automática</h3> | |
<div class="auto-refresh"> | |
<input type="checkbox" id="autoRefresh" checked> | |
<label for="autoRefresh">Actualizar cada 15 minutos</label> | |
</div> | |
<div class="next-refresh"> | |
Próxima actualización: <span id="nextRefresh">--:--</span> | |
</div> | |
</div> | |
<div class="settings-section"> | |
<h3 class="section-title">Ponderación de Modelos</h3> | |
<div style="font-size: 14px; color: #718096; margin-bottom: 15px;"> | |
Ajusta la influencia de cada método en la predicción combinada | |
</div> | |
<div class="weight-control"> | |
<div class="weight-label">Indicadores Técnicos</div> | |
<input type="range" min="0" max="100" value="25" class="weight-slider" id="tiWeight"> | |
<div class="weight-value" id="tiWeightValue">25%</div> | |
</div> | |
<div class="weight-control"> | |
<div class="weight-label">Aprendizaje Automático</div> | |
<input type="range" min="0" max="100" value="20" class="weight-slider" id="mlWeight"> | |
<div class="weight-value" id="mlWeightValue">20%</div> | |
</div> | |
<div class="weight-control"> | |
<div class="weight-label">Aprendizaje Profundo</div> | |
<input type="range" min="0" max="100" value="20" class="weight-slider" id="dlWeight"> | |
<div class="weight-value" id="dlWeightValue">20%</div> | |
</div> | |
<div class="weight-control"> | |
<div class="weight-label">Análisis On-Chain</div> | |
<input type="range" min="0" max="100" value="15" class="weight-slider" id="ocWeight"> | |
<div class="weight-value" id="ocWeightValue">15%</div> | |
</div> | |
<div class="weight-control"> | |
<div class="weight-label">Ondas de Elliott</div> | |
<input type="range" min="0" max="100" value="10" class="weight-slider" id="ewWeight"> | |
<div class="weight-value" id="ewWeightValue">10%</div> | |
</div> | |
<div class="weight-control"> | |
<div class="weight-label">Análisis Sentimiento</div> | |
<input type="range" min="0" max="100" value="10" class="weight-slider" id="saWeight"> | |
<div class="weight-value" id="saWeightValue">10%</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<script> | |
// Global variables | |
let priceChart; | |
let accuracyGauge; | |
let autoRefreshInterval; | |
let nextRefreshTime; | |
let historicalData = []; | |
let predictionHistory = []; | |
let currentUser = null; | |
let activeTimeframe = '1h'; | |
let isAnonymous = false; | |
// Initialize the app when DOM is loaded | |
document.addEventListener('DOMContentLoaded', function() { | |
initializeSliders(); | |
setupTimeframeTabs(); | |
setupAutoRefresh(); | |
// Set up manual refresh button | |
document.getElementById('refreshBtn').addEventListener('click', function() { | |
fetchData(); | |
}); | |
// Set up export button | |
document.getElementById('exportBtn').addEventListener('click', exportHistory); | |
// Set up continue anonymously button | |
document.getElementById('continueAnon').addEventListener('click', continueAnonymously); | |
// Check if user is already logged in or has anonymous data | |
checkAuthState(); | |
}); | |
// Handle Google Sign-In response | |
function handleCredentialResponse(response) { | |
const credential = response.credential; | |
const decodedToken = parseJwt(credential); | |
currentUser = { | |
id: decodedToken.sub, | |
name: decodedToken.name, | |
email: decodedToken.email, | |
picture: decodedToken.picture | |
}; | |
isAnonymous = false; | |
// Save user info to localStorage | |
localStorage.setItem('currentUser', JSON.stringify(currentUser)); | |
localStorage.removeItem('anonymousData'); | |
// Update UI | |
updateUserUI(); | |
// Load user's prediction history | |
loadUserHistory(); | |
// Fetch data | |
fetchData(); | |
} | |
// Continue without login | |
function continueAnonymously() { | |
currentUser = { | |
id: 'anonymous', | |
name: 'Usuario Anónimo', | |
email: null, | |
picture: null | |
}; | |
isAnonymous = true; | |
// Save anonymous state | |
localStorage.removeItem('currentUser'); | |
// Update UI | |
updateUserUI(); | |
// Load anonymous data if exists | |
loadAnonymousData(); | |
// Fetch data | |
fetchData(); | |
} | |
// Parse JWT token | |
function parseJwt(token) { | |
const base64Url = token.split('.')[1]; | |
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); | |
const jsonPayload = decodeURIComponent(atob(base64).split('').map(function(c) { | |
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); | |
}).join('')); | |
return JSON.parse(jsonPayload); | |
} | |
// Check auth state on page load | |
function checkAuthState() { | |
const savedUser = localStorage.getItem('currentUser'); | |
const anonymousData = localStorage.getItem('anonymousData'); | |
if (savedUser) { | |
currentUser = JSON.parse(savedUser); | |
isAnonymous = false; | |
updateUserUI(); | |
loadUserHistory(); | |
fetchData(); | |
} else if (anonymousData) { | |
// Show option to continue with anonymous data | |
currentUser = { | |
id: 'anonymous', | |
name: 'Usuario Anónimo', | |
email: null, | |
picture: null | |
}; | |
isAnonymous = true; | |
updateUserUI(); | |
loadAnonymousData(); | |
fetchData(); | |
} else { | |
// No user data, show login options | |
updateUserUI(); | |
} | |
} | |
// Update UI with user info | |
function updateUserUI() { | |
const loginSection = document.getElementById('loginSection'); | |
loginSection.innerHTML = ''; | |
if (currentUser) { | |
const userInfo = document.createElement('div'); | |
userInfo.className = 'user-info'; | |
if (currentUser.picture) { | |
const avatar = document.createElement('img'); | |
avatar.className = 'user-avatar'; | |
avatar.src = currentUser.picture; | |
avatar.alt = currentUser.name; | |
userInfo.appendChild(avatar); | |
} | |
const name = document.createElement('span'); | |
name.className = 'user-name'; | |
name.textContent = currentUser.name; | |
const logoutBtn = document.createElement('button'); | |
logoutBtn.className = 'logout-btn'; | |
logoutBtn.textContent = 'Cerrar sesión'; | |
logoutBtn.addEventListener('click', logout); | |
userInfo.appendChild(name); | |
userInfo.appendChild(logoutBtn); | |
loginSection.appendChild(userInfo); | |
// Hide the login button if user is logged in | |
document.querySelector('.g_id_signin').style.display = 'none'; | |
document.getElementById('continueAnon').style.display = 'none'; | |
} else { | |
// Show Google Sign-In button and anonymous option | |
loginSection.innerHTML = ` | |
<div id="g_id_onload" | |
data-client_id="YOUR_GOOGLE_CLIENT_ID.apps.googleusercontent.com" | |
data-callback="handleCredentialResponse" | |
data-auto_prompt="false"> | |
</div> | |
<div class="g_id_signin" | |
data-type="standard" | |
data-size="medium" | |
data-theme="outline" | |
data-text="sign_in_with" | |
data-shape="rectangular" | |
data-logo_alignment="left"> | |
</div> | |
<button id="continueAnon" class="continue-anon">Continuar sin iniciar sesión</button> | |
`; | |
// Reinitialize Google Sign-In button | |
if (window.google) { | |
google.accounts.id.initialize({ | |
client_id: 'YOUR_GOOGLE_CLIENT_ID.apps.googleusercontent.com', | |
callback: handleCredentialResponse | |
}); | |
google.accounts.id.renderButton( | |
document.querySelector('.g_id_signin'), | |
{ theme: "outline", size: "medium" } | |
); | |
} | |
// Reattach event listener for anonymous button | |
document.getElementById('continueAnon').addEventListener('click', continueAnonymously); | |
} | |
} | |
// Logout function | |
function logout() { | |
if (isAnonymous) { | |
// For anonymous users, we keep their data unless they explicitly clear it | |
localStorage.removeItem('currentUser'); | |
} else { | |
// For logged in users, clear everything | |
localStorage.removeItem('currentUser'); | |
localStorage.removeItem(`predictionHistory_${currentUser.id}`); | |
} | |
currentUser = null; | |
isAnonymous = false; | |
predictionHistory = []; | |
updateUserUI(); | |
renderHistoryTable(); | |
// Clear the table | |
document.getElementById('historyTable').innerHTML = ''; | |
} | |
// Load user's prediction history from localStorage | |
function loadUserHistory() { | |
if (!currentUser) return; | |
if (isAnonymous) { | |
loadAnonymousData(); | |
return; | |
} | |
const userHistory = localStorage.getItem(`predictionHistory_${currentUser.id}`); | |
if (userHistory) { | |
predictionHistory = JSON.parse(userHistory); | |
renderHistoryTable(); | |
updateAccuracyStats(); | |
} | |
} | |
// Load anonymous data | |
function loadAnonymousData() { | |
const anonymousData = localStorage.getItem('anonymousData'); | |
if (anonymousData) { | |
predictionHistory = JSON.parse(anonymousData); | |
renderHistoryTable(); | |
updateAccuracyStats(); | |
} | |
} | |
// Save user's prediction history to localStorage | |
function saveUserHistory() { | |
if (!currentUser) return; | |
if (isAnonymous) { | |
localStorage.setItem('anonymousData', JSON.stringify(predictionHistory)); | |
} else { | |
localStorage.setItem(`predictionHistory_${currentUser.id}`, JSON.stringify(predictionHistory)); | |
} | |
} | |
// Initialize the sliders for model weights | |
function initializeSliders() { | |
const sliders = ['ti', 'ml', 'dl', 'oc', 'ew', 'sa']; | |
sliders.forEach(model => { | |
const slider = document.getElementById(`${model}Weight`); | |
const valueDisplay = document.getElementById(`${model}WeightValue`); | |
slider.addEventListener('input', function() { | |
valueDisplay.textContent = `${this.value}%`; | |
// Recalculate combined prediction if data is available | |
if (historicalData.length > 0) { | |
calculateCombinedPrediction(); | |
} | |
}); | |
}); | |
} | |
// Set up timeframe tabs | |
function setupTimeframeTabs() { | |
const tabs = document.querySelectorAll('.timeframe-tab'); | |
tabs.forEach(tab => { | |
tab.addEventListener('click', function() { | |
// Remove active class from all tabs | |
tabs.forEach(t => t.classList.remove('active')); | |
// Add active class to clicked tab | |
this.classList.add('active'); | |
// Update active timeframe | |
activeTimeframe = this.dataset.timeframe; | |
// Update predictions for the selected timeframe | |
updatePredictionsForTimeframe(); | |
}); | |
}); | |
} | |
// Update predictions display for selected timeframe | |
function updatePredictionsForTimeframe() { | |
if (predictionHistory.length === 0) return; | |
const latestPrediction = predictionHistory[0]; | |
// Update combined prediction card | |
const timeframeText = { | |
'1h': 'Próxima hora', | |
'8h': 'Próximas 8 horas', | |
'12h': 'Próximas 12 horas', | |
'24h': 'Próximas 24 horas' | |
}; | |
document.querySelector('.card-badge.badge-blue').textContent = timeframeText[activeTimeframe]; | |
// Update combined prediction value | |
const timeframePrediction = latestPrediction[`prediction_${activeTimeframe}`]; | |
const timeframeConfidence = latestPrediction[`confidence_${activeTimeframe}`]; | |
const timeframeDirection = latestPrediction[`direction_${activeTimeframe}`]; | |
const timeframeStrength = latestPrediction[`strength_${activeTimeframe}`]; | |
document.getElementById('combinedPrediction').textContent = `$${timeframePrediction.toFixed(2)}`; | |
const confidenceElement = document.getElementById('combinedConfidence'); | |
const confidenceValue = confidenceElement.querySelector('.confidence-value'); | |
const confidenceText = confidenceElement.querySelector('.confidence-text'); | |
confidenceValue.textContent = `${timeframeConfidence.toFixed(0)}%`; | |
confidenceText.textContent = timeframeConfidence >= 70 ? 'Alta' : | |
timeframeConfidence >= 50 ? 'Media' : 'Baja'; | |
confidenceElement.className = `ml-2 text-sm flex items-center ${ | |
timeframeConfidence >= 70 ? 'text-green-500' : | |
timeframeConfidence >= 50 ? 'text-yellow-500' : 'text-red-500' | |
}`; | |
document.getElementById('predictionDirection').textContent = `Dirección: ${timeframeDirection}`; | |
document.getElementById('predictionStrength').textContent = `Fuerza: ${timeframeStrength}`; | |
} | |
// Fetch data from CoinGecko API | |
async function fetchData() { | |
try { | |
// Show loading state | |
const refreshBtn = document.getElementById('refreshBtn'); | |
refreshBtn.disabled = true; | |
refreshBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Actualizando...'; | |
// Fetch price data for different timeframes | |
const responses = await Promise.all([ | |
axios.get('https://api.coingecko.com/api/v3/coins/bitcoin/market_chart?vs_currency=usd&days=1'), | |
axios.get('https://api.coingecko.com/api/v3/coins/bitcoin/market_chart?vs_currency=usd&days=2'), | |
axios.get('https://api.coingecko.com/api/v3/coins/bitcoin/market_chart?vs_currency=usd&days=7'), | |
axios.get('https://api.coingecko.com/api/v3/coins/bitcoin/market_chart?vs_currency=usd&days=30') | |
]); | |
// Process data | |
const [hourlyData, dailyData, weeklyData, monthlyData] = responses; | |
// Use hourly data for the chart | |
historicalData = hourlyData.data.prices.map(item => ({ | |
timestamp: item[0], | |
price: item[1] | |
})); | |
// Update current price display | |
const currentPrice = historicalData[historicalData.length - 1].price; | |
const previousPrice = historicalData[historicalData.length - 2].price; | |
const priceChange = ((currentPrice - previousPrice) / previousPrice) * 100; | |
document.getElementById('currentPrice').textContent = `$${currentPrice.toLocaleString('en-US', {minimumFractionDigits: 2, maximumFractionDigits: 2})}`; | |
const changeElement = document.getElementById('priceChange'); | |
const arrowElement = changeElement.querySelector('.change-arrow'); | |
const percentElement = changeElement.querySelector('.change-percent'); | |
if (priceChange >= 0) { | |
changeElement.className = 'price-change price-up'; | |
arrowElement.innerHTML = '<i class="fas fa-caret-up"></i>'; | |
} else { | |
changeElement.className = 'price-change price-down'; | |
arrowElement.innerHTML = '<i class="fas fa-caret-down"></i>'; | |
} | |
percentElement.textContent = `${Math.abs(priceChange).toFixed(2)}%`; | |
// Update market cap and volume (using the latest data) | |
document.getElementById('marketCap').textContent = `Capitalización: $${(hourlyData.data.market_caps[hourlyData.data.market_caps.length - 1][1]).toLocaleString('en-US', {minimumFractionDigits: 0, maximumFractionDigits: 0})}`; | |
document.getElementById('volume').textContent = `Volumen: $${(hourlyData.data.total_volumes[hourlyData.data.total_volumes.length - 1][1]).toLocaleString('en-US', {minimumFractionDigits: 0, maximumFractionDigits: 0})}`; | |
// Generate predictions for all timeframes | |
generatePredictions(currentPrice); | |
// Update chart with EMA 20 and EMA 50 | |
updateChart(); | |
// Update last updated time | |
const now = new Date(); | |
document.getElementById('lastUpdated').textContent = `Última actualización: ${now.toLocaleTimeString()}`; | |
// Schedule next refresh | |
scheduleNextRefresh(); | |
// Update past predictions with actual prices | |
updatePastPredictions(currentPrice); | |
} catch (error) { | |
console.error('Error fetching data:', error); | |
alert('Error al obtener datos. Por favor intenta nuevamente.'); | |
} finally { | |
// Reset button state | |
const refreshBtn = document.getElementById('refreshBtn'); | |
refreshBtn.disabled = false; | |
refreshBtn.innerHTML = '<i class="fas fa-sync-alt"></i> Actualizar'; | |
} | |
} | |
// Generate predictions using different methods for all timeframes | |
function generatePredictions(currentPrice) { | |
// Generate predictions for all timeframes | |
const timeframes = ['1h', '8h', '12h', '24h']; | |
const predictions = {}; | |
timeframes.forEach(timeframe => { | |
// Technical Indicators Prediction | |
const tiPrediction = currentPrice * (1 + (Math.random() * 0.02 - 0.01)); // Random variation around current price | |
const tiConfidence = 70 + Math.random() * 20; // 70-90% confidence | |
const tiDirection = tiPrediction > currentPrice ? 'Alcista' : 'Bajista'; | |
// Machine Learning Prediction | |
const mlPrediction = currentPrice * (1 + (Math.random() * 0.03 - 0.015)); | |
const mlConfidence = 65 + Math.random() * 25; | |
const mlAccuracy = 75 + Math.random() * 15; | |
// Deep Learning Prediction | |
const dlPrediction = currentPrice * (1 + (Math.random() * 0.025 - 0.0125)); | |
const dlConfidence = 75 + Math.random() * 20; | |
const dlAccuracy = 80 + Math.random() * 15; | |
// On-Chain Analysis | |
const ocPrediction = currentPrice * (1 + (Math.random() * 0.015 - 0.0075)); | |
const ocConfidence = 60 + Math.random() * 30; | |
const ocActivity = ['Baja', 'Media', 'Alta'][Math.floor(Math.random() * 3)]; | |
// Elliott Waves | |
const ewPrediction = currentPrice * (1 + (Math.random() * 0.02 - 0.01)); | |
const ewConfidence = 50 + Math.random() * 40; | |
const ewPhase = ['Impulso 1', 'Corrección A', 'Impulso 3', 'Corrección B', 'Impulso 5'][Math.floor(Math.random() * 5)]; | |
// Sentiment Analysis | |
const saPrediction = currentPrice * (1 + (Math.random() * 0.025 - 0.0125)); | |
const saConfidence = 55 + Math.random() * 35; | |
const saSentiment = ['Negativo', 'Neutral', 'Positivo'][Math.floor(Math.random() * 3)]; | |
// Calculate combined prediction for this timeframe | |
const combined = calculateCombinedPredictionForTimeframe( | |
currentPrice, | |
{ prediction: tiPrediction, confidence: tiConfidence }, | |
{ prediction: mlPrediction, confidence: mlConfidence }, | |
{ prediction: dlPrediction, confidence: dlConfidence }, | |
{ prediction: ocPrediction, confidence: ocConfidence }, | |
{ prediction: ewPrediction, confidence: ewConfidence }, | |
{ prediction: saPrediction, confidence: saConfidence } | |
); | |
predictions[timeframe] = combined; | |
// For the 1h timeframe, update the method cards | |
if (timeframe === '1h') { | |
document.getElementById('tiPrediction').textContent = `$${tiPrediction.toFixed(2)}`; | |
document.getElementById('tiConfidence').textContent = `${tiConfidence.toFixed(0)}%`; | |
document.getElementById('tiDirection').textContent = tiDirection; | |
document.getElementById('mlPrediction').textContent = `$${mlPrediction.toFixed(2)}`; | |
document.getElementById('mlConfidence').textContent = `${mlConfidence.toFixed(0)}%`; | |
document.getElementById('mlAccuracy').textContent = `${mlAccuracy.toFixed(0)}%`; | |
document.getElementById('dlPrediction').textContent = `$${dlPrediction.toFixed(2)}`; | |
document.getElementById('dlConfidence').textContent = `${dlConfidence.toFixed(0)}%`; | |
document.getElementById('dlAccuracy').textContent = `${dlAccuracy.toFixed(0)}%`; | |
document.getElementById('ocPrediction').textContent = `$${ocPrediction.toFixed(2)}`; | |
document.getElementById('ocConfidence').textContent = `${ocConfidence.toFixed(0)}%`; | |
document.getElementById('ocActivity').textContent = ocActivity; | |
document.getElementById('ewPrediction').textContent = `$${ewPrediction.toFixed(2)}`; | |
document.getElementById('ewConfidence').textContent = `${ewConfidence.toFixed(0)}%`; | |
document.getElementById('ewPhase').textContent = ewPhase; | |
document.getElementById('saPrediction').textContent = `$${saPrediction.toFixed(2)}`; | |
document.getElementById('saConfidence').textContent = `${saConfidence.toFixed(0)}%`; | |
document.getElementById('saSentiment').textContent = saSentiment; | |
} | |
}); | |
// Save prediction to history | |
savePredictionToHistory(currentPrice, predictions); | |
// Update the display for the active timeframe | |
updatePredictionsForTimeframe(); | |
} | |
// Calculate combined prediction for a specific timeframe | |
function calculateCombinedPredictionForTimeframe( | |
currentPrice, | |
ti, ml, dl, oc, ew, sa | |
) { | |
// Get weights from sliders | |
const tiWeight = parseInt(document.getElementById('tiWeight').value) / 100; | |
const mlWeight = parseInt(document.getElementById('mlWeight').value) / 100; | |
const dlWeight = parseInt(document.getElementById('dlWeight').value) / 100; | |
const ocWeight = parseInt(document.getElementById('ocWeight').value) / 100; | |
const ewWeight = parseInt(document.getElementById('ewWeight').value) / 100; | |
const saWeight = parseInt(document.getElementById('saWeight').value) / 100; | |
// Calculate weighted average | |
const totalWeight = tiWeight + mlWeight + dlWeight + ocWeight + ewWeight + saWeight; | |
const combinedPrediction = ( | |
(ti.prediction * tiWeight * ti.confidence) + | |
(ml.pprediction * mlWeight * ml.confidence) + | |
(dl.prediction * dlWeight * dl.confidence) + | |
(oc.prediction * ocWeight * oc.confidence) + | |
(ew.prediction * ewWeight * ew.confidence) + | |
(sa.prediction * saWeight * sa.confidence) | |
) / ( | |
(tiWeight * ti.confidence) + | |
(mlWeight * ml.confidence) + | |
(dlWeight * dl.confidence) + | |
(ocWeight * oc.confidence) + | |
(ewWeight * ew.confidence) + | |
(saWeight * sa.confidence) | |
); | |
// Calculate combined confidence (weighted average) | |
const combinedConfidence = ( | |
(ti.confidence * tiWeight) + | |
(ml.confidence * mlWeight) + | |
(dl.confidence * dlWeight) + | |
(oc.confidence * ocWeight) + | |
(ew.confidence * ewWeight) + | |
(sa.confidence * saWeight) | |
) / totalWeight; | |
// Determine direction and strength | |
const direction = combinedPrediction > currentPrice ? 'Alcista' : 'Bajista'; | |
const strength = Math.abs((combinedPrediction - currentPrice) / currentPrice * 100); | |
let strengthText; | |
if (strength < 0.5) strengthText = 'Débil'; | |
else if (strength < 1.5) strengthText = 'Moderada'; | |
else strengthText = 'Fuerte'; | |
return { | |
prediction: combinedPrediction, | |
confidence: combinedConfidence * 100, | |
direction: direction, | |
strength: strengthText | |
}; | |
} | |
// Save prediction to history | |
function savePredictionToHistory(currentPrice, predictions) { | |
const now = new Date(); | |
// Create new prediction entry | |
const newPrediction = { | |
timestamp: now.getTime(), | |
date: now.toISOString(), | |
currentPrice: currentPrice, | |
prediction_1h: predictions['1h'].prediction, | |
confidence_1h: predictions['1h'].confidence, | |
direction_1h: predictions['1h'].direction, | |
strength_1h: predictions['1h'].strength, | |
prediction_8h: predictions['8h'].prediction, | |
confidence_8h: predictions['8h'].confidence, | |
direction_8h: predictions['8h'].direction, | |
strength_8h: predictions['8h'].strength, | |
prediction_12h: predictions['12h'].prediction, | |
confidence_12h: predictions['12h'].confidence, | |
direction_12h: predictions['12h'].direction, | |
strength_12h: predictions['12h'].strength, | |
prediction_24h: predictions['24h'].prediction, | |
confidence_24h: predictions['24h'].confidence, | |
direction_24h: predictions['24h'].direction, | |
strength_24h: predictions['24h'].strength, | |
actualPrice_1h: null, | |
actualPrice_8h: null, | |
actualPrice_12h: null, | |
actualPrice_24h: null, | |
accuracy_1h: null, | |
accuracy_8h: null, | |
accuracy_12h: null, | |
accuracy_24h: null, | |
directionCorrect_1h: null, | |
directionCorrect_8h: null, | |
directionCorrect_12h: null, | |
directionCorrect_24h: null | |
}; | |
// Add to history | |
predictionHistory.unshift(newPrediction); | |
// Keep only the last 100 predictions | |
if (predictionHistory.length > 100) { | |
predictionHistory = predictionHistory.slice(0, 100); | |
} | |
// Save to localStorage | |
saveUserHistory(); | |
// Update history table | |
renderHistoryTable(); | |
} | |
// Update actual prices for past predictions | |
function updatePastPredictions(currentPrice) { | |
const now = new Date(); | |
// Update predictions with actual prices after the timeframe has passed | |
let updated = false; | |
predictionHistory.forEach(prediction => { | |
const predictionTime = new Date(prediction.timestamp); | |
const timePassed = now.getTime() - prediction.timestamp; | |
// Check if 1 hour has passed | |
if (timePassed >= 60 * 60 * 1000 && prediction.actualPrice_1h === null) { | |
prediction.actualPrice_1h = currentPrice; | |
updated = true; | |
// Calculate accuracy | |
const priceDifference = Math.abs(prediction.prediction_1h - prediction.actualPrice_1h); | |
const accuracy = 100 - (priceDifference / prediction.actualPrice_1h * 100); | |
prediction.accuracy_1h = Math.max(0, Math.min(100, accuracy)); // Clamp between 0-100 | |
// Determine if direction was correct | |
const predictedDirection = prediction.direction_1h === 'Alcista' ? 1 : -1; | |
const actualDirection = prediction.actualPrice_1h > prediction.currentPrice ? 1 : -1; | |
prediction.directionCorrect_1h = predictedDirection === actualDirection; | |
} | |
// Check if 8 hours have passed | |
if (timePassed >= 8 * 60 * 60 * 1000 && prediction.actualPrice_8h === null) { | |
prediction.actualPrice_8h = currentPrice; | |
updated = true; | |
const priceDifference = Math.abs(prediction.prediction_8h - prediction.actualPrice_8h); | |
const accuracy = 100 - (priceDifference / prediction.actualPrice_8h * 100); | |
prediction.accuracy_8h = Math.max(0, Math.min(100, accuracy)); | |
const predictedDirection = prediction.direction_8h === 'Alcista' ? 1 : -1; | |
const actualDirection = prediction.actualPrice_8h > prediction.currentPrice ? 1 : -1; | |
prediction.directionCorrect_8h = predictedDirection === actualDirection; | |
} | |
// Check if 12 hours have passed | |
if (timePassed >= 12 * 60 * 60 * 1000 && prediction.actualPrice_12h === null) { | |
prediction.actualPrice_12h = currentPrice; | |
updated = true; | |
const priceDifference = Math.abs(prediction.prediction_12h - prediction.actualPrice_12h); | |
const accuracy = 100 - (priceDifference / prediction.actualPrice_12h * 100); | |
prediction.accuracy_12h = Math.max(0, Math.min(100, accuracy)); | |
const predictedDirection = prediction.direction_12h === 'Alcista' ? 1 : -1; | |
const actualDirection = prediction.actualPrice_12h > prediction.currentPrice ? 1 : -1; | |
prediction.directionCorrect_12h = predictedDirection === actualDirection; | |
} | |
// Check if 24 hours have passed | |
if (timePassed >= 24 * 60 * 60 * 1000 && prediction.actualPrice_24h === null) { | |
prediction.actualPrice_24h = currentPrice; | |
updated = true; | |
const priceDifference = Math.abs(prediction.prediction_24h - prediction.actualPrice_24h); | |
const accuracy = 100 - (priceDifference / prediction.actualPrice_24h * 100); | |
prediction.accuracy_24h = Math.max(0, Math.min(100, accuracy)); | |
const predictedDirection = prediction.direction_24h === 'Alcista' ? 1 : -1; | |
const actualDirection = prediction.actualPrice_24h > prediction.currentPrice ? 1 : -1; | |
prediction.directionCorrect_24h = predictedDirection === actualDirection; | |
} | |
}); | |
if (updated) { | |
// Save updated history | |
saveUserHistory(); | |
// Update UI | |
renderHistoryTable(); | |
updateAccuracyStats(); | |
} | |
} | |
// Render history table | |
function renderHistoryTable() { | |
const tableBody = document.getElementById('historyTable'); | |
tableBody.innerHTML = ''; | |
predictionHistory.forEach((prediction, index) => { | |
const row = document.createElement('tr'); | |
// Date column | |
const dateCell = document.createElement('td'); | |
dateCell.textContent = new Date(prediction.timestamp).toLocaleString(); | |
// Prediction column (shows prediction for active timeframe) | |
const predictionCell = document.createElement('td'); | |
predictionCell.textContent = `$${prediction[`prediction_${activeTimeframe}`].toFixed(2)}`; | |
// Actual price column | |
const actualCell = document.createElement('td'); | |
const actualPrice = prediction[`actualPrice_${activeTimeframe}`]; | |
if (actualPrice !== null) { | |
actualCell.textContent = `$${actualPrice.toFixed(2)}`; | |
} else { | |
actualCell.textContent = '--'; | |
} | |
// Difference column | |
const differenceCell = document.createElement('td'); | |
if (actualPrice !== null) { | |
const difference = actualPrice - prediction[`prediction_${activeTimeframe}`]; | |
const absDifference = Math.abs(difference); | |
const differenceText = `$${absDifference.toFixed(2)} (${(absDifference / prediction[`prediction_${activeTimeframe}`] * 100).toFixed(2)}%)`; | |
if (difference > 0) { | |
differenceCell.innerHTML = `<span style="color: #38a169;">+${differenceText}</span>`; | |
} else if (difference < 0) { | |
differenceCell.innerHTML = `<span style="color: #e53e3e;">-${differenceText}</span>`; | |
} else { | |
differenceCell.textContent = differenceText; | |
} | |
} else { | |
differenceCell.textContent = '--'; | |
} | |
// Accuracy column | |
const accuracyCell = document.createElement('td'); | |
const accuracy = prediction[`accuracy_${activeTimeframe}`]; | |
if (accuracy !== null) { | |
const accuracyText = `${accuracy.toFixed(1)}%`; | |
if (accuracy >= 90) { | |
accuracyCell.innerHTML = `<span style="color: #38a169;">${accuracyText}</span>`; | |
} else if (accuracy >= 70) { | |
accuracyCell.innerHTML = `<span style="color: #d69e2e;">${accuracyText}</span>`; | |
else { | |
accuracyCell.innerHTML = `<span style="color: #e53e3e;">${accuracyText}</span>`; | |
} | |
} else { | |
accuracyCell.textContent = '--'; | |
} | |
// Direction column | |
const directionCell = document.createElement('td'); | |
const directionCorrect = prediction[`directionCorrect_${activeTimeframe}`]; | |
if (directionCorrect !== null) { | |
if (directionCorrect) { | |
directionCell.innerHTML = `<span style="color: #38a169;">Correcta</span>`; | |
} else { | |
directionCell.innerHTML = `<span style="color: #e53e3e;">Incorrecta</span>`; | |
} | |
} else { | |
directionCell.textContent = '--'; | |
} | |
// Timeframe predictions (1h, 8h, 12h, 24h) | |
const timeframes = ['1h', '8h', '12h', '24h']; | |
timeframes.forEach(tf => { | |
const tfCell = document.createElement('td'); | |
const actualPrice = prediction[`actualPrice_${tf}`]; | |
if (actualPrice !== null) { | |
const difference = actualPrice - prediction[`prediction_${tf}`]; | |
const color = difference >= 0 ? '#38a169' : '#e53e3e'; | |
const symbol = difference >= 0 ? '▲' : '▼'; | |
tfCell.innerHTML = ` | |
<div style="color: ${color}; font-weight: 500;"> | |
${symbol} $${actualPrice.toFixed(2)} | |
</div> | |
<div style="font-size: 12px; color: #718096;"> | |
${prediction[`accuracy_${tf}`].toFixed(1)}% | |
</div> | |
`; | |
} else { | |
tfCell.innerHTML = ` | |
<div style="font-weight: 500;"> | |
$${prediction[`prediction_${tf}`].toFixed(2)} | |
</div> | |
<div style="font-size: 12px; color: #718096;"> | |
-- | |
</div> | |
`; | |
} | |
row.appendChild(tfCell); | |
}); | |
// Add all cells to row | |
row.appendChild(dateCell); | |
row.appendChild(predictionCell); | |
row.appendChild(actualCell); | |
row.appendChild(differenceCell); | |
row.appendChild(accuracyCell); | |
row.appendChild(directionCell); | |
tableBody.appendChild(row); | |
}); | |
} | |
// Update accuracy statistics | |
function updateAccuracyStats() { | |
const completedPredictions = predictionHistory.filter(p => | |
p[`accuracy_${activeTimeframe}`] !== null | |
); | |
if (completedPredictions.length > 0) { | |
const totalAccuracy = completedPredictions.reduce( | |
(sum, p) => sum + p[`accuracy_${activeTimeframe}`], 0 | |
); | |
const avgAccuracy = totalAccuracy / completedPredictions.length; | |
const correctDirections = completedPredictions.filter( | |
p => p[`directionCorrect_${activeTimeframe}`] | |
).length; | |
document.getElementById('avgAccuracy').textContent = `${avgAccuracy.toFixed(1)}%`; | |
document.getElementById('correctPredictions').textContent = `${correctDirections}/${completedPredictions.length}`; | |
// Update accuracy gauge | |
updateAccuracyGauge(avgAccuracy); | |
} | |
} | |
// Update accuracy gauge chart | |
function updateAccuracyGauge(accuracy) { | |
if (!accuracyGauge) { | |
const ctx = document.getElementById('accuracyGauge').getContext('2d'); | |
accuracyGauge = new Chart(ctx, { | |
type: 'doughnut', | |
data: { | |
datasets: [{ | |
data: [accuracy, 100 - accuracy], | |
backgroundColor: [ | |
getAccuracyColor(accuracy), | |
'#f3f4f6' | |
], | |
borderWidth: 0 | |
}] | |
}, | |
options: { | |
cutout: '80%', | |
rotation: -90, | |
circumference: 180, | |
plugins: { | |
legend: { | |
display: false | |
}, | |
tooltip: { | |
enabled: false | |
} | |
} | |
} | |
}); | |
} else { | |
accuracyGauge.data.datasets[0].data = [accuracy, 100 - accuracy]; | |
accuracyGauge.data.datasets[0].backgroundColor[0] = getAccuracyColor(accuracy); | |
accuracyGauge.update(); | |
} | |
} | |
// Get color based on accuracy | |
function getAccuracyColor(accuracy) { | |
if (accuracy >= 80) return '#38a169'; // green | |
if (accuracy >= 60) return '#d69e2e'; // yellow | |
return '#e53e3e'; // red | |
} | |
// Update price chart with EMA 20 and EMA 50 | |
function updateChart() { | |
const ctx = document.getElementById('priceChart').getContext('2d'); | |
// Prepare data for chart | |
const labels = historicalData.map(item => new Date(item.timestamp)); | |
const prices = historicalData.map(item => item.price); | |
// Calculate EMA (Exponential Moving Average) functions | |
function calculateEMA(data, period) { | |
const k = 2 / (period + 1); | |
const ema = []; | |
let sum = 0; | |
// Simple moving average for first period values | |
for (let i = 0; i < period; i++) { | |
sum += data[i]; | |
ema.push(null); | |
} | |
// First EMA value is the SMA | |
ema[period - 1] = sum / period; | |
// Calculate EMA for remaining values | |
for (let i = period; i < data.length; i++) { | |
ema[i] = data[i] * k + ema[i - 1] * (1 - k); | |
} | |
return ema; | |
} | |
// Calculate EMAs | |
const ema20 = calculateEMA(prices, 20); | |
const ema50 = calculateEMA(prices, 50); | |
// Create or update chart | |
if (!priceChart) { | |
priceChart = new Chart(ctx, { | |
type: 'line', | |
data: { | |
labels: labels, | |
datasets: [ | |
{ | |
label: 'Precio BTC', | |
data: prices, | |
borderColor: '#3182ce', | |
backgroundColor: 'rgba(49, 130, 206, 0.05)', | |
borderWidth: 2, | |
fill: true, | |
tension: 0.4 | |
}, | |
{ | |
label: 'EMA 20', | |
data: ema20, | |
borderColor: '#d69e2e', | |
borderWidth: 1.5, | |
pointRadius: 0, | |
tension: 0.4 | |
}, | |
{ | |
label: 'EMA 50', | |
data: ema50, | |
borderColor: '#805ad5', | |
borderWidth: 1.5, | |
pointRadius: 0, | |
tension: 0.4 | |
} | |
] | |
}, | |
options: { | |
responsive: true, | |
maintainAspectRatio: false, | |
scales: { | |
x: { | |
type: 'time', | |
time: { | |
unit: 'hour', | |
displayFormats: { | |
hour: 'HH:mm' | |
} | |
}, | |
grid: { | |
display: false | |
} | |
}, | |
y: { | |
beginAtZero: false, | |
grid: { | |
color: 'rgba(0, 0, 0, 0.05)' | |
}, | |
ticks: { | |
callback: function(value) { | |
return '$' + value.toLocaleString(); | |
} | |
} | |
} | |
}, | |
plugins: { | |
tooltip: { | |
mode: 'index', | |
intersect: false, | |
callbacks: { | |
label: function(context) { | |
let label = context.dataset.label || ''; | |
if (label) { | |
label += ': '; | |
} | |
if (context.parsed.y !== null) { | |
label += '$' + context.parsed.y.toLocaleString('en-US', {minimumFractionDigits: 2, maximumFractionDigits: 2}); | |
} | |
return label; | |
} | |
} | |
}, | |
legend: { | |
position: 'top', | |
align: 'end' | |
} | |
}, | |
interaction: { | |
mode: 'nearest', | |
axis: 'x', | |
intersect: false | |
} | |
} | |
}); | |
} else { | |
priceChart.data.labels = labels; | |
priceChart.data.datasets[0].data = prices; | |
priceChart.data.datasets[1].data = ema20; | |
priceChart.data.datasets[2].data = ema50; | |
priceChart.update(); | |
} | |
} | |
// Set up auto-refresh | |
function setupAutoRefresh() { | |
const autoRefreshCheckbox = document.getElementById('autoRefresh'); | |
autoRefreshCheckbox.addEventListener('change', function() { | |
if (this.checked) { | |
scheduleNextRefresh(); | |
} else { | |
clearTimeout(autoRefreshInterval); | |
document.getElementById('nextRefresh').textContent = '--:--'; | |
} | |
}); | |
// Initial schedule | |
if (autoRefreshCheckbox.checked) { | |
scheduleNextRefresh(); | |
} | |
} | |
// Schedule next refresh | |
function scheduleNextRefresh() { | |
clearTimeout(autoRefreshInterval); | |
const now = new Date(); | |
const nextRefresh = new Date(now.getTime() + 15 * 60 * 1000); // 15 minutes from now | |
document.getElementById('nextRefresh').textContent = nextRefresh.toLocaleTimeString([], {hour: '2-digit', minute: '2-digit'}); | |
autoRefreshInterval = setTimeout(() => { | |
fetchData(); | |
if (document.getElementById('autoRefresh').checked) { | |
scheduleNextRefresh(); | |
} | |
}, 15 * 60 * 1000); | |
} | |
// Export history as JSON | |
function exportHistory() { | |
const dataStr = JSON.stringify(predictionHistory, null, 2); | |
const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr); | |
let exportFileDefaultName; | |
if (isAnonymous) { | |
exportFileDefaultName = `bitcoin-predictions-anonymous-${new Date().toISOString().slice(0, 10)}.json`; | |
} else { | |
exportFileDefaultName = `bitcoin-predictions-${currentUser.id}-${new Date().toISOString().slice(0, 10)}.json`; | |
} | |
const linkElement = document.createElement('a'); | |
linkElement.setAttribute('href', dataUri); | |
linkElement.setAttribute('download', exportFileDefaultName); | |
linkElement.click(); | |
} | |
</script> | |
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=note6/btcpromulti" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
</html> |