2026-01-23 17:49:48 +01:00
<!DOCTYPE html>
< html lang = "en" >
2026-01-23 18:09:00 +01:00
2026-01-23 17:49:48 +01:00
< 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;
2026-01-23 18:17:02 +01:00
background-color: #0b1120;
2026-01-23 17:49:48 +01:00
color: #e2e8f0;
}
2026-01-23 18:09:00 +01:00
2026-01-23 17:49:48 +01:00
.glass {
2026-01-23 18:17:02 +01:00
background: rgba(30, 41, 59, 0.4);
2026-01-23 17:49:48 +01:00
backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 16px;
}
2026-01-23 18:09:00 +01:00
2026-01-23 18:17:02 +01:00
.active-nav {
2026-01-23 17:49:48 +01:00
background: rgba(56, 189, 248, 0.1);
color: #38bdf8;
2026-01-23 18:17:02 +01:00
border-right: 3px solid #38bdf8;
2026-01-23 17:49:48 +01:00
}
2026-01-23 18:09:00 +01:00
2026-01-23 17:49:48 +01:00
canvas {
max-width: 100%;
}
2026-01-23 18:17:02 +01:00
::-webkit-scrollbar {
width: 6px;
}
::-webkit-scrollbar-thumb {
background: #1e293b;
border-radius: 10px;
}
2026-01-23 19:15:48 +01:00
.config-sidebar {
width: 320px;
border-right: 1px solid rgba(255, 255, 255, 0.05);
}
.btn-primary {
background: #38bdf8;
color: #0b1120;
padding: 8px 16px;
border-radius: 8px;
font-weight: 600;
transition: all 0.2s;
}
.btn-primary:hover {
background: #7dd3fc;
transform: translateY(-1px);
}
2026-01-23 17:49:48 +01:00
< / style >
< / head >
2026-01-23 18:09:00 +01:00
2026-01-23 18:17:02 +01:00
< body class = "flex min-h-screen overflow-hidden" >
2026-01-23 17:49:48 +01:00
2026-01-23 19:15:48 +01:00
<!-- Sidebar (Main Nav) -->
2026-01-23 18:17:02 +01:00
< aside class = "w-64 glass m-4 mr-0 flex flex-col hidden lg:flex" >
2026-01-23 17:49:48 +01:00
< div class = "p-6" >
2026-01-23 18:09:00 +01:00
< h1 class = "text-2xl font-bold bg-gradient-to-r from-sky-400 to-blue-500 bg-clip-text text-transparent" >
Antigravity Trade< / h1 >
2026-01-23 17:49:48 +01:00
< / div >
2026-01-23 18:17:02 +01:00
< nav class = "flex-1 px-4 space-y-1 mt-4" id = "sidebar" >
< a href = "#" onclick = "showView('dashboard')" id = "nav-dashboard"
2026-01-23 19:15:48 +01:00
class="flex items-center p-3 rounded-xl transition active-nav">< span class = "mr-3" > 📊< / span >
Dashboard< / a >
2026-01-23 18:17:02 +01:00
< a href = "#" onclick = "showView('analytics')" id = "nav-analytics"
2026-01-23 19:15:48 +01:00
class="flex items-center p-3 rounded-xl transition">< span class = "mr-3" > 📈< / span > Reports (GC Style)< / a >
2026-01-23 18:17:02 +01:00
< a href = "#" onclick = "showView('metadata')" id = "nav-metadata"
2026-01-23 19:15:48 +01:00
class="flex items-center p-3 rounded-xl transition">< span class = "mr-3" > 🏢< / span > Companies< / a >
2026-01-23 17:49:48 +01:00
< / nav >
< / aside >
2026-01-23 19:15:48 +01:00
<!-- Main Content Area -->
< main class = "flex-1 flex flex-col min-w-0" >
< header class = "p-8 pb-0 flex justify-between items-center" >
2026-01-23 18:17:02 +01:00
< div id = "viewHeader" >
< h2 class = "text-3xl font-bold" > Market Intelligence< / h2 >
< p class = "text-slate-400 mt-1" > Cross-exchange analysis and real-time visualization.< / p >
2026-01-23 17:49:48 +01:00
< / div >
2026-01-23 19:15:48 +01:00
< div id = "globalControls" class = "flex items-center space-x-4" >
< div id = "activeIsins" class = "flex -space-x-2" > < / div >
< button onclick = "fetchData()" class = "glass p-2 px-4 text-sky-400 hover:text-sky-300" > ↻ Refresh< / button >
2026-01-23 17:49:48 +01:00
< / div >
< / header >
2026-01-23 18:17:02 +01:00
<!-- DASHBOARD VIEW -->
2026-01-23 19:15:48 +01:00
< div id = "view-dashboard" class = "view flex-1 p-8 overflow-y-auto" >
2026-01-23 18:17:02 +01:00
< div class = "grid grid-cols-1 md:grid-cols-3 gap-6 mb-10" >
2026-01-23 19:15:48 +01:00
< div class = "glass p-6 border-l-4 border-sky-500" >
2026-01-23 18:17:02 +01:00
< p class = "text-slate-400 text-xs uppercase tracking-widest mb-2" > Transaction Volume< / p >
2026-01-23 19:15:48 +01:00
< h3 id = "statVolume" class = "text-4xl font-bold" > --< / h3 >
2026-01-23 18:09:00 +01:00
< / div >
2026-01-23 18:17:02 +01:00
< div class = "glass p-6 border-l-4 border-amber-500" >
< p class = "text-slate-400 text-xs uppercase tracking-widest mb-2" > Total Executions< / p >
2026-01-23 19:15:48 +01:00
< h3 id = "statTrades" class = "text-4xl font-bold" > --< / h3 >
2026-01-23 18:09:00 +01:00
< / div >
2026-01-23 18:17:02 +01:00
< div class = "glass p-6 border-l-4 border-emerald-500" >
< p class = "text-slate-400 text-xs uppercase tracking-widest mb-2" > Unique Assets< / p >
2026-01-23 19:15:48 +01:00
< h3 id = "statIsins" class = "text-4xl font-bold" > --< / h3 >
2026-01-23 18:09:00 +01:00
< / div >
2026-01-23 17:49:48 +01:00
< / div >
2026-01-23 19:15:48 +01:00
< div class = "grid grid-cols-1 xl:grid-cols-2 gap-8" >
2026-01-23 18:17:02 +01:00
< div class = "glass p-8" >
2026-01-23 19:15:48 +01:00
< h3 class = "text-xl font-bold mb-6" > Price Trend (Recent)< / h3 >
2026-01-23 18:17:02 +01:00
< div class = "h-80" > < canvas id = "priceChart" > < / canvas > < / div >
2026-01-23 18:09:00 +01:00
< / div >
2026-01-23 18:17:02 +01:00
< div class = "glass p-8" >
2026-01-23 19:15:48 +01:00
< h3 class = "text-xl font-bold mb-6" > Market Distribution< / h3 >
2026-01-23 18:17:02 +01:00
< div class = "h-80" > < canvas id = "continentChart" > < / canvas > < / div >
2026-01-23 18:09:00 +01:00
< / div >
2026-01-23 17:49:48 +01:00
< / div >
< / div >
2026-01-23 19:15:48 +01:00
<!-- ANALYTICS VIEW (Google Cloud Billing Style) -->
< div id = "view-analytics" class = "view hidden flex-1 flex overflow-hidden" >
<!-- Configuration Sidebar -->
< div class = "config-sidebar p-6 space-y-8 overflow-y-auto bg-slate-900/20" >
< div >
< label class = "block text-xs font-bold text-slate-500 uppercase tracking-widest mb-4" > Time
Range< / label >
< div class = "space-y-2" >
< select id = "timeRangePreset" class = "w-full glass p-2 text-sm outline-none"
onchange="handlePresetChange()">
< option value = "1" > 1 Day< / option >
< option value = "7" selected > Last 7 Days< / option >
< option value = "30" > Last 30 Days< / option >
< option value = "ytd" > YTD (Year to Date)< / option >
< option value = "year" > Current Year< / option >
< option value = "custom" > Custom Range< / option >
2026-01-23 18:17:02 +01:00
< / select >
2026-01-23 19:15:48 +01:00
< div id = "customDates" class = "hidden space-y-2 pt-2" >
< input type = "date" id = "dateFrom" class = "w-full glass p-2 text-xs" >
< input type = "date" id = "dateTo" class = "w-full glass p-2 text-xs" >
< / div >
2026-01-23 18:17:02 +01:00
< / div >
< / div >
2026-01-23 19:15:48 +01:00
< div >
< label class = "block text-xs font-bold text-slate-500 uppercase tracking-widest mb-4" > Group by
(X-Axis)< / label >
< select id = "axisX" class = "w-full glass p-2 text-sm outline-none" >
< option value = "day" > Day< / option >
< option value = "month" > Month< / option >
< option value = "exchange" > Exchange< / option >
< option value = "continent" > Continent< / option >
< option value = "sector" > Sector< / option >
< option value = "name" > Company Name< / option >
< / select >
< / div >
< div >
< label class = "block text-xs font-bold text-slate-500 uppercase tracking-widest mb-4" > Breakdown by
(Series)< / label >
< select id = "axisSub" class = "w-full glass p-2 text-sm outline-none" >
< option value = "" > None< / option >
< option value = "exchange" > Exchange< / option >
< option value = "continent" > Continent< / option >
< option value = "sector" > Sector< / option >
< / select >
< / div >
< div >
< label class = "block text-xs font-bold text-slate-500 uppercase tracking-widest mb-4" > Metric
(Y-Axis)< / label >
< select id = "axisY" class = "w-full glass p-2 text-sm outline-none" >
< option value = "volume" > Trade Volume (€)< / option >
< option value = "count" > Trade Count< / option >
< option value = "avg_price" > Avg. Price< / option >
< / select >
< / div >
< div >
< label class = "block text-xs font-bold text-slate-500 uppercase tracking-widest mb-4" > Filters< / label >
< input type = "text" id = "isinFilter" placeholder = "Filter ISINs (split by ,)"
class="w-full glass p-2 text-xs outline-none">
2026-01-23 18:09:00 +01:00
< / div >
2026-01-23 19:15:48 +01:00
< button onclick = "renderAnalyticsReport()" class = "btn-primary w-full" > Run Report< / button >
< / div >
<!-- Report Content Area -->
< div class = "flex-1 p-8 overflow-y-auto" >
< div class = "glass p-8 h-full flex flex-col" >
< div class = "flex justify-between items-center mb-8" >
< h3 class = "text-xl font-bold" id = "reportTitle" > Custom Trade Analysis< / h3 >
< div class = "flex space-x-2" >
< button class = "glass p-2 px-3 text-xs" onclick = "setChartType('line')" > Line< / button >
< button class = "glass p-2 px-3 text-xs" onclick = "setChartType('bar')" > Bar< / button >
< / div >
2026-01-23 18:09:00 +01:00
< / div >
2026-01-23 19:15:48 +01:00
< div class = "flex-1 min-h-0" > < canvas id = "analyticsChart" > < / canvas > < / div >
2026-01-23 18:09:00 +01:00
< / div >
2026-01-23 17:49:48 +01:00
< / div >
< / div >
2026-01-23 18:17:02 +01:00
<!-- COMPANIES VIEW -->
2026-01-23 19:15:48 +01:00
< div id = "view-metadata" class = "view hidden flex-1 p-8 overflow-y-auto" >
2026-01-23 18:09:00 +01:00
< div class = "glass overflow-hidden" >
< div class = "p-6 border-b border-white/5 flex justify-between items-center" >
2026-01-23 18:17:02 +01:00
< h3 class = "text-xl font-bold text-sky-400" > ISIN Metadata Directory< / h3 >
2026-01-23 19:15:48 +01:00
< input type = "text" id = "metadataSearch" onkeyup = "filterMetadata()" placeholder = "Search..."
class="glass px-4 py-2 text-sm w-80 outline-none">
2026-01-23 18:09:00 +01:00
< / div >
2026-01-23 19:15:48 +01:00
< table class = "w-full text-left text-sm" >
< tbody id = "metadataRows" > < / tbody >
2026-01-23 18:09:00 +01:00
< / table >
2026-01-23 17:49:48 +01:00
< / div >
2026-01-23 18:17:02 +01:00
< / div >
2026-01-23 17:49:48 +01:00
< / main >
< script >
2026-01-23 18:17:02 +01:00
const API = '/api';
2026-01-23 19:15:48 +01:00
let store = { trades: [], metadata: [], summary: [], pinnedIsins: [] };
2026-01-23 18:17:02 +01:00
let charts = {};
2026-01-23 19:15:48 +01:00
let currentChartType = 'bar';
function showView(viewId) {
document.querySelectorAll('.view').forEach(v => v.classList.add('hidden'));
document.getElementById(`view-${viewId}`).classList.remove('hidden');
document.querySelectorAll('#sidebar a').forEach(a => a.classList.remove('active-nav'));
document.getElementById(`nav-${viewId}`).classList.add('active-nav');
if (viewId === 'analytics') renderAnalyticsReport(); else fetchData();
}
function handlePresetChange() {
const v = document.getElementById('timeRangePreset').value;
document.getElementById('customDates').classList.toggle('hidden', v !== 'custom');
}
function getDates() {
const preset = document.getElementById('timeRangePreset').value;
const now = new Date();
let from, to = now.toISOString().split('T')[0];
if (preset === '1') from = new Date(now.setDate(now.getDate() - 1)).toISOString().split('T')[0];
else if (preset === '7') from = new Date(now.setDate(now.getDate() - 7)).toISOString().split('T')[0];
else if (preset === '30') from = new Date(now.setDate(now.getDate() - 30)).toISOString().split('T')[0];
else if (preset === 'ytd') from = new Date(now.getFullYear(), 0, 1).toISOString().split('T')[0];
else if (preset === 'year') { from = new Date(now.getFullYear(), 0, 1).toISOString().split('T')[0]; to = new Date(now.getFullYear(), 11, 31).toISOString().split('T')[0]; }
else if (preset === 'custom') { from = document.getElementById('dateFrom').value; to = document.getElementById('dateTo').value; }
return { from, to };
}
2026-01-23 18:09:00 +01:00
2026-01-23 17:49:48 +01:00
async function fetchData() {
try {
2026-01-23 19:15:48 +01:00
const [t, m, s] = await Promise.all([
fetch(`${API}/trades?days=7`).then(r => r.json()),
2026-01-23 18:17:02 +01:00
fetch(`${API}/metadata`).then(r => r.json()),
2026-01-23 19:15:48 +01:00
fetch(`${API}/summary`).then(r => r.json())
2026-01-23 17:49:48 +01:00
]);
2026-01-23 19:15:48 +01:00
store = { ...store, trades: t.dataset || [], metadata: m.dataset || [], summary: s.dataset || [] };
updateDashboard();
fillMetadataTable();
} catch (err) { console.error(err); }
2026-01-23 17:49:48 +01:00
}
2026-01-23 19:15:48 +01:00
function updateDashboard() {
let vol = store.trades.reduce((acc, r) => acc + (parseFloat(r[4]) * parseFloat(r[5] || 0)), 0);
document.getElementById('statVolume').innerText = vol >= 1e6 ? `€${(vol / 1e6).toFixed(2)}M` : `€${(vol / 1e3).toFixed(1)}k`;
2026-01-23 18:17:02 +01:00
document.getElementById('statTrades').innerText = store.trades.length.toLocaleString();
document.getElementById('statIsins').innerText = store.metadata.length.toLocaleString();
2026-01-23 19:15:48 +01:00
renderDashboardCharts();
2026-01-23 18:17:02 +01:00
}
2026-01-23 18:09:00 +01:00
2026-01-23 19:15:48 +01:00
function setChartType(type) { currentChartType = type; renderAnalyticsReport(); }
async function renderAnalyticsReport() {
const dates = getDates();
const x = document.getElementById('axisX').value;
const sub = document.getElementById('axisSub').value;
const y = document.getElementById('axisY').value;
const isins = document.getElementById('isinFilter').value;
let url = `${API}/analytics?metric=${y}&group_by=${x}`;
if (sub) url += `&sub_group_by=${sub}`;
if (dates.from) url += `&date_from=${dates.from}`;
if (dates.to) url += `&date_to=${dates.to}`;
if (isins) url += `&isins=${isins}`;
try {
const res = await fetch(url).then(r => r.json());
const data = res.dataset || [];
const ctx = document.getElementById('analyticsChart').getContext('2d');
// Grouping logic for stacked/multi-series charts
let labels = [...new Set(data.map(r => r[0]))];
let datasets = [];
if (sub) {
let series = [...new Set(data.map(r => r[1]))];
series.forEach((s, idx) => {
datasets.push({
label: s,
data: labels.map(l => {
let match = data.find(r => r[0] === l & & r[1] === s);
return match ? match[2] : 0;
}),
backgroundColor: `hsla(${idx * 40 + 200}, 70%, 50%, 0.8)`,
borderColor: `hsla(${idx * 40 + 200}, 70%, 50%, 1)`,
borderWidth: 2,
fill: currentChartType === 'line'
});
});
} else {
datasets.push({
label: y,
data: data.map(r => r[1]),
backgroundColor: '#38bdf888',
borderColor: '#38bdf8',
borderWidth: 2,
fill: currentChartType === 'line'
});
}
if (charts.analytics) charts.analytics.destroy();
charts.analytics = new Chart(ctx, {
type: currentChartType,
data: { labels: labels.map(l => x === 'day' ? new Date(l).toLocaleDateString() : l), datasets },
options: {
responsive: true, maintainAspectRatio: false,
scales: { y: { stacked: true, grid: { color: 'rgba(255,255,255,0.05)' } }, x: { stacked: true, grid: { display: false } } },
plugins: { legend: { position: 'bottom', labels: { color: '#94a3b8' } } }
}
});
} catch (err) { console.error(err); }
2026-01-23 18:17:02 +01:00
}
2026-01-23 18:09:00 +01:00
2026-01-23 18:17:02 +01:00
function renderDashboardCharts() {
const trendCtx = document.getElementById('priceChart').getContext('2d');
2026-01-23 19:15:48 +01:00
const samples = [...store.trades].sort((a, b) => new Date(a[3]) - new Date(b[3])).slice(-50);
2026-01-23 18:17:02 +01:00
if (charts.price) charts.price.destroy();
charts.price = new Chart(trendCtx, {
2026-01-23 18:09:00 +01:00
type: 'line',
2026-01-23 19:15:48 +01:00
data: { labels: samples.map(r => new Date(r[3]).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })), datasets: [{ data: samples.map(r => r[4]), borderColor: '#38bdf8', tension: 0.4, pointRadius: 0 }] },
options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false } } }
2026-01-23 18:09:00 +01:00
});
2026-01-23 18:17:02 +01:00
const contCtx = document.getElementById('continentChart').getContext('2d');
if (charts.continent) charts.continent.destroy();
charts.continent = new Chart(contCtx, {
type: 'doughnut',
2026-01-23 19:15:48 +01:00
data: { labels: store.summary.map(r => r[0]), datasets: [{ data: store.summary.map(r => r[2]), backgroundColor: ['#38bdf8', '#fbbf24', '#10b981', '#f87171'], borderWidth: 0 }] },
options: { responsive: true, maintainAspectRatio: false, cutout: '70%', plugins: { legend: { position: 'bottom', labels: { color: '#94a3b8' } } } }
2026-01-23 17:49:48 +01:00
});
}
2026-01-23 18:17:02 +01:00
function fillMetadataTable() {
const tbody = document.getElementById('metadataRows');
2026-01-23 19:15:48 +01:00
tbody.innerHTML = store.metadata.map(r => `
< tr class = "hover:bg-white/5 transition border-b border-white/5" >
2026-01-23 18:17:02 +01:00
< td class = "p-4 px-6 font-mono text-sky-400 font-bold" > ${r[0]}< / td >
2026-01-23 19:15:48 +01:00
< td class = "p-4 font-semibold" > ${r[1]}< / td >
< td class = "p-4 text-slate-500" > ${r[2]}< / td >
< td class = "p-4 text-slate-400 text-xs italic" > ${r[4]}< / td >
< / tr >
`).join('');
2026-01-23 17:49:48 +01:00
}
2026-01-23 19:15:48 +01:00
window.onload = () => { fetchData(); setInterval(fetchData, 30000); };
2026-01-23 17:49:48 +01:00
< / script >
< / body >
2026-01-23 18:09:00 +01:00
< / html >