From f34b2a46a2b9027774b75d05339b728251f968cc Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Tue, 29 Jul 2025 09:49:09 +0300 Subject: [PATCH] better decision details --- core/orchestrator.py | 83 ++++++++++++++++++++++++++++++++++++++++-- data/ui_state.json | 2 +- web/clean_dashboard.py | 17 ++++++++- 3 files changed, 95 insertions(+), 7 deletions(-) diff --git a/core/orchestrator.py b/core/orchestrator.py index 04a4f6f..0ea5525 100644 --- a/core/orchestrator.py +++ b/core/orchestrator.py @@ -256,6 +256,7 @@ class TradingDecision: timestamp: datetime reasoning: Dict[str, Any] # Why this decision was made memory_usage: Dict[str, int] # Memory usage of models + source: str = "orchestrator" # Source of the decision (model name or system) # NEW: Aggressiveness parameters entry_aggressiveness: float = 0.5 # 0.0 = conservative, 1.0 = very aggressive exit_aggressiveness: float = 0.5 # 0.0 = conservative, 1.0 = very aggressive @@ -4637,6 +4638,56 @@ class TradingOrchestrator: except Exception as e: logger.error(f"Error creating RL state for {symbol}: {e}") return None + + def _determine_decision_source(self, models_used: List[str], confidence: float) -> str: + """Determine the source of a trading decision based on contributing models""" + try: + if not models_used: + return "no_models" + + # If only one model contributed, use that as source + if len(models_used) == 1: + model_name = models_used[0] + # Map internal model names to user-friendly names + model_mapping = { + "dqn_agent": "DQN", + "cnn_model": "CNN", + "cob_rl": "COB-RL", + "decision_fusion": "Fusion", + "extrema_trainer": "Extrema", + "transformer": "Transformer" + } + return model_mapping.get(model_name, model_name) + + # Multiple models - determine primary contributor + # Priority order: COB-RL > DQN > CNN > Others + priority_order = ["cob_rl", "dqn_agent", "cnn_model", "decision_fusion", "transformer", "extrema_trainer"] + + for priority_model in priority_order: + if priority_model in models_used: + model_mapping = { + "cob_rl": "COB-RL", + "dqn_agent": "DQN", + "cnn_model": "CNN", + "decision_fusion": "Fusion", + "transformer": "Transformer", + "extrema_trainer": "Extrema" + } + primary_model = model_mapping.get(priority_model, priority_model) + + # If high confidence, show primary model + if confidence > 0.7: + return primary_model + else: + # Lower confidence, show it's a combination + return f"{primary_model}+{len(models_used)-1}" + + # Fallback: show number of models + return f"Ensemble({len(models_used)})" + + except Exception as e: + logger.error(f"Error determining decision source: {e}") + return "orchestrator" def _combine_predictions( self, @@ -4798,6 +4849,9 @@ class TradingOrchestrator: symbol, current_position_pnl ) + # Determine decision source based on contributing models + source = self._determine_decision_source(reasoning.get("models_used", []), best_confidence) + # Create final decision decision = TradingDecision( action=best_action, @@ -4807,6 +4861,7 @@ class TradingOrchestrator: timestamp=timestamp, reasoning=reasoning, memory_usage=memory_usage.get("models", {}) if memory_usage else {}, + source=source, entry_aggressiveness=entry_aggressiveness, exit_aggressiveness=exit_aggressiveness, current_position_pnl=current_position_pnl, @@ -4828,6 +4883,7 @@ class TradingOrchestrator: action="HOLD", confidence=0.0, symbol=symbol, + source="error_fallback", price=price, timestamp=timestamp, reasoning={"error": str(e)}, @@ -5465,6 +5521,9 @@ class TradingOrchestrator: except Exception: pass + # Determine decision source + source = self._determine_decision_source(reasoning.get("models_used", []), best_confidence) + # Create final decision decision = TradingDecision( action=best_action, @@ -5474,6 +5533,7 @@ class TradingOrchestrator: timestamp=timestamp, reasoning=reasoning, memory_usage=memory_usage.get("models", {}) if memory_usage else {}, + source=source, entry_aggressiveness=self.entry_aggressiveness, exit_aggressiveness=self.exit_aggressiveness, current_position_pnl=current_position_pnl, @@ -6643,10 +6703,25 @@ class TradingOrchestrator: } target = target_mapping.get(action, [0, 0, 1]) - # Add training sample - self.decision_fusion_network.add_training_sample( - fusion_input, target, weight=confidence - ) + # Decision fusion network doesn't have add_training_sample method + # Instead, we'll store the training data for later batch training + if not hasattr(self, 'decision_fusion_training_data'): + self.decision_fusion_training_data = [] + + # Convert target list to action string for compatibility + target_action = "BUY" if target[0] == 1 else "SELL" if target[1] == 1 else "HOLD" + + self.decision_fusion_training_data.append({ + 'input_features': fusion_input, + 'target_action': target_action, + 'weight': confidence, + 'timestamp': datetime.now() + }) + + # Train the network if we have enough samples + if len(self.decision_fusion_training_data) >= 5: # Train every 5 samples + self._train_decision_fusion_network() + self.decision_fusion_training_data = [] # Clear after training models_trained.append("decision_fusion") logger.debug(f"🤝 Added decision fusion training sample: {action} {symbol}") diff --git a/data/ui_state.json b/data/ui_state.json index 55c7121..b61cf10 100644 --- a/data/ui_state.json +++ b/data/ui_state.json @@ -21,5 +21,5 @@ "training_enabled": true } }, - "timestamp": "2025-07-29T09:07:36.747677" + "timestamp": "2025-07-29T09:18:36.627596" } \ No newline at end of file diff --git a/web/clean_dashboard.py b/web/clean_dashboard.py index 34520a7..fbf3273 100644 --- a/web/clean_dashboard.py +++ b/web/clean_dashboard.py @@ -2491,7 +2491,14 @@ class CleanTradingDashboard: # Extract source information from signal signal_source = 'Unknown' - if hasattr(signal, 'reasoning') and signal.reasoning: + + # First try to get source directly from the signal (new method) + if hasattr(signal, 'source') and signal.source: + signal_source = signal.source + elif isinstance(signal, dict) and 'source' in signal and signal['source']: + signal_source = signal['source'] + # Fallback to old method using reasoning.models_used + elif hasattr(signal, 'reasoning') and signal.reasoning: models_used = signal.reasoning.get('models_used', []) if models_used: signal_source = ', '.join(models_used) @@ -5107,6 +5114,7 @@ class CleanTradingDashboard: 'blocked': False, 'manual': True, # CRITICAL: Mark as manual for special handling 'reason': f'Manual {action} button', + 'source': 'Manual', # Clear source for manual trades 'model_inputs': model_inputs, # Store for training 'persistent': True, # MARK for persistent display 'chart_priority': 'HIGH' # High priority for chart display @@ -8169,7 +8177,9 @@ class CleanTradingDashboard: 'symbol': symbol, 'confidence': confidence, 'timestamp': datetime.now(), - 'executed': False + 'executed': False, + 'source': getattr(decision, 'source', 'Unknown'), + 'reasoning': getattr(decision, 'reasoning', {}) } # Add any other attributes from the decision object for attr in ['price', 'quantity', 'reasoning', 'model_source']: @@ -8179,6 +8189,9 @@ class CleanTradingDashboard: dashboard_decision = decision.copy() dashboard_decision['timestamp'] = datetime.now() dashboard_decision['executed'] = False + # Ensure source is preserved + if 'source' not in dashboard_decision: + dashboard_decision['source'] = 'Unknown' logger.info(f"[ORCHESTRATOR SIGNAL] Received: {action} for {symbol} (confidence: {confidence:.3f})")