Refine dashboard UI/UX with storyboard-aligned instrument drill-down

- Introduced card-based layout matching storyboard visuals
- Added tracked instrument input (symbol + type) with per-instrument cards
- Implemented intuitive drill-down: dashboard metrics switch to selected instrument
- Displayed P&L, drawdown, and win rate per instrument using static mock data
- Updated charts and recent performance table to reflect selected instrument
- Prepared for future integration with real Moomoo data layer
This commit is contained in:
2026-02-04 19:54:43 +08:00
parent dd599259de
commit a42ea8dddf

View File

@@ -6,179 +6,337 @@
<title>Quantitative Trading Platform - Dashboard</title>
<!-- Bootstrap 5 CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Bootstrap Icons -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
<!-- Chart.js -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
body {
background-color: #f8f9fa;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
:root {
--bg-dark: #060913;
--bg-elevated: #0c1220;
--bg-card: #111827;
--border-subtle: #1f2937;
--text-main: #e5e7eb;
--text-muted: #9ca3af;
--accent-green: #22c55e;
--accent-red: #ef4444;
--accent-blue: #3b82f6;
--accent-amber: #f59e0b;
}
body {
background-color: var(--bg-dark);
color: var(--text-main);
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}
.navbar {
background: linear-gradient(90deg, #020617, #0f172a);
border-bottom: 1px solid var(--border-subtle);
}
.navbar-brand {
font-weight: 600;
letter-spacing: 0.03em;
}
.metric-card {
border-radius: 10px;
border: none;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
transition: transform 0.2s;
.page-header {
border-bottom: 1px solid var(--border-subtle);
padding-bottom: 0.75rem;
margin-bottom: 1.5rem;
}
.metric-card:hover {
transform: translateY(-5px);
.metric-card, .instrument-card, .panel-card {
background: radial-gradient(circle at top left, rgba(59,130,246,0.18), transparent 55%),
var(--bg-card);
border-radius: 14px;
border: 1px solid var(--border-subtle);
box-shadow: 0 18px 35px rgba(0,0,0,0.55);
transition: transform 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease;
}
.metric-card:hover, .instrument-card:hover, .panel-card:hover {
transform: translateY(-3px);
box-shadow: 0 20px 40px rgba(0,0,0,0.7);
border-color: rgba(59,130,246,0.7);
}
.metric-value {
font-size: 2.5rem;
font-size: 2.4rem;
font-weight: 700;
}
.metric-positive {
color: #198754;
.metric-label {
text-transform: uppercase;
letter-spacing: 0.08em;
font-size: 0.75rem;
color: var(--text-muted);
}
.metric-negative {
color: #dc3545;
.metric-positive { color: var(--accent-green); }
.metric-negative { color: var(--accent-red); }
.metric-neutral { color: var(--text-muted); }
.instrument-input-card {
background: var(--bg-elevated);
border-radius: 14px;
border: 1px solid var(--border-subtle);
padding: 1.25rem 1.5rem;
margin-bottom: 1rem;
}
.metric-neutral {
color: #6c757d;
.instrument-card {
cursor: pointer;
}
.instrument-card.active {
border-color: var(--accent-blue);
box-shadow: 0 0 0 1px rgba(59,130,246,0.5);
}
.instrument-symbol {
font-weight: 600;
letter-spacing: 0.06em;
font-size: 0.95rem;
}
.instrument-type-badge {
font-size: 0.7rem;
text-transform: uppercase;
letter-spacing: 0.08em;
}
.badge-soft {
border-radius: 999px;
background: rgba(148,163,184,0.1);
color: var(--text-muted);
padding: 0.35rem 0.7rem;
font-size: 0.7rem;
}
.chart-container {
background: white;
border-radius: 10px;
padding: 20px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
margin-bottom: 20px;
padding: 1.25rem 1.5rem;
}
.table-dark.table-hover tbody tr:hover {
background-color: rgba(31,41,55,0.95);
}
.subtle-label {
font-size: 0.75rem;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.08em;
}
.kpi-chip {
border-radius: 999px;
background: rgba(15,23,42,0.9);
border: 1px solid rgba(148,163,184,0.35);
padding: 0.35rem 0.7rem;
font-size: 0.75rem;
color: var(--text-muted);
}
.kpi-dot {
width: 8px;
height: 8px;
border-radius: 50%;
display: inline-block;
margin-right: 0.35rem;
}
.dot-green { background: var(--accent-green); }
.dot-red { background: var(--accent-red); }
.dot-amber { background: var(--accent-amber); }
.footer {
margin-top: 30px;
padding: 20px;
padding: 16px;
text-align: center;
color: #6c757d;
font-size: 0.9rem;
color: var(--text-muted);
font-size: 0.8rem;
border-top: 1px solid var(--border-subtle);
}
</style>
</head>
<body>
<!-- Navigation -->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<nav class="navbar navbar-expand-lg navbar-dark">
<div class="container-fluid">
<a class="navbar-brand" href="/">
📈 Quantitative Trading Platform
<i class="bi bi-graph-up-arrow me-2 text-primary"></i>
Quantitative Trading Platform
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto">
<li class="nav-item">
<a class="nav-link active" href="/">Dashboard</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Strategies</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Risk Management</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Trade Journal</a>
</li>
<li class="nav-item"><a class="nav-link active" href="/">Dashboard</a></li>
<li class="nav-item"><a class="nav-link" href="#">Strategies</a></li>
<li class="nav-item"><a class="nav-link" href="#">Risk Management</a></li>
<li class="nav-item"><a class="nav-link" href="#">Trade Journal</a></li>
</ul>
</div>
</div>
</nav>
<div class="container-fluid mt-4">
<!-- Page Title -->
<div class="row mb-4">
<div class="col">
<h1 class="h2">Trading Dashboard</h1>
<p class="text-muted">Real-time metrics and performance monitoring</p>
<!-- Header & Instrument Selector -->
<div class="row page-header align-items-center">
<div class="col-md-6">
<h1 class="h3 mb-1">Trading Dashboard</h1>
<p class="text-muted mb-0">Card-based view of P&L, drawdown, and win rate with per-instrument drill-down</p>
</div>
<div class="col-auto">
<button class="btn btn-outline-primary" onclick="refreshMetrics()">
🔄 Refresh
<div class="col-md-6 text-md-end mt-3 mt-md-0">
<span class="badge-soft me-2"><i class="bi bi-lightbulb me-1"></i> Storyboard-aligned UI</span>
<span class="badge-soft me-2"><i class="bi bi-shield-check me-1"></i> Risk-aware metrics</span>
<button class="btn btn-outline-light btn-sm" id="refresh-btn">
<i class="bi bi-arrow-repeat me-1"></i> Simulate Refresh
</button>
</div>
</div>
<!-- Key Metrics Row -->
<div class="row mb-4">
<div class="col-md-4 mb-3">
<div class="card metric-card">
<div class="card-body">
<h5 class="card-title text-muted">Total P&L</h5>
<div class="metric-value" id="total-pnl">$0.00</div>
<p class="card-text">
<span id="pnl-trend" class="badge bg-success">+0.0%</span>
vs. last month
</p>
<!-- Instrument Input & List (Drill-down layer) -->
<div class="row mt-3 mb-3">
<div class="col-lg-4 mb-3 mb-lg-0">
<div class="instrument-input-card">
<div class="d-flex justify-content-between align-items-center mb-2">
<div>
<div class="subtle-label">Tracked Instruments</div>
<small class="text-muted">Add symbols to monitor and click a card to drill into its metrics.</small>
</div>
</div>
</div>
</div>
<div class="col-md-4 mb-3">
<div class="card metric-card">
<div class="card-body">
<h5 class="card-title text-muted">Max Drawdown</h5>
<div class="metric-value metric-negative" id="max-drawdown">$0.00</div>
<p class="card-text">
<span id="drawdown-percent" class="badge bg-danger">0.0%</span>
of portfolio
</p>
</div>
</div>
</div>
<div class="col-md-4 mb-3">
<div class="card metric-card">
<div class="card-body">
<h5 class="card-title text-muted">Win Rate</h5>
<div class="metric-value metric-neutral" id="win-rate">0.0%</div>
<p class="card-text">
<span id="profitable-days" class="badge bg-info">0/30</span>
profitable days
</p>
</div>
</div>
</div>
</div>
<form class="mt-3" id="instrument-form">
<div class="row g-2 align-items-center">
<div class="col-6">
<div class="form-floating">
<input type="text" class="form-control form-control-sm bg-dark text-light border-secondary" id="instrument-symbol" placeholder="AAPL, EUR/USD" required>
<label for="instrument-symbol" class="text-muted">Symbol</label>
</div>
</div>
<div class="col-4">
<div class="form-floating">
<select class="form-select form-select-sm bg-dark text-light border-secondary" id="instrument-type">
<option value="equity" selected>Equity</option>
<option value="forex">Forex</option>
</select>
<label for="instrument-type" class="text-muted">Type</label>
</div>
</div>
<div class="col-2 d-grid">
<button class="btn btn-primary btn-sm" type="submit" title="Add instrument">
<i class="bi bi-plus-lg"></i>
</button>
</div>
</div>
</form>
<!-- Charts Row -->
<div class="row">
<div class="col-lg-8">
<div class="chart-container">
<h4>Cumulative P&L Over Time</h4>
<canvas id="pnlChart"></canvas>
<div class="mt-3" id="instrument-list">
<!-- Instrument cards populated by JS -->
</div>
</div>
</div>
<div class="col-lg-4">
<div class="chart-container">
<h4>Portfolio Overview</h4>
<div class="mt-4">
<p><strong>Portfolio Value:</strong> <span id="portfolio-value">$100,000.00</span></p>
<p><strong>Peak Value:</strong> <span id="peak-value">$100,000.00</span></p>
<p><strong>Trough Value:</strong> <span id="trough-value">$100,000.00</span></p>
<p><strong>Last Updated:</strong> <span id="last-updated">Just now</span></p>
<!-- Key Metrics Cards (drill-down target) -->
<div class="col-lg-8">
<div class="row g-3">
<div class="col-md-4">
<div class="card metric-card h-100">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start mb-2">
<div>
<div class="metric-label">Total P&L</div>
<div class="metric-value metric-positive" id="total-pnl">$0.00</div>
</div>
<span class="badge bg-success-subtle text-success" id="pnl-trend">+0.0%</span>
</div>
<small class="text-muted" id="pnl-caption">vs. initial capital</small>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card metric-card h-100">
<div class="card-body">
<div class="metric-label mb-1">Max Drawdown</div>
<div class="metric-value metric-negative" id="max-drawdown">$0.00</div>
<small class="text-muted">Peak-to-trough • <span id="drawdown-percent">0.0%</span> of equity</small>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card metric-card h-100">
<div class="card-body">
<div class="metric-label mb-1">Win Rate</div>
<div class="metric-value metric-neutral" id="win-rate">0.0%</div>
<small class="text-muted">Profitable days: <span id="profitable-days">0/0</span></small>
</div>
</div>
</div>
</div>
<div class="row g-3 mt-2">
<div class="col-md-6">
<div class="panel-card chart-container h-100">
<div class="d-flex justify-content-between align-items-center mb-1">
<div>
<div class="subtle-label">Cumulative P&L</div>
<small class="text-muted">For selected instrument</small>
</div>
<span class="kpi-chip">
<span class="kpi-dot dot-green"></span>
Trend • <span id="kpi-trend">Stable</span>
</span>
</div>
<canvas id="pnlChart" height="150"></canvas>
</div>
</div>
<div class="col-md-6">
<div class="panel-card chart-container h-100">
<div class="d-flex justify-content-between align-items-center mb-1">
<div>
<div class="subtle-label">Daily P&L Distribution</div>
<small class="text-muted">Win/loss ratio</small>
</div>
<span class="kpi-chip">
<span class="kpi-dot dot-amber"></span>
Volatility • <span id="kpi-volatility">Normal</span>
</span>
</div>
<canvas id="dailyDistributionChart" height="150"></canvas>
</div>
</div>
<hr>
<h5>Daily P&L Distribution</h5>
<canvas id="dailyDistributionChart"></canvas>
</div>
</div>
</div>
<!-- Recent Activity Table -->
<div class="row mt-4">
<div class="row mt-3">
<div class="col">
<div class="chart-container">
<h4>Recent Daily Performance</h4>
<table class="table table-hover">
<thead>
<tr>
<th>Date</th>
<th>Daily P&L</th>
<th>Cumulative P&L</th>
<th>Status</th>
</tr>
</thead>
<tbody id="daily-table">
<!-- Filled by JavaScript -->
</tbody>
</table>
<div class="panel-card chart-container">
<div class="d-flex justify-content-between align-items-center mb-2">
<div>
<div class="subtle-label">Recent Daily Performance</div>
<small class="text-muted">Last 10 days for <span id="table-instrument">selected instrument</span></small>
</div>
<span class="badge-soft"><i class="bi bi-info-circle me-1"></i> Static mock data for UI/UX demo</span>
</div>
<div class="table-responsive">
<table class="table table-dark table-hover align-middle mb-0">
<thead>
<tr>
<th scope="col">Date</th>
<th scope="col">Daily P&L</th>
<th scope="col">Cumulative P&L</th>
<th scope="col">Status</th>
</tr>
</thead>
<tbody id="daily-table">
<!-- Filled by JavaScript -->
</tbody>
</table>
</div>
</div>
</div>
</div>
@@ -186,18 +344,67 @@
<!-- Footer -->
<div class="footer">
<p>Quantitative Trading Platform • UI/UX Module • Dashboard v1.0</p>
<p class="small">Mock data generated for demonstration. Real trading data will be integrated in later phases.</p>
<p>Quantitative Trading Platform • UI/UX Module • Dashboard v2.0 (Storyboard-aligned)</p>
<p class="small">Static working prototype using mock per-instrument data. Real trading data and Moomoo API integration will be wired in next phase.</p>
</div>
<!-- Bootstrap JS Bundle with Popper -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<!-- Dashboard Script -->
<!-- Dashboard Script (static mock, storyboard-driven) -->
<script>
let pnlChart, distributionChart;
// Format currency
const instrumentData = {
"AAPL": {
type: "equity",
label: "Apple Inc.",
metrics: {
total_pnl: 1460,
max_drawdown: -912,
win_rate: 0.72,
portfolio_value: 25000,
peak: 26000,
trough: 24088,
kpiTrend: "Uptrend",
kpiVolatility: "Moderate"
},
daily_pnl: generateMockDaily(30, 1460)
},
"EUR/USD": {
type: "forex",
label: "EUR vs USD",
metrics: {
total_pnl: 425,
max_drawdown: -600,
win_rate: 0.6,
portfolio_value: 20000,
peak: 20550,
trough: 19400,
kpiTrend: "Range-bound",
kpiVolatility: "Elevated (pre-NFP)"
},
daily_pnl: generateMockDaily(30, 425, true)
},
"USD/JPY": {
type: "forex",
label: "US Dollar vs Yen",
metrics: {
total_pnl: 310,
max_drawdown: -480,
win_rate: 0.58,
portfolio_value: 15000,
peak: 15420,
trough: 14540,
kpiTrend: "Bullish bias",
kpiVolatility: "Normal"
},
daily_pnl: generateMockDaily(30, 310, true)
}
};
let selectedInstrument = "AAPL";
function formatCurrency(value) {
return new Intl.NumberFormat('en-US', {
style: 'currency',
@@ -206,47 +413,92 @@
}).format(value);
}
// Format percentage
function formatPercent(value) {
return value.toFixed(1) + '%';
return (value * 100).toFixed(1) + '%';
}
// Update UI with metrics
function updateUI(metrics) {
// Key metrics
document.getElementById('total-pnl').textContent = formatCurrency(metrics.total_pnl);
document.getElementById('max-drawdown').textContent = formatCurrency(metrics.max_drawdown);
document.getElementById('win-rate').textContent = formatPercent(metrics.win_rate);
document.getElementById('portfolio-value').textContent = formatCurrency(metrics.portfolio_value);
document.getElementById('peak-value').textContent = formatCurrency(metrics.peak);
document.getElementById('trough-value').textContent = formatCurrency(metrics.trough);
document.getElementById('last-updated').textContent = new Date(metrics.last_updated).toLocaleString();
// Calculate trend badges
const pnlTrend = (metrics.total_pnl / 100000 * 100).toFixed(1);
document.getElementById('pnl-trend').textContent = (pnlTrend >= 0 ? '+' : '') + pnlTrend + '%';
document.getElementById('pnl-trend').className = pnlTrend >= 0 ? 'badge bg-success' : 'badge bg-danger';
const drawdownPercent = (metrics.max_drawdown / metrics.portfolio_value * 100).toFixed(1);
document.getElementById('drawdown-percent').textContent = drawdownPercent + '%';
const profitableDays = metrics.daily_pnl.filter(d => d.pnl > 0).length;
document.getElementById('profitable-days').textContent = profitableDays + '/30';
// Update charts
updateCharts(metrics);
// Update table
updateTable(metrics.daily_pnl.slice(-10).reverse());
function generateMockDaily(days, totalTarget, allowBiggerSwings = false) {
const data = [];
let cumulative = 0;
for (let i = days - 1; i >= 0; i--) {
const date = new Date();
date.setDate(date.getDate() - i);
const base = totalTarget / days;
const noise = allowBiggerSwings ? (Math.random() - 0.5) * base * 2 : (Math.random() - 0.5) * base;
const pnl = base + noise;
cumulative += pnl;
data.push({
date: date.toISOString().slice(0, 10),
pnl: pnl,
cumulative: cumulative
});
}
return data;
}
// Update charts
function updateCharts(metrics) {
const dates = metrics.daily_pnl.map(d => d.date.substring(5)); // MM-DD
const cumulative = metrics.daily_pnl.map(d => d.cumulative);
const dailyPnl = metrics.daily_pnl.map(d => d.pnl);
function buildInstrumentList() {
const container = document.getElementById('instrument-list');
container.innerHTML = '';
Object.entries(instrumentData).forEach(([symbol, data]) => {
const card = document.createElement('div');
card.className = 'instrument-card p-2 mb-2';
if (symbol === selectedInstrument) card.classList.add('active');
card.dataset.symbol = symbol;
card.innerHTML = `
<div class="d-flex justify-content-between align-items-center">
<div>
<div class="instrument-symbol">${symbol}</div>
<small class="text-muted">${data.label}</small>
</div>
<div class="text-end">
<span class="badge bg-${data.type === 'equity' ? 'info' : 'warning'} text-dark instrument-type-badge">${data.type}</span>
<div class="mt-1 small ${data.metrics.total_pnl >= 0 ? 'text-success' : 'text-danger'}">
${data.metrics.total_pnl >= 0 ? '+' : ''}${formatCurrency(data.metrics.total_pnl)}
</div>
</div>
</div>`;
card.addEventListener('click', () => {
selectedInstrument = symbol;
buildInstrumentList();
updateUI();
});
container.appendChild(card);
});
}
function updateUI() {
const data = instrumentData[selectedInstrument];
if (!data) return;
const m = data.metrics;
document.getElementById('total-pnl').textContent = formatCurrency(m.total_pnl);
document.getElementById('max-drawdown').textContent = formatCurrency(m.max_drawdown);
document.getElementById('win-rate').textContent = formatPercent(m.win_rate);
const pnlTrendEl = document.getElementById('pnl-trend');
const pnlTrendPct = (m.total_pnl / m.portfolio_value * 100).toFixed(1);
pnlTrendEl.textContent = `${pnlTrendPct >= 0 ? '+' : ''}${pnlTrendPct}%`;
pnlTrendEl.className = `badge ${pnlTrendPct >= 0 ? 'bg-success-subtle text-success' : 'bg-danger-subtle text-danger'}`;
const drawdownPercent = (Math.abs(m.max_drawdown) / m.portfolio_value * 100).toFixed(1);
document.getElementById('drawdown-percent').textContent = `${drawdownPercent}%`;
const profitableDays = data.daily_pnl.filter(d => d.pnl > 0).length;
document.getElementById('profitable-days').textContent = `${profitableDays}/${data.daily_pnl.length}`;
document.getElementById('kpi-trend').textContent = m.kpiTrend;
document.getElementById('kpi-volatility').textContent = m.kpiVolatility;
document.getElementById('table-instrument').textContent = `${selectedInstrument} (${data.label})`;
updateCharts(data.daily_pnl, m);
updateTable(data.daily_pnl.slice(-10).reverse());
}
function updateCharts(dailyData, metrics) {
const dates = dailyData.map(d => d.date.substring(5));
const cumulative = dailyData.map(d => d.cumulative);
const dailyPnl = dailyData.map(d => d.pnl);
// Cumulative P&L Chart
if (pnlChart) pnlChart.destroy();
const ctx1 = document.getElementById('pnlChart').getContext('2d');
pnlChart = new Chart(ctx1, {
@@ -256,28 +508,34 @@
datasets: [{
label: 'Cumulative P&L',
data: cumulative,
borderColor: '#198754',
backgroundColor: 'rgba(25, 135, 84, 0.1)',
borderColor: '#22c55e',
backgroundColor: 'rgba(34,197,94,0.12)',
fill: true,
tension: 0.4
tension: 0.35,
pointRadius: 0
}]
},
options: {
responsive: true,
plugins: {
legend: { display: true }
legend: { display: false }
},
scales: {
x: {
ticks: { color: '#6b7280' },
grid: { color: 'rgba(31,41,55,0.4)' }
},
y: {
ticks: {
color: '#6b7280',
callback: value => formatCurrency(value)
}
},
grid: { color: 'rgba(31,41,55,0.4)' }
}
}
}
});
// Daily Distribution Chart
if (distributionChart) distributionChart.destroy();
const ctx2 = document.getElementById('dailyDistributionChart').getContext('2d');
const profitable = dailyPnl.filter(p => p > 0).length;
@@ -288,19 +546,19 @@
labels: ['Profitable Days', 'Unprofitable Days'],
datasets: [{
data: [profitable, unprofitable],
backgroundColor: ['#198754', '#dc3545']
backgroundColor: ['#22c55e', '#ef4444'],
borderWidth: 0
}]
},
options: {
responsive: true,
plugins: {
legend: { position: 'bottom' }
legend: { position: 'bottom', labels: { color: '#9ca3af' } }
}
}
});
}
// Update table
function updateTable(dailyData) {
const tbody = document.getElementById('daily-table');
tbody.innerHTML = '';
@@ -313,44 +571,65 @@
</td>
<td>${formatCurrency(day.cumulative)}</td>
<td>
<span class="badge ${day.pnl >= 0 ? 'bg-success' : 'bg-danger'}">
<span class="badge ${day.pnl >= 0 ? 'bg-success-subtle text-success' : 'bg-danger-subtle text-danger'}">
${day.pnl >= 0 ? 'Profit' : 'Loss'}
</span>
</td>
`;
</td>`;
tbody.appendChild(row);
});
}
// Fetch metrics from API
async function fetchMetrics() {
try {
const response = await fetch('/api/metrics');
if (!response.ok) throw new Error('Network response was not ok');
const metrics = await response.json();
updateUI(metrics);
} catch (error) {
console.error('Failed to fetch metrics:', error);
alert('Unable to load metrics. Please check server connection.');
}
}
document.addEventListener('DOMContentLoaded', () => {
buildInstrumentList();
updateUI();
// Refresh metrics
function refreshMetrics() {
const btn = event?.target || document.querySelector('button[onclick="refreshMetrics()"]');
btn.innerHTML = '🔄 Loading...';
btn.disabled = true;
fetchMetrics().finally(() => {
btn.innerHTML = '🔄 Refresh';
btn.disabled = false;
document.getElementById('instrument-form').addEventListener('submit', (e) => {
e.preventDefault();
const symbolInput = document.getElementById('instrument-symbol');
const typeSelect = document.getElementById('instrument-type');
const symbol = symbolInput.value.trim().toUpperCase();
const type = typeSelect.value;
if (!symbol) return;
if (!instrumentData[symbol]) {
instrumentData[symbol] = {
type,
label: type === 'equity' ? `${symbol} (Equity)` : `${symbol} (FX pair)`,
metrics: {
total_pnl: 0,
max_drawdown: 0,
win_rate: 0.5,
portfolio_value: 10000,
peak: 10000,
trough: 10000,
kpiTrend: 'New',
kpiVolatility: 'Unknown'
},
daily_pnl: generateMockDaily(30, 0)
};
}
selectedInstrument = symbol;
symbolInput.value = '';
buildInstrumentList();
updateUI();
});
}
// Initialize on page load
document.addEventListener('DOMContentLoaded', fetchMetrics);
// Auto-refresh every 30 seconds
setInterval(fetchMetrics, 30000);
document.getElementById('refresh-btn').addEventListener('click', (e) => {
e.preventDefault();
const btn = e.currentTarget;
const original = btn.innerHTML;
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span>Simulating';
btn.disabled = true;
setTimeout(() => {
Object.values(instrumentData).forEach(inst => {
inst.daily_pnl = generateMockDaily(30, inst.metrics.total_pnl, inst.type === 'forex');
});
updateUI();
btn.innerHTML = original;
btn.disabled = false;
}, 600);
});
});
</script>
</body>
</html>