feat: Integrated Dashboard v2.0 with LLM-driven Mock Backend

- Promoted prototype dashboard to main app
- Added /api/market-data endpoint
- Implemented market_simulator.py for realistic OHLCV generation
- Updated MoomooClient to serve simulated scenarios
- Wired frontend to backend API
This commit is contained in:
2026-02-07 06:34:19 +08:00
parent dfb7b9e619
commit f30bcdc680
8 changed files with 5796 additions and 47 deletions

122
app.py
View File

@@ -4,62 +4,92 @@ Simple dashboard server for Quantitative Trading Platform.
Provides mock metrics for PnL, drawdown, and win rate. Provides mock metrics for PnL, drawdown, and win rate.
""" """
from flask import Flask, render_template, jsonify from flask import Flask, render_template, jsonify, request
import random import random
from datetime import datetime, timedelta from datetime import datetime, timedelta
from data.connectors import create_moomoo_client, Interval
app = Flask(__name__) app = Flask(__name__)
def generate_mock_metrics(): # Initialize MoomooClient in mock mode (using our LLM scenario generator)
"""Generate realistic mock trading metrics.""" client = create_moomoo_client(mock_mode=True)
# 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('/') @app.route('/')
def dashboard(): def dashboard():
"""Serve the dashboard HTML page.""" """Serve the dashboard HTML page."""
return render_template('dashboard.html') return render_template('index.html')
@app.route('/api/metrics') @app.route('/api/market-data/<symbol>')
def metrics(): def market_data(symbol):
"""API endpoint returning current metrics.""" """
return jsonify(generate_mock_metrics()) API endpoint returning market data for a symbol.
Fetches 30 days of daily data by default for the dashboard.
"""
try:
# Fetch OHLCV data from our connector (which uses the simulator)
end_date = datetime.now()
start_date = end_date - timedelta(days=30)
# Determine interval from query param, default to 1d
interval_str = request.args.get('interval', '1d')
interval = Interval(interval_str)
ohlcv_data = client.get_ohlcv(
symbol=symbol.upper(),
interval=interval,
start_date=start_date,
end_date=end_date,
limit=100
)
# Transform for frontend
daily_pnl = []
cumulative = 0
# Simple simulation of PnL based on close price changes
# In a real app, this would come from the Strategy/Portfolio engine
initial_balance = 10000.0
position_size = 10 # shares
prev_close = ohlcv_data[0].close if ohlcv_data else 0
for point in ohlcv_data:
change = point.close - prev_close
pnl = change * position_size
cumulative += pnl
daily_pnl.append({
'date': point.timestamp.strftime('%Y-%m-%d'),
'pnl': round(pnl, 2),
'cumulative': round(cumulative, 2),
'close': point.close
})
prev_close = point.close
# Calculate summary metrics
total_pnl = cumulative
peak = max([d['cumulative'] for d in daily_pnl]) if daily_pnl else 0
trough = min([d['cumulative'] for d in daily_pnl]) if daily_pnl else 0
max_drawdown = min(0, trough - peak)
profitable_days = sum(1 for d in daily_pnl if d['pnl'] > 0)
win_rate = (profitable_days / len(daily_pnl) * 100) if daily_pnl else 0
return jsonify({
'symbol': symbol.upper(),
'metrics': {
'total_pnl': round(total_pnl, 2),
'max_drawdown': round(max_drawdown, 2),
'win_rate': round(win_rate, 1),
'portfolio_value': round(initial_balance + total_pnl, 2),
'peak': round(peak, 2),
'trough': round(trough, 2)
},
'daily_pnl': daily_pnl
})
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/health') @app.route('/health')
def health(): def health():

View File

@@ -265,7 +265,27 @@ class MoomooClient(DataConnector):
interval = params.get("interval", "1d") if params else "1d" interval = params.get("interval", "1d") if params else "1d"
limit = params.get("limit", 100) if params else 100 limit = params.get("limit", 100) if params else 100
# Generate mock OHLCV data # Try to load LLM-generated scenario first
scenario_file = f"data/mock_scenarios/{symbol}_rally.json"
try:
import os
if os.path.exists(scenario_file):
with open(scenario_file, 'r') as f:
data = json.load(f)
# Filter/slice data if needed
return {
"code": 0,
"msg": "success",
"data": {
"symbol": symbol,
"interval": interval,
"list": data[:limit]
}
}
except Exception as e:
logger.warning(f"Failed to load mock scenario: {e}")
# Fallback to algorithmic generation
data = [] data = []
base_price = random.uniform(100, 200) base_price = random.uniform(100, 200)
for i in range(limit): for i in range(limit):

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,87 @@
import json
import random
import math
from datetime import datetime, timedelta
def generate_ohlcv_from_pattern(symbol, date_str, pattern_segments):
"""
Generates minute-by-minute OHLCV data based on a list of trend segments.
Args:
symbol: Ticker symbol
date_str: Date (YYYY-MM-DD)
pattern_segments: List of dicts with:
- duration_mins: int
- start_price: float
- end_price: float
- volatility: float (standard deviation)
- volume_mult: float (multiplier of average volume)
"""
base_date = datetime.strptime(date_str, "%Y-%m-%d")
current_time = base_date.replace(hour=9, minute=30)
data = []
for segment in pattern_segments:
duration = segment['duration_mins']
start_p = segment['start_price']
end_p = segment['end_price']
volatility = segment['volatility']
vol_mult = segment.get('volume_mult', 1.0)
price_step = (end_p - start_p) / duration
for i in range(duration):
trend_price = start_p + (price_step * i)
# Random walk component
noise = random.gauss(0, volatility)
# OHLC generation
open_p = trend_price + noise
high_p = open_p + abs(random.gauss(0, volatility/2))
low_p = open_p - abs(random.gauss(0, volatility/2))
close_p = (open_p + high_p + low_p) / 3 + random.gauss(0, volatility/4)
# Ensure logical constraints
high_p = max(open_p, close_p, high_p)
low_p = min(open_p, close_p, low_p)
# Volume profile (U-shaped usually, but modulated by segment)
time_factor = 1.0
if current_time.hour < 10 or current_time.hour >= 15:
time_factor = 1.5
base_vol = 50000 * time_factor * vol_mult
volume = int(max(100, random.gauss(base_vol, base_vol * 0.3)))
data.append({
"code": symbol,
"time": current_time.strftime("%Y-%m-%d %H:%M:%S"),
"open": round(open_p, 2),
"high": round(high_p, 2),
"low": round(low_p, 2),
"close": round(close_p, 2),
"volume": volume
})
current_time += timedelta(minutes=1)
return data
if __name__ == "__main__":
# Example: AAPL "Morning Rally then Chop"
# Starting at 150.00
segments = [
{"duration_mins": 30, "start_price": 150.0, "end_price": 152.5, "volatility": 0.15, "volume_mult": 1.5}, # Rally
{"duration_mins": 60, "start_price": 152.5, "end_price": 151.8, "volatility": 0.10, "volume_mult": 0.8}, # Pullback
{"duration_mins": 120, "start_price": 151.8, "end_price": 152.2, "volatility": 0.08, "volume_mult": 0.5}, # Chop
{"duration_mins": 180, "start_price": 152.2, "end_price": 153.5, "volatility": 0.12, "volume_mult": 1.2}, # Afternoon push
]
data = generate_ohlcv_from_pattern("AAPL", "2026-02-06", segments)
with open("data/mock_scenarios/AAPL_rally.json", "w") as f:
json.dump(data, f, indent=2)
print(f"Generated {len(data)} points for AAPL")

