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.
"""
from flask import Flask, render_template, jsonify
from flask import Flask, render_template, jsonify, request
import random
from datetime import datetime, timedelta
from data.connectors import create_moomoo_client, Interval
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()
}
# Initialize MoomooClient in mock mode (using our LLM scenario generator)
client = create_moomoo_client(mock_mode=True)
@app.route('/')
def dashboard():
"""Serve the dashboard HTML page."""
return render_template('dashboard.html')
return render_template('index.html')
@app.route('/api/metrics')
def metrics():
"""API endpoint returning current metrics."""
return jsonify(generate_mock_metrics())
@app.route('/api/market-data/<symbol>')
def market_data(symbol):
"""
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')
def health():