Edwin Salguero
feat(ui): add robust multi-interface UI system (Streamlit, Dash, Jupyter, WebSocket) with launcher, docs, and integration tests [skip ci]
9f44dc9
| """ | |
| Dash UI for Algorithmic Trading System | |
| Enterprise-grade interactive dashboard with: | |
| - Real-time market data visualization | |
| - Advanced trading analytics | |
| - Portfolio management | |
| - Risk monitoring | |
| - Strategy backtesting | |
| """ | |
| import dash | |
| from dash import dcc, html, Input, Output, State, callback_context | |
| import dash_bootstrap_components as dbc | |
| from dash_bootstrap_components import themes | |
| import plotly.graph_objects as go | |
| import plotly.express as px | |
| import pandas as pd | |
| import numpy as np | |
| import yaml | |
| import os | |
| import sys | |
| from datetime import datetime, timedelta | |
| import asyncio | |
| import threading | |
| import time | |
| from typing import Dict, Any, Optional | |
| # Add project root to path | |
| sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | |
| from agentic_ai_system.main import load_config | |
| from agentic_ai_system.data_ingestion import load_data, validate_data, add_technical_indicators | |
| from agentic_ai_system.finrl_agent import FinRLAgent, FinRLConfig | |
| from agentic_ai_system.alpaca_broker import AlpacaBroker | |
| from agentic_ai_system.orchestrator import run_backtest, run_live_trading | |
| class TradingDashApp: | |
| def __init__(self): | |
| self.app = dash.Dash( | |
| __name__, | |
| external_stylesheets=[ | |
| themes.BOOTSTRAP, | |
| "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" | |
| ], | |
| suppress_callback_exceptions=True | |
| ) | |
| self.config = None | |
| self.data = None | |
| self.alpaca_broker = None | |
| self.finrl_agent = None | |
| self.setup_layout() | |
| self.setup_callbacks() | |
| def setup_layout(self): | |
| """Setup the main application layout""" | |
| self.app.layout = dbc.Container([ | |
| # Header | |
| dbc.Row([ | |
| dbc.Col([ | |
| html.H1([ | |
| html.I(className="fas fa-chart-line me-3"), | |
| "Algorithmic Trading System" | |
| ], className="text-primary mb-4 text-center") | |
| ]) | |
| ]), | |
| # Navigation tabs | |
| dbc.Tabs([ | |
| dbc.Tab(self.create_dashboard_tab(), label="Dashboard", tab_id="dashboard"), | |
| dbc.Tab(self.create_data_tab(), label="Data", tab_id="data"), | |
| dbc.Tab(self.create_trading_tab(), label="Trading", tab_id="trading"), | |
| dbc.Tab(self.create_analytics_tab(), label="Analytics", tab_id="analytics"), | |
| dbc.Tab(self.create_portfolio_tab(), label="Portfolio", tab_id="portfolio"), | |
| dbc.Tab(self.create_settings_tab(), label="Settings", tab_id="settings") | |
| ], id="tabs", active_tab="dashboard"), | |
| # Store components for data persistence | |
| dcc.Store(id="config-store"), | |
| dcc.Store(id="data-store"), | |
| dcc.Store(id="alpaca-store"), | |
| dcc.Store(id="finrl-store"), | |
| dcc.Store(id="trading-status-store"), | |
| # Interval for real-time updates | |
| dcc.Interval( | |
| id="interval-component", | |
| interval=5*1000, # 5 seconds | |
| n_intervals=0 | |
| ) | |
| ], fluid=True) | |
| def create_dashboard_tab(self): | |
| """Create the main dashboard tab""" | |
| return dbc.Container([ | |
| # System status cards | |
| dbc.Row([ | |
| dbc.Col(self.create_status_card("Trading Status", "Active", "success"), width=3), | |
| dbc.Col(self.create_status_card("Portfolio Value", "$100,000", "info"), width=3), | |
| dbc.Col(self.create_status_card("Daily P&L", "+$1,250", "success"), width=3), | |
| dbc.Col(self.create_status_card("Risk Level", "Low", "warning"), width=3) | |
| ], className="mb-4"), | |
| # Charts row | |
| dbc.Row([ | |
| dbc.Col([ | |
| dbc.Card([ | |
| dbc.CardHeader("Price Chart"), | |
| dbc.CardBody([ | |
| dcc.Graph(id="price-chart", style={"height": "400px"}) | |
| ]) | |
| ]) | |
| ], width=8), | |
| dbc.Col([ | |
| dbc.Card([ | |
| dbc.CardHeader("Portfolio Allocation"), | |
| dbc.CardBody([ | |
| dcc.Graph(id="allocation-chart", style={"height": "400px"}) | |
| ]) | |
| ]) | |
| ], width=4) | |
| ], className="mb-4"), | |
| # Trading activity and alerts | |
| dbc.Row([ | |
| dbc.Col([ | |
| dbc.Card([ | |
| dbc.CardHeader("Recent Trades"), | |
| dbc.CardBody([ | |
| html.Div(id="trades-table") | |
| ]) | |
| ]) | |
| ], width=6), | |
| dbc.Col([ | |
| dbc.Card([ | |
| dbc.CardHeader("System Alerts"), | |
| dbc.CardBody([ | |
| html.Div(id="alerts-list") | |
| ]) | |
| ]) | |
| ], width=6) | |
| ]) | |
| ]) | |
| def create_data_tab(self): | |
| """Create the data management tab""" | |
| return dbc.Container([ | |
| dbc.Row([ | |
| dbc.Col([ | |
| dbc.Card([ | |
| dbc.CardHeader("Data Configuration"), | |
| dbc.CardBody([ | |
| dbc.Row([ | |
| dbc.Col([ | |
| dbc.Label("Data Source"), | |
| dbc.Select( | |
| id="data-source-select", | |
| options=[ | |
| {"label": "CSV File", "value": "csv"}, | |
| {"label": "Alpaca API", "value": "alpaca"}, | |
| {"label": "Synthetic Data", "value": "synthetic"} | |
| ], | |
| value="csv" | |
| ) | |
| ], width=4), | |
| dbc.Col([ | |
| dbc.Label("Symbol"), | |
| dbc.Input( | |
| id="symbol-input", | |
| type="text", | |
| value="AAPL", | |
| placeholder="Enter symbol" | |
| ) | |
| ], width=4), | |
| dbc.Col([ | |
| dbc.Label("Timeframe"), | |
| dbc.Select( | |
| id="timeframe-select", | |
| options=[ | |
| {"label": "1 Minute", "value": "1m"}, | |
| {"label": "5 Minutes", "value": "5m"}, | |
| {"label": "15 Minutes", "value": "15m"}, | |
| {"label": "1 Hour", "value": "1h"}, | |
| {"label": "1 Day", "value": "1d"} | |
| ], | |
| value="1m" | |
| ) | |
| ], width=4) | |
| ], className="mb-3"), | |
| dbc.Row([ | |
| dbc.Col([ | |
| dbc.Button("Load Data", id="load-data-btn", color="primary", className="me-2"), | |
| dbc.Button("Refresh Data", id="refresh-data-btn", color="secondary") | |
| ]) | |
| ]) | |
| ]) | |
| ]) | |
| ], width=6), | |
| dbc.Col([ | |
| dbc.Card([ | |
| dbc.CardHeader("Data Statistics"), | |
| dbc.CardBody([ | |
| html.Div(id="data-stats") | |
| ]) | |
| ]) | |
| ], width=6) | |
| ], className="mb-4"), | |
| # Data visualization | |
| dbc.Row([ | |
| dbc.Col([ | |
| dbc.Card([ | |
| dbc.CardHeader([ | |
| html.Span("Market Data Visualization"), | |
| dbc.ButtonGroup([ | |
| dbc.Button("Candlestick", id="candlestick-btn", size="sm"), | |
| dbc.Button("Line", id="line-btn", size="sm"), | |
| dbc.Button("Volume", id="volume-btn", size="sm") | |
| ], className="float-end") | |
| ]), | |
| dbc.CardBody([ | |
| dcc.Graph(id="market-chart", style={"height": "500px"}) | |
| ]) | |
| ]) | |
| ]) | |
| ]) | |
| ]) | |
| def create_trading_tab(self): | |
| """Create the trading controls tab""" | |
| return dbc.Container([ | |
| # Trading configuration | |
| dbc.Row([ | |
| dbc.Col([ | |
| dbc.Card([ | |
| dbc.CardHeader("Trading Configuration"), | |
| dbc.CardBody([ | |
| dbc.Row([ | |
| dbc.Col([ | |
| dbc.Label("Capital"), | |
| dbc.Input( | |
| id="capital-input", | |
| type="number", | |
| value=100000, | |
| step=1000 | |
| ) | |
| ], width=3), | |
| dbc.Col([ | |
| dbc.Label("Order Size"), | |
| dbc.Input( | |
| id="order-size-input", | |
| type="number", | |
| value=10, | |
| step=1 | |
| ) | |
| ], width=3), | |
| dbc.Col([ | |
| dbc.Label("Max Position"), | |
| dbc.Input( | |
| id="max-position-input", | |
| type="number", | |
| value=100, | |
| step=10 | |
| ) | |
| ], width=3), | |
| dbc.Col([ | |
| dbc.Label("Max Drawdown"), | |
| dbc.Input( | |
| id="max-drawdown-input", | |
| type="number", | |
| value=0.05, | |
| step=0.01, | |
| min=0, | |
| max=1 | |
| ) | |
| ], width=3) | |
| ], className="mb-3"), | |
| dbc.Row([ | |
| dbc.Col([ | |
| dbc.Button("Start Trading", id="start-trading-btn", color="success", className="me-2"), | |
| dbc.Button("Stop Trading", id="stop-trading-btn", color="danger", className="me-2"), | |
| dbc.Button("Emergency Stop", id="emergency-stop-btn", color="warning") | |
| ]) | |
| ]) | |
| ]) | |
| ]) | |
| ], width=6), | |
| dbc.Col([ | |
| dbc.Card([ | |
| dbc.CardHeader("Alpaca Connection"), | |
| dbc.CardBody([ | |
| dbc.Row([ | |
| dbc.Col([ | |
| dbc.Label("API Key"), | |
| dbc.Input( | |
| id="alpaca-api-key", | |
| type="password", | |
| placeholder="Enter Alpaca API key" | |
| ) | |
| ], width=6), | |
| dbc.Col([ | |
| dbc.Label("Secret Key"), | |
| dbc.Input( | |
| id="alpaca-secret-key", | |
| type="password", | |
| placeholder="Enter Alpaca secret key" | |
| ) | |
| ], width=6) | |
| ], className="mb-3"), | |
| dbc.Row([ | |
| dbc.Col([ | |
| dbc.Button("Connect", id="connect-alpaca-btn", color="primary", className="me-2"), | |
| dbc.Button("Disconnect", id="disconnect-alpaca-btn", color="secondary") | |
| ]) | |
| ]) | |
| ]) | |
| ]) | |
| ], width=6) | |
| ], className="mb-4"), | |
| # Trading activity | |
| dbc.Row([ | |
| dbc.Col([ | |
| dbc.Card([ | |
| dbc.CardHeader("Live Trading Activity"), | |
| dbc.CardBody([ | |
| html.Div(id="trading-activity") | |
| ]) | |
| ]) | |
| ]) | |
| ]) | |
| ]) | |
| def create_analytics_tab(self): | |
| """Create the analytics tab""" | |
| return dbc.Container([ | |
| # FinRL training | |
| dbc.Row([ | |
| dbc.Col([ | |
| dbc.Card([ | |
| dbc.CardHeader("FinRL Model Training"), | |
| dbc.CardBody([ | |
| dbc.Row([ | |
| dbc.Col([ | |
| dbc.Label("Algorithm"), | |
| dbc.Select( | |
| id="finrl-algorithm-select", | |
| options=[ | |
| {"label": "PPO", "value": "PPO"}, | |
| {"label": "A2C", "value": "A2C"}, | |
| {"label": "DDPG", "value": "DDPG"}, | |
| {"label": "TD3", "value": "TD3"} | |
| ], | |
| value="PPO" | |
| ) | |
| ], width=3), | |
| dbc.Col([ | |
| dbc.Label("Learning Rate"), | |
| dbc.Input( | |
| id="learning-rate-input", | |
| type="number", | |
| value=0.0003, | |
| step=0.0001, | |
| min=0.0001, | |
| max=0.01 | |
| ) | |
| ], width=3), | |
| dbc.Col([ | |
| dbc.Label("Training Steps"), | |
| dbc.Input( | |
| id="training-steps-input", | |
| type="number", | |
| value=100000, | |
| step=1000 | |
| ) | |
| ], width=3), | |
| dbc.Col([ | |
| dbc.Label("Batch Size"), | |
| dbc.Select( | |
| id="batch-size-select", | |
| options=[ | |
| {"label": "32", "value": 32}, | |
| {"label": "64", "value": 64}, | |
| {"label": "128", "value": 128}, | |
| {"label": "256", "value": 256} | |
| ], | |
| value=64 | |
| ) | |
| ], width=3) | |
| ], className="mb-3"), | |
| dbc.Row([ | |
| dbc.Col([ | |
| dbc.Button("Start Training", id="start-training-btn", color="primary", className="me-2"), | |
| dbc.Button("Stop Training", id="stop-training-btn", color="danger") | |
| ]) | |
| ]) | |
| ]) | |
| ]) | |
| ], width=6), | |
| dbc.Col([ | |
| dbc.Card([ | |
| dbc.CardHeader("Training Progress"), | |
| dbc.CardBody([ | |
| dbc.Progress(id="training-progress", value=0, className="mb-3"), | |
| html.Div(id="training-metrics") | |
| ]) | |
| ]) | |
| ], width=6) | |
| ], className="mb-4"), | |
| # Backtesting | |
| dbc.Row([ | |
| dbc.Col([ | |
| dbc.Card([ | |
| dbc.CardHeader("Strategy Backtesting"), | |
| dbc.CardBody([ | |
| dbc.Row([ | |
| dbc.Col([ | |
| dbc.Button("Run Backtest", id="run-backtest-btn", color="primary", className="me-2"), | |
| dbc.Button("Export Results", id="export-backtest-btn", color="secondary") | |
| ]) | |
| ]), | |
| html.Div(id="backtest-results") | |
| ]) | |
| ]) | |
| ]) | |
| ]) | |
| ]) | |
| def create_portfolio_tab(self): | |
| """Create the portfolio management tab""" | |
| return dbc.Container([ | |
| # Portfolio overview | |
| dbc.Row([ | |
| dbc.Col([ | |
| dbc.Card([ | |
| dbc.CardHeader("Portfolio Overview"), | |
| dbc.CardBody([ | |
| dbc.Row([ | |
| dbc.Col([ | |
| html.H4("Total Value", className="text-muted"), | |
| html.H3(id="total-value", children="$100,000") | |
| ], width=3), | |
| dbc.Col([ | |
| html.H4("Cash", className="text-muted"), | |
| html.H3(id="cash-value", children="$25,000") | |
| ], width=3), | |
| dbc.Col([ | |
| html.H4("Invested", className="text-muted"), | |
| html.H3(id="invested-value", children="$75,000") | |
| ], width=3), | |
| dbc.Col([ | |
| html.H4("P&L", className="text-muted"), | |
| html.H3(id="pnl-value", children="+$1,250", className="text-success") | |
| ], width=3) | |
| ]) | |
| ]) | |
| ]) | |
| ]) | |
| ], className="mb-4"), | |
| # Positions and allocation | |
| dbc.Row([ | |
| dbc.Col([ | |
| dbc.Card([ | |
| dbc.CardHeader("Current Positions"), | |
| dbc.CardBody([ | |
| html.Div(id="positions-table") | |
| ]) | |
| ]) | |
| ], width=8), | |
| dbc.Col([ | |
| dbc.Card([ | |
| dbc.CardHeader("Allocation Chart"), | |
| dbc.CardBody([ | |
| dcc.Graph(id="portfolio-allocation-chart", style={"height": "300px"}) | |
| ]) | |
| ]) | |
| ], width=4) | |
| ]) | |
| ]) | |
| def create_settings_tab(self): | |
| """Create the settings tab""" | |
| return dbc.Container([ | |
| dbc.Row([ | |
| dbc.Col([ | |
| dbc.Card([ | |
| dbc.CardHeader("System Configuration"), | |
| dbc.CardBody([ | |
| dbc.Row([ | |
| dbc.Col([ | |
| dbc.Label("Config File"), | |
| dbc.Input( | |
| id="config-file-input", | |
| type="text", | |
| value="config.yaml", | |
| placeholder="Enter config file path" | |
| ) | |
| ], width=6), | |
| dbc.Col([ | |
| dbc.Label("Log Level"), | |
| dbc.Select( | |
| id="log-level-select", | |
| options=[ | |
| {"label": "DEBUG", "value": "DEBUG"}, | |
| {"label": "INFO", "value": "INFO"}, | |
| {"label": "WARNING", "value": "WARNING"}, | |
| {"label": "ERROR", "value": "ERROR"} | |
| ], | |
| value="INFO" | |
| ) | |
| ], width=6) | |
| ], className="mb-3"), | |
| dbc.Row([ | |
| dbc.Col([ | |
| dbc.Button("Load Config", id="load-config-btn", color="primary", className="me-2"), | |
| dbc.Button("Save Config", id="save-config-btn", color="success") | |
| ]) | |
| ]) | |
| ]) | |
| ]) | |
| ], width=6), | |
| dbc.Col([ | |
| dbc.Card([ | |
| dbc.CardHeader("System Status"), | |
| dbc.CardBody([ | |
| html.Div(id="system-status") | |
| ]) | |
| ]) | |
| ], width=6) | |
| ]) | |
| ]) | |
| def create_status_card(self, title, value, color): | |
| """Create a status card component""" | |
| return dbc.Card([ | |
| dbc.CardBody([ | |
| html.H5(title, className="card-title text-muted"), | |
| html.H3(value, className=f"text-{color}") | |
| ]) | |
| ]) | |
| def setup_callbacks(self): | |
| """Setup all Dash callbacks""" | |
| def load_configuration(n_clicks, config_file): | |
| if n_clicks: | |
| try: | |
| config = load_config(config_file) | |
| return config | |
| except Exception as e: | |
| return {"error": str(e)} | |
| return dash.no_update | |
| def load_market_data(n_clicks, config): | |
| if n_clicks and config: | |
| try: | |
| data = load_data(config) | |
| if data is not None: | |
| return data.to_dict('records') | |
| except Exception as e: | |
| return {"error": str(e)} | |
| return dash.no_update | |
| def update_price_chart(data, n_intervals): | |
| if data and isinstance(data, list): | |
| df = pd.DataFrame(data) | |
| if not df.empty: | |
| fig = go.Figure(data=[go.Candlestick( | |
| x=df['timestamp'], | |
| open=df['open'], | |
| high=df['high'], | |
| low=df['low'], | |
| close=df['close'] | |
| )]) | |
| fig.update_layout( | |
| title="Market Data", | |
| xaxis_title="Date", | |
| yaxis_title="Price ($)", | |
| height=400 | |
| ) | |
| return fig | |
| return go.Figure() | |
| def update_allocation_chart(alpaca_data, n_intervals): | |
| # Mock portfolio allocation data | |
| labels = ['AAPL', 'GOOGL', 'MSFT', 'TSLA', 'Cash'] | |
| values = [30, 25, 20, 15, 10] | |
| fig = go.Figure(data=[go.Pie(labels=labels, values=values)]) | |
| fig.update_layout( | |
| title="Portfolio Allocation", | |
| height=400 | |
| ) | |
| return fig | |
| def update_trading_activity(n_intervals): | |
| # Mock trading activity | |
| trades = [ | |
| {"time": "09:30:15", "symbol": "AAPL", "action": "BUY", "quantity": 10, "price": 150.25}, | |
| {"time": "09:35:22", "symbol": "GOOGL", "action": "SELL", "quantity": 5, "price": 2750.50}, | |
| {"time": "09:40:08", "symbol": "MSFT", "action": "BUY", "quantity": 15, "price": 320.75} | |
| ] | |
| table_rows = [] | |
| for trade in trades: | |
| color = "success" if trade["action"] == "BUY" else "danger" | |
| table_rows.append( | |
| dbc.Row([ | |
| dbc.Col(trade["time"], width=2), | |
| dbc.Col(trade["symbol"], width=2), | |
| dbc.Col(trade["action"], width=2, className=f"text-{color}"), | |
| dbc.Col(str(trade["quantity"]), width=2), | |
| dbc.Col(f"${trade['price']:.2f}", width=2), | |
| dbc.Col(f"${trade['quantity'] * trade['price']:.2f}", width=2) | |
| ], className="mb-2") | |
| ) | |
| return table_rows | |
| def create_dash_app(): | |
| """Create and return the Dash application""" | |
| app = TradingDashApp() | |
| return app.app | |
| if __name__ == "__main__": | |
| app = create_dash_app() | |
| app.run_server(debug=True, host="0.0.0.0", port=8050) |