- Basic Flask backend with mock metrics API - Bootstrap/Chart.js frontend dashboard - Displays key metrics: PnL, drawdown, win rate - Interactive charts and auto-refresh - Ready for integration with real data sources
356 lines
14 KiB
HTML
356 lines
14 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">
|
|
<!-- 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;
|
|
}
|
|
.navbar-brand {
|
|
font-weight: 600;
|
|
}
|
|
.metric-card {
|
|
border-radius: 10px;
|
|
border: none;
|
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
transition: transform 0.2s;
|
|
}
|
|
.metric-card:hover {
|
|
transform: translateY(-5px);
|
|
}
|
|
.metric-value {
|
|
font-size: 2.5rem;
|
|
font-weight: 700;
|
|
}
|
|
.metric-positive {
|
|
color: #198754;
|
|
}
|
|
.metric-negative {
|
|
color: #dc3545;
|
|
}
|
|
.metric-neutral {
|
|
color: #6c757d;
|
|
}
|
|
.chart-container {
|
|
background: white;
|
|
border-radius: 10px;
|
|
padding: 20px;
|
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
margin-bottom: 20px;
|
|
}
|
|
.footer {
|
|
margin-top: 30px;
|
|
padding: 20px;
|
|
text-align: center;
|
|
color: #6c757d;
|
|
font-size: 0.9rem;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<!-- Navigation -->
|
|
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
|
<div class="container-fluid">
|
|
<a class="navbar-brand" href="/">
|
|
📈 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">
|
|
<!-- 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>
|
|
</div>
|
|
<div class="col-auto">
|
|
<button class="btn btn-outline-primary" onclick="refreshMetrics()">
|
|
🔄 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>
|
|
</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>
|
|
|
|
<!-- 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>
|
|
</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>
|
|
</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="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>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 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>
|
|
</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 -->
|
|
<script>
|
|
let pnlChart, distributionChart;
|
|
|
|
// Format currency
|
|
function formatCurrency(value) {
|
|
return new Intl.NumberFormat('en-US', {
|
|
style: 'currency',
|
|
currency: 'USD',
|
|
minimumFractionDigits: 2
|
|
}).format(value);
|
|
}
|
|
|
|
// Format percentage
|
|
function formatPercent(value) {
|
|
return value.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());
|
|
}
|
|
|
|
// 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);
|
|
|
|
// Cumulative P&L Chart
|
|
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: '#198754',
|
|
backgroundColor: 'rgba(25, 135, 84, 0.1)',
|
|
fill: true,
|
|
tension: 0.4
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
plugins: {
|
|
legend: { display: true }
|
|
},
|
|
scales: {
|
|
y: {
|
|
ticks: {
|
|
callback: value => formatCurrency(value)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Daily Distribution Chart
|
|
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: ['#198754', '#dc3545']
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
plugins: {
|
|
legend: { position: 'bottom' }
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Update table
|
|
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' : 'bg-danger'}">
|
|
${day.pnl >= 0 ? 'Profit' : 'Loss'}
|
|
</span>
|
|
</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.');
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
});
|
|
}
|
|
|
|
// Initialize on page load
|
|
document.addEventListener('DOMContentLoaded', fetchMetrics);
|
|
|
|
// Auto-refresh every 30 seconds
|
|
setInterval(fetchMetrics, 30000);
|
|
</script>
|
|
</body>
|
|
</html> |