485
static/css/style.css Normal file
View File

@@ -0,0 +1,485 @@
/*
* Quantitative Trading Platform - Interactive Mockup
* CSS Styles for the complete platform mockup
*/
:root {
/* Color Palette */
--primary-dark: #0a1929;
--secondary-dark: #1e2a38;
--tertiary-dark: #2a3a4d;
/* Accent Colors */
--accent-blue: #1976d2;
--accent-green: #4caf50;
--accent-red: #f44336;
--accent-purple: #9c27b0;
--accent-orange: #ff9800;
--accent-cyan: #00bcd4;
--accent-yellow: #ffc107;
/* Text Colors */
--text-primary: #ffffff;
--text-secondary: #b0bec5;
--text-muted: #78909c;
/* Border Colors */
--border-dark: #37474f;
--border-light: #546e7a;
/* Status Colors */
--status-success: #4caf50;
--status-warning: #ff9800;
--status-danger: #f44336;
--status-info: #2196f3;
/* Chart Colors */
--chart-line: #4caf50;
--chart-area: rgba(76, 175, 80, 0.1);
--chart-grid: #37474f;
}
/* Base Styles */
body {
font-family: 'Segoe UI', system-ui, -apple-system, BlinkMacSystemFont, 'Roboto', sans-serif;
background-color: var(--primary-dark);
color: var(--text-primary);
overflow-x: hidden;
font-size: 0.9rem;
line-height: 1.5;
}
/* Typography */
h1, h2, h3, h4, h5, h6 {
font-weight: 600;
color: var(--text-primary);
}
.text-muted {
color: var(--text-muted) !important;
}
/* Cards */
.card {
background-color: var(--primary-dark);
border: 1px solid var(--border-dark);
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
transition: all 0.2s ease;
}
.card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
transform: translateY(-2px);
}
.card-header {
background-color: rgba(255, 255, 255, 0.03);
border-bottom: 1px solid var(--border-dark);
padding: 0.75rem 1rem;
}
.card-body {
padding: 1rem;
}
/* Border accent colors */
.border-accent-blue {
border-color: var(--accent-blue) !important;
}
.border-accent-green {
border-color: var(--accent-green) !important;
}
.border-accent-red {
border-color: var(--accent-red) !important;
}
.border-accent-purple {
border-color: var(--accent-purple) !important;
}
.border-accent-orange {
border-color: var(--accent-orange) !important;
}
.border-secondary-dark {
border-color: var(--secondary-dark) !important;
}
/* Sidebar Navigation */
.sidebar {
min-height: calc(100vh - 56px);
padding-top: 1rem;
}
.nav-link {
color: var(--text-secondary);
padding: 0.75rem 1rem;
margin: 0.125rem 0.5rem;
border-radius: 6px;
transition: all 0.2s ease;
}
.nav-link:hover {
color: var(--text-primary);
background-color: rgba(255, 255, 255, 0.05);
}
.nav-link.active {
color: var(--text-primary);
background-color: rgba(25, 118, 210, 0.15);
border-left: 3px solid var(--accent-blue);
}
.nav-link i {
width: 20px;
text-align: center;
}
/* Progress Bars */
.progress {
background-color: var(--tertiary-dark);
border-radius: 4px;
}
.progress-bar {
border-radius: 4px;
}
/* Tables */
.table-dark {
--bs-table-bg: transparent;
--bs-table-striped-bg: rgba(255, 255, 255, 0.03);
--bs-table-hover-bg: rgba(255, 255, 255, 0.05);
color: var(--text-primary);
border-color: var(--border-dark);
}
.table-dark th {
font-weight: 600;
color: var(--text-secondary);
border-color: var(--border-dark);
}
.table-dark td {
border-color: var(--border-dark);
}
/* Forms */
.form-control, .form-select {
background-color: var(--secondary-dark);
border: 1px solid var(--border-dark);
color: var(--text-primary);
}
.form-control:focus, .form-select:focus {
background-color: var(--secondary-dark);
border-color: var(--accent-blue);
color: var(--text-primary);
box-shadow: 0 0 0 0.25rem rgba(25, 118, 210, 0.25);
}
.form-control::placeholder {
color: var(--text-muted);
}
.input-group-text {
background-color: var(--tertiary-dark);
border: 1px solid var(--border-dark);
color: var(--text-secondary);
}
/* Buttons */
.btn {
border-radius: 6px;
font-weight: 500;
transition: all 0.2s ease;
}
.btn-outline-light {
color: var(--text-secondary);
border-color: var(--border-dark);
}
.btn-outline-light:hover {
background-color: rgba(255, 255, 255, 0.1);
border-color: var(--border-light);
color: var(--text-primary);
}
.btn-primary {
background-color: var(--accent-blue);
border-color: var(--accent-blue);
}
.btn-primary:hover {
background-color: #1565c0;
border-color: #1565c0;
}
.btn-success {
background-color: var(--accent-green);
border-color: var(--accent-green);
}
.btn-danger {
background-color: var(--accent-red);
border-color: var(--accent-red);
}
.btn-warning {
background-color: var(--accent-orange);
border-color: var(--accent-orange);
color: #000;
}
.btn-info {
background-color: var(--accent-cyan);
border-color: var(--accent-cyan);
}
/* Badges */
.badge {
font-weight: 500;
padding: 0.35em 0.65em;
border-radius: 4px;
}
.bg-purple {
background-color: var(--accent-purple) !important;
}
/* Alerts */
.alert {
border-radius: 6px;
border: 1px solid transparent;
}
.alert-warning {
background-color: rgba(255, 152, 0, 0.1);
border-color: rgba(255, 152, 0, 0.3);
color: var(--accent-orange);
}
.alert-success {
background-color: rgba(76, 175, 80, 0.1);
border-color: rgba(76, 175, 80, 0.3);
color: var(--accent-green);
}
.alert-info {
background-color: rgba(33, 150, 243, 0.1);
border-color: rgba(33, 150, 243, 0.3);
color: var(--accent-blue);
}
/* Charts Container */
canvas {
max-width: 100%;
}
/* Section Transitions */
.section-content {
animation: fadeIn 0.3s ease;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Responsive Adjustments */
@media (max-width: 768px) {
.sidebar {
min-height: auto;
border-right: none;
border-bottom: 1px solid var(--border-dark);
}
.nav-link {
margin: 0.125rem 0;
}
.display-4 {
font-size: 2.5rem;
}
}
/* Utility Classes */
.text-success {
color: var(--accent-green) !important;
}
.text-danger {
color: var(--accent-red) !important;
}
.text-warning {
color: var(--accent-orange) !important;
}
.text-info {
color: var(--accent-cyan) !important;
}
.bg-primary-dark {
background-color: var(--primary-dark) !important;
}
.bg-secondary-dark {
background-color: var(--secondary-dark) !important;
}
.border-secondary-dark {
border-color: var(--secondary-dark) !important;
}
/* Scrollbar Styling */
::-webkit-scrollbar {
width: 10px;
height: 10px;
}
::-webkit-scrollbar-track {
background: var(--secondary-dark);
}
::-webkit-scrollbar-thumb {
background: var(--tertiary-dark);
border-radius: 5px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--border-light);
}
/* List Groups */
.list-group-item {
background-color: transparent;
color: var(--text-primary);
border-color: var(--border-dark);
}
.list-group-item:hover {
background-color: rgba(255, 255, 255, 0.03);
}
/* Hover Effects */
.hover-lift {
transition: transform 0.2s ease;
}
.hover-lift:hover {
transform: translateY(-2px);
}
/* Highlight animation for updated values */
@keyframes highlight {
0% { color: var(--text-primary); }
50% { color: var(--accent-yellow); }
100% { color: var(--text-primary); }
}
.text-highlight {
animation: highlight 1s ease;
}
/* Status Indicators */
.status-indicator {
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
margin-right: 6px;
}
.status-indicator.online {
background-color: var(--accent-green);
box-shadow: 0 0 8px var(--accent-green);
}
.status-indicator.warning {
background-color: var(--accent-orange);
box-shadow: 0 0 8px var(--accent-orange);
}
.status-indicator.offline {
background-color: var(--accent-red);
box-shadow: 0 0 8px var(--accent-red);
}
/* Metric Cards */
.metric-card {
position: relative;
overflow: hidden;
}
.metric-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 3px;
background: linear-gradient(90deg, var(--accent-blue), var(--accent-cyan));
}
.metric-card.green::before {
background: linear-gradient(90deg, var(--accent-green), #81c784);
}
.metric-card.red::before {
background: linear-gradient(90deg, var(--accent-red), #e57373);
}
.metric-card.orange::before {
background: linear-gradient(90deg, var(--accent-orange), #ffb74d);
}
/* Loading States */
.loading-skeleton {
background: linear-gradient(90deg, var(--secondary-dark) 25%, var(--tertiary-dark) 50%, var(--secondary-dark) 75%);
background-size: 200% 100%;
animation: loading 1.5s infinite;
}
@keyframes loading {
0% {
background-position: 200% 0;
}
100% {
background-position: -200% 0;
}
}
/* Tooltips */
.tooltip-inner {
background-color: var(--tertiary-dark);
color: var(--text-primary);
border-radius: 4px;
padding: 0.5rem 0.75rem;
}
.tooltip.bs-tooltip-top .tooltip-arrow::before {
border-top-color: var(--tertiary-dark);
}
.tooltip.bs-tooltip-bottom .tooltip-arrow::before {
border-bottom-color: var(--tertiary-dark);
}
.tooltip.bs-tooltip-start .tooltip-arrow::before {
border-left-color: var(--tertiary-dark);
}
.tooltip.bs-tooltip-end .tooltip-arrow::before {
border-right-color: var(--tertiary-dark);
}

381
static/js/app.js Normal file
View File

@@ -0,0 +1,381 @@
/*
* Quantitative Trading Platform - Interactive Mockup
* Main JavaScript for navigation and interactivity
*/
// Global state
const AppState = {
currentSection: 'dashboard',
charts: {},
mockData: {}
};
// Initialize the application
function initApp() {
console.log('Quantitative Trading Platform Mockup Initialized');
// Set up navigation
setupNavigation();
// Load mock data
loadMockData();
// Initialize charts
initCharts();
// Show default section
showSection('dashboard');
// Set up event listeners for interactive elements
setupEventListeners();
}
// Set up sidebar navigation
function setupNavigation() {
const navLinks = document.querySelectorAll('.nav-link[data-section]');
navLinks.forEach(link => {
link.addEventListener('click', function(e) {
e.preventDefault();
const section = this.getAttribute('data-section');
showSection(section);
// Update active state
navLinks.forEach(l => l.classList.remove('active'));
this.classList.add('active');
});
});
}
// Show a specific section
function showSection(sectionId) {
// Hide all sections
const sections = document.querySelectorAll('.section-content');
sections.forEach(section => {
section.classList.add('d-none');
});
// Show the selected section
const targetSection = document.getElementById(`${sectionId}-section`);
if (targetSection) {
targetSection.classList.remove('d-none');
AppState.currentSection = sectionId;
// Update page title
const sectionTitle = targetSection.querySelector('h1, h2')?.textContent || 'Quantitative Trading Platform';
document.title = `${sectionTitle} - Mockup`;
// Refresh charts for the section if needed
refreshSectionCharts(sectionId);
}
}
// Refresh charts for a specific section
function refreshSectionCharts(sectionId) {
switch(sectionId) {
case 'dashboard':
if (AppState.charts.portfolioChart) {
updatePortfolioChart();
}
break;
case 'data-management':
if (AppState.charts.dataQualityChart) {
updateDataQualityChart();
}
break;
case 'strategy-engine':
if (AppState.charts.backtestChart) {
updateBacktestChart();
}
break;
case 'risk-management':
if (AppState.charts.riskChart) {
updateRiskChart();
}
break;
}
}
// Set up event listeners for buttons and interactive elements
function setupEventListeners() {
// Refresh buttons
const refreshButtons = document.querySelectorAll('button:contains("Refresh"), .btn-refresh, .btn-outline-light');
refreshButtons.forEach(btn => {
// Look for the refresh button specifically
if (btn.querySelector('.bi-arrow-clockwise') || btn.textContent.includes('Refresh')) {
btn.addEventListener('click', function() {
// Fetch real market data from backend
const symbol = 'AAPL'; // Default for now
fetch(`/api/market-data/${symbol}`)
.then(response => response.json())
.then(data => {
// Update UI with real data
updateDashboardWithRealData(data);
showToast(`Data refreshed for ${symbol}`, 'success');
})
.catch(error => {
console.error('Error fetching data:', error);
showToast('Failed to refresh data', 'danger');
});
});
}
});
// Form submissions
const forms = document.querySelectorAll('form');
forms.forEach(form => {
form.addEventListener('submit', function(e) {
e.preventDefault();
showToast('Settings saved successfully', 'success');
});
});
// Toggle buttons
const toggleButtons = document.querySelectorAll('.btn[data-toggle]');
toggleButtons.forEach(btn => {
btn.addEventListener('click', function() {
const target = this.getAttribute('data-toggle');
const state = this.getAttribute('data-state');
if (target && state) {
// Toggle state
const newState = state === 'on' ? 'off' : 'on';
this.setAttribute('data-state', newState);
// Update button appearance
if (newState === 'on') {
this.classList.remove('btn-outline-secondary');
this.classList.add('btn-success');
this.innerHTML = '<i class="bi bi-toggle-on me-1"></i> ON';
} else {
this.classList.remove('btn-success');
this.classList.add('btn-outline-secondary');
this.innerHTML = '<i class="bi bi-toggle-off me-1"></i> OFF';
}
showToast(`Feature ${newState === 'on' ? 'enabled' : 'disabled'}`, 'info');
}
});
});
// Tab clicks
const tabs = document.querySelectorAll('[data-bs-toggle="tab"]');
tabs.forEach(tab => {
tab.addEventListener('shown.bs.tab', function() {
const tabId = this.getAttribute('data-bs-target');
console.log(`Tab activated: ${tabId}`);
});
});
// Simulate live data updates
setInterval(updateLiveData, 5000);
}
// Update live data (simulated)
function updateLiveData() {
if (AppState.currentSection === 'dashboard') {
// Update metrics with small random changes
updateDashboardMetrics();
}
}
// Update dashboard metrics with random changes
function updateDashboardMetrics() {
const metrics = {
'total-pnl': { base: 42580, range: 500 },
'max-drawdown': { base: 8420, range: 200 },
'win-rate': { base: 64.8, range: 1.5 }
};
for (const [id, config] of Object.entries(metrics)) {
const element = document.getElementById(id);
if (element) {
const current = parseFloat(element.textContent.replace(/[^\d.-]/g, ''));
const change = (Math.random() - 0.5) * 2 * config.range;
const newValue = config.base + change;
// Format based on metric type
if (id === 'total-pnl' || id === 'max-drawdown') {
element.textContent = `$${Math.round(newValue).toLocaleString()}`;
} else if (id === 'win-rate') {
element.textContent = `${newValue.toFixed(1)}%`;
}
// Add subtle animation
element.classList.add('text-highlight');
setTimeout(() => {
element.classList.remove('text-highlight');
}, 1000);
}
}
}
function updateDashboardWithRealData(data) {
// Update key metrics cards
const metrics = data.metrics;
// Total P&L
const pnlEl = document.querySelector('.card-title.text-success');
if (pnlEl) pnlEl.textContent = `$${metrics.total_pnl.toLocaleString()}`;
// Max Drawdown
const ddEl = document.querySelector('.card-title.text-danger');
if (ddEl) ddEl.textContent = `$${metrics.max_drawdown.toLocaleString()}`;
// Win Rate
const winEl = document.querySelector('.card-title.text-info');
if (winEl) winEl.textContent = `${metrics.win_rate}%`;
// Update chart
if (AppState.charts.portfolioChart) {
AppState.charts.portfolioChart.data.labels = data.daily_pnl.map(d => d.date);
AppState.charts.portfolioChart.data.datasets[0].data = data.daily_pnl.map(d => d.cumulative);
AppState.charts.portfolioChart.update();
}
}
// Show toast notification
function showToast(message, type = 'info') {
// Create toast container if it doesn't exist
let toastContainer = document.getElementById('toast-container');
if (!toastContainer) {
toastContainer = document.createElement('div');
toastContainer.id = 'toast-container';
toastContainer.className = 'toast-container position-fixed bottom-0 end-0 p-3';
document.body.appendChild(toastContainer);
}
// Create toast
const toastId = 'toast-' + Date.now();
const toast = document.createElement('div');
toast.id = toastId;
toast.className = `toast align-items-center text-bg-${type} border-0`;
toast.setAttribute('role', 'alert');
toast.setAttribute('aria-live', 'assertive');
toast.setAttribute('aria-atomic', 'true');
toast.innerHTML = `
<div class="d-flex">
<div class="toast-body">
${message}
</div>
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
`;
toastContainer.appendChild(toast);
// Initialize and show toast
const bsToast = new bootstrap.Toast(toast, { delay: 3000 });
bsToast.show();
// Remove toast after it's hidden
toast.addEventListener('hidden.bs.toast', function() {
toast.remove();
});
}
// Load mock data
function loadMockData() {
// Generate mock data for charts
AppState.mockData = {
portfolioData: generatePortfolioData(),
dataQualityData: generateDataQualityData(),
backtestData: generateBacktestData(),
riskData: generateRiskData()
};
}
// Generate mock portfolio data
function generatePortfolioData() {
const data = [];
let value = 100000;
for (let i = 0; i < 30; i++) {
const date = new Date();
date.setDate(date.getDate() - (30 - i));
// Random walk with drift
const change = (Math.random() - 0.4) * 0.02 * value;
value += change;
data.push({
date: date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }),
value: Math.max(90000, value)
});
}
return data;
}
// Generate mock data quality data
function generateDataQualityData() {
const data = [];
for (let i = 0; i < 7; i++) {
const date = new Date();
date.setDate(date.getDate() - (7 - i));
data.push({
date: date.toLocaleDateString('en-US', { weekday: 'short' }),
completeness: 90 + Math.random() * 8,
latency: 5 + Math.random() * 15,
errors: Math.floor(Math.random() * 5)
});
}
return data;
}
// Generate mock backtest data
function generateBacktestData() {
const strategies = ['Dual Momentum', 'Mean Reversion', 'Pairs Trading'];
const data = [];
strategies.forEach(strategy => {
let returns = 100;
const returnsData = [];
for (let i = 0; i < 12; i++) {
const monthlyReturn = (Math.random() - 0.2) * 0.1;
returns *= (1 + monthlyReturn);
returnsData.push(returns);
}
data.push({
strategy,
returns: returnsData,
sharpe: 0.8 + Math.random() * 1.5,
winRate: 50 + Math.random() * 30
});
});
return data;
}
// Generate mock risk data
function generateRiskData() {
const metrics = ['VaR', 'CVaR', 'Max Drawdown', 'Volatility'];
const data = [];
metrics.forEach(metric => {
const values = [];
for (let i = 0; i < 10; i++) {
values.push(Math.random() * 10);
}
data.push({ metric, values });
});
return data;
}
// Initialize when DOM is loaded
document.addEventListener('DOMContentLoaded', initApp);
// Export for use in other scripts
window.App = {
showSection,
showToast,
refreshSectionCharts,
state: AppState
};

414
static/js/mock-data.js Normal file
View File

@@ -0,0 +1,414 @@
/*
* Quantitative Trading Platform - Interactive Mockup
* Mock data generation for charts and displays
*/
// Chart initialization
function initCharts() {
// Initialize Portfolio Chart
initPortfolioChart();
// Initialize Data Quality Chart
initDataQualityChart();
// Initialize Backtest Chart
initBacktestChart();
// Initialize Risk Chart
initRiskChart();
}
// Portfolio Performance Chart
function initPortfolioChart() {
const ctx = document.getElementById('portfolioChart');
if (!ctx) return;
const data = generatePortfolioData();
AppState.charts.portfolioChart = new Chart(ctx, {
type: 'line',
data: {
labels: data.map(d => d.date),
datasets: [{
label: 'Portfolio Value',
data: data.map(d => d.value),
borderColor: '#4caf50',
backgroundColor: 'rgba(76, 175, 80, 0.1)',
fill: true,
tension: 0.4,
pointRadius: 2,
pointHoverRadius: 5
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
},
tooltip: {
mode: 'index',
intersect: false,
callbacks: {
label: function(context) {
return `$${context.parsed.y.toLocaleString()}`;
}
}
}
},
scales: {
x: {
grid: {
color: 'rgba(255, 255, 255, 0.05)'
},
ticks: {
color: '#b0bec5'
}
},
y: {
grid: {
color: 'rgba(255, 255, 255, 0.05)'
},
ticks: {
color: '#b0bec5',
callback: function(value) {
return '$' + (value / 1000).toFixed(0) + 'k';
}
}
}
}
}
});
}
// Data Quality Chart
function initDataQualityChart() {
const ctx = document.getElementById('dataQualityChart');
if (!ctx) return;
const data = generateDataQualityData();
AppState.charts.dataQualityChart = new Chart(ctx, {
type: 'line',
data: {
labels: data.map(d => d.date),
datasets: [
{
label: 'Completeness (%)',
data: data.map(d => d.completeness),
borderColor: '#4caf50',
backgroundColor: 'rgba(76, 175, 80, 0.1)',
fill: true,
tension: 0.4,
yAxisID: 'y'
},
{
label: 'Latency (ms)',
data: data.map(d => d.latency),
borderColor: '#2196f3',
backgroundColor: 'rgba(33, 150, 243, 0.1)',
fill: true,
tension: 0.4,
yAxisID: 'y1'
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'top',
labels: {
color: '#b0bec5'
}
}
},
scales: {
x: {
grid: {
color: 'rgba(255, 255, 255, 0.05)'
},
ticks: {
color: '#b0bec5'
}
},
y: {
type: 'linear',
display: true,
position: 'left',
min: 80,
max: 100,
grid: {
color: 'rgba(255, 255, 255, 0.05)'
},
ticks: {
color: '#b0bec5',
callback: function(value) {
return value + '%';
}
}
},
y1: {
type: 'linear',
display: true,
position: 'right',
min: 0,
max: 30,
grid: {
drawOnChartArea: false
},
ticks: {
color: '#b0bec5',
callback: function(value) {
return value + 'ms';
}
}
}
}
}
});
}
// Backtest Results Chart
function initBacktestChart() {
const ctx = document.getElementById('backtestChart');
if (!ctx) return;
const data = generateBacktestData();
AppState.charts.backtestChart = new Chart(ctx, {
type: 'line',
data: {
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
datasets: data.map((strategy, index) => {
const colors = ['#4caf50', '#2196f3', '#9c27b0'];
return {
label: strategy.strategy,
data: strategy.returns,
borderColor: colors[index],
backgroundColor: colors[index] + '20',
fill: false,
tension: 0.4
};
})
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'top',
labels: {
color: '#b0bec5'
}
}
},
scales: {
x: {
grid: {
color: 'rgba(255, 255, 255, 0.05)'
},
ticks: {
color: '#b0bec5'
}
},
y: {
grid: {
color: 'rgba(255, 255, 255, 0.05)'
},
ticks: {
color: '#b0bec5',
callback: function(value) {
return '$' + value.toLocaleString();
}
}
}
}
}
});
}
// Risk Management Chart
function initRiskChart() {
const ctx = document.getElementById('riskChart');
if (!ctx) return;
const data = generateRiskData();
AppState.charts.riskChart = new Chart(ctx, {
type: 'radar',
data: {
labels: data.map(d => d.metric),
datasets: [{
label: 'Risk Metrics',
data: data.map(d => d.values[0]),
backgroundColor: 'rgba(255, 152, 0, 0.2)',
borderColor: 'rgba(255, 152, 0, 1)',
pointBackgroundColor: 'rgba(255, 152, 0, 1)',
pointBorderColor: '#fff',
pointHoverBackgroundColor: '#fff',
pointHoverBorderColor: 'rgba(255, 152, 0, 1)'
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
}
},
scales: {
r: {
angleLines: {
color: 'rgba(255, 255, 255, 0.1)'
},
grid: {
color: 'rgba(255, 255, 255, 0.1)'
},
pointLabels: {
color: '#b0bec5'
},
ticks: {
color: '#b0bec5',
backdropColor: 'transparent'
},
min: 0,
max: 10
}
}
}
});
}
// Update functions for live data
function updatePortfolioChart() {
if (!AppState.charts.portfolioChart) return;
const newData = generatePortfolioData();
AppState.charts.portfolioChart.data.labels = newData.map(d => d.date);
AppState.charts.portfolioChart.data.datasets[0].data = newData.map(d => d.value);
AppState.charts.portfolioChart.update('none');
}
function updateDataQualityChart() {
if (!AppState.charts.dataQualityChart) return;
const newData = generateDataQualityData();
AppState.charts.dataQualityChart.data.labels = newData.map(d => d.date);
AppState.charts.dataQualityChart.data.datasets[0].data = newData.map(d => d.completeness);
AppState.charts.dataQualityChart.data.datasets[1].data = newData.map(d => d.latency);
AppState.charts.dataQualityChart.update('none');
}
function updateBacktestChart() {
if (!AppState.charts.backtestChart) return;
const newData = generateBacktestData();
newData.forEach((strategy, index) => {
AppState.charts.backtestChart.data.datasets[index].data = strategy.returns;
});
AppState.charts.backtestChart.update('none');
}
function updateRiskChart() {
if (!AppState.charts.riskChart) return;
const newData = generateRiskData();
AppState.charts.riskChart.data.datasets[0].data = newData.map(d => d.values[0]);
AppState.charts.riskChart.update('none');
}
// Data generation functions (same as in app.js but exported for use)
function generatePortfolioData() {
const data = [];
let value = 100000;
for (let i = 0; i < 30; i++) {
const date = new Date();
date.setDate(date.getDate() - (30 - i));
const change = (Math.random() - 0.4) * 0.02 * value;
value += change;
data.push({
date: date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }),
value: Math.max(90000, value)
});
}
return data;
}
function generateDataQualityData() {
const data = [];
for (let i = 0; i < 7; i++) {
const date = new Date();
date.setDate(date.getDate() - (7 - i));
data.push({
date: date.toLocaleDateString('en-US', { weekday: 'short' }),
completeness: 90 + Math.random() * 8,
latency: 5 + Math.random() * 15,
errors: Math.floor(Math.random() * 5)
});
}
return data;
}
function generateBacktestData() {
const strategies = ['Dual Momentum', 'Mean Reversion', 'Pairs Trading'];
const data = [];
strategies.forEach(strategy => {
let returns = 100;
const returnsData = [];
for (let i = 0; i < 12; i++) {
const monthlyReturn = (Math.random() - 0.2) * 0.1;
returns *= (1 + monthlyReturn);
returnsData.push(returns);
}
data.push({
strategy,
returns: returnsData,
sharpe: 0.8 + Math.random() * 1.5,
winRate: 50 + Math.random() * 30
});
});
return data;
}
function generateRiskData() {
const metrics = ['VaR', 'CVaR', 'Max Drawdown', 'Volatility'];
const data = [];
metrics.forEach(metric => {
const values = [];
for (let i = 0; i < 10; i++) {
values.push(Math.random() * 10);
}
data.push({ metric, values });
});
return data;
}
// Export for use in other scripts
window.MockData = {
initCharts,
updatePortfolioChart,
updateDataQualityChart,
updateBacktestChart,
updateRiskChart,
generatePortfolioData,
generateDataQualityData,
generateBacktestData,
generateRiskData
};

