Initial commit: UI/UX dashboard skeleton

- 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
This commit is contained in:
2026-02-04 10:41:02 +08:00
commit 0121cb6097
5 changed files with 511 additions and 0 deletions

53
.gitignore vendored Normal file
View File

@@ -0,0 +1,53 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# Virtual Environment
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# IDE
.vscode/
.idea/
*.swp
*.swo
# OS
.DS_Store
Thumbs.db
# Logs
*.log
# Environment variables
.env.local
.env.development.local
.env.test.local
.env.production.local
# Temporary files
tmp/
temp/

31
README.md Normal file
View File

@@ -0,0 +1,31 @@
# Quantitative Trading Platform
A quantitative trading platform with modular architecture for data acquisition, strategy execution, risk management, and UI/UX.
## Architecture
See `component_diagram.md` and `architecture_diagram.txt` in the parent directory (`/home/thanthos/clawd/`) for detailed system design.
## UI/UX Module
### Dashboard
Simple dashboard displaying key trading metrics:
- PnL (Profit and Loss)
- Drawdown
- Win Rate
## Development
### Prerequisites
- Python 3.8+
- Node.js (for frontend)
- Docker (optional)
### Setup
1. Clone the repository
2. Install dependencies: `pip install -r requirements.txt`
3. Run the dashboard: `python app.py`
## License
Proprietary

70
app.py Normal file
View File

@@ -0,0 +1,70 @@
#!/usr/bin/env python3
"""
Simple dashboard server for Quantitative Trading Platform.
Provides mock metrics for PnL, drawdown, and win rate.
"""
from flask import Flask, render_template, jsonify
import random
from datetime import datetime, timedelta
app = Flask(__name__)
def generate_mock_metrics():
"""Generate realistic mock trading metrics."""
# Simulate daily PnL over last 30 days
end_date = datetime.now()
start_date = end_date - timedelta(days=30)
daily_pnl = []
current_pnl = 0
for i in range(30):
day_pnl = random.uniform(-500, 1500)
current_pnl += day_pnl
daily_pnl.append({
'date': (start_date + timedelta(days=i)).strftime('%Y-%m-%d'),
'pnl': round(day_pnl, 2),
'cumulative': round(current_pnl, 2)
})
# Calculate derived metrics
total_pnl = current_pnl
peak = max([d['cumulative'] for d in daily_pnl])
trough = min([d['cumulative'] for d in daily_pnl])
max_drawdown = round(abs(trough - peak), 2) if peak > 0 else 0
# Win rate (percentage of profitable days)
profitable_days = sum(1 for d in daily_pnl if d['pnl'] > 0)
win_rate = round(profitable_days / len(daily_pnl) * 100, 1)
# Current portfolio value (simulated)
portfolio_value = 100000 + total_pnl
return {
'total_pnl': round(total_pnl, 2),
'daily_pnl': daily_pnl,
'max_drawdown': max_drawdown,
'win_rate': win_rate,
'portfolio_value': round(portfolio_value, 2),
'peak': round(peak, 2),
'trough': round(trough, 2),
'last_updated': end_date.isoformat()
}
@app.route('/')
def dashboard():
"""Serve the dashboard HTML page."""
return render_template('dashboard.html')
@app.route('/api/metrics')
def metrics():
"""API endpoint returning current metrics."""
return jsonify(generate_mock_metrics())
@app.route('/health')
def health():
"""Health check endpoint."""
return jsonify({'status': 'healthy', 'timestamp': datetime.now().isoformat()})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)

1
requirements.txt Normal file
View File

@@ -0,0 +1 @@
Flask==2.3.3

356
templates/dashboard.html Normal file
View File

@@ -0,0 +1,356 @@
<!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>