Files
trading-daemon/dashboard/public/index.html
Melchior Reimers 3c9d277a4c
All checks were successful
Deployment / deploy-docker (push) Successful in 16s
updated
2026-01-25 18:24:36 +01:00

739 lines
37 KiB
HTML

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Trading Intelligence Hub</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="/static/analytics-config.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: #0b1120; color: #e2e8f0; }
.glass { background: rgba(30, 41, 59, 0.4); backdrop-filter: blur(12px); border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 16px; }
.active-nav { background: rgba(56, 189, 248, 0.1); color: #38bdf8; border-right: 3px solid #38bdf8; }
.input-glass { width: 100%; background: rgba(255, 255, 255, 0.03); border: 1px solid rgba(255, 255, 255, 0.1); padding: 10px; border-radius: 10px; outline: none; transition: all 0.2s; color: #e2e8f0; }
.input-glass:focus { border-color: #38bdf8; background: rgba(56, 189, 248, 0.05); }
.spinner { width: 40px; height: 40px; border: 3px solid rgba(56, 189, 248, 0.1); border-top: 3px solid #38bdf8; border-radius: 50%; animation: spin 1s linear infinite; }
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
</style>
</head>
<body class="flex min-h-screen overflow-hidden">
<aside class="w-20 lg:w-64 glass m-4 mr-0 flex flex-col hidden sm:flex">
<div class="p-6 hidden lg:block">
<h1 class="text-2xl font-bold bg-gradient-to-r from-sky-400 to-blue-500 bg-clip-text text-transparent">Trading Hub</h1>
</div>
<nav class="flex-1 px-4 space-y-2 mt-4" id="sidebar">
<a href="#" onclick="showView('dashboard')" id="nav-dashboard" class="flex items-center p-3 rounded-xl transition active-nav">
<span class="mr-3">📊</span> <span class="hidden lg:inline">Dashboard</span>
</a>
<a href="#" onclick="showView('custom-analytics')" id="nav-custom-analytics" class="flex items-center p-3 rounded-xl transition">
<span class="mr-3">📈</span> <span class="hidden lg:inline">Custom Analytics</span>
</a>
</nav>
</aside>
<main class="flex-1 flex flex-col min-w-0">
<header class="p-8 pb-4 flex justify-between items-center flex-shrink-0">
<div>
<h2 class="text-3xl font-bold" id="pageTitle">Market Overview</h2>
</div>
<button onclick="fetchData()" class="glass p-2 px-6 text-sky-400 font-bold hover:text-sky-200 transition">↻ REFRESH</button>
</header>
<div id="view-dashboard" class="view flex-1 p-8 pt-4 overflow-y-auto">
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-10">
<div class="glass p-8 border-l-4 border-sky-400">
<p class="text-slate-500 text-xs font-bold uppercase tracking-wider mb-2" id="statVolumeLabel">Volume</p>
<h3 id="statVolume" class="text-4xl font-bold">€0.0</h3>
</div>
<div class="glass p-8 border-l-4 border-amber-400">
<p class="text-slate-500 text-xs font-bold uppercase tracking-wider mb-2" id="statTradesLabel">Total Trades</p>
<h3 id="statTrades" class="text-4xl font-bold">0</h3>
</div>
<div class="glass p-8 border-l-4 border-emerald-400">
<p class="text-slate-500 text-xs font-bold uppercase tracking-wider mb-2">Assets</p>
<h3 id="statIsins" class="text-4xl font-bold">0</h3>
</div>
</div>
<div class="mt-10 mb-6">
<div class="flex items-center justify-between mb-6">
<h2 class="text-2xl font-bold text-slate-200">Erweiterte Statistiken</h2>
<select id="statisticsPeriod" class="input-glass px-4 py-2" onchange="loadStatistics()">
<option value="7">7 Tage</option>
<option value="30">30 Tage</option>
<option value="42">42 Tage</option>
<option value="69">69 Tage</option>
<option value="180">180 Tage</option>
<option value="365">365 Tage</option>
</select>
</div>
<div class="glass p-8 mb-8">
<h3 class="text-lg font-bold mb-6 text-slate-300">Moving Average: Tradezahlen & Volumen je Exchange</h3>
<div class="h-96"><canvas id="movingAverageChart"></canvas></div>
</div>
<div class="glass p-8 mb-8">
<h3 class="text-lg font-bold mb-6 text-slate-300">Tradingvolumen & Anzahl Änderungen</h3>
<div class="h-96"><canvas id="volumeChangesChart"></canvas></div>
</div>
<div class="glass p-8">
<h3 class="text-lg font-bold mb-6 text-slate-300">Trendanalyse: Häufig gehandelte Aktien</h3>
<div class="h-96"><canvas id="stockTrendsChart"></canvas></div>
<div id="stockTrendsTable" class="mt-6 overflow-x-auto"></div>
</div>
</div>
</div>
<!-- CUSTOM ANALYTICS VIEW -->
<div id="view-custom-analytics" class="view hidden flex-1 p-8 pt-4 overflow-y-auto">
<div class="glass p-8 mb-6">
<h2 class="text-2xl font-bold text-slate-200 mb-6">Custom Analytics Graph</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
<div>
<label class="block text-sm font-bold text-slate-400 mb-2">X-Achse (Zeitraum)</label>
<div class="grid grid-cols-2 gap-2">
<input type="date" id="customDateFrom" class="input-glass" onchange="updateCustomGraph(); updateUrlParams()">
<input type="date" id="customDateTo" class="input-glass" onchange="updateCustomGraph(); updateUrlParams()">
</div>
</div>
<div>
<label class="block text-sm font-bold text-slate-400 mb-2">Y-Achse (Metrik)</label>
<select id="customYAxis" class="input-glass" onchange="updateCustomGraph(); updateUrlParams()">
<option value="volume">Volumen</option>
<option value="trade_count">Tradezahlen</option>
<option value="avg_price">Durchschnittspreis</option>
</select>
<p class="text-xs text-slate-500 mt-1" id="yAxisDescription"></p>
</div>
<div>
<label class="block text-sm font-bold text-slate-400 mb-2">Gruppierung</label>
<select id="customGroupBy" class="input-glass" onchange="updateCustomGraph(); updateUrlParams()">
<option value="exchange">Exchange</option>
<option value="isin">ISIN</option>
<option value="date">Datum</option>
</select>
<p class="text-xs text-slate-500 mt-1" id="groupByDescription"></p>
</div>
<div>
<label class="block text-sm font-bold text-slate-400 mb-2">Exchanges (optional, komma-separiert)</label>
<input type="text" id="customExchanges" class="input-glass" placeholder="z.B. EIX,LS" onchange="updateCustomGraph(); updateUrlParams()">
</div>
</div>
<button onclick="updateCustomGraph()" class="btn-primary px-6 py-2">Graph aktualisieren</button>
<button onclick="shareCustomGraph()" class="glass px-6 py-2 ml-2 text-sky-400 hover:text-sky-200">🔗 Link teilen</button>
</div>
<div class="glass p-8">
<h3 class="text-lg font-bold mb-6 text-slate-300" id="customGraphTitle">Custom Analytics Graph</h3>
<div class="h-96"><canvas id="customAnalyticsChart"></canvas></div>
</div>
</div>
</main>
<script>
const API = '/api';
let store = { trades: [], metadata: [], summary: [], totalTrades: 0 };
let charts = {};
async function fetchData() {
try {
const days = parseInt(document.getElementById('statisticsPeriod')?.value || '7');
// Lade alle Trades (aggregiert) und Total Trades aus vorberechneten Daten
const [t, m, s, totalTrades] = await Promise.all([
fetch(`${API}/trades?days=${days}`).then(r => r.json()),
fetch(`${API}/metadata`).then(r => r.json()),
fetch(`${API}/summary?days=${days}`).then(r => r.json()),
fetch(`${API}/statistics/total-trades?days=${days}`).then(r => r.json())
]);
store = { ...store, trades: t.dataset || [], metadata: m.dataset || [], summary: s.dataset || [] };
store.totalTrades = totalTrades.total_trades || 0;
updateDashboard(days);
} catch (err) {
console.error('Error fetching data:', err);
}
}
function updateDashboard(days = 7) {
// Volumen aus dem gewählten Zeitraum (aggregiert)
let vol = 0;
if (store.trades && store.trades.length > 0) {
// Trades sind jetzt aggregiert: [date, exchange, trade_count, volume]
vol = store.trades.reduce((acc, r) => acc + (parseFloat(r[3] || 0)), 0);
}
document.getElementById('statVolume').innerText = vol >= 1e6 ? `${(vol / 1e6).toFixed(1)}M` : `${(vol / 1e3).toFixed(0)}k`;
document.getElementById('statVolumeLabel').innerText = `Volume (${days}d)`;
// Total Trades aus vorberechneten Daten für den gewählten Zeitraum
document.getElementById('statTrades').innerText = (store.totalTrades || 0).toLocaleString();
document.getElementById('statTradesLabel').innerText = `Total Trades (${days}d)`;
document.getElementById('statIsins').innerText = store.metadata.length.toLocaleString();
// Lade Statistiken
loadStatistics();
}
function showView(viewId) {
window.activeView = 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');
const titles = { 'dashboard': 'Market Overview', 'custom-analytics': 'Custom Analytics' };
document.getElementById('pageTitle').innerText = titles[viewId] || 'Dashboard';
updateUrlParams();
}
async function loadStatistics() {
const days = parseInt(document.getElementById('statisticsPeriod')?.value || '7');
// Aktualisiere auch die Statistik-Karten
await fetchData();
// Lade dann die Graphen
await Promise.all([
loadMovingAverage(days),
loadVolumeChanges(days),
loadStockTrends(days)
]);
}
async function loadMovingAverage(days) {
try {
const res = await fetch(`${API}/statistics/moving-average?days=${days}`).then(r => r.json());
const data = res.dataset || [];
const columns = res.columns || [];
if (!data.length) {
console.log('No moving average data available');
return;
}
const ctx = document.getElementById('movingAverageChart').getContext('2d');
if (charts.movingAverage) charts.movingAverage.destroy();
const dateIdx = columns.findIndex(c => c.name === 'date' || c.name === 'timestamp');
const exchangeIdx = columns.findIndex(c => c.name === 'exchange');
const countIdx = columns.findIndex(c => c.name === 'trade_count');
const volumeIdx = columns.findIndex(c => c.name === 'volume');
const maCountIdx = columns.findIndex(c => c.name === 'ma_count');
const maVolumeIdx = columns.findIndex(c => c.name === 'ma_volume');
const exchanges = [...new Set(data.map(r => r[exchangeIdx]))];
const dates = [...new Set(data.map(r => r[dateIdx]))].sort();
const datasets = [];
const colors = ['#38bdf8', '#f43f5e', '#10b981', '#fbbf24', '#8b5cf6'];
exchanges.forEach((exchange, idx) => {
datasets.push({
label: `${exchange} - Trade Count`,
data: dates.map(d => {
const row = data.find(r => r[dateIdx] === d && r[exchangeIdx] === exchange);
return row ? (row[countIdx] || 0) : 0;
}),
borderColor: colors[idx % colors.length],
backgroundColor: colors[idx % colors.length] + '33',
borderWidth: 2,
yAxisID: 'y',
tension: 0.3
});
datasets.push({
label: `${exchange} - MA Count`,
data: dates.map(d => {
const row = data.find(r => r[dateIdx] === d && r[exchangeIdx] === exchange);
return row ? (row[maCountIdx] || 0) : 0;
}),
borderColor: colors[idx % colors.length],
backgroundColor: 'transparent',
borderWidth: 2,
borderDash: [5, 5],
yAxisID: 'y',
tension: 0.3
});
datasets.push({
label: `${exchange} - Volume`,
data: dates.map(d => {
const row = data.find(r => r[dateIdx] === d && r[exchangeIdx] === exchange);
return row ? (row[volumeIdx] || 0) : 0;
}),
borderColor: colors[(idx + 2) % colors.length],
backgroundColor: colors[(idx + 2) % colors.length] + '33',
borderWidth: 2,
yAxisID: 'y1',
tension: 0.3
});
});
charts.movingAverage = new Chart(ctx, {
type: 'line',
data: {
labels: dates.map(d => new Date(d).toLocaleDateString()),
datasets: datasets
},
options: {
responsive: true,
maintainAspectRatio: false,
interaction: { mode: 'index', intersect: false },
scales: {
y: {
type: 'linear',
display: true,
position: 'left',
title: { display: true, text: 'Trade Count', color: '#94a3b8' },
grid: { color: 'rgba(255,255,255,0.05)' },
ticks: { color: '#64748b' }
},
y1: {
type: 'linear',
display: true,
position: 'right',
title: { display: true, text: 'Volume (€)', color: '#94a3b8' },
grid: { drawOnChartArea: false },
ticks: { color: '#64748b' }
},
x: {
grid: { display: false },
ticks: { color: '#64748b' }
}
},
plugins: {
legend: {
display: true,
position: 'bottom',
labels: { color: '#94a3b8', boxWidth: 12, usePointStyle: true, padding: 15 }
}
}
}
});
} catch (err) {
console.error('Error loading moving average:', err);
}
}
async function loadVolumeChanges(days) {
try {
const res = await fetch(`${API}/statistics/volume-changes?days=${days}`).then(r => r.json());
const data = res.dataset || [];
const columns = res.columns || [];
if (!data.length) {
console.log('No volume changes data available');
return;
}
const ctx = document.getElementById('volumeChangesChart').getContext('2d');
if (charts.volumeChanges) charts.volumeChanges.destroy();
const exchangeIdx = columns.findIndex(c => c.name === 'exchange');
const countChangeIdx = columns.findIndex(c => c.name === 'count_change_pct');
const volumeChangeIdx = columns.findIndex(c => c.name === 'volume_change_pct');
const trendIdx = columns.findIndex(c => c.name === 'trend');
const exchanges = data.map(r => r[exchangeIdx]);
const countChanges = data.map(r => r[countChangeIdx] || 0);
const volumeChanges = data.map(r => r[volumeChangeIdx] || 0);
charts.volumeChanges = new Chart(ctx, {
type: 'bar',
data: {
labels: exchanges,
datasets: [
{
label: 'Anzahl Änderung (%)',
data: countChanges,
backgroundColor: '#38bdf866',
borderColor: '#38bdf8',
borderWidth: 2,
yAxisID: 'y'
},
{
label: 'Volumen Änderung (%)',
data: volumeChanges,
backgroundColor: '#fbbf2466',
borderColor: '#fbbf24',
borderWidth: 2,
yAxisID: 'y'
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
type: 'linear',
display: true,
title: { display: true, text: 'Änderung (%)', color: '#94a3b8' },
grid: { color: 'rgba(255,255,255,0.05)' },
ticks: { color: '#64748b' }
},
x: {
grid: { display: false },
ticks: { color: '#64748b' }
}
},
plugins: {
legend: {
display: true,
position: 'top',
labels: { color: '#94a3b8', boxWidth: 12, usePointStyle: true, padding: 15 }
},
tooltip: {
callbacks: {
afterLabel: (context) => {
const idx = context.dataIndex;
const trend = data[idx][trendIdx];
return `Trend: ${trend || 'N/A'}`;
}
}
}
}
}
});
} catch (err) {
console.error('Error loading volume changes:', err);
}
}
async function loadStockTrends(days) {
try {
const res = await fetch(`${API}/statistics/stock-trends?days=${days}&limit=20`).then(r => r.json());
const data = res.dataset || [];
const columns = res.columns || [];
if (!data.length) {
console.log('No stock trends data available');
return;
}
const ctx = document.getElementById('stockTrendsChart').getContext('2d');
const tableContainer = document.getElementById('stockTrendsTable');
if (charts.stockTrends) charts.stockTrends.destroy();
const isinIdx = columns.findIndex(c => c.name === 'isin');
const volumeIdx = columns.findIndex(c => c.name === 'volume');
const countIdx = columns.findIndex(c => c.name === 'trade_count');
const countChangeIdx = columns.findIndex(c => c.name === 'count_change_pct');
const volumeChangeIdx = columns.findIndex(c => c.name === 'volume_change_pct');
const sorted = [...data].sort((a, b) => (b[volumeIdx] || 0) - (a[volumeIdx] || 0)).slice(0, 10);
const isins = sorted.map(r => r[isinIdx]);
const volumes = sorted.map(r => r[volumeIdx] || 0);
charts.stockTrends = new Chart(ctx, {
type: 'bar',
data: {
labels: isins.map(i => i ? i.substring(0, 12) + '...' : 'N/A'),
datasets: [{
label: 'Volumen (€)',
data: volumes,
backgroundColor: '#10b98166',
borderColor: '#10b981',
borderWidth: 2,
yAxisID: 'y'
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
type: 'linear',
display: true,
title: { display: true, text: 'Volumen (€)', color: '#94a3b8' },
grid: { color: 'rgba(255,255,255,0.05)' },
ticks: { color: '#64748b' }
},
x: {
grid: { display: false },
ticks: { color: '#64748b', maxRotation: 45, minRotation: 45 }
}
},
plugins: {
legend: {
display: true,
position: 'top',
labels: { color: '#94a3b8', boxWidth: 12, usePointStyle: true, padding: 15 }
}
}
}
});
tableContainer.innerHTML = `
<table class="w-full text-left text-sm">
<thead>
<tr class="border-b border-white/10">
<th class="p-3 text-slate-400 font-bold">ISIN</th>
<th class="p-3 text-slate-400 font-bold">Trades</th>
<th class="p-3 text-slate-400 font-bold">Volumen (€)</th>
<th class="p-3 text-slate-400 font-bold">Anzahl Δ (%)</th>
<th class="p-3 text-slate-400 font-bold">Volumen Δ (%)</th>
</tr>
</thead>
<tbody>
${sorted.map(r => `
<tr class="border-b border-white/5 hover:bg-white/5">
<td class="p-3 font-mono text-sky-400">${r[isinIdx] || 'N/A'}</td>
<td class="p-3 text-slate-300">${(r[countIdx] || 0).toLocaleString()}</td>
<td class="p-3 text-slate-300">€${((r[volumeIdx] || 0) / 1e6).toFixed(2)}M</td>
<td class="p-3 ${(r[countChangeIdx] || 0) >= 0 ? 'text-green-400' : 'text-red-400'}">${((r[countChangeIdx] || 0)).toFixed(2)}%</td>
<td class="p-3 ${(r[volumeChangeIdx] || 0) >= 0 ? 'text-green-400' : 'text-red-400'}">${((r[volumeChangeIdx] || 0)).toFixed(2)}%</td>
</tr>
`).join('')}
</tbody>
</table>
`;
} catch (err) {
console.error('Error loading stock trends:', err);
}
}
// Custom Analytics Funktionen
async function updateCustomGraph() {
const dateFrom = document.getElementById('customDateFrom').value;
const dateTo = document.getElementById('customDateTo').value;
const yAxis = document.getElementById('customYAxis').value;
const groupBy = document.getElementById('customGroupBy').value;
const exchanges = document.getElementById('customExchanges').value;
if (!dateFrom || !dateTo) {
console.log('Bitte wählen Sie Start- und Enddatum');
return;
}
try {
let url = `${API}/custom-analytics?date_from=${dateFrom}&date_to=${dateTo}&x_axis=date&y_axis=${yAxis}&group_by=${groupBy}`;
if (exchanges) {
url += `&exchanges=${encodeURIComponent(exchanges)}`;
}
const res = await fetch(url).then(r => r.json());
const data = res.dataset || [];
const columns = res.columns || [];
if (!data.length) {
console.log('Keine Daten für den gewählten Zeitraum');
return;
}
const ctx = document.getElementById('customAnalyticsChart').getContext('2d');
if (charts.customAnalytics) charts.customAnalytics.destroy();
const xIdx = columns.findIndex(c => c.name === 'x_value');
const groupIdx = columns.findIndex(c => c.name === 'group_value');
const yIdx = columns.findIndex(c => c.name === 'y_value');
const groups = [...new Set(data.map(r => r[groupIdx]))];
const dates = [...new Set(data.map(r => r[xIdx]))].sort();
const colors = ['#38bdf8', '#f43f5e', '#10b981', '#fbbf24', '#8b5cf6', '#f97316', '#ec4899'];
const datasets = groups.map((group, idx) => ({
label: group || 'Unknown',
data: dates.map(d => {
const row = data.find(r => r[xIdx] === d && r[groupIdx] === group);
return row ? (row[yIdx] || 0) : 0;
}),
borderColor: colors[idx % colors.length],
backgroundColor: colors[idx % colors.length] + '33',
borderWidth: 2,
tension: 0.3,
fill: false
}));
// Nutze Config für Y-Achsen-Label (falls verfügbar)
let yAxisLabel = 'Wert';
if (typeof ANALYTICS_CONFIG !== 'undefined' && ANALYTICS_CONFIG.yAxis && ANALYTICS_CONFIG.yAxis[yAxis]) {
const config = ANALYTICS_CONFIG.yAxis[yAxis];
yAxisLabel = `${config.label}${config.unit ? ' (' + config.unit + ')' : ''}`;
} else {
// Fallback
const labels = {
'volume': 'Volumen (€)',
'trade_count': 'Anzahl Trades',
'avg_price': 'Durchschnittspreis (€)'
};
yAxisLabel = labels[yAxis] || 'Wert';
}
charts.customAnalytics = new Chart(ctx, {
type: 'line',
data: {
labels: dates.map(d => new Date(d).toLocaleDateString()),
datasets: datasets
},
options: {
responsive: true,
maintainAspectRatio: false,
interaction: { mode: 'index', intersect: false },
scales: {
y: {
type: 'linear',
display: true,
title: { display: true, text: yAxisLabel, color: '#94a3b8' },
grid: { color: 'rgba(255,255,255,0.05)' },
ticks: { color: '#64748b' }
},
x: {
title: { display: true, text: 'Datum', color: '#94a3b8' },
grid: { display: false },
ticks: { color: '#64748b' }
}
},
plugins: {
legend: {
display: true,
position: 'top',
labels: { color: '#94a3b8', boxWidth: 12, usePointStyle: true, padding: 15 }
},
tooltip: {
backgroundColor: '#1e293b',
titleColor: '#38bdf8',
bodyColor: '#e2e8f0',
borderColor: 'rgba(255,255,255,0.1)',
borderWidth: 1
}
}
}
});
document.getElementById('customGraphTitle').innerText =
`${yAxisLabel} nach ${groupBy === 'exchange' ? 'Exchange' : groupBy === 'isin' ? 'ISIN' : 'Datum'} (${dateFrom} bis ${dateTo})`;
} catch (err) {
console.error('Error loading custom analytics:', err);
}
}
function shareCustomGraph() {
const params = new URLSearchParams();
params.set('view', 'custom-analytics');
params.set('date_from', document.getElementById('customDateFrom').value);
params.set('date_to', document.getElementById('customDateTo').value);
params.set('y_axis', document.getElementById('customYAxis').value);
params.set('group_by', document.getElementById('customGroupBy').value);
const exchanges = document.getElementById('customExchanges').value;
if (exchanges) params.set('exchanges', exchanges);
const url = window.location.origin + window.location.pathname + '?' + params.toString();
navigator.clipboard.writeText(url).then(() => {
alert('Link in Zwischenablage kopiert!');
}).catch(() => {
prompt('Link zum Teilen:', url);
});
}
function updateUrlParams() {
const params = new URLSearchParams(window.location.search);
const view = window.activeView;
if (view) params.set('view', view);
if (view === 'custom-analytics') {
params.set('date_from', document.getElementById('customDateFrom').value || '');
params.set('date_to', document.getElementById('customDateTo').value || '');
params.set('y_axis', document.getElementById('customYAxis').value || '');
params.set('group_by', document.getElementById('customGroupBy').value || '');
const exchanges = document.getElementById('customExchanges').value;
if (exchanges) params.set('exchanges', exchanges);
else params.delete('exchanges');
} else if (view === 'dashboard') {
const period = document.getElementById('statisticsPeriod')?.value;
if (period) params.set('period', period);
}
window.history.replaceState({}, '', window.location.pathname + '?' + params.toString());
}
function loadFromUrlParams() {
const params = new URLSearchParams(window.location.search);
const view = params.get('view');
if (view) {
showView(view);
if (view === 'custom-analytics') {
const dateFrom = params.get('date_from');
const dateTo = params.get('date_to');
const yAxis = params.get('y_axis');
const groupBy = params.get('group_by');
const exchanges = params.get('exchanges');
if (dateFrom) document.getElementById('customDateFrom').value = dateFrom;
if (dateTo) document.getElementById('customDateTo').value = dateTo;
if (yAxis) document.getElementById('customYAxis').value = yAxis;
if (groupBy) document.getElementById('customGroupBy').value = groupBy;
if (exchanges) document.getElementById('customExchanges').value = exchanges;
if (dateFrom && dateTo) {
setTimeout(() => updateCustomGraph(), 500);
}
} else if (view === 'dashboard') {
const period = params.get('period');
if (period) {
document.getElementById('statisticsPeriod').value = period;
setTimeout(() => loadStatistics(), 500);
}
}
}
}
// Initialisiere Custom Analytics Dropdowns aus Config (falls verfügbar)
function initCustomAnalyticsDropdowns() {
if (typeof ANALYTICS_CONFIG !== 'undefined') {
// Y-Achse Optionen
const yAxisSelect = document.getElementById('customYAxis');
if (yAxisSelect && ANALYTICS_CONFIG.yAxis) {
yAxisSelect.innerHTML = '';
Object.entries(ANALYTICS_CONFIG.yAxis).forEach(([key, config]) => {
const option = document.createElement('option');
option.value = key;
option.textContent = config.label;
yAxisSelect.appendChild(option);
});
}
// GroupBy Optionen
const groupBySelect = document.getElementById('customGroupBy');
if (groupBySelect && ANALYTICS_CONFIG.groupBy) {
groupBySelect.innerHTML = '';
Object.entries(ANALYTICS_CONFIG.groupBy).forEach(([key, config]) => {
const option = document.createElement('option');
option.value = key;
option.textContent = config.label;
groupBySelect.appendChild(option);
});
}
}
}
window.onload = async () => {
// Initialisiere Dropdowns aus Config
initCustomAnalyticsDropdowns();
// Setze Standard-Datum (letzte 30 Tage)
const today = new Date();
const thirtyDaysAgo = new Date(today);
thirtyDaysAgo.setDate(today.getDate() - 30);
if (!document.getElementById('customDateTo').value) {
document.getElementById('customDateTo').value = today.toISOString().split('T')[0];
}
if (!document.getElementById('customDateFrom').value) {
document.getElementById('customDateFrom').value = thirtyDaysAgo.toISOString().split('T')[0];
}
await fetchData();
loadFromUrlParams();
setInterval(fetchData, 30000);
setTimeout(() => loadStatistics(), 1000);
};
</script>
</body>
</html>