820
templates/index.html Normal file
View File

@@ -0,0 +1,820 @@
<!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 - Interactive Mockup</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>
<!-- Custom CSS -->
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
<style>
/* Base styles will be in css/style.css, but some critical ones here */
:root {
--primary-dark: #0a1929;
--secondary-dark: #1e2a38;
--accent-blue: #1976d2;
--accent-green: #4caf50;
--accent-red: #f44336;
--accent-purple: #9c27b0;
--accent-orange: #ff9800;
--text-primary: #ffffff;
--text-secondary: #b0bec5;
}
body {
font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
overflow-x: hidden;
}
</style>
</head>
<body class="bg-dark text-light">
<!-- Top Navigation Bar -->
<nav class="navbar navbar-dark bg-primary-dark border-bottom border-secondary-dark">
<div class="container-fluid">
<a class="navbar-brand fw-bold" href="#">
<i class="bi bi-graph-up-arrow me-2"></i>
Quantitative Trading Platform
</a>
<div class="d-flex align-items-center">
<div class="me-3">
<span class="badge bg-success">LIVE</span>
<small class="text-muted ms-2">Mock Data</small>
</div>
<div class="btn-group">
<button class="btn btn-outline-light btn-sm">
<i class="bi bi-person-circle"></i> James Tang
</button>
<button class="btn btn-outline-light btn-sm">
<i class="bi bi-gear"></i>
</button>
</div>
</div>
</div>
</nav>
<div class="container-fluid">
<div class="row">
<!-- Sidebar Navigation -->
<div class="col-md-3 col-lg-2 d-md-block bg-secondary-dark sidebar collapse border-end border-secondary-dark" id="sidebarMenu">
<div class="position-sticky pt-3">
<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link active" href="#dashboard" data-section="dashboard">
<i class="bi bi-speedometer2 me-2"></i>
Dashboard
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#data-management" data-section="data-management">
<i class="bi bi-database me-2"></i>
Data Management
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#strategy-engine" data-section="strategy-engine">
<i class="bi bi-cpu me-2"></i>
Strategy Engine
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#risk-management" data-section="risk-management">
<i class="bi bi-shield-exclamation me-2"></i>
Risk Management
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#trade-execution" data-section="trade-execution">
<i class="bi bi-arrow-left-right me-2"></i>
Trade Execution
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#reports" data-section="reports">
<i class="bi bi-file-text me-2"></i>
Reports & Analytics
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#alerts" data-section="alerts">
<i class="bi bi-bell me-2"></i>
Alerting System
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#configuration" data-section="configuration">
<i class="bi bi-sliders me-2"></i>
Configuration
</a>
</li>
</ul>
<hr class="border-secondary-dark my-4">
<div class="px-3">
<h6 class="text-uppercase text-muted mb-3">System Status</h6>
<div class="mb-2">
<small class="text-muted">Data Layer</small>
<div class="progress bg-dark" style="height: 4px;">
<div class="progress-bar bg-success" style="width: 95%"></div>
</div>
</div>
<div class="mb-2">
<small class="text-muted">Strategy Engine</small>
<div class="progress bg-dark" style="height: 4px;">
<div class="progress-bar bg-success" style="width: 88%"></div>
</div>
</div>
<div class="mb-2">
<small class="text-muted">Risk Management</small>
<div class="progress bg-dark" style="height: 4px;">
<div class="progress-bar bg-warning" style="width: 72%"></div>
</div>
</div>
</div>
</div>
</div>
<!-- Main Content Area -->
<main class="col-md-9 ms-sm-auto col-lg-10 px-md-4 pt-3">
<!-- Section: Dashboard (Default) -->
<div id="dashboard-section" class="section-content">
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom border-secondary-dark">
<h1 class="h2">
<i class="bi bi-speedometer2 text-accent-blue me-2"></i>
Dashboard
</h1>
<div class="btn-toolbar">
<div class="btn-group me-2">
<button class="btn btn-outline-light btn-sm">
<i class="bi bi-arrow-clockwise"></i> Refresh
</button>
<button class="btn btn-outline-light btn-sm">
<i class="bi bi-calendar"></i> Today
</button>
</div>
</div>
</div>
<!-- Key Metrics Cards -->
<div class="row mb-4">
<div class="col-md-3 mb-3">
<div class="card bg-primary-dark border-accent-green">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<h6 class="card-subtitle text-muted">Total P&L</h6>
<h3 class="card-title mt-2 text-success">$42,580</h3>
</div>
<i class="bi bi-graph-up text-success fs-1"></i>
</div>
<p class="card-text">
<small class="text-muted">+12.4% MTD</small>
</p>
</div>
</div>
</div>
<div class="col-md-3 mb-3">
<div class="card bg-primary-dark border-accent-red">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<h6 class="card-subtitle text-muted">Max Drawdown</h6>
<h3 class="card-title mt-2 text-danger">-$8,420</h3>
</div>
<i class="bi bi-graph-down text-danger fs-1"></i>
</div>
<p class="card-text">
<small class="text-muted">-4.2% from peak</small>
</p>
</div>
</div>
</div>
<div class="col-md-3 mb-3">
<div class="card bg-primary-dark border-accent-blue">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<h6 class="card-subtitle text-muted">Win Rate</h6>
<h3 class="card-title mt-2 text-info">64.8%</h3>
</div>
<i class="bi bi-percent text-info fs-1"></i>
</div>
<p class="card-text">
<small class="text-muted">182 winning trades</small>
</p>
</div>
</div>
</div>
<div class="col-md-3 mb-3">
<div class="card bg-primary-dark border-accent-orange">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<h6 class="card-subtitle text-muted">Active Strategies</h6>
<h3 class="card-title mt-2 text-warning">4</h3>
</div>
<i class="bi bi-cpu text-warning fs-1"></i>
</div>
<p class="card-text">
<small class="text-muted">2 in paper trading</small>
</p>
</div>
</div>
</div>
</div>
<!-- Charts Row -->
<div class="row mb-4">
<div class="col-lg-8">
<div class="card bg-primary-dark border-secondary-dark mb-3">
<div class="card-header bg-transparent border-secondary-dark">
<h6 class="mb-0">Portfolio Performance</h6>
</div>
<div class="card-body">
<canvas id="portfolioChart" height="250"></canvas>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="card bg-primary-dark border-secondary-dark mb-3">
<div class="card-header bg-transparent border-secondary-dark">
<h6 class="mb-0">Top Holdings</h6>
</div>
<div class="card-body">
<div class="list-group list-group-flush">
<div class="list-group-item bg-transparent border-secondary-dark d-flex justify-content-between">
<span>AAPL</span>
<span class="text-success">+12.4%</span>
</div>
<div class="list-group-item bg-transparent border-secondary-dark d-flex justify-content-between">
<span>MSFT</span>
<span class="text-success">+8.7%</span>
</div>
<div class="list-group-item bg-transparent border-secondary-dark d-flex justify-content-between">
<span>GOOGL</span>
<span class="text-danger">-2.3%</span>
</div>
<div class="list-group-item bg-transparent border-secondary-dark d-flex justify-content-between">
<span>TSLA</span>
<span class="text-success">+15.2%</span>
</div>
<div class="list-group-item bg-transparent border-secondary-dark d-flex justify-content-between">
<span>NVDA</span>
<span class="text-success">+24.1%</span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Recent Activity -->
<div class="row">
<div class="col-12">
<div class="card bg-primary-dark border-secondary-dark">
<div class="card-header bg-transparent border-secondary-dark d-flex justify-content-between align-items-center">
<h6 class="mb-0">Recent Trades</h6>
<button class="btn btn-sm btn-outline-light">View All</button>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-dark table-hover mb-0">
<thead>
<tr>
<th>Time</th>
<th>Symbol</th>
<th>Action</th>
<th>Quantity</th>
<th>Price</th>
<th>P&L</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<td>10:45 AM</td>
<td>AAPL</td>
<td><span class="badge bg-success">BUY</span></td>
<td>50</td>
<td>$182.34</td>
<td class="text-success">+$420</td>
<td><span class="badge bg-success">Filled</span></td>
</tr>
<tr>
<td>10:30 AM</td>
<td>TSLA</td>
<td><span class="badge bg-danger">SELL</span></td>
<td>25</td>
<td>$245.67</td>
<td class="text-success">+$1,250</td>
<td><span class="badge bg-success">Filled</span></td>
</tr>
<tr>
<td>09:15 AM</td>
<td>MSFT</td>
<td><span class="badge bg-success">BUY</span></td>
<td>100</td>
<td>$412.89</td>
<td class="text-warning">Pending</td>
<td><span class="badge bg-warning">Pending</span></td>
</tr>
<tr>
<td>Yesterday</td>
<td>GOOGL</td>
<td><span class="badge bg-danger">SELL</span></td>
<td>75</td>
<td>$145.23</td>
<td class="text-danger">-$320</td>
<td><span class="badge bg-success">Filled</span></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Section: Data Management (Hidden) -->
<div id="data-management-section" class="section-content d-none">
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom border-secondary-dark">
<h1 class="h2">
<i class="bi bi-database text-accent-blue me-2"></i>
Data Management
</h1>
<div class="btn-toolbar">
<div class="btn-group me-2">
<button class="btn btn-outline-light btn-sm">
<i class="bi bi-plus-circle"></i> Add Source
</button>
<button class="btn btn-outline-light btn-sm">
<i class="bi bi-arrow-clockwise"></i> Sync Now
</button>
</div>
</div>
</div>
<!-- Data Sources Cards -->
<div class="row mb-4">
<div class="col-md-6 mb-3">
<div class="card bg-primary-dark border-accent-blue">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-3">
<h5 class="card-title mb-0">Moomoo API</h5>
<span class="badge bg-success">Connected</span>
</div>
<p class="card-text text-muted">Real-time market data, historical OHLCV, order execution</p>
<div class="mb-3">
<small class="text-muted">Rate Limit</small>
<div class="progress bg-dark" style="height: 6px;">
<div class="progress-bar bg-info" style="width: 65%"></div>
</div>
<small class="text-muted">42/60 requests per minute</small>
</div>
<div class="d-grid gap-2">
<button class="btn btn-outline-light btn-sm">Configure</button>
<button class="btn btn-outline-danger btn-sm">Disconnect</button>
</div>
</div>
</div>
</div>
<div class="col-md-6 mb-3">
<div class="card bg-primary-dark border-accent-purple">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-3">
<h5 class="card-title mb-0">Alternative Data</h5>
<span class="badge bg-warning">Partial</span>
</div>
<p class="card-text text-muted">Economic calendar, news feeds, sentiment analysis</p>
<div class="mb-3">
<small class="text-muted">Data Quality</small>
<div class="progress bg-dark" style="height: 6px;">
<div class="progress-bar bg-warning" style="width: 78%"></div>
</div>
<small class="text-muted">78% completeness</small>
</div>
<div class="d-grid gap-2">
<button class="btn btn-outline-light btn-sm">Add Sources</button>
<button class="btn btn-outline-info btn-sm">Quality Report</button>
</div>
</div>
</div>
</div>
</div>
<!-- Data Quality Monitoring -->
<div class="row mb-4">
<div class="col-12">
<div class="card bg-primary-dark border-secondary-dark">
<div class="card-header bg-transparent border-secondary-dark">
<h6 class="mb-0">Data Quality Dashboard</h6>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-3 text-center mb-3">
<div class="display-4 text-success">99.2%</div>
<small class="text-muted">Uptime</small>
</div>
<div class="col-md-3 text-center mb-3">
<div class="display-4 text-info">12ms</div>
<small class="text-muted">Avg Latency</small>
</div>
<div class="col-md-3 text-center mb-3">
<div class="display-4 text-warning">0.8%</div>
<small class="text-muted">Missing Data</small>
</div>
<div class="col-md-3 text-center mb-3">
<div class="display-4 text-danger">3</div>
<small class="text-muted">Anomalies Today</small>
</div>
</div>
<canvas id="dataQualityChart" height="150"></canvas>
</div>
</div>
</div>
</div>
</div>
<!-- Section: Strategy Engine (Hidden) -->
<div id="strategy-engine-section" class="section-content d-none">
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom border-secondary-dark">
<h1 class="h2">
<i class="bi bi-cpu text-accent-purple me-2"></i>
Strategy Engine
</h1>
<div class="btn-toolbar">
<div class="btn-group me-2">
<button class="btn btn-outline-light btn-sm">
<i class="bi bi-plus-circle"></i> New Strategy
</button>
<button class="btn btn-outline-light btn-sm">
<i class="bi bi-play-circle"></i> Run Backtest
</button>
</div>
</div>
</div>
<!-- Active Strategies -->
<div class="row mb-4">
<div class="col-12">
<div class="card bg-primary-dark border-secondary-dark">
<div class="card-header bg-transparent border-secondary-dark d-flex justify-content-between align-items-center">
<h6 class="mb-0">Active Strategies</h6>
<div class="btn-group">
<button class="btn btn-sm btn-outline-success">Live</button>
<button class="btn btn-sm btn-outline-warning">Paper</button>
<button class="btn btn-sm btn-outline-secondary">Paused</button>
</div>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-dark table-hover mb-0">
<thead>
<tr>
<th>Strategy</th>
<th>Type</th>
<th>Status</th>
<th>P&L</th>
<th>Win Rate</th>
<th>Sharpe</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<strong>Dual Momentum</strong>
<br><small class="text-muted">Large-cap US stocks</small>
</td>
<td><span class="badge bg-info">Trend Following</span></td>
<td><span class="badge bg-success">Live</span></td>
<td class="text-success">+$18,420</td>
<td>68.2%</td>
<td>1.85</td>
<td>
<button class="btn btn-sm btn-outline-light">Edit</button>
<button class="btn btn-sm btn-outline-warning">Pause</button>
</td>
</tr>
<tr>
<td>
<strong>Mean Reversion</strong>
<br><small class="text-muted">S&P 500 components</small>
</td>
<td><span class="badge bg-warning">Mean Reversion</span></td>
<td><span class="badge bg-warning">Paper Trading</span></td>
<td class="text-success">+$5,230</td>
<td>59.7%</td>
<td>1.12</td>
<td>
<button class="btn btn-sm btn-outline-light">Edit</button>
<button class="btn btn-sm btn-outline-success">Go Live</button>
</td>
</tr>
<tr>
<td>
<strong>Pairs Trading</strong>
<br><small class="text-muted">Tech sector pairs</small>
</td>
<td><span class="badge bg-purple">Statistical Arbitrage</span></td>
<td><span class="badge bg-secondary">Paused</span></td>
<td class="text-danger">-$2,150</td>
<td>52.4%</td>
<td>0.45</td>
<td>
<button class="btn btn-sm btn-outline-light">Edit</button>
<button class="btn btn-sm btn-outline-warning">Resume</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<!-- Strategy Configuration -->
<div class="row">
<div class="col-md-6 mb-3">
<div class="card bg-primary-dark border-secondary-dark">
<div class="card-header bg-transparent border-secondary-dark">
<h6 class="mb-0">Backtest Results</h6>
</div>
<div class="card-body">
<canvas id="backtestChart" height="200"></canvas>
</div>
</div>
</div>
<div class="col-md-6 mb-3">
<div class="card bg-primary-dark border-secondary-dark">
<div class="card-header bg-transparent border-secondary-dark">
<h6 class="mb-0">Create New Strategy</h6>
</div>
<div class="card-body">
<form>
<div class="mb-3">
<label class="form-label">Strategy Name</label>
<input type="text" class="form-control bg-dark border-secondary-dark text-light" placeholder="e.g., Momentum Breakout">
</div>
<div class="mb-3">
<label class="form-label">Strategy Type</label>
<select class="form-select bg-dark border-secondary-dark text-light">
<option>Trend Following</option>
<option>Mean Reversion</option>
<option>Arbitrage</option>
<option>Machine Learning</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">Initial Capital</label>
<div class="input-group">
<span class="input-group-text bg-dark border-secondary-dark text-light">$</span>
<input type="number" class="form-control bg-dark border-secondary-dark text-light" value="100000">
</div>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary">Create & Backtest</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<!-- Section: Risk Management (Hidden) -->
<div id="risk-management-section" class="section-content d-none">
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom border-secondary-dark">
<h1 class="h2">
<i class="bi bi-shield-exclamation text-accent-orange me-2"></i>
Risk Management
</h1>
<div class="btn-toolbar">
<div class="btn-group me-2">
<button class="btn btn-outline-light btn-sm">
<i class="bi bi-shield-check"></i> Run Audit
</button>
<button class="btn btn-outline-light btn-sm">
<i class="bi bi-file-earmark-text"></i> Export Report
</button>
</div>
</div>
</div>
<!-- Risk Metrics -->
<div class="row mb-4">
<div class="col-md-3 mb-3">
<div class="card bg-primary-dark border-danger">
<div class="card-body text-center">
<div class="display-4 text-danger">4.2%</div>
<small class="text-muted">Max Drawdown</small>
</div>
</div>
</div>
<div class="col-md-3 mb-3">
<div class="card bg-primary-dark border-warning">
<div class="card-body text-center">
<div class="display-4 text-warning">1.85</div>
<small class="text-muted">Sharpe Ratio</small>
</div>
</div>
</div>
<div class="col-md-3 mb-3">
<div class="card bg-primary-dark border-info">
<div class="card-body text-center">
<div class="display-4 text-info">12.4%</div>
<small class="text-muted">Volatility (Annual)</small>
</div>
</div>
</div>
<div class="col-md-3 mb-3">
<div class="card bg-primary-dark border-success">
<div class="card-body text-center">
<div class="display-4 text-success">95%</div>
<small class="text-muted">VaR (1-day, 95%)</small>
</div>
</div>
</div>
</div>
<!-- Risk Controls -->
<div class="row">
<div class="col-md-6 mb-3">
<div class="card bg-primary-dark border-secondary-dark">
<div class="card-header bg-transparent border-secondary-dark">
<h6 class="mb-0">Circuit Breakers</h6>
</div>
<div class="card-body">
<div class="mb-3">
<label class="form-label">Daily Loss Limit</label>
<div class="input-group">
<input type="number" class="form-control bg-dark border-secondary-dark text-light" value="5000">
<span class="input-group-text bg-dark border-secondary-dark text-light">USD</span>
</div>
<div class="form-text text-muted">Trading pauses if daily loss exceeds this amount</div>
</div>
<div class="mb-3">
<label class="form-label">Max Position Size</label>
<div class="input-group">
<input type="number" class="form-control bg-dark border-secondary-dark text-light" value="25">
<span class="input-group-text bg-dark border-secondary-dark text-light">%</span>
</div>
<div class="form-text text-muted">Maximum allocation to any single position</div>
</div>
<div class="mb-3">
<label class="form-label">Sector Concentration</label>
<div class="input-group">
<input type="number" class="form-control bg-dark border-secondary-dark text-light" value="40">
<span class="input-group-text bg-dark border-secondary-dark text-light">%</span>
</div>
<div class="form-text text-muted">Maximum allocation to any single sector</div>
</div>
<div class="d-grid">
<button class="btn btn-primary">Update Limits</button>
</div>
</div>
</div>
</div>
<div class="col-md-6 mb-3">
<div class="card bg-primary-dark border-secondary-dark">
<div class="card-header bg-transparent border-secondary-dark">
<h6 class="mb-0">Risk Dashboard</h6>
</div>
<div class="card-body">
<canvas id="riskChart" height="250"></canvas>
<div class="mt-3">
<div class="alert alert-warning mb-2">
<i class="bi bi-exclamation-triangle me-2"></i>
<strong>Warning:</strong> Tech sector concentration at 38% (limit: 40%)
</div>
<div class="alert alert-success mb-0">
<i class="bi bi-check-circle me-2"></i>
<strong>All Systems Normal:</strong> All risk limits within bounds
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Other sections would follow similar pattern -->
<!-- For brevity, showing only key sections in this mockup -->
<!-- Section: Trade Execution (Hidden - Placeholder) -->
<div id="trade-execution-section" class="section-content d-none">
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom border-secondary-dark">
<h1 class="h2">
<i class="bi bi-arrow-left-right text-accent-green me-2"></i>
Trade Execution
</h1>
</div>
<div class="alert alert-info">
<i class="bi bi-info-circle me-2"></i>
Trade Execution interface would show real-time order management, execution quality metrics, and manual trading controls.
</div>
</div>
<!-- Section: Reports (Hidden - Placeholder) -->
<div id="reports-section" class="section-content d-none">
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom border-secondary-dark">
<h1 class="h2">
<i class="bi bi-file-text text-accent-blue me-2"></i>
Reports & Analytics
</h1>
</div>
<div class="alert alert-info">
<i class="bi bi-info-circle me-2"></i>
Reports section would include performance attribution, trade journals, compliance reports, and export functionality.
</div>
</div>
<!-- Section: Alerts (Hidden - Placeholder) -->
<div id="alerts-section" class="section-content d-none">
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom border-secondary-dark">
<h1 class="h2">
<i class="bi bi-bell text-accent-orange me-2"></i>
Alerting System
</h1>
</div>
<div class="alert alert-info">
<i class="bi bi-info-circle me-2"></i>
Alerting system would configure Telegram/email notifications for critical events, risk breaches, and performance milestones.
</div>
</div>
<!-- Section: Configuration (Hidden - Placeholder) -->
<div id="configuration-section" class="section-content d-none">
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom border-secondary-dark">
<h1 class="h2">
<i class="bi bi-sliders text-accent-purple me-2"></i>
Configuration
</h1>
</div>
<div class="alert alert-info">
<i class="bi bi-info-circle me-2"></i>
Configuration interface would manage API keys, system settings, user preferences, and integration settings.
</div>
</div>
</main>
</div>
</div>
<!-- Bootstrap JS Bundle with Popper -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<!-- Custom JavaScript -->
<script src="{{ url_for('static', filename='js/mock-data.js') }}"></script>
<script src="{{ url_for('static', filename='js/app.js') }}"></script>
<script>
// Initialize the mockup when page loads
document.addEventListener('DOMContentLoaded', function() {
// Initialize the application
if (typeof initApp === 'function') {
initApp();
} else {
// Fallback initialization
console.log('Using fallback initialization');
// Set up navigation
const navLinks = document.querySelectorAll('.nav-link[data-section]');
navLinks.forEach(link => {
link.addEventListener('click', function(e) {
e.preventDefault();
const section = this.getAttribute('data-section');
// Hide all sections
document.querySelectorAll('.section-content').forEach(s => {
s.classList.add('d-none');
});
// Show selected section
const target = document.getElementById(`${section}-section`);
if (target) {
target.classList.remove('d-none');
}
// Update active state
navLinks.forEach(l => l.classList.remove('active'));
this.classList.add('active');
});
});
// Show dashboard by default
document.getElementById('dashboard-section').classList.remove('d-none');
document.querySelector('.nav-link[data-section="dashboard"]').classList.add('active');
}
});
</script>
</body>
</html>