From 0b5fa074989ef926d6aaa96a1f3c4f2f36ef3959 Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Tue, 29 Jul 2025 19:02:44 +0300 Subject: [PATCH] ui fixes --- data/ui_state.json | 16 ++++--- web/clean_dashboard.py | 96 ++++++++++++++++++++++++++-------------- web/component_manager.py | 32 ++++++++++---- web/layout_manager.py | 55 ++++++++++++++++++----- 4 files changed, 143 insertions(+), 56 deletions(-) diff --git a/data/ui_state.json b/data/ui_state.json index fcafb5e..3a20b14 100644 --- a/data/ui_state.json +++ b/data/ui_state.json @@ -9,15 +9,21 @@ "training_enabled": true }, "cob_rl": { - "inference_enabled": true, + "inference_enabled": false, "training_enabled": true }, "decision_fusion": { "inference_enabled": false, - "training_enabled": false + "training_enabled": true + }, + "transformer": { + "inference_enabled": false, + "training_enabled": true + }, + "dqn_agent": { + "inference_enabled": false, + "training_enabled": true } - - }, - "timestamp": "2025-07-29T15:55:43.690404" + "timestamp": "2025-07-29T18:37:29.759605" } \ No newline at end of file diff --git a/web/clean_dashboard.py b/web/clean_dashboard.py index 6c6baeb..68336b3 100644 --- a/web/clean_dashboard.py +++ b/web/clean_dashboard.py @@ -327,7 +327,8 @@ class CleanTradingDashboard: self.app = 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' - ], suppress_callback_exceptions=True) + ]) + #, suppress_callback_exceptions=True) # Suppress Dash development mode logging self.app.enable_dev_tools(debug=False, dev_tools_silence_routes_logging=True) @@ -1363,43 +1364,72 @@ class CleanTradingDashboard: error_msg = html.P(f"COB Error: {str(e)}", className="text-danger small") return error_msg, error_msg + # Original training metrics callback - temporarily disabled for testing + # @self.app.callback( + # Output('training-metrics', 'children'), + # [Input('slow-interval-component', 'n_intervals'), + # Input('fast-interval-component', 'n_intervals'), # Add fast interval for testing + # Input('refresh-training-metrics-btn', 'n_clicks')] # Add manual refresh button + # ) + # def update_training_metrics(slow_intervals, fast_intervals, n_clicks): + # """Update training metrics""" + # logger.info(f"update_training_metrics callback triggered with slow_intervals={slow_intervals}, fast_intervals={fast_intervals}, n_clicks={n_clicks}") + # try: + # # Get toggle states from orchestrator + # toggle_states = {} + # if self.orchestrator: + # # Get all available models dynamically + # available_models = self._get_available_models() + # logger.info(f"Available models: {list(available_models.keys())}") + # for model_name in available_models.keys(): + # toggle_states[model_name] = self.orchestrator.get_model_toggle_state(model_name) + # else: + # # Fallback to dashboard dynamic state + # toggle_states = {} + # for model_name, state in self.model_toggle_states.items(): + # toggle_states[model_name] = state + # # Now using slow-interval-component (10s) - no batching needed + # + # logger.info(f"Getting training metrics with toggle_states: {toggle_states}") + # metrics_data = self._get_training_metrics(toggle_states) + # logger.info(f"update_training_metrics callback: got metrics_data type={type(metrics_data)}") + # if metrics_data and isinstance(metrics_data, dict): + # logger.info(f"Metrics data keys: {list(metrics_data.keys())}") + # if 'loaded_models' in metrics_data: + # logger.info(f"Loaded models count: {len(metrics_data['loaded_models'])}") + # logger.info(f"Loaded model names: {list(metrics_data['loaded_models'].keys())}") + # else: + # logger.warning("No 'loaded_models' key in metrics_data!") + # else: + # logger.warning(f"Invalid metrics_data: {metrics_data}") + # + # logger.info("Formatting training metrics...") + # formatted_metrics = self.component_manager.format_training_metrics(metrics_data) + # logger.info(f"Formatted metrics type: {type(formatted_metrics)}, length: {len(formatted_metrics) if isinstance(formatted_metrics, list) else 'N/A'}") + # return formatted_metrics + # except PreventUpdate: + # logger.info("PreventUpdate raised in training metrics callback") + # raise + # except Exception as e: + # logger.error(f"Error updating training metrics: {e}") + # import traceback + # logger.error(f"Traceback: {traceback.format_exc()}") + # return [html.P(f"Error: {str(e)}", className="text-danger")] + + # Test callback for training metrics @self.app.callback( Output('training-metrics', 'children'), - [Input('slow-interval-component', 'n_intervals')] # OPTIMIZED: Move to 10s interval + [Input('refresh-training-metrics-btn', 'n_clicks')], + prevent_initial_call=False ) - def update_training_metrics(n): - """Update training metrics""" + def test_training_metrics_callback(n_clicks): + """Test callback for training metrics""" + logger.info(f"test_training_metrics_callback triggered with n_clicks={n_clicks}") try: - # Get toggle states from orchestrator - toggle_states = {} - if self.orchestrator: - # Get all available models dynamically - available_models = self._get_available_models() - for model_name in available_models.keys(): - toggle_states[model_name] = self.orchestrator.get_model_toggle_state(model_name) - else: - # Fallback to dashboard dynamic state - toggle_states = {} - for model_name, state in self.model_toggle_states.items(): - toggle_states[model_name] = state - # Now using slow-interval-component (10s) - no batching needed - - metrics_data = self._get_training_metrics(toggle_states) - logger.debug(f"update_training_metrics callback: got metrics_data type={type(metrics_data)}") - if metrics_data and isinstance(metrics_data, dict): - logger.debug(f"Metrics data keys: {list(metrics_data.keys())}") - if 'loaded_models' in metrics_data: - logger.debug(f"Loaded models count: {len(metrics_data['loaded_models'])}") - logger.debug(f"Loaded model names: {list(metrics_data['loaded_models'].keys())}") - else: - logger.warning("No 'loaded_models' key in metrics_data!") - else: - logger.warning(f"Invalid metrics_data: {metrics_data}") - return self.component_manager.format_training_metrics(metrics_data) - except PreventUpdate: - raise + # Return a simple test message + return [html.P("Training metrics test - callback is working!", className="text-success")] except Exception as e: - logger.error(f"Error updating training metrics: {e}") + logger.error(f"Error in test callback: {e}") return [html.P(f"Error: {str(e)}", className="text-danger")] # Manual trading buttons diff --git a/web/component_manager.py b/web/component_manager.py index 70c9a24..99f102b 100644 --- a/web/component_manager.py +++ b/web/component_manager.py @@ -140,7 +140,8 @@ class DashboardComponentManager: # Create table headers headers = html.Thead([ html.Tr([ - html.Th("Time", className="small"), + html.Th("Entry Time", className="small"), + html.Th("Exit Time", className="small"), html.Th("Side", className="small"), html.Th("Size", className="small"), html.Th("Entry", className="small"), @@ -158,6 +159,7 @@ class DashboardComponentManager: if hasattr(trade, 'entry_time'): # This is a trade object entry_time = getattr(trade, 'entry_time', 'Unknown') + exit_time = getattr(trade, 'exit_time', 'Unknown') side = getattr(trade, 'side', 'UNKNOWN') size = getattr(trade, 'size', 0) entry_price = getattr(trade, 'entry_price', 0) @@ -168,6 +170,7 @@ class DashboardComponentManager: else: # This is a dictionary format entry_time = trade.get('entry_time', 'Unknown') + exit_time = trade.get('exit_time', 'Unknown') side = trade.get('side', 'UNKNOWN') size = trade.get('quantity', trade.get('size', 0)) # Try 'quantity' first, then 'size' entry_price = trade.get('entry_price', 0) @@ -176,11 +179,17 @@ class DashboardComponentManager: fees = trade.get('fees', 0) hold_time_seconds = trade.get('hold_time_seconds', 0.0) - # Format time + # Format entry time if isinstance(entry_time, datetime): - time_str = entry_time.strftime('%H:%M:%S') + entry_time_str = entry_time.strftime('%H:%M:%S') else: - time_str = str(entry_time) + entry_time_str = str(entry_time) + + # Format exit time + if isinstance(exit_time, datetime): + exit_time_str = exit_time.strftime('%H:%M:%S') + else: + exit_time_str = str(exit_time) # Determine P&L color pnl_class = "text-success" if pnl >= 0 else "text-danger" @@ -197,7 +206,8 @@ class DashboardComponentManager: net_pnl = pnl - fees row = html.Tr([ - html.Td(time_str, className="small"), + html.Td(entry_time_str, className="small"), + html.Td(exit_time_str, className="small"), html.Td(side, className=f"small {side_class}"), html.Td(f"${position_size_usd:.2f}", className="small"), # Show size in USD html.Td(f"${entry_price:.2f}", className="small"), @@ -714,11 +724,11 @@ class DashboardComponentManager: """Format training metrics for display - Enhanced with loaded models""" try: # DEBUG: Log what we're receiving - logger.debug(f"format_training_metrics received: {type(metrics_data)}") + logger.info(f"format_training_metrics received: {type(metrics_data)}") if metrics_data: - logger.debug(f"Metrics keys: {list(metrics_data.keys()) if isinstance(metrics_data, dict) else 'Not a dict'}") + logger.info(f"Metrics keys: {list(metrics_data.keys()) if isinstance(metrics_data, dict) else 'Not a dict'}") if isinstance(metrics_data, dict) and 'loaded_models' in metrics_data: - logger.debug(f"Loaded models: {list(metrics_data['loaded_models'].keys())}") + logger.info(f"Loaded models: {list(metrics_data['loaded_models'].keys())}") if not metrics_data or 'error' in metrics_data: logger.warning(f"No training data or error in metrics_data: {metrics_data}") @@ -772,6 +782,7 @@ class DashboardComponentManager: checkpoint_status = "LOADED" if model_info.get('checkpoint_loaded', False) else "FRESH" # Model card + logger.info(f"Creating model card for {model_name} with toggles: inference={model_info.get('inference_enabled', True)}, training={model_info.get('training_enabled', True)}") model_card = html.Div([ # Header with model name and toggle html.Div([ @@ -1043,10 +1054,15 @@ class DashboardComponentManager: html.Span(f"{enhanced_stats['recent_validation_score']:.3f}", className="text-primary small fw-bold") ], className="mb-1")) + logger.info(f"format_training_metrics returning {len(content)} components") + for i, component in enumerate(content[:3]): # Log first 3 components + logger.info(f" Component {i}: {type(component)}") return content except Exception as e: logger.error(f"Error formatting training metrics: {e}") + import traceback + logger.error(f"Traceback: {traceback.format_exc()}") return [html.P(f"Error: {str(e)}", className="text-danger small")] def _format_cnn_pivot_prediction(self, model_info): diff --git a/web/layout_manager.py b/web/layout_manager.py index cb33448..8409af2 100644 --- a/web/layout_manager.py +++ b/web/layout_manager.py @@ -17,11 +17,32 @@ class DashboardLayoutManager: def create_main_layout(self): """Create the main dashboard layout""" - return html.Div([ - self._create_header(), - self._create_interval_component(), - self._create_main_content() - ], className="container-fluid") + try: + print("Creating main layout...") + header = self._create_header() + print("Header created") + interval_component = self._create_interval_component() + print("Interval component created") + main_content = self._create_main_content() + print("Main content created") + + layout = html.Div([ + header, + interval_component, + main_content + ], className="container-fluid") + + print("Main layout created successfully") + return layout + except Exception as e: + print(f"Error creating main layout: {e}") + import traceback + traceback.print_exc() + # Return a simple error layout + return html.Div([ + html.H1("Dashboard Error", className="text-danger"), + html.P(f"Error creating layout: {str(e)}", className="text-danger") + ]) def _create_header(self): """Create the dashboard header""" @@ -52,7 +73,15 @@ class DashboardLayoutManager: dcc.Interval( id='slow-interval-component', interval=10000, # Update every 10 seconds (0.1 Hz) - OPTIMIZED - n_intervals=0 + n_intervals=0, + disabled=False + ), + # Fast interval for testing (5 seconds) + dcc.Interval( + id='fast-interval-component', + interval=5000, # Update every 5 seconds for testing + n_intervals=0, + disabled=False ), # WebSocket-based updates for high-frequency data (no interval needed) html.Div(id='websocket-updates-container', style={'display': 'none'}) @@ -357,10 +386,16 @@ class DashboardLayoutManager: html.Div([ html.Div([ html.Div([ - html.H6([ - html.I(className="fas fa-brain me-2"), - "Models & Training Progress", - ], className="card-title mb-2"), + html.Div([ + html.H6([ + html.I(className="fas fa-brain me-2"), + "Models & Training Progress", + ], className="card-title mb-2"), + html.Button([ + html.I(className="fas fa-sync-alt me-1"), + "Refresh" + ], id="refresh-training-metrics-btn", className="btn btn-sm btn-outline-primary") + ], className="d-flex justify-content-between align-items-center mb-2"), html.Div( id="training-metrics", style={"height": "300px", "overflowY": "auto"},