This commit is contained in:
Dobromir Popov 2025-05-24 02:01:13 +03:00
parent 509ad0ae17
commit 6e8ec97539
3 changed files with 566 additions and 0 deletions

View File

@ -18,6 +18,8 @@ import argparse
import logging import logging
import sys import sys
from pathlib import Path from pathlib import Path
from threading import Thread
import time
# Add project root to path # Add project root to path
project_root = Path(__file__).parent project_root = Path(__file__).parent
@ -179,6 +181,95 @@ def run_orchestrator_test():
logger.error(traceback.format_exc()) logger.error(traceback.format_exc())
raise raise
def run_web_dashboard(port: int = 8050, demo_mode: bool = True):
"""Run the web dashboard"""
try:
from web.dashboard import TradingDashboard
logger.info("Starting Web Dashboard...")
# Initialize components
data_provider = DataProvider(symbols=['ETH/USDT'], timeframes=['1h', '4h'])
orchestrator = TradingOrchestrator(data_provider)
# Create dashboard
dashboard = TradingDashboard(data_provider, orchestrator)
# Add orchestrator callback to send decisions to dashboard
async def decision_callback(decision):
dashboard.add_trading_decision(decision)
orchestrator.add_decision_callback(decision_callback)
if demo_mode:
# Start demo mode with mock decisions
logger.info("Starting demo mode with simulated trading decisions...")
def demo_thread():
"""Generate demo trading decisions"""
import random
import time
from datetime import datetime
from core.orchestrator import TradingDecision
actions = ['BUY', 'SELL', 'HOLD']
base_price = 3000.0
while True:
try:
# Simulate price movement
price_change = random.uniform(-50, 50)
current_price = max(base_price + price_change, 1000)
# Create mock decision
action = random.choice(actions)
confidence = random.uniform(0.6, 0.95)
decision = TradingDecision(
action=action,
confidence=confidence,
symbol='ETH/USDT',
price=current_price,
timestamp=datetime.now(),
reasoning={'demo_mode': True, 'random_decision': True},
memory_usage={'demo': 0}
)
dashboard.add_trading_decision(decision)
logger.info(f"Demo decision: {action} ETH/USDT @${current_price:.2f} (confidence: {confidence:.2f})")
# Update base price occasionally
if random.random() < 0.1:
base_price = current_price
time.sleep(5) # New decision every 5 seconds
except Exception as e:
logger.error(f"Error in demo thread: {e}")
time.sleep(10)
# Start demo thread
demo_thread_instance = Thread(target=demo_thread, daemon=True)
demo_thread_instance.start()
# Start data streaming if available
try:
logger.info("Starting real-time data streaming...")
# Don't use asyncio.run here as we're already in an event loop context
# Just log that streaming would be started in a real deployment
logger.info("Real-time streaming would be started in production deployment")
except Exception as e:
logger.warning(f"Could not start real-time streaming: {e}")
# Run dashboard
dashboard.run(port=port, debug=False)
except Exception as e:
logger.error(f"Error running web dashboard: {e}")
import traceback
logger.error(traceback.format_exc())
raise
async def main(): async def main():
"""Main entry point""" """Main entry point"""
parser = argparse.ArgumentParser(description='Clean Trading System') parser = argparse.ArgumentParser(description='Clean Trading System')
@ -187,6 +278,10 @@ async def main():
parser.add_argument('--symbol', type=str, help='Override default symbol') parser.add_argument('--symbol', type=str, help='Override default symbol')
parser.add_argument('--config', type=str, default='config.yaml', parser.add_argument('--config', type=str, default='config.yaml',
help='Configuration file path') help='Configuration file path')
parser.add_argument('--port', type=int, default=8050,
help='Port for web dashboard')
parser.add_argument('--demo', action='store_true',
help='Run web dashboard in demo mode with simulated data')
args = parser.parse_args() args = parser.parse_args()
@ -203,6 +298,8 @@ async def main():
run_data_test() run_data_test()
elif args.mode == 'orchestrator': elif args.mode == 'orchestrator':
run_orchestrator_test() run_orchestrator_test()
elif args.mode == 'web':
run_web_dashboard(port=args.port, demo_mode=args.demo)
else: else:
logger.info(f"Mode '{args.mode}' not yet implemented in clean architecture") logger.info(f"Mode '{args.mode}' not yet implemented in clean architecture")

View File

@ -0,0 +1 @@
# Web module for trading system dashboard

468
web/dashboard.py Normal file
View File

