Files
trading-daemon/dashboard/public/index.html
Melchior Reimers 1086c4aa1d
All checks were successful
Deployment / deploy-docker (push) Successful in 10s
fix: EIX scraping, LS endpoints, and premium Dashboard analytics
2026-01-23 18:09:00 +01:00

381 lines
16 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Trading Intelligence Dashboard</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Outfit', sans-serif;
background-color: #0b0e14;
color: #e2e8f0;
}
.glass {
background: rgba(30, 41, 59, 0.5);
backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 16px;
}
.glow {
box-shadow: 0 0 20px rgba(56, 189, 248, 0.2);
}
.sidebar-item:hover {
background: rgba(56, 189, 248, 0.1);
color: #38bdf8;
}
canvas {
max-width: 100%;
}
</style>
</head>
<body class="flex min-h-screen">
<!-- Sidebar -->
<aside class="w-64 glass m-4 flex flex-col hidden lg:flex">
<div class="p-6">
<h1 class="text-2xl font-bold bg-gradient-to-r from-sky-400 to-blue-500 bg-clip-text text-transparent">
Antigravity Trade</h1>
</div>
<nav class="flex-1 px-4 space-y-2 mt-4" id="sidebar">
<a href="#" onclick="showView('dashboard')"
class="sidebar-item flex items-center p-3 rounded-xl transition bg-sky-500/10 text-sky-400"
id="nav-dashboard">
<span class="mr-3">📊</span> Dashboard
</a>
<a href="#" onclick="showView('analytics')" class="sidebar-item flex items-center p-3 rounded-xl transition"
id="nav-analytics">
<span class="mr-3">📈</span> Analytics
</a>
<a href="#" onclick="showView('metadata')" class="sidebar-item flex items-center p-3 rounded-xl transition"
id="nav-metadata">
<span class="mr-3">🏢</span> Companies
</a>
</nav>
<div class="p-6 mt-auto">
<div class="glass p-4 text-xs text-sky-400 border-sky-500/30">
System Status: <span class="text-green-400">Live</span>
</div>
</div>
</aside>
<!-- Main Content -->
<main class="flex-1 p-8 overflow-y-auto">
<header class="flex justify-between items-center mb-8">
<div>
<h2 class="text-3xl font-bold">Market Overview</h2>
<p class="text-slate-400">Real-time trading analytics across multiple exchanges.</p>
</div>
<div class="flex space-x-4">
<select id="timeRange" class="glass px-4 py-2 text-sm outline-none">
<option value="7">Last 7 Days</option>
<option value="1">Last 24 Hours</option>
<option value="30">Last 30 Days</option>
</select>
<div class="glass px-4 py-2 flex items-center text-sm font-semibold text-sky-400">
<span class="w-2 h-2 bg-green-500 rounded-full mr-2"></span> Connected
</div>
</div>
</header>
<!-- View: Dashboard -->
<div id="content-dashboard" class="content-view">
<!-- Stats Grid -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
<div class="glass p-6 glow">
<p class="text-slate-400 text-sm mb-1 text-uppercase tracking-wider">Total Trades</p>
<h3 id="statTotalTrades" class="text-4xl font-bold">--</h3>
</div>
<div class="glass p-6">
<p class="text-slate-400 text-sm mb-1 text-uppercase tracking-wider">Total Volume</p>
<h3 id="statTotalVolume" class="text-4xl font-bold">--</h3>
</div>
<div class="glass p-6">
<p class="text-slate-400 text-sm mb-1 text-uppercase tracking-wider">Tracked ISINs</p>
<h3 id="statIsins" class="text-4xl font-bold">--</h3>
</div>
</div>
<!-- Charts Container -->
<div class="grid grid-cols-1 xl:grid-cols-2 gap-8 mb-8">
<div class="glass p-6">
<h3 class="text-xl font-bold mb-6">Price Evolution (Recent)</h3>
<div class="h-80"><canvas id="mainChart"></canvas></div>
</div>
<div class="glass p-6">
<h3 class="text-xl font-bold mb-6">Distribution by Continent</h3>
<div class="h-80"><canvas id="pieChart"></canvas></div>
</div>
</div>
</div>
<!-- View: Analytics -->
<div id="content-analytics" class="content-view hidden">
<div class="glass p-6 mb-8">
<h3 class="text-xl font-bold mb-6">Exchange Comparison (Volume)</h3>
<div class="h-96"><canvas id="comparisonChart"></canvas></div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
<div class="glass p-6">
<h3 class="text-xl font-bold mb-6">EIX vs LS Trade Count</h3>
<div class="h-64"><canvas id="tradeCountChart"></canvas></div>
</div>
<div class="glass p-6">
<h3 class="text-xl font-bold mb-6">Average Trade Size</h3>
<div id="avgTradeData" class="space-y-4">
<!-- Dynamic data -->
</div>
</div>
</div>
</div>
<!-- View: Metadata (Table) -->
<div id="content-metadata" class="content-view hidden">
<!-- Data Table -->
<div class="glass overflow-hidden">
<div class="p-6 border-b border-white/5 flex justify-between items-center">
<h3 class="text-xl font-bold">Company Metadata</h3>
<input type="text" placeholder="Search ISIN..." class="glass px-4 py-1 text-sm">
</div>
<table class="w-full text-left">
<thead class="bg-white/5 text-slate-400 text-sm uppercase">
<tr>
<th class="p-4 px-6">ISIN</th>
<th class="p-4">Name</th>
<th class="p-4">Country</th>
<th class="p-4">Continent</th>
<th class="p-4">Sector</th>
</tr>
</thead>
<tbody id="metadataTable" class="divide-y divide-white/5">
<!-- Rows injected via JS -->
</tbody>
</table>
</div>
</main>
<script>
const API_BASE = '/api';
function showView(viewId) {
document.querySelectorAll('.content-view').forEach(v => v.classList.add('hidden'));
document.getElementById(`content-${viewId}`).classList.remove('hidden');
document.querySelectorAll('#sidebar a').forEach(a => {
a.classList.remove('bg-sky-500/10', 'text-sky-400');
});
document.getElementById(`nav-${viewId}`).classList.add('bg-sky-500/10', 'text-sky-400');
const titles = {
'dashboard': ['Market Overview', 'Real-time trading analytics across multiple exchanges.'],
'analytics': ['Advanced Analytics', 'Deep dive comparison and historical trends.'],
'metadata': ['Enriched Company Data', 'Unified metadata from GLEIF and Financial APIs.']
};
document.querySelector('#viewTitle h2').innerText = titles[viewId][0];
document.querySelector('#viewTitle p').innerText = titles[viewId][1];
fetchData();
}
async function fetchData() {
try {
const days = document.getElementById('timeRange').value;
const [tradesRes, metaRes, summaryRes, compRes] = await Promise.all([
fetch(`${API_BASE}/trades?days=${days}`),
fetch(`${API_BASE}/metadata`),
fetch(`${API_BASE}/summary`),
fetch(`${API_BASE}/comparison?days=${days}`)
]);
const trades = await tradesRes.json();
const metadata = await metaRes.json();
const summary = await summaryRes.json();
const comparison = await compRes.json();
updateStats(trades, metadata);
renderMainChart(trades);
renderPieChart(summary);
updateMetadataTable(metadata);
renderComparisonCharts(comparison);
} catch (e) {
console.error("Dashboard error:", e);
}
}
function updateStats(trades, metadata) {
const rowCount = trades.dataset ? trades.dataset.length : 0;
document.getElementById('statTotalTrades').innerText = rowCount.toLocaleString();
let totalVol = 0;
if (trades.dataset) {
trades.dataset.forEach(row => {
const p = parseFloat(row[4]) || 0;
const q = parseFloat(row[5]) || 0;
totalVol += (p * q);
});
}
if (totalVol >= 1000000) {
document.getElementById('statTotalVolume').innerText = '€' + (totalVol / 1000000).toFixed(2) + 'M';
} else {
document.getElementById('statTotalVolume').innerText = '€' + (totalVol / 1000).toFixed(1) + 'k';
}
const metaCount = metadata.dataset ? metadata.dataset.length : 0;
document.getElementById('statIsins').innerText = metaCount.toLocaleString();
}
let mainChart, pieChart, compChart, countChart;
function renderComparisonCharts(data) {
if (!data.dataset) return;
const ctxComp = document.getElementById('comparisonChart').getContext('2d');
const ctxCount = document.getElementById('tradeCountChart').getContext('2d');
// Organize data: {exchange: {days: [], volumes: []}}
const exchanges = {};
const daysSet = new Set();
data.dataset.forEach(row => {
const ex = row[0];
const day = new Date(row[1]).toLocaleDateString();
const vol = row[2];
const count = row[3];
if (!exchanges[ex]) exchanges[ex] = { days: {}, volumes: {}, counts: {} };
exchanges[ex].volumes[day] = vol;
exchanges[ex].counts[day] = count;
daysSet.add(day);
});
const labels = Array.from(daysSet).sort((a, b) => new Date(a) - new Date(b));
const createDataset = (ex, type, color) => ({
label: ex,
data: labels.map(l => exchanges[ex][type][l] || 0),
borderColor: color,
backgroundColor: color + '33',
fill: true,
tension: 0.3
});
if (compChart) compChart.destroy();
compChart = new Chart(ctxComp, {
type: 'line',
data: {
labels,
datasets: [
createDataset('LS', 'volumes', '#38bdf8'),
createDataset('EIX', 'volumes', '#fbbf24')
]
},
options: { responsive: true, maintainAspectRatio: false }
});
if (countChart) countChart.destroy();
countChart = new Chart(ctxCount, {
type: 'bar',
data: {
labels,
datasets: [
{ label: 'LS', data: labels.map(l => exchanges['LS']?.counts[l] || 0), backgroundColor: '#38bdf8' },
{ label: 'EIX', data: labels.map(l => exchanges['EIX']?.counts[l] || 0), backgroundColor: '#fbbf24' }
]
},
options: { responsive: true, maintainAspectRatio: false }
});
}
function renderMainChart(trades) {
if (!trades.dataset) return;
const ctx = document.getElementById('mainChart').getContext('2d');
// Basic aggregation for demonstration
const labels = trades.dataset.slice(-20).map(r => new Date(r[2]).toLocaleTimeString());
const data = trades.dataset.slice(-20).map(r => r[4]);
if (mainChart) mainChart.destroy();
mainChart = new Chart(ctx, {
type: 'line',
data: {
labels,
datasets: [{
label: 'Price Evolution',
data,
borderColor: '#38bdf8',
backgroundColor: 'rgba(56, 189, 248, 0.2)',
fill: true,
tension: 0.4,
borderWidth: 3
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: { grid: { color: 'rgba(255,255,255,0.05)' }, ticks: { color: '#94a3b8' } },
x: { grid: { color: 'rgba(255,255,255,0.05)' }, ticks: { color: '#94a3b8' } }
},
plugins: { legend: { display: false } }
}
});
}
function renderPieChart(summary) {
if (!summary.dataset) return;
const ctx = document.getElementById('pieChart').getContext('2d');
const continents = summary.dataset.map(r => r[0]);
const volumes = summary.dataset.map(r => r[3]);
if (pieChart) pieChart.destroy();
pieChart = new Chart(ctx, {
type: 'doughnut',
data: {
labels: continents,
datasets: [{
data: volumes,
backgroundColor: ['#38bdf8', '#fbbf24', '#f87171', '#34d399', '#818cf8'],
borderWidth: 0
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { position: 'bottom', labels: { color: '#94a3b8' } } }
}
});
}
function updateMetadataTable(metadata) {
const container = document.getElementById('metadataTable');
container.innerHTML = '';
if (!metadata.dataset) return;
metadata.dataset.forEach(row => {
const tr = document.createElement('tr');
tr.innerHTML = `
<td class="p-4 px-6 font-mono text-sky-400">${row[0]}</td>
<td class="p-4 font-semibold">${row[1]}</td>
<td class="p-4"><span class="bg-white/10 px-2 py-1 rounded text-xs">${row[2]}</span></td>
<td class="p-4 text-slate-400">${row[3]}</td>
<td class="p-4 text-slate-400">${row[4]}</td>
`;
container.appendChild(tr);
});
}
document.getElementById('timeRange').addEventListener('change', fetchData);
fetchData();
setInterval(fetchData, 30000);
</script>
</body>
</html>