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
|
|
|
|
.glass-btn {
|
|
|
|
|
|
background: rgba(56, 189, 248, 0.1);
|
|
|
|
|
|
border: 1px solid rgba(56, 189, 248, 0.2);
|
|
|
|
|
|
transition: all 0.3s;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.glass-btn:hover {
|
|
|
|
|
|
background: rgba(56, 189, 248, 0.2);
|
|
|
|
|
|
border-color: #38bdf8;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-23 17:49:48 +01:00
|
|
|
|
.glow {
|
2026-01-23 18:17:02 +01:00
|
|
|
|
box-shadow: 0 0 20px rgba(56, 189, 248, 0.1);
|
2026-01-23 17:49:48 +01:00
|
|
|
|
}
|
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 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
|
|
|
|
|
|
|
|
|
|
<!-- Sidebar -->
|
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"
|
|
|
|
|
|
class="flex items-center p-3 rounded-xl transition active-nav">
|
2026-01-23 17:49:48 +01:00
|
|
|
|
<span class="mr-3">📊</span> Dashboard
|
|
|
|
|
|
</a>
|
2026-01-23 18:17:02 +01:00
|
|
|
|
<a href="#" onclick="showView('analytics')" id="nav-analytics"
|
|
|
|
|
|
class="flex items-center p-3 rounded-xl transition">
|
|
|
|
|
|
<span class="mr-3">📈</span> Analytics Hub
|
2026-01-23 17:49:48 +01:00
|
|
|
|
</a>
|
2026-01-23 18:17:02 +01:00
|
|
|
|
<a href="#" onclick="showView('metadata')" id="nav-metadata"
|
|
|
|
|
|
class="flex items-center p-3 rounded-xl transition">
|
2026-01-23 17:49:48 +01:00
|
|
|
|
<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">
|
2026-01-23 18:17:02 +01:00
|
|
|
|
Data Stream: <span class="text-green-400 animate-pulse">● Live</span>
|
2026-01-23 17:49:48 +01:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</aside>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Main Content -->
|
|
|
|
|
|
<main class="flex-1 p-8 overflow-y-auto">
|
2026-01-23 18:17:02 +01:00
|
|
|
|
<header class="flex justify-between items-center mb-10">
|
|
|
|
|
|
<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 18:17:02 +01:00
|
|
|
|
<div class="flex items-center space-x-4">
|
|
|
|
|
|
<div id="activeIsins" class="flex -space-x-2">
|
|
|
|
|
|
<!-- Pinned ISINs will show up here -->
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<select id="timeRange" class="glass px-4 py-2 text-sm outline-none focus:ring-1 ring-sky-500"
|
|
|
|
|
|
onchange="fetchData()">
|
2026-01-23 17:49:48 +01:00
|
|
|
|
<option value="1">Last 24 Hours</option>
|
2026-01-23 18:17:02 +01:00
|
|
|
|
<option value="7" selected>Last 7 Days</option>
|
2026-01-23 17:49:48 +01:00
|
|
|
|
<option value="30">Last 30 Days</option>
|
2026-01-23 18:17:02 +01:00
|
|
|
|
<option value="90">Last Quarter</option>
|
2026-01-23 17:49:48 +01:00
|
|
|
|
</select>
|
2026-01-23 18:17:02 +01:00
|
|
|
|
<button onclick="fetchData()" class="glass p-2 px-4 text-sky-400 hover:text-sky-300">↻</button>
|
2026-01-23 17:49:48 +01:00
|
|
|
|
</div>
|
|
|
|
|
|
</header>
|
|
|
|
|
|
|
2026-01-23 18:17:02 +01:00
|
|
|
|
<!-- DASHBOARD VIEW -->
|
|
|
|
|
|
<div id="view-dashboard" class="view">
|
|
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-10">
|
|
|
|
|
|
<div class="glass p-6 glow border-l-4 border-sky-500">
|
|
|
|
|
|
<p class="text-slate-400 text-xs uppercase tracking-widest mb-2">Transaction Volume</p>
|
|
|
|
|
|
<h3 id="statVolume" class="text-4xl font-bold">€0.0k</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>
|
|
|
|
|
|
<h3 id="statTrades" class="text-4xl font-bold">0</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>
|
|
|
|
|
|
<h3 id="statIsins" class="text-4xl font-bold">0</h3>
|
2026-01-23 18:09:00 +01:00
|
|
|
|
</div>
|
2026-01-23 17:49:48 +01:00
|
|
|
|
</div>
|
2026-01-23 18:09:00 +01:00
|
|
|
|
|
2026-01-23 18:17:02 +01:00
|
|
|
|
<div class="grid grid-cols-1 xl:grid-cols-2 gap-8 mb-10">
|
|
|
|
|
|
<div class="glass p-8">
|
|
|
|
|
|
<h3 class="text-xl font-bold mb-6">Execution Price Trend</h3>
|
|
|
|
|
|
<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">
|
|
|
|
|
|
<h3 class="text-xl font-bold mb-6">Geographic Distribution</h3>
|
|
|
|
|
|
<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 18:17:02 +01:00
|
|
|
|
<!-- ANALYTICS VIEW -->
|
|
|
|
|
|
<div id="view-analytics" class="view hidden">
|
|
|
|
|
|
<div class="glass p-8 mb-8">
|
|
|
|
|
|
<div class="flex justify-between items-center mb-8">
|
|
|
|
|
|
<h3 class="text-xl font-bold">Custom Feature Comparison</h3>
|
|
|
|
|
|
<div class="flex space-x-4">
|
|
|
|
|
|
<select id="axisX" class="glass px-3 py-1 text-xs" onchange="renderCustomChart()">
|
|
|
|
|
|
<option value="time">Time</option>
|
|
|
|
|
|
<option value="exchange">Exchange</option>
|
|
|
|
|
|
<option value="continent">Continent</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
<select id="axisY" class="glass px-3 py-1 text-xs" onchange="renderCustomChart()">
|
|
|
|
|
|
<option value="volume">Trade Volume</option>
|
|
|
|
|
|
<option value="count">Trade Count</option>
|
|
|
|
|
|
<option value="avg_price">Avg. Price</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="h-96"><canvas id="customChart"></canvas></div>
|
2026-01-23 17:49:48 +01:00
|
|
|
|
</div>
|
2026-01-23 18:17:02 +01:00
|
|
|
|
|
2026-01-23 18:09:00 +01:00
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
|
|
|
|
|
<div class="glass p-6">
|
2026-01-23 18:17:02 +01:00
|
|
|
|
<h3 class="text-lg font-bold mb-6">Exchange Dominance</h3>
|
|
|
|
|
|
<div class="h-64"><canvas id="exchangeChart"></canvas></div>
|
2026-01-23 18:09:00 +01:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="glass p-6">
|
2026-01-23 18:17:02 +01:00
|
|
|
|
<h3 class="text-lg font-bold mb-6">Market Dynamics</h3>
|
|
|
|
|
|
<div id="dynamicStats" class="space-y-4">
|
|
|
|
|
|
<!-- Stats injected here -->
|
2026-01-23 18:09:00 +01:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-01-23 17:49:48 +01:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-01-23 18:17:02 +01:00
|
|
|
|
<!-- COMPANIES VIEW -->
|
|
|
|
|
|
<div id="view-metadata" class="view hidden">
|
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>
|
|
|
|
|
|
<input type="text" id="metadataSearch" onkeyup="filterMetadata()"
|
|
|
|
|
|
placeholder="Search ISIN, Name or Sector..."
|
|
|
|
|
|
class="glass px-4 py-2 text-sm w-80 outline-none focus:border-sky-500">
|
2026-01-23 18:09:00 +01:00
|
|
|
|
</div>
|
|
|
|
|
|
<table class="w-full text-left">
|
2026-01-23 18:17:02 +01:00
|
|
|
|
<thead class="bg-white/5 text-slate-400 text-xs uppercase tracking-tighter font-bold">
|
2026-01-23 18:09:00 +01:00
|
|
|
|
<tr>
|
|
|
|
|
|
<th class="p-4 px-6">ISIN</th>
|
2026-01-23 18:17:02 +01:00
|
|
|
|
<th class="p-4">Entity Legal Name</th>
|
|
|
|
|
|
<th class="p-4">Origin</th>
|
2026-01-23 18:09:00 +01:00
|
|
|
|
<th class="p-4">Continent</th>
|
2026-01-23 18:17:02 +01:00
|
|
|
|
<th class="p-4">Market Sector</th>
|
|
|
|
|
|
<th class="p-4 text-center">Actions</th>
|
2026-01-23 18:09:00 +01:00
|
|
|
|
</tr>
|
|
|
|
|
|
</thead>
|
2026-01-23 18:17:02 +01:00
|
|
|
|
<tbody id="metadataRows" class="divide-y divide-white/5 text-sm">
|
|
|
|
|
|
<!-- Filled by JS -->
|
2026-01-23 18:09:00 +01:00
|
|
|
|
</tbody>
|
|
|
|
|
|
</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';
|
|
|
|
|
|
let store = { trades: [], metadata: [], summary: [], comparison: [], pinnedIsins: [] };
|
|
|
|
|
|
let charts = {};
|
2026-01-23 18:09:00 +01:00
|
|
|
|
|
2026-01-23 17:49:48 +01:00
|
|
|
|
async function fetchData() {
|
|
|
|
|
|
try {
|
2026-01-23 18:09:00 +01:00
|
|
|
|
const days = document.getElementById('timeRange').value;
|
2026-01-23 18:17:02 +01:00
|
|
|
|
const [t, m, s, c] = await Promise.all([
|
|
|
|
|
|
fetch(`${API}/trades?days=${days}`).then(r => r.json()),
|
|
|
|
|
|
fetch(`${API}/metadata`).then(r => r.json()),
|
|
|
|
|
|
fetch(`${API}/summary`).then(r => r.json()),
|
|
|
|
|
|
fetch(`${API}/comparison?days=${days}`).then(r => r.json())
|
2026-01-23 17:49:48 +01:00
|
|
|
|
]);
|
2026-01-23 18:17:02 +01:00
|
|
|
|
store = { trades: t.dataset || [], metadata: m.dataset || [], summary: s.dataset || [], comparison: c.dataset || [], pinnedIsins: store.pinnedIsins };
|
|
|
|
|
|
updateUI();
|
|
|
|
|
|
} catch (err) { console.error("Fetch error:", err); }
|
2026-01-23 17:49:48 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-23 18:17:02 +01:00
|
|
|
|
function updateUI() {
|
|
|
|
|
|
updateStats();
|
|
|
|
|
|
renderDashboardCharts();
|
|
|
|
|
|
renderCustomChart();
|
|
|
|
|
|
renderExchangeChart();
|
|
|
|
|
|
fillMetadataTable();
|
2026-01-23 17:49:48 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-23 18:17:02 +01:00
|
|
|
|
function updateStats() {
|
|
|
|
|
|
document.getElementById('statTrades').innerText = store.trades.length.toLocaleString();
|
|
|
|
|
|
document.getElementById('statIsins').innerText = store.metadata.length.toLocaleString();
|
|
|
|
|
|
let vol = store.trades.reduce((acc, r) => acc + (parseFloat(r[4]) * parseFloat(r[5])), 0);
|
|
|
|
|
|
document.getElementById('statVolume').innerText = vol >= 1e6 ? `€${(vol / 1e6).toFixed(2)}M` : `€${(vol / 1e3).toFixed(1)}k`;
|
|
|
|
|
|
}
|
2026-01-23 18:09:00 +01:00
|
|
|
|
|
2026-01-23 18:17:02 +01:00
|
|
|
|
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');
|
|
|
|
|
|
window.activeView = viewId;
|
|
|
|
|
|
fetchData();
|
|
|
|
|
|
}
|
2026-01-23 18:09:00 +01:00
|
|
|
|
|
2026-01-23 18:17:02 +01:00
|
|
|
|
function renderDashboardCharts() {
|
|
|
|
|
|
// Price Trend
|
|
|
|
|
|
const trendCtx = document.getElementById('priceChart').getContext('2d');
|
|
|
|
|
|
const sorted = [...store.trades].sort((a, b) => new Date(a[3]) - new Date(b[3]));
|
|
|
|
|
|
const samples = sorted.slice(-100);
|
2026-01-23 18:09:00 +01:00
|
|
|
|
|
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',
|
|
|
|
|
|
data: {
|
2026-01-23 18:17:02 +01:00
|
|
|
|
labels: samples.map(r => {
|
|
|
|
|
|
const d = new Date(r[3]);
|
|
|
|
|
|
return isNaN(d) ? '?' : d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
|
|
|
|
}),
|
|
|
|
|
|
datasets: [{
|
|
|
|
|
|
label: 'Execution Price',
|
|
|
|
|
|
data: samples.map(r => parseFloat(r[4])),
|
|
|
|
|
|
borderColor: '#38bdf8',
|
|
|
|
|
|
borderWidth: 2,
|
|
|
|
|
|
fill: true,
|
|
|
|
|
|
backgroundColor: 'rgba(56, 189, 248, 0.05)',
|
|
|
|
|
|
tension: 0.4,
|
|
|
|
|
|
pointRadius: 0
|
|
|
|
|
|
}]
|
2026-01-23 18:09:00 +01:00
|
|
|
|
},
|
2026-01-23 18:17:02 +01:00
|
|
|
|
options: { responsive: true, maintainAspectRatio: false, scales: { x: { grid: { display: false } }, y: { grid: { color: 'rgba(255,255,255,0.05)' } } }, plugins: { legend: { display: false } } }
|
2026-01-23 18:09:00 +01:00
|
|
|
|
});
|
|
|
|
|
|
|
2026-01-23 18:17:02 +01:00
|
|
|
|
// Continent Pie
|
|
|
|
|
|
const contCtx = document.getElementById('continentChart').getContext('2d');
|
|
|
|
|
|
if (charts.continent) charts.continent.destroy();
|
|
|
|
|
|
charts.continent = new Chart(contCtx, {
|
|
|
|
|
|
type: 'doughnut',
|
2026-01-23 18:09:00 +01:00
|
|
|
|
data: {
|
2026-01-23 18:17:02 +01:00
|
|
|
|
labels: store.summary.map(r => r[0]),
|
|
|
|
|
|
datasets: [{
|
|
|
|
|
|
data: store.summary.map(r => r[2]),
|
|
|
|
|
|
backgroundColor: ['#38bdf8', '#fbbf24', '#10b981', '#f87171', '#818cf8', '#a78bfa'],
|
|
|
|
|
|
borderWidth: 0,
|
|
|
|
|
|
hoverOffset: 20
|
|
|
|
|
|
}]
|
2026-01-23 18:09:00 +01:00
|
|
|
|
},
|
2026-01-23 18:17:02 +01:00
|
|
|
|
options: { responsive: true, maintainAspectRatio: false, cutout: '70%', plugins: { legend: { position: 'right', labels: { color: '#94a3b8', font: { size: 12 } } } } }
|
2026-01-23 18:09:00 +01:00
|
|
|
|
});
|
|
|
|
|
|
}
|
2026-01-23 17:49:48 +01:00
|
|
|
|
|
2026-01-23 18:17:02 +01:00
|
|
|
|
function renderCustomChart() {
|
|
|
|
|
|
const ctx = document.getElementById('customChart').getContext('2d');
|
|
|
|
|
|
const x = document.getElementById('axisX').value;
|
|
|
|
|
|
const y = document.getElementById('axisY').value;
|
|
|
|
|
|
|
|
|
|
|
|
// Simple Dynamic Aggregation
|
|
|
|
|
|
let grouped = {};
|
|
|
|
|
|
store.comparison.forEach(r => {
|
|
|
|
|
|
const label = x === 'time' ? new Date(r[1]).toLocaleDateString() : r[0]; // Simplified
|
|
|
|
|
|
if (!grouped[label]) grouped[label] = { val: 0, count: 0, prices: 0 };
|
|
|
|
|
|
grouped[label].val += r[2]; // Volume
|
|
|
|
|
|
grouped[label].count += r[3]; // Count
|
|
|
|
|
|
grouped[label].prices += r[2] / (r[3] || 1); // Mock avg price
|
|
|
|
|
|
});
|
2026-01-23 18:09:00 +01:00
|
|
|
|
|
2026-01-23 18:17:02 +01:00
|
|
|
|
const labels = Object.keys(grouped);
|
|
|
|
|
|
let finalData = labels.map(l => {
|
|
|
|
|
|
if (y === 'volume') return grouped[l].val;
|
|
|
|
|
|
if (y === 'count') return grouped[l].count;
|
|
|
|
|
|
return grouped[l].prices / (grouped[l].count || 1);
|
|
|
|
|
|
});
|
2026-01-23 17:49:48 +01:00
|
|
|
|
|
2026-01-23 18:17:02 +01:00
|
|
|
|
if (charts.custom) charts.custom.destroy();
|
|
|
|
|
|
charts.custom = new Chart(ctx, {
|
|
|
|
|
|
type: x === 'time' ? 'line' : 'bar',
|
2026-01-23 17:49:48 +01:00
|
|
|
|
data: {
|
|
|
|
|
|
labels,
|
|
|
|
|
|
datasets: [{
|
2026-01-23 18:17:02 +01:00
|
|
|
|
label: `${y} by ${x}`,
|
|
|
|
|
|
data: finalData,
|
|
|
|
|
|
backgroundColor: '#38bdf8',
|
2026-01-23 17:49:48 +01:00
|
|
|
|
borderColor: '#38bdf8',
|
2026-01-23 18:17:02 +01:00
|
|
|
|
borderRadius: 8
|
2026-01-23 17:49:48 +01:00
|
|
|
|
}]
|
|
|
|
|
|
},
|
2026-01-23 18:17:02 +01:00
|
|
|
|
options: { responsive: true, maintainAspectRatio: false }
|
2026-01-23 17:49:48 +01:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-23 18:17:02 +01:00
|
|
|
|
function renderExchangeChart() {
|
|
|
|
|
|
const ctx = document.getElementById('exchangeChart').getContext('2d');
|
|
|
|
|
|
const dataMap = {};
|
|
|
|
|
|
store.trades.forEach(r => {
|
|
|
|
|
|
dataMap[r[0]] = (dataMap[r[0]] || 0) + 1;
|
|
|
|
|
|
});
|
2026-01-23 17:49:48 +01:00
|
|
|
|
|
2026-01-23 18:17:02 +01:00
|
|
|
|
if (charts.exchange) charts.exchange.destroy();
|
|
|
|
|
|
charts.exchange = new Chart(ctx, {
|
|
|
|
|
|
type: 'bar',
|
2026-01-23 17:49:48 +01:00
|
|
|
|
data: {
|
2026-01-23 18:17:02 +01:00
|
|
|
|
labels: Object.keys(dataMap),
|
2026-01-23 17:49:48 +01:00
|
|
|
|
datasets: [{
|
2026-01-23 18:17:02 +01:00
|
|
|
|
data: Object.values(dataMap),
|
|
|
|
|
|
backgroundColor: ['#38bdf8', '#fbbf24'],
|
|
|
|
|
|
borderRadius: 10
|
2026-01-23 17:49:48 +01:00
|
|
|
|
}]
|
|
|
|
|
|
},
|
2026-01-23 18:17:02 +01:00
|
|
|
|
options: { indexAxis: 'y', responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false } } }
|
2026-01-23 17:49:48 +01:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-23 18:17:02 +01:00
|
|
|
|
function fillMetadataTable() {
|
|
|
|
|
|
const tbody = document.getElementById('metadataRows');
|
|
|
|
|
|
tbody.innerHTML = '';
|
|
|
|
|
|
store.metadata.forEach(r => {
|
2026-01-23 17:49:48 +01:00
|
|
|
|
const tr = document.createElement('tr');
|
2026-01-23 18:17:02 +01:00
|
|
|
|
tr.className = 'hover:bg-white/5 transition cursor-default group';
|
2026-01-23 17:49:48 +01:00
|
|
|
|
tr.innerHTML = `
|
2026-01-23 18:17:02 +01:00
|
|
|
|
<td class="p-4 px-6 font-mono text-sky-400 font-bold">${r[0]}</td>
|
|
|
|
|
|
<td class="p-4 font-semibold text-slate-200">${r[1]}</td>
|
|
|
|
|
|
<td class="p-4"><span class="px-2 py-1 bg-sky-500/10 text-sky-400 rounded-lg text-xs font-bold ring-1 ring-sky-500/20">${r[2]}</span></td>
|
|
|
|
|
|
<td class="p-4 text-slate-400">${r[3]}</td>
|
|
|
|
|
|
<td class="p-4"><span class="text-slate-500 italic">${r[4]}</span></td>
|
|
|
|
|
|
<td class="p-4 text-center">
|
|
|
|
|
|
<button onclick="pinIsin('${r[0]}')" class="glass-btn p-2 rounded-lg text-lg opacity-0 group-hover:opacity-100">📌</button>
|
|
|
|
|
|
</td>
|
2026-01-23 17:49:48 +01:00
|
|
|
|
`;
|
2026-01-23 18:17:02 +01:00
|
|
|
|
tbody.appendChild(tr);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function pinIsin(isin) {
|
|
|
|
|
|
if (!store.pinnedIsins.includes(isin)) {
|
|
|
|
|
|
store.pinnedIsins.push(isin);
|
|
|
|
|
|
updatePinnedUI();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function updatePinnedUI() {
|
|
|
|
|
|
const container = document.getElementById('activeIsins');
|
|
|
|
|
|
container.innerHTML = '';
|
|
|
|
|
|
store.pinnedIsins.forEach(isin => {
|
|
|
|
|
|
const chip = document.createElement('div');
|
|
|
|
|
|
chip.className = 'glass p-1 px-3 text-xs text-sky-400 border-sky-500/50 flex items-center bg-sky-950/50 cursor-pointer hover:bg-sky-900 transition';
|
|
|
|
|
|
chip.innerHTML = `${isin} <span class="ml-2 text-slate-500" onclick="unpinIsin('${isin}')">×</span>`;
|
|
|
|
|
|
container.appendChild(chip);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function unpinIsin(isin) {
|
|
|
|
|
|
store.pinnedIsins = store.pinnedIsins.filter(i => i !== isin);
|
|
|
|
|
|
updatePinnedUI();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function filterMetadata() {
|
|
|
|
|
|
const q = document.getElementById('metadataSearch').value.toLowerCase();
|
|
|
|
|
|
const rows = document.querySelectorAll('#metadataRows tr');
|
|
|
|
|
|
rows.forEach(r => {
|
|
|
|
|
|
const text = r.innerText.toLowerCase();
|
|
|
|
|
|
r.style.display = text.includes(q) ? '' : 'none';
|
2026-01-23 17:49:48 +01:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-23 18:17:02 +01:00
|
|
|
|
window.onload = () => { window.activeView = 'dashboard'; fetchData(); setInterval(fetchData, 30000); };
|
2026-01-23 17:49:48 +01:00
|
|
|
|
</script>
|
|
|
|
|
|
</body>
|
2026-01-23 18:09:00 +01:00
|
|
|
|
|
|
|
|
|
|
</html>
|