diff --git a/core/orchestrator.py b/core/orchestrator.py index f5ee289..2b2083e 100644 --- a/core/orchestrator.py +++ b/core/orchestrator.py @@ -410,11 +410,11 @@ class TradingOrchestrator: # Model toggle states - control which models contribute to decisions self.model_toggle_states = { - "dqn": {"inference_enabled": True, "training_enabled": True}, - "cnn": {"inference_enabled": True, "training_enabled": True}, - "cob_rl": {"inference_enabled": True, "training_enabled": True}, - "decision_fusion": {"inference_enabled": True, "training_enabled": True}, - "transformer": {"inference_enabled": True, "training_enabled": True}, + "dqn": {"inference_enabled": True, "training_enabled": True, "routing_enabled": True}, + "cnn": {"inference_enabled": True, "training_enabled": True, "routing_enabled": True}, + "cob_rl": {"inference_enabled": True, "training_enabled": True, "routing_enabled": True}, + "decision_fusion": {"inference_enabled": True, "training_enabled": True, "routing_enabled": True}, + "transformer": {"inference_enabled": True, "training_enabled": True, "routing_enabled": True}, } # UI state persistence @@ -537,6 +537,23 @@ class TradingOrchestrator: self._initialize_transformer_model() # Initialize transformer model self._initialize_enhanced_training_system() # Initialize real-time training + def _normalize_model_name(self, name: str) -> str: + """Map various registry/UI names to canonical toggle keys.""" + try: + mapping = { + "dqn_agent": "dqn", + "enhanced_cnn": "cnn", + "cnn_model": "cnn", + "decision": "decision_fusion", + "decision_fusion": "decision_fusion", + "cob_rl_model": "cob_rl", + "cob_rl": "cob_rl", + "transformer_model": "transformer", + } + return mapping.get(name, name) + except Exception: + return name + def _initialize_ml_models(self): """Initialize ML models for enhanced trading""" try: @@ -1411,7 +1428,22 @@ class TradingOrchestrator: with open(self.ui_state_file, "r") as f: ui_state = json.load(f) if "model_toggle_states" in ui_state: - self.model_toggle_states.update(ui_state["model_toggle_states"]) + # Normalize and sanitize loaded toggle states + loaded = {} + for raw_name, raw_state in ui_state["model_toggle_states"].items(): + key = self._normalize_model_name(raw_name) + state = { + "inference_enabled": bool(raw_state.get("inference_enabled", True)) if isinstance(raw_state.get("inference_enabled", True), bool) else True, + "training_enabled": bool(raw_state.get("training_enabled", True)) if isinstance(raw_state.get("training_enabled", True), bool) else True, + "routing_enabled": bool(raw_state.get("routing_enabled", True)) if isinstance(raw_state.get("routing_enabled", True), bool) else True, + } + loaded[key] = state + # Merge into current defaults + for k, v in loaded.items(): + if k not in self.model_toggle_states: + self.model_toggle_states[k] = v + else: + self.model_toggle_states[k].update(v) logger.info(f"UI state loaded from {self.ui_state_file}") except Exception as e: logger.error(f"Error loading UI state: {e}") @@ -1432,29 +1464,33 @@ class TradingOrchestrator: def get_model_toggle_state(self, model_name: str) -> Dict[str, bool]: """Get toggle state for a model""" - return self.model_toggle_states.get(model_name, {"inference_enabled": True, "training_enabled": True}) + key = self._normalize_model_name(model_name) + return self.model_toggle_states.get(key, {"inference_enabled": True, "training_enabled": True, "routing_enabled": True}) - def set_model_toggle_state(self, model_name: str, inference_enabled: bool = None, training_enabled: bool = None): + def set_model_toggle_state(self, model_name: str, inference_enabled: bool = None, training_enabled: bool = None, routing_enabled: bool = None): """Set toggle state for a model - Universal handler for any model""" + key = self._normalize_model_name(model_name) # Initialize model toggle state if it doesn't exist - if model_name not in self.model_toggle_states: - self.model_toggle_states[model_name] = {"inference_enabled": True, "training_enabled": True} - logger.info(f"Initialized toggle state for new model: {model_name}") + if key not in self.model_toggle_states: + self.model_toggle_states[key] = {"inference_enabled": True, "training_enabled": True, "routing_enabled": True} + logger.info(f"Initialized toggle state for new model: {key}") # Update the toggle states if inference_enabled is not None: - self.model_toggle_states[model_name]["inference_enabled"] = inference_enabled + self.model_toggle_states[key]["inference_enabled"] = inference_enabled if training_enabled is not None: - self.model_toggle_states[model_name]["training_enabled"] = training_enabled + self.model_toggle_states[key]["training_enabled"] = training_enabled + if routing_enabled is not None: + self.model_toggle_states[key]["routing_enabled"] = routing_enabled # Save the updated state self._save_ui_state() # Log the change - logger.info(f"Model {model_name} toggle state updated: inference={self.model_toggle_states[model_name]['inference_enabled']}, training={self.model_toggle_states[model_name]['training_enabled']}") + logger.info(f"Model {key} toggle state updated: inference={self.model_toggle_states[key]['inference_enabled']}, training={self.model_toggle_states[key]['training_enabled']}, routing={self.model_toggle_states[key].get('routing_enabled', True)}") # Notify any listeners about the toggle change - self._notify_model_toggle_change(model_name, self.model_toggle_states[model_name]) + self._notify_model_toggle_change(key, self.model_toggle_states[key]) def _notify_model_toggle_change(self, model_name: str, toggle_state: Dict[str, bool]): """Notify components about model toggle changes""" @@ -1513,11 +1549,23 @@ class TradingOrchestrator: def is_model_inference_enabled(self, model_name: str) -> bool: """Check if model inference is enabled""" - return self.model_toggle_states.get(model_name, {}).get("inference_enabled", True) + key = self._normalize_model_name(model_name) + return self.model_toggle_states.get(key, {}).get("inference_enabled", True) def is_model_training_enabled(self, model_name: str) -> bool: """Check if model training is enabled""" - return self.model_toggle_states.get(model_name, {}).get("training_enabled", True) + key = self._normalize_model_name(model_name) + return self.model_toggle_states.get(key, {}).get("training_enabled", True) + + def is_model_routing_enabled(self, model_name: str) -> bool: + """Check if model output should be routed into decision making""" + key = self._normalize_model_name(model_name) + return self.model_toggle_states.get(key, {}).get("routing_enabled", True) + + def set_model_routing_state(self, model_name: str, routing_enabled: bool): + """Set routing state for a model""" + key = self._normalize_model_name(model_name) + self.set_model_toggle_state(key, routing_enabled=routing_enabled) def disable_decision_fusion_temporarily(self, reason: str = "overconfidence detected"): """Temporarily disable decision fusion model due to issues""" @@ -2346,6 +2394,10 @@ class TradingOrchestrator: for model_name, model in self.model_registry.models.items(): try: + # Respect inference toggle: skip inference entirely when disabled + if not self.is_model_inference_enabled(model_name): + logger.debug(f"Inference disabled for {model_name}; skipping model call") + continue prediction = None model_input = base_data # Use the same base data for all models @@ -5295,6 +5347,10 @@ class TradingOrchestrator: if not self.is_model_inference_enabled(pred.model_name): logger.debug(f"Skipping disabled model {pred.model_name} in decision making") continue + # Check routing toggle: even if inference happened, we may ignore it in decision fusion/programmatic fusion + if not self.is_model_routing_enabled(pred.model_name): + logger.debug(f"Routing disabled for {pred.model_name}; excluding from decision aggregation") + continue # DEBUG: Log individual model predictions logger.debug(f"Model {pred.model_name}: {pred.action} (confidence: {pred.confidence:.3f})") @@ -6175,8 +6231,12 @@ class TradingOrchestrator: except Exception: pass - # Determine decision source - source = self._determine_decision_source(reasoning.get("models_used", []), best_confidence) + # Determine decision source, honoring routing toggles: only count models whose routing is enabled + try: + routed_models = [m for m in reasoning.get("models_used", []) if self.is_model_routing_enabled(m)] + except Exception: + routed_models = reasoning.get("models_used", []) + source = self._determine_decision_source(routed_models, best_confidence) # Create final decision decision = TradingDecision( @@ -6413,11 +6473,12 @@ class TradingOrchestrator: predicted_action = record.get("action", "HOLD") # Determine if the decision was correct based on price movement - if predicted_action == "BUY" and price_change_pct > 0.1: + # Use realistic microstructure thresholds (approx 0.1%) + if predicted_action == "BUY" and price_change_pct > 0.001: target_action = "BUY" - elif predicted_action == "SELL" and price_change_pct < -0.1: + elif predicted_action == "SELL" and price_change_pct < -0.001: target_action = "SELL" - elif predicted_action == "HOLD" and abs(price_change_pct) < 0.1: + elif predicted_action == "HOLD" and abs(price_change_pct) < 0.001: target_action = "HOLD" else: # Decision was wrong - use opposite action as target @@ -7416,8 +7477,10 @@ class TradingOrchestrator: if self.decision_fusion_network and self.is_model_training_enabled("decision_fusion"): try: # Create decision fusion input + # Build market_data on demand (avoid undefined reference) + market_snapshot = self._get_current_market_data(symbol) fusion_input = self._create_decision_fusion_training_input( - symbol, market_data + symbol, market_snapshot if market_snapshot else {} ) # Create target based on action diff --git a/web/clean_dashboard.py b/web/clean_dashboard.py index 5c0e259..9985eee 100644 --- a/web/clean_dashboard.py +++ b/web/clean_dashboard.py @@ -1662,20 +1662,42 @@ class CleanTradingDashboard: if self.orchestrator and hasattr(self.orchestrator, 'set_model_toggle_state'): # Map dashboard names to orchestrator names model_mapping = { - 'dqn_agent': 'dqn_agent', - 'enhanced_cnn': 'enhanced_cnn', - 'cob_rl_model': 'cob_rl_model', + 'dqn_agent': 'dqn', + 'enhanced_cnn': 'cnn', + 'cob_rl_model': 'cob_rl', 'extrema_trainer': 'extrema_trainer', 'transformer': 'transformer', 'decision_fusion': 'decision_fusion' } orchestrator_name = model_mapping.get(model_name, model_name) - self.orchestrator.set_model_toggle_state( - orchestrator_name, - toggle_type + '_enabled', - is_enabled - ) + # Support three toggles: inference_enabled, training_enabled, routing_enabled + if toggle_type == 'inference': + self.orchestrator.set_model_toggle_state( + orchestrator_name, + inference_enabled=is_enabled + ) + elif toggle_type == 'training': + self.orchestrator.set_model_toggle_state( + orchestrator_name, + training_enabled=is_enabled + ) + elif toggle_type == 'routing': + # New: whether the model output is routed into decision making + if hasattr(self.orchestrator, 'set_model_routing_state'): + self.orchestrator.set_model_routing_state(orchestrator_name, routing_enabled=is_enabled) + else: + # Fallback: store in orchestrator.model_toggle_states under 'routing_enabled' + if not hasattr(self.orchestrator, 'model_toggle_states'): + self.orchestrator.model_toggle_states = {} + if orchestrator_name not in self.orchestrator.model_toggle_states: + self.orchestrator.model_toggle_states[orchestrator_name] = {} + self.orchestrator.model_toggle_states[orchestrator_name]['routing_enabled'] = is_enabled + if hasattr(self.orchestrator, '_save_ui_state'): + try: + self.orchestrator._save_ui_state() + except Exception: + pass logger.info(f"Updated {orchestrator_name} {toggle_type}_enabled = {is_enabled}") # Return all current values (no change needed) diff --git a/web/models_training_panel.py b/web/models_training_panel.py index 0d257f4..d5089c5 100644 --- a/web/models_training_panel.py +++ b/web/models_training_panel.py @@ -215,6 +215,7 @@ class ModelsTrainingPanel: if isinstance(toggle_state, dict): model_data['training_enabled'] = toggle_state.get('training_enabled', True) model_data['inference_enabled'] = toggle_state.get('inference_enabled', True) + model_data['routing_enabled'] = toggle_state.get('routing_enabled', True) # Get model statistics if hasattr(self.orchestrator, 'get_model_statistics'): @@ -584,6 +585,16 @@ class ModelsTrainingPanel: className="form-check-input", style={"transform": "scale(0.7)"} ) + ], className="d-flex align-items-center me-2"), + html.Div([ + html.Label("Route", className="text-muted small me-1", style={"font-size": "10px"}), + dcc.Checklist( + id={'type': 'model-toggle', 'model': model_name, 'toggle_type': 'routing'}, + options=[{"label": "", "value": True}], + value=[True] if model_data.get('routing_enabled', True) else [], + className="form-check-input", + style={"transform": "scale(0.7)"} + ) ], className="d-flex align-items-center") ], className="d-flex") ], className="d-flex align-items-center mb-2"),