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:
53
.gitignore
vendored
Normal file
53
.gitignore
vendored
Normal 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
31
README.md
Normal 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
70
app.py
Normal 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
1
requirements.txt
Normal file
@@ -0,0 +1 @@
|
||||
Flask==2.3.3
|
||||
356
templates/dashboard.html
Normal file
356
templates/dashboard.html
Normal 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>
|
||||
Reference in New Issue
Block a user