From 23f0caea7416f694f92a19552cd089995b6dfacc Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Thu, 17 Jul 2025 21:06:49 +0300 Subject: [PATCH] safety measures - 5 consequtive losses --- core/trading_executor.py | 173 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 173 insertions(+) diff --git a/core/trading_executor.py b/core/trading_executor.py index 305f672..174e3c7 100644 --- a/core/trading_executor.py +++ b/core/trading_executor.py @@ -156,6 +156,13 @@ class TradingExecutor: # Store trading mode for compatibility self.trading_mode = self.primary_config.get('trading_mode', 'simulation') + # Safety feature: Auto-disable live trading after consecutive losses + self.max_consecutive_losses = 5 # Disable live trading after 5 consecutive losses + self.min_success_rate_to_reenable = 0.55 # Require 55% success rate to re-enable + self.trades_to_evaluate = 20 # Evaluate last 20 trades for success rate + self.original_trading_mode = self.trading_mode # Store original mode + self.safety_triggered = False # Track if safety feature was triggered + # Initialize session stats self.session_start_time = datetime.now() self.session_trades = 0 @@ -615,12 +622,96 @@ class TradingExecutor: logger.error(f"Error cancelling open orders for {symbol}: {e}") return 0 + def _can_reenable_live_trading(self) -> bool: + """Check if trading performance has improved enough to re-enable live trading + + Returns: + bool: True if performance meets criteria to re-enable live trading + """ + try: + # Need enough trades to evaluate + if len(self.trade_history) < self.trades_to_evaluate: + logger.debug(f"Not enough trades to evaluate for re-enabling live trading: {len(self.trade_history)}/{self.trades_to_evaluate}") + return False + + # Get the most recent trades for evaluation + recent_trades = self.trade_history[-self.trades_to_evaluate:] + + # Calculate success rate + winning_trades = sum(1 for trade in recent_trades if trade.pnl > 0.001) + success_rate = winning_trades / len(recent_trades) + + # Calculate average PnL + avg_pnl = sum(trade.pnl for trade in recent_trades) / len(recent_trades) + + # Calculate win/loss ratio + losing_trades = sum(1 for trade in recent_trades if trade.pnl < -0.001) + win_loss_ratio = winning_trades / max(1, losing_trades) # Avoid division by zero + + logger.info(f"SAFETY FEATURE: Performance evaluation - Success rate: {success_rate:.2%}, Avg PnL: ${avg_pnl:.2f}, Win/Loss ratio: {win_loss_ratio:.2f}") + + # Criteria to re-enable live trading: + # 1. Success rate must exceed minimum threshold + # 2. Average PnL must be positive + # 3. Win/loss ratio must be at least 1.0 (equal wins and losses) + if (success_rate >= self.min_success_rate_to_reenable and + avg_pnl > 0 and + win_loss_ratio >= 1.0): + logger.info(f"SAFETY FEATURE: Performance criteria met for re-enabling live trading") + return True + else: + logger.debug(f"SAFETY FEATURE: Performance criteria not yet met for re-enabling live trading") + return False + + except Exception as e: + logger.error(f"Error evaluating trading performance: {e}") + return False + + except Exception as e: + logger.error(f"Error evaluating trading performance: {e}") + return False + def _check_safety_conditions(self, symbol: str, action: str) -> bool: """Check if it's safe to execute a trade""" # Check if trading is stopped if self.exchange_config.get('emergency_stop', False): logger.warning("Emergency stop is active - no trades allowed") return False + + # Safety feature: Check consecutive losses and switch to simulation mode if needed + if not self.simulation_mode and self.consecutive_losses >= self.max_consecutive_losses: + logger.warning(f"SAFETY FEATURE ACTIVATED: {self.consecutive_losses} consecutive losses detected") + logger.warning(f"Switching from live trading to simulation mode for safety") + + # Store original mode and switch to simulation + self.original_trading_mode = self.trading_mode + self.trading_mode = 'simulation' + self.simulation_mode = True + self.safety_triggered = True + + # Log the event + logger.info(f"Trading mode changed to SIMULATION due to safety feature") + logger.info(f"Will continue to monitor performance and re-enable live trading when success rate improves") + + # Continue allowing trades in simulation mode + return True + + # Check if we should try to re-enable live trading after safety feature was triggered + if self.simulation_mode and self.safety_triggered and self.original_trading_mode != 'simulation': + # Check if performance has improved enough to re-enable live trading + if self._can_reenable_live_trading(): + logger.info(f"SAFETY FEATURE: Performance has improved, re-enabling live trading") + + # Switch back to original mode + self.trading_mode = self.original_trading_mode + self.simulation_mode = (self.trading_mode == 'simulation') + self.safety_triggered = False + self.consecutive_losses = 0 # Reset consecutive losses counter + + logger.info(f"Trading mode restored to {self.trading_mode}") + + # Continue with the trade + return True # Check symbol allowlist allowed_symbols = self.exchange_config.get('allowed_symbols', []) @@ -2209,6 +2300,88 @@ class TradingExecutor: logger.info("TRADING EXECUTOR: Test mode enabled - bypassing safety checks") else: logger.info("TRADING EXECUTOR: Test mode disabled - normal safety checks active") + + def get_status(self) -> Dict[str, Any]: + """Get trading executor status with safety feature information""" + try: + # Get account balance + if self.simulation_mode: + balance = self.simulation_balance + else: + balance = self.exchange.get_balance('USDT') if self.exchange else 0.0 + + # Get open positions + positions = self.get_positions() + + # Calculate total fees paid + total_fees = sum(trade.fees for trade in self.trade_history) + total_volume = sum(trade.quantity * trade.exit_price for trade in self.trade_history) + + # Estimate fee breakdown (since we don't track maker vs taker separately) + maker_fee_rate = self.exchange_config.get('maker_fee', 0.0002) + taker_fee_rate = self.exchange_config.get('taker_fee', 0.0006) + avg_fee_rate = (maker_fee_rate + taker_fee_rate) / 2 + + # Fee impact analysis + total_pnl = sum(trade.pnl for trade in self.trade_history) + gross_pnl = total_pnl + total_fees + fee_impact_percent = (total_fees / max(1, abs(gross_pnl))) * 100 if gross_pnl != 0 else 0 + + # Calculate success rate for recent trades + recent_trades = self.trade_history[-self.trades_to_evaluate:] if len(self.trade_history) >= self.trades_to_evaluate else self.trade_history + winning_trades = sum(1 for trade in recent_trades if trade.pnl > 0.001) if recent_trades else 0 + success_rate = (winning_trades / len(recent_trades)) if recent_trades else 0 + + # Safety feature status + safety_status = { + 'active': self.safety_triggered, + 'consecutive_losses': self.consecutive_losses, + 'max_consecutive_losses': self.max_consecutive_losses, + 'original_mode': self.original_trading_mode if self.safety_triggered else self.trading_mode, + 'success_rate': success_rate, + 'min_success_rate_to_reenable': self.min_success_rate_to_reenable, + 'trades_evaluated': len(recent_trades), + 'trades_needed': self.trades_to_evaluate, + 'can_reenable': self._can_reenable_live_trading() if self.safety_triggered else False + } + + return { + 'trading_enabled': self.trading_enabled, + 'simulation_mode': self.simulation_mode, + 'trading_mode': self.trading_mode, + 'balance': balance, + 'positions': len(positions), + 'daily_trades': self.daily_trades, + 'daily_pnl': self.daily_pnl, + 'daily_loss': self.daily_loss, + 'consecutive_losses': self.consecutive_losses, + 'total_trades': len(self.trade_history), + 'safety_feature': safety_status, + 'pnl': { + 'total': total_pnl, + 'gross': gross_pnl, + 'fees': total_fees, + 'fee_impact_percent': fee_impact_percent, + 'pnl_after_fees': total_pnl, + 'pnl_before_fees': gross_pnl, + 'avg_fee_per_trade': total_fees / max(1, len(self.trade_history)) + }, + 'fee_efficiency': { + 'total_volume': total_volume, + 'total_fees': total_fees, + 'effective_fee_rate': (total_fees / max(0.01, total_volume)) if total_volume > 0 else 0, + 'expected_fee_rate': avg_fee_rate, + 'fee_efficiency': (avg_fee_rate / ((total_fees / max(0.01, total_volume)) if total_volume > 0 else 1)) if avg_fee_rate > 0 else 0 + } + } + except Exception as e: + logger.error(f"Error getting trading executor status: {e}") + return { + 'trading_enabled': self.trading_enabled, + 'simulation_mode': self.simulation_mode, + 'trading_mode': self.trading_mode, + 'error': str(e) + } def sync_position_with_mexc(self, symbol: str, desired_state: str) -> bool: """Synchronize dashboard position state with actual MEXC account positions