Files
gogo2/web/enhanced_cob_dashboard.html
Dobromir Popov 0c4dc8269c cob dash
2025-06-24 16:56:02 +03:00

419 lines
14 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Enhanced RL COB Trading Dashboard</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns/dist/chartjs-adapter-date-fns.bundle.min.js"></script>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #0c0c0c 0%, #1a1a1a 100%);
color: #e0e0e0;
margin: 0;
padding: 20px;
}
.header {
background: rgba(20, 20, 20, 0.9);
padding: 20px;
border-radius: 12px;
margin-bottom: 20px;
border: 1px solid #333;
}
.title {
font-size: 1.8rem;
font-weight: 700;
background: linear-gradient(45deg, #00d4ff, #00ff88);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.status-indicators {
display: flex;
gap: 20px;
margin-top: 10px;
}
.status-dot {
width: 12px;
height: 12px;
border-radius: 50%;
background: #ff4444;
display: inline-block;
margin-right: 8px;
animation: pulse 2s infinite;
}
.status-dot.active {
background: #00ff88;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.main-container {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 20px;
}
.chart-section, .sidebar {
background: rgba(20, 20, 20, 0.8);
border-radius: 12px;
padding: 20px;
border: 1px solid #333;
}
.chart-container {
width: 100%;
height: 400px;
position: relative;
}
.predictions-list {
max-height: 300px;
overflow-y: auto;
}
.prediction-item {
background: rgba(40, 40, 40, 0.6);
border-radius: 8px;
padding: 12px;
margin-bottom: 8px;
border-left: 4px solid #666;
}
.prediction-item.up { border-left-color: #00ff88; }
.prediction-item.down { border-left-color: #ff4444; }
.prediction-item.sideways { border-left-color: #ffaa00; }
</style>
</head>
<body>
<div class="header">
<div class="title">🔥 Enhanced RL COB Trading Dashboard</div>
<div class="status-indicators">
<div>
<span class="status-dot" id="rl-status"></span>
<span>RL Model</span>
</div>
<div>
<span class="status-dot" id="cob-status"></span>
<span>COB Data</span>
</div>
<div>
<span class="status-dot" id="ws-status"></span>
<span>WebSocket</span>
</div>
</div>
</div>
<div class="main-container">
<div class="chart-section">
<h3>📈 Price Chart with RL Predictions</h3>
<div class="chart-container">
<canvas id="priceChart"></canvas>
</div>
</div>
<div class="sidebar">
<h3>🤖 RL Predictions</h3>
<div class="predictions-list" id="predictions-list">
<div>Loading predictions...</div>
</div>
<h3 style="margin-top: 30px;">📊 COB Data</h3>
<div id="cob-data">
<div>Loading COB data...</div>
</div>
</div>
</div>
<script>
let priceChart = null;
let websocket = null;
let currentSymbol = 'BTC/USDT';
// Initialize dashboard
document.addEventListener('DOMContentLoaded', function() {
initializeChart();
initializeWebSocket();
loadInitialData();
// Update data every 2 seconds
setInterval(loadData, 2000);
});
function initializeChart() {
const ctx = document.getElementById('priceChart').getContext('2d');
priceChart = new Chart(ctx, {
type: 'line',
data: {
datasets: [
{
label: 'Price',
data: [],
borderColor: '#00d4ff',
backgroundColor: 'rgba(0, 212, 255, 0.1)',
borderWidth: 2,
fill: false,
pointRadius: 0
},
{
label: 'RL Predictions (UP)',
data: [],
borderColor: '#00ff88',
backgroundColor: '#00ff88',
pointRadius: 6,
pointStyle: 'triangle',
showLine: false
},
{
label: 'RL Predictions (DOWN)',
data: [],
borderColor: '#ff4444',
backgroundColor: '#ff4444',
pointRadius: 6,
pointStyle: 'triangle',
rotation: 180,
showLine: false
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
type: 'time',
time: {
unit: 'minute'
},
grid: { color: 'rgba(255, 255, 255, 0.1)' },
ticks: { color: '#aaa' }
},
y: {
grid: { color: 'rgba(255, 255, 255, 0.1)' },
ticks: { color: '#aaa' }
}
},
plugins: {
legend: {
labels: { color: '#e0e0e0' }
}
}
}
});
}
function initializeWebSocket() {
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsUrl = `${protocol}//${window.location.host}/ws`;
websocket = new WebSocket(wsUrl);
websocket.onopen = function() {
updateStatusDot('ws-status', true);
websocket.send(JSON.stringify({
type: 'subscribe',
symbol: currentSymbol
}));
};
websocket.onmessage = function(event) {
const data = JSON.parse(event.data);
if (data.type === 'cob_update') {
handleCOBUpdate(data);
}
};
websocket.onerror = function() {
updateStatusDot('ws-status', false);
};
websocket.onclose = function() {
updateStatusDot('ws-status', false);
setTimeout(initializeWebSocket, 5000);
};
}
async function loadInitialData() {
await loadRLStatus();
await loadData();
}
async function loadData() {
await loadCOBData();
await loadRLPredictions();
}
async function loadRLStatus() {
try {
const response = await fetch('/api/rl-status');
const data = await response.json();
updateStatusDot('rl-status', data.status === 'active');
} catch (error) {
updateStatusDot('rl-status', false);
}
}
async function loadCOBData() {
try {
const response = await fetch(`/api/cob-data/${encodeURIComponent(currentSymbol)}`);
const data = await response.json();
if (data.data) {
updateCOBDisplay(data.data);
updateStatusDot('cob-status', true);
// Add price point to chart
if (data.data.stats && data.data.stats.mid_price) {
addPricePoint(data.data.stats.mid_price);
}
}
} catch (error) {
updateStatusDot('cob-status', false);
}
}
async function loadRLPredictions() {
try {
const response = await fetch(`/api/rl-predictions/${encodeURIComponent(currentSymbol)}`);
const data = await response.json();
if (data.predictions) {
updatePredictionsDisplay(data.predictions);
updatePredictionsChart(data.predictions);
}
} catch (error) {
console.error('Error loading RL predictions:', error);
}
}
function handleCOBUpdate(data) {
if (data.symbol === currentSymbol) {
updateCOBDisplay(data.data);
if (data.data.stats && data.data.stats.mid_price) {
addPricePoint(data.data.stats.mid_price);
}
if (data.data.rl_data && data.data.rl_data.rl_predictions) {
updatePredictionsFromWS(data.data.rl_data.rl_predictions);
}
}
}
function addPricePoint(price) {
const now = new Date();
priceChart.data.datasets[0].data.push({
x: now,
y: price
});
// Keep last 300 points (5 minutes)
if (priceChart.data.datasets[0].data.length > 300) {
priceChart.data.datasets[0].data.shift();
}
priceChart.update('none');
}
function updateCOBDisplay(cobData) {
const container = document.getElementById('cob-data');
if (!cobData.bids || !cobData.asks) {
container.innerHTML = '<div>No COB data available</div>';
return;
}
let html = '<div style="font-size: 0.9rem;">';
// Show top 3 asks
cobData.asks.slice(0, 3).reverse().forEach(ask => {
html += `<div style="color: #ff4444; margin: 2px 0;">Ask: $${ask.price.toFixed(2)} (${ask.size.toFixed(4)})</div>`;
});
// Show spread
if (cobData.stats && cobData.stats.mid_price) {
html += `<div style="background: rgba(255,255,255,0.1); padding: 4px; margin: 4px 0; text-align: center;">Mid: $${cobData.stats.mid_price.toFixed(2)}</div>`;
}
// Show top 3 bids
cobData.bids.slice(0, 3).forEach(bid => {
html += `<div style="color: #00ff88; margin: 2px 0;">Bid: $${bid.price.toFixed(2)} (${bid.size.toFixed(4)})</div>`;
});
html += '</div>';
container.innerHTML = html;
}
function updatePredictionsDisplay(predictions) {
const container = document.getElementById('predictions-list');
if (!predictions.recent_predictions || predictions.recent_predictions.length === 0) {
container.innerHTML = '<div>No recent predictions</div>';
return;
}
let html = '';
const recent = predictions.recent_predictions.slice(-10).reverse();
recent.forEach(pred => {
const directionClass = pred.direction === 2 ? 'up' : (pred.direction === 0 ? 'down' : 'sideways');
const directionText = ['DOWN', 'SIDEWAYS', 'UP'][pred.direction];
const confidence = (pred.confidence * 100).toFixed(1);
const time = new Date(pred.timestamp).toLocaleTimeString();
html += `
<div class="prediction-item ${directionClass}">
<div style="font-weight: bold;">${directionText}</div>
<div style="font-size: 0.8rem; color: #aaa;">${time} - ${confidence}% confidence</div>
</div>
`;
});
container.innerHTML = html;
}
function updatePredictionsChart(predictions) {
if (!predictions.prediction_history) return;
const upPredictions = [];
const downPredictions = [];
predictions.prediction_history.slice(-50).forEach(pred => {
const point = {
x: new Date(pred.timestamp),
y: pred.price || 0
};
if (pred.direction === 2) upPredictions.push(point);
else if (pred.direction === 0) downPredictions.push(point);
});
priceChart.data.datasets[1].data = upPredictions;
priceChart.data.datasets[2].data = downPredictions;
priceChart.update('none');
}
function updatePredictionsFromWS(predictions) {
predictions.forEach(pred => {
const point = {
x: new Date(pred.timestamp),
y: pred.price || priceChart.data.datasets[0].data[priceChart.data.datasets[0].data.length - 1]?.y || 0
};
if (pred.direction === 2) {
priceChart.data.datasets[1].data.push(point);
} else if (pred.direction === 0) {
priceChart.data.datasets[2].data.push(point);
}
});
priceChart.update('none');
}
function updateStatusDot(elementId, isActive) {
const dot = document.getElementById(elementId);
if (dot) {
dot.classList.toggle('active', isActive);
}
}
</script>
</body>
</html>