diff --git a/core/config.py b/core/config.py index 72e7210..15af10c 100644 --- a/core/config.py +++ b/core/config.py @@ -317,4 +317,32 @@ def setup_logging(config: Optional[Config] = None): except Exception as e: logger.warning(f"Failed to initialize error log handler: {e}") + # Add dedicated trade log to capture all NN signals (executed and ignored) + try: + from logging.handlers import RotatingFileHandler + trade_log_dir = Path('logs') + trade_log_dir.mkdir(parents=True, exist_ok=True) + trade_log_path = trade_log_dir / 'trades.log' + + class FlushingTradeHandler(RotatingFileHandler): + def emit(self, record): + super().emit(record) + self.flush() + + trade_handler = FlushingTradeHandler( + str(trade_log_path), maxBytes=20*1024*1024, backupCount=10, encoding='utf-8' + ) + trade_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') + trade_handler.setFormatter(trade_formatter) + trade_handler.setLevel(logging.INFO) + + trade_logger = logging.getLogger('trade_log') + trade_logger.setLevel(logging.INFO) + trade_logger.addHandler(trade_handler) + # Avoid double propagation to root to keep trade log clean + trade_logger.propagate = False + logger.info("Trade log handler initialized at logs/trades.log") + except Exception as e: + logger.warning(f"Failed to initialize trade log handler: {e}") + logger.info("Logging configured successfully with SafeFormatter") diff --git a/web/clean_dashboard.py b/web/clean_dashboard.py index 74cb010..5ac0097 100644 --- a/web/clean_dashboard.py +++ b/web/clean_dashboard.py @@ -608,21 +608,18 @@ class CleanTradingDashboard: def _monitor_order_execution(self): """Monitor order execution status in live mode and update dashboard signals""" - try: - logger.info("Starting order execution monitoring for live mode") - while True: + logger.info("Starting order execution monitoring for live mode") + while True: + try: time.sleep(5) # Check every 5 seconds - # Check for signals that were attempted but not yet executed for decision in self.recent_decisions: if (decision.get('execution_attempted', False) and not decision.get('executed', False) and not decision.get('execution_failure', False)): - # Check if the order was actually filled symbol = decision.get('symbol', 'ETH/USDT') action = decision.get('action', 'HOLD') - # Check if position was actually opened/closed if self.trading_executor and hasattr(self.trading_executor, 'positions'): if symbol in self.trading_executor.positions: @@ -639,9 +636,8 @@ class CleanTradingDashboard: else: # No position exists, order might still be pending logger.debug(f"No position found for {symbol}, order may still be pending") - - except Exception as e: - logger.error(f"Error in order execution monitoring: {e}") + except Exception as e: + logger.warning(f"Order execution monitoring iteration error: {e}") def _delayed_training_check(self): """Check and start training after a delay to allow initialization""" @@ -8898,6 +8894,7 @@ class CleanTradingDashboard: async def _on_trading_decision(self, decision): """Handle trading decision from orchestrator and execute through trading executor.""" try: + trade_logger = logging.getLogger('trade_log') # Handle both object and dict formats if hasattr(decision, 'action'): action = getattr(decision, 'action', 'HOLD') @@ -8940,6 +8937,10 @@ class CleanTradingDashboard: dashboard_decision['source'] = 'Unknown' logger.info(f"[ORCHESTRATOR SIGNAL] Received: {action} for {symbol} (confidence: {confidence:.3f})") + try: + trade_logger.info(f"SIGNAL action={action} symbol={symbol} confidence={confidence:.4f} price={price if price is not None else 'NA'} source={dashboard_decision.get('source','Unknown')}") + except Exception: + pass # EXECUTE THE DECISION THROUGH TRADING EXECUTOR # check if we are entering or exiting a position with this signal @@ -9003,18 +9004,37 @@ class CleanTradingDashboard: else: logger.warning(f"[ORCHESTRATOR EXECUTION] FAILED: {action} execution blocked for {symbol}") dashboard_decision['execution_failure'] = True + try: + trade_logger.info(f"IGNORED action={action} symbol={symbol} confidence={confidence:.4f} reason=blocked") + except Exception: + pass except Exception as e: logger.error(f"[ORCHESTRATOR EXECUTION] ERROR: Failed to execute {action} for {symbol}: {e}") dashboard_decision['execution_error'] = str(e) - else: - if not self.trading_executor: - logger.warning("[ORCHESTRATOR EXECUTION] No trading executor available") - elif confidence <= 0.5: - logger.info(f"[ORCHESTRATOR EXECUTION] Low confidence signal ignored: {action} for {symbol} (confidence: {confidence:.3f})") + else: + if not self.trading_executor: + logger.warning("[ORCHESTRATOR EXECUTION] No trading executor available") + try: + trade_logger.info(f"IGNORED action={action} symbol={symbol} confidence={confidence:.4f} reason=no_executor") + except Exception: + pass + elif confidence <= 0.5: + logger.info(f"[ORCHESTRATOR EXECUTION] Low confidence signal ignored: {action} for {symbol} (confidence: {confidence:.3f})") + try: + trade_logger.info(f"IGNORED action={action} symbol={symbol} confidence={confidence:.4f} reason=low_conf_threshold") + except Exception: + pass # Store decision in dashboard self.recent_decisions.append(dashboard_decision) + try: + if dashboard_decision.get('executed'): + trade_logger.info(f"EXECUTED action={action} symbol={symbol} confidence={confidence:.4f} executed={dashboard_decision.get('executed')} sim={self.trading_executor.simulation_mode if self.trading_executor else 'NA'}") + elif dashboard_decision.get('execution_attempted'): + trade_logger.info(f"ATTEMPTED action={action} symbol={symbol} confidence={confidence:.4f} live=True") + except Exception: + pass if len(self.recent_decisions) > 200: self.recent_decisions.pop(0) @@ -9102,8 +9122,18 @@ class CleanTradingDashboard: logger.info("WebSocket connected") self.is_streaming = True ws_url = "wss://stream.binance.com:9443/ws/ethusdt@kline_1s" - ws = websocket.WebSocketApp(ws_url, on_message=on_message, on_error=on_error, on_close=on_close, on_open=on_open) - ws.run_forever() + backoff = 1 + while True: + try: + ws = websocket.WebSocketApp(ws_url, on_message=on_message, on_error=on_error, on_close=on_close, on_open=on_open) + ws.run_forever(ping_interval=20, ping_timeout=10) + logger.info(f"WebSocket stopped, restarting in {backoff}s") + time.sleep(backoff) + backoff = min(backoff * 2, 60) + except Exception as _ws_e: + logger.error(f"WebSocket loop error: {_ws_e}") + time.sleep(backoff) + backoff = min(backoff * 2, 60) except Exception as e: logger.error(f"WebSocket worker error: {e}") self.is_streaming = False