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:
122
app.py
122
app.py
@@ -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():
|
||||
|
||||
Reference in New Issue
Block a user