- 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
636 lines
26 KiB
HTML
636 lines
26 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<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>
|
|
: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;
|
|
}
|
|
|
|
.page-header {
|
|
border-bottom: 1px solid var(--border-subtle);
|
|
padding-bottom: 0.75rem;
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
.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.4rem;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.metric-label {
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.08em;
|
|
font-size: 0.75rem;
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
.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;
|
|
}
|
|
|
|
.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 {
|
|
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: 16px;
|
|
text-align: center;
|
|
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">
|
|
<div class="container-fluid">
|
|
<a class="navbar-brand" href="/">
|
|
<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>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
|
|
<div class="container-fluid mt-4">
|
|
<!-- 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-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>
|
|
|
|
<!-- 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>
|
|
<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>
|
|
|
|
<div class="mt-3" id="instrument-list">
|
|
<!-- Instrument cards populated by JS -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 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>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Recent Activity Table -->
|
|
<div class="row mt-3">
|
|
<div class="col">
|
|
<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>
|
|
</div>
|
|
|
|
<!-- Footer -->
|
|
<div class="footer">
|
|
<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 (static mock, storyboard-driven) -->
|
|
<script>
|
|
let pnlChart, distributionChart;
|
|
|
|
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',
|
|
currency: 'USD',
|
|
minimumFractionDigits: 2
|
|
}).format(value);
|
|
}
|
|
|
|
function formatPercent(value) {
|
|
return (value * 100).toFixed(1) + '%';
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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);
|
|
|
|
if (pnlChart) pnlChart.destroy();
|
|
const ctx1 = document.getElementById('pnlChart').getContext('2d');
|
|
pnlChart = new Chart(ctx1, {
|
|
type: 'line',
|
|
data: {
|
|
labels: dates,
|
|
datasets: [{
|
|
label: 'Cumulative P&L',
|
|
data: cumulative,
|
|
borderColor: '#22c55e',
|
|
backgroundColor: 'rgba(34,197,94,0.12)',
|
|
fill: true,
|
|
tension: 0.35,
|
|
pointRadius: 0
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
plugins: {
|
|
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)' }
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
if (distributionChart) distributionChart.destroy();
|
|
const ctx2 = document.getElementById('dailyDistributionChart').getContext('2d');
|
|
const profitable = dailyPnl.filter(p => p > 0).length;
|
|
const unprofitable = dailyPnl.length - profitable;
|
|
distributionChart = new Chart(ctx2, {
|
|
type: 'doughnut',
|
|
data: {
|
|
labels: ['Profitable Days', 'Unprofitable Days'],
|
|
datasets: [{
|
|
data: [profitable, unprofitable],
|
|
backgroundColor: ['#22c55e', '#ef4444'],
|
|
borderWidth: 0
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
plugins: {
|
|
legend: { position: 'bottom', labels: { color: '#9ca3af' } }
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function updateTable(dailyData) {
|
|
const tbody = document.getElementById('daily-table');
|
|
tbody.innerHTML = '';
|
|
dailyData.forEach(day => {
|
|
const row = document.createElement('tr');
|
|
row.innerHTML = `
|
|
<td>${day.date}</td>
|
|
<td class="${day.pnl >= 0 ? 'text-success' : 'text-danger'}">
|
|
${formatCurrency(day.pnl)}
|
|
</td>
|
|
<td>${formatCurrency(day.cumulative)}</td>
|
|
<td>
|
|
<span class="badge ${day.pnl >= 0 ? 'bg-success-subtle text-success' : 'bg-danger-subtle text-danger'}">
|
|
${day.pnl >= 0 ? 'Profit' : 'Loss'}
|
|
</span>
|
|
</td>`;
|
|
tbody.appendChild(row);
|
|
});
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
buildInstrumentList();
|
|
updateUI();
|
|
|
|
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();
|
|
});
|
|
|
|
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>
|