@ -0,0 +1,468 @@
"""
Trading Dashboard - Clean Web Interface
This module provides a modern, responsive web dashboard for the trading system:
- Real-time price charts with multiple timeframes
- Model performance monitoring
- Trading decisions visualization
- System health monitoring
- Memory usage tracking
"""
import asyncio
import json
import logging
import time
from datetime import datetime, timedelta
from threading import Thread
from typing import Dict, List, Optional, Any
import dash
from dash import dcc, html, Input, Output, State, callback_context
import plotly.graph_objects as go
import plotly.express as px
from plotly.subplots import make_subplots
import pandas as pd
import numpy as np
from core.config import get_config
from core.data_provider import DataProvider
from core.orchestrator import TradingOrchestrator, TradingDecision
from models import get_model_registry
logger = logging.getLogger(__name__)
class TradingDashboard:
"""Modern trading dashboard with real-time updates"""
def __init__(self, data_provider: DataProvider = None, orchestrator: TradingOrchestrator = None):
"""Initialize the dashboard"""
self.config = get_config()
self.data_provider = data_provider or DataProvider()
self.orchestrator = orchestrator or TradingOrchestrator(self.data_provider)
self.model_registry = get_model_registry()
# Dashboard state
self.recent_decisions = []
self.performance_data = {}
self.current_prices = {}
self.last_update = datetime.now()
# Create Dash app
self.app = dash.Dash(__name__, external_stylesheets=[
'https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css',
'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css'
])
# Setup layout and callbacks
self._setup_layout()
self._setup_callbacks()
logger.info("Trading Dashboard initialized")
def _setup_layout(self):
"""Setup the dashboard layout"""
self.app.layout = html.Div([
# Header
html.Div([
html.H1([
html.I(className="fas fa-chart-line me-3"),
"Trading System Dashboard"
], className="text-white mb-0"),
html.P(f"Multi-Modal AI Trading • Memory: {self.model_registry.total_memory_limit_mb/1024:.1f}GB Limit",
className="text-light mb-0 opacity-75")
], className="bg-dark p-4 mb-4"),
# Auto-refresh component
dcc.Interval(
id='interval-component',
interval=2000, # Update every 2 seconds
n_intervals=0
),
# Main content
html.Div([
# Top row - Key metrics
html.Div([
html.Div([
html.Div([
html.H4(id="current-price", className="text-success mb-1"),
html.P("Current Price", className="text-muted mb-0 small")
], className="card-body text-center")
], className="card bg-light"),
html.Div([
html.Div([
html.H4(id="total-pnl", className="mb-1"),
html.P("Total P&L", className="text-muted mb-0 small")
], className="card-body text-center")
], className="card bg-light"),
html.Div([
html.Div([
html.H4(id="win-rate", className="text-info mb-1"),
html.P("Win Rate", className="text-muted mb-0 small")
], className="card-body text-center")
], className="card bg-light"),
html.Div([
html.Div([
html.H4(id="memory-usage", className="text-warning mb-1"),
html.P("Memory Usage", className="text-muted mb-0 small")
], className="card-body text-center")
], className="card bg-light"),
], className="row g-3 mb-4"),
# Charts row
html.Div([
# Price chart
html.Div([
html.Div([
html.H5([
html.I(className="fas fa-chart-candlestick me-2"),
"Price Chart"
], className="card-title"),
dcc.Graph(id="price-chart", style={"height": "400px"})
], className="card-body")
], className="card"),
# Model performance chart
html.Div([
html.Div([
html.H5([
html.I(className="fas fa-brain me-2"),
"Model Performance"
], className="card-title"),
dcc.Graph(id="model-performance-chart", style={"height": "400px"})
], className="card-body")
], className="card")
], className="row g-3 mb-4"),
# Bottom row - Recent decisions and system status
html.Div([
# Recent decisions
html.Div([
html.Div([
html.H5([
html.I(className="fas fa-robot me-2"),
"Recent Trading Decisions"
], className="card-title"),
html.Div(id="recent-decisions", style={"maxHeight": "300px", "overflowY": "auto"})
], className="card-body")
], className="card"),
# System status
html.Div([
html.Div([
html.H5([
html.I(className="fas fa-server me-2"),
"System Status"
], className="card-title"),
html.Div(id="system-status")
], className="card-body")
], className="card")
], className="row g-3")
], className="container-fluid")
])
def _setup_callbacks(self):
"""Setup dashboard callbacks for real-time updates"""
@self.app.callback(
[
Output('current-price', 'children'),
Output('total-pnl', 'children'),
Output('total-pnl', 'className'),
Output('win-rate', 'children'),
Output('memory-usage', 'children'),
Output('price-chart', 'figure'),
Output('model-performance-chart', 'figure'),
Output('recent-decisions', 'children'),
Output('system-status', 'children')
],
[Input('interval-component', 'n_intervals')]
)
def update_dashboard(n_intervals):
"""Update all dashboard components"""
try:
# Get current prices
symbol = self.config.symbols[0] if self.config.symbols else "ETH/USDT"
current_price = self.data_provider.get_current_price(symbol)
# Get model performance metrics
performance_metrics = self.orchestrator.get_performance_metrics()
# Get memory stats
memory_stats = self.model_registry.get_memory_stats()
# Calculate P&L from recent decisions
total_pnl = 0.0
wins = 0
total_trades = len(self.recent_decisions)
for decision in self.recent_decisions[-20:]: # Last 20 decisions
if hasattr(decision, 'pnl') and decision.pnl:
total_pnl += decision.pnl
if decision.pnl > 0:
wins += 1
# Format outputs
price_text = f"${current_price:.2f}" if current_price else "Loading..."
pnl_text = f"${total_pnl:.2f}"
pnl_class = "text-success mb-1" if total_pnl >= 0 else "text-danger mb-1"
win_rate_text = f"{(wins/total_trades*100):.1f}%" if total_trades > 0 else "0.0%"
memory_text = f"{memory_stats['utilization_percent']:.1f}%"
# Create charts
price_chart = self._create_price_chart(symbol)
performance_chart = self._create_performance_chart(performance_metrics)
# Create recent decisions list
decisions_list = self._create_decisions_list()
# Create system status
system_status = self._create_system_status(memory_stats)
return (
price_text, pnl_text, pnl_class, win_rate_text, memory_text,
price_chart, performance_chart, decisions_list, system_status
)
except Exception as e:
logger.error(f"Error updating dashboard: {e}")
# Return safe defaults
empty_fig = go.Figure()
empty_fig.add_annotation(text="Loading...", xref="paper", yref="paper", x=0.5, y=0.5)
return (
"Loading...", "$0.00", "text-muted mb-1", "0.0%", "0.0%",
empty_fig, empty_fig, [], html.P("Loading system status...")
)
def _create_price_chart(self, symbol: str) -> go.Figure:
"""Create price chart with multiple timeframes"""
try:
# Get recent data
df = self.data_provider.get_latest_candles(symbol, '1h', limit=24)
if df.empty:
fig = go.Figure()
fig.add_annotation(text="No data available", xref="paper", yref="paper", x=0.5, y=0.5)
return fig
# Create candlestick chart
fig = go.Figure(data=[go.Candlestick(
x=df['timestamp'],
open=df['open'],
high=df['high'],
low=df['low'],
close=df['close'],
name=symbol
)])
# Add moving averages if available
if 'sma_20' in df.columns:
fig.add_trace(go.Scatter(
x=df['timestamp'],
y=df['sma_20'],
name='SMA 20',
line=dict(color='orange', width=1)
))
# Mark recent trading decisions
for decision in self.recent_decisions[-10:]:
if hasattr(decision, 'timestamp') and hasattr(decision, 'price'):
color = 'green' if decision.action == 'BUY' else 'red' if decision.action == 'SELL' else 'gray'
fig.add_trace(go.Scatter(
x=[decision.timestamp],
y=[decision.price],
mode='markers',
marker=dict(color=color, size=10, symbol='triangle-up' if decision.action == 'BUY' else 'triangle-down'),
name=f"{decision.action}",
showlegend=False
))
fig.update_layout(
title=f"{symbol} Price Chart (1H)",
template="plotly_dark",
height=400,
xaxis_rangeslider_visible=False,
margin=dict(l=0, r=0, t=30, b=0)
)
return fig
except Exception as e:
logger.error(f"Error creating price chart: {e}")
fig = go.Figure()
fig.add_annotation(text=f"Error: {str(e)}", xref="paper", yref="paper", x=0.5, y=0.5)
return fig
def _create_performance_chart(self, performance_metrics: Dict) -> go.Figure:
"""Create model performance comparison chart"""
try:
if not performance_metrics.get('model_performance'):
fig = go.Figure()
fig.add_annotation(text="No model performance data", xref="paper", yref="paper", x=0.5, y=0.5)
return fig
models = list(performance_metrics['model_performance'].keys())
accuracies = [performance_metrics['model_performance'][model]['accuracy'] * 100
for model in models]
fig = go.Figure(data=[
go.Bar(x=models, y=accuracies, marker_color=['#1f77b4', '#ff7f0e', '#2ca02c'])
])
fig.update_layout(
title="Model Accuracy Comparison",
yaxis_title="Accuracy (%)",
template="plotly_dark",
height=400,
margin=dict(l=0, r=0, t=30, b=0)
)
return fig
except Exception as e:
logger.error(f"Error creating performance chart: {e}")
fig = go.Figure()
fig.add_annotation(text=f"Error: {str(e)}", xref="paper", yref="paper", x=0.5, y=0.5)
return fig
def _create_decisions_list(self) -> List:
"""Create list of recent trading decisions"""
try:
if not self.recent_decisions:
return [html.P("No recent decisions", className="text-muted")]
decisions_html = []
for decision in self.recent_decisions[-10:][::-1]: # Last 10, newest first
# Determine action color and icon
if decision.action == 'BUY':
action_class = "text-success"
icon_class = "fas fa-arrow-up"
elif decision.action == 'SELL':
action_class = "text-danger"
icon_class = "fas fa-arrow-down"
else:
action_class = "text-secondary"
icon_class = "fas fa-minus"
time_str = decision.timestamp.strftime("%H:%M:%S") if hasattr(decision, 'timestamp') else "N/A"
confidence_pct = f"{decision.confidence*100:.1f}%" if hasattr(decision, 'confidence') else "N/A"
decisions_html.append(
html.Div([
html.Div([
html.I(className=f"{icon_class} me-2"),
html.Strong(decision.action, className=action_class),
html.Span(f" {decision.symbol} ", className="text-muted"),
html.Small(f"@${decision.price:.2f}", className="text-muted")
], className="d-flex align-items-center"),
html.Small([
html.Span(f"Confidence: {confidence_pct}", className="text-info"),
html.Span(time_str, className="text-muted")
])
], className="border-bottom pb-2 mb-2")
)
return decisions_html
except Exception as e:
logger.error(f"Error creating decisions list: {e}")
return [html.P(f"Error: {str(e)}", className="text-danger")]
def _create_system_status(self, memory_stats: Dict) -> List:
"""Create system status display"""
try:
status_items = []
# Memory usage
memory_pct = memory_stats.get('utilization_percent', 0)
memory_class = "text-success" if memory_pct < 70 else "text-warning" if memory_pct < 90 else "text-danger"
status_items.append(
html.Div([
html.I(className="fas fa-memory me-2"),
html.Span("Memory: "),
html.Strong(f"{memory_pct:.1f}%", className=memory_class),
html.Small(f" ({memory_stats.get('total_used_mb', 0):.0f}MB / {memory_stats.get('total_limit_mb', 0):.0f}MB)", className="text-muted")
], className="mb-2")
)
# Model status
models_count = len(memory_stats.get('models', {}))
status_items.append(
html.Div([
html.I(className="fas fa-brain me-2"),
html.Span("Models: "),
html.Strong(f"{models_count} active", className="text-info")
], className="mb-2")
)
# Data provider status
data_health = self.data_provider.health_check()
streaming_status = "✓ Streaming" if data_health.get('streaming') else "✗ Offline"
streaming_class = "text-success" if data_health.get('streaming') else "text-danger"
status_items.append(
html.Div([
html.I(className="fas fa-wifi me-2"),
html.Span("Data: "),
html.Strong(streaming_status, className=streaming_class)
], className="mb-2")
)
# System uptime
uptime = datetime.now() - self.last_update
status_items.append(
html.Div([
html.I(className="fas fa-clock me-2"),
html.Span("Uptime: "),
html.Strong(f"{uptime.seconds//3600:02d}:{(uptime.seconds//60)%60:02d}:{uptime.seconds%60:02d}", className="text-info")
], className="mb-2")
)
return status_items
except Exception as e:
logger.error(f"Error creating system status: {e}")
return [html.P(f"Error: {str(e)}", className="text-danger")]
def add_trading_decision(self, decision: TradingDecision):
"""Add a trading decision to the dashboard"""
self.recent_decisions.append(decision)
# Keep only last 100 decisions
if len(self.recent_decisions) > 100:
self.recent_decisions = self.recent_decisions[-100:]
def run(self, host: str = '127.0.0.1', port: int = 8050, debug: bool = False):
"""Run the dashboard server"""
try:
logger.info("="*60)
logger.info("STARTING TRADING DASHBOARD")
logger.info(f"ACCESS WEB UI AT: http://{host}:{port}/")
logger.info("Real-time trading data and charts")
logger.info("AI model performance monitoring")
logger.info("Memory usage tracking")
logger.info("="*60)
# Run the app (updated API for newer Dash versions)
self.app.run(
host=host,
port=port,
debug=debug,
use_reloader=False, # Disable reloader to avoid conflicts
threaded=True # Enable threading for better performance
)
except Exception as e:
logger.error(f"Error running dashboard: {e}")
raise
# Convenience function for integration
def create_dashboard(data_provider: DataProvider = None, orchestrator: TradingOrchestrator = None) -> TradingDashboard:
"""Create and return a trading dashboard instance"""
return TradingDashboard(data_provider, orchestrator)