import logging import time import threading import pandas as pd from technicalanalyzer import TechnicalAnalyzer from database_manager import DatabaseManager logger = logging.getLogger(__name__) class TradingStrategy: """Trading Strategy (Comprehensive Optimized Version)""" def __init__(self, symbol, config, api, risk_manager, deepseek): self.symbol = symbol self.config = config self.api = api self.risk_manager = risk_manager self.deepseek = deepseek self.technical_analyzer = TechnicalAnalyzer() self.db = DatabaseManager() # Use default configuration # Position status self.base_amount = 0.0 self.entry_price = 0.0 # Dynamic stop-loss monitoring - using environment variable configured interval self.monitor_interval = 300 # Default 5 minutes if hasattr(risk_manager, 'monitor_interval'): self.monitor_interval = risk_manager.monitor_interval self.monitor_running = False self.monitor_thread = None # Running status control self.strategy_running = False # Load position status self.load_position() # Start dynamic stop-loss monitoring self.start_dynamic_stop_monitor() def load_position(self): """Load position status from database""" try: position_data = self.db.load_position(self.symbol) if position_data: self.base_amount = position_data['base_amount'] self.entry_price = position_data['entry_price'] logger.info(f"Loaded {self.symbol} position: currency quantity={self.base_amount:.10f}, entry price=${self.entry_price:.2f}") else: # If no database record, sync with exchange position self.sync_with_exchange() except Exception as e: logger.error(f"Error loading position status: {e}") self.sync_with_exchange() def save_position(self): """Save position status to database""" try: if self.base_amount > 0: self.db.save_position(self.symbol, self.base_amount, self.entry_price) else: # If position is 0, delete record self.db.delete_position(self.symbol) # Also delete dynamic stop record self.db.delete_dynamic_stop(self.symbol) except Exception as e: logger.error(f"Error saving position status: {e}") def sync_with_exchange(self): """Sync with exchange position""" try: logger.debug(f"Starting {self.symbol} position status sync") # Get all currency balances balances = self.api.get_currency_balances() if not balances: logger.warning("Unable to get currency balances") return False # Extract base currency base_currency = self.symbol.split('-')[0] # Update local balance if base_currency in balances: new_base_amount = balances[base_currency].get('amount', 0.0) # If position quantity changes, print log if abs(new_base_amount - self.base_amount) > 1e-10: logger.info(f"Synced {self.symbol} latest balance: {base_currency}={new_base_amount:.10f}") # If position decreases but entry price not updated, keep original entry price (for calculating profit/loss) if new_base_amount < self.base_amount and self.entry_price > 0: # Position decreased, reset entry price to conservative estimate (current price) if new_base_amount <= 0: self.entry_price = 0 logger.info(f"{self.symbol} position cleared, reset entry price to 0") else: current_price = self.api.get_current_price(self.symbol) if current_price: self.entry_price = current_price logger.info(f"{self.symbol} partial sell, reset entry price to current price: ${current_price:.2f}") else: logger.warning(f"{self.symbol} partial sell but unable to get current price, keep original entry price") # If from no position to having position, and no entry price record elif self.base_amount == 0 and new_base_amount > 0 and self.entry_price == 0: # Try to load historical entry price from database position_data = self.db.load_position(self.symbol) if position_data and position_data['entry_price'] > 0: self.entry_price = position_data['entry_price'] logger.info(f"Restored entry price from database: ${self.entry_price:.2f}") else: # Get current price as reference (but note this is reference price) current_price = self.api.get_current_price(self.symbol) if current_price: self.entry_price = current_price logger.info(f"Set reference entry price: ${current_price:.2f} (Note: This is reference price, actual entry price may differ)") self.base_amount = new_base_amount else: self.base_amount = 0.0 # Don't clear entry price, keep for historical record analysis logger.info(f"Synced {self.symbol}: No position") # Save position status self.save_position() return True except Exception as e: logger.error(f"Error syncing exchange position: {e}") return False def get_actual_position_from_exchange(self): """Get actual position data from exchange""" try: balances = self.api.get_currency_balances() if not balances: return 0.0 base_currency = self.symbol.split('-')[0] return balances.get(base_currency, {}).get('amount', 0.0) except Exception as e: logger.error(f"Error getting exchange position: {e}") return 0.0 def analyze_market(self): """Comprehensive market analysis (optimized version)""" try: # Get market data df = self.api.get_market_data(self.symbol, self.config['timeframe']) if df is None or len(df) < 20: logger.error(f"Failed to get {self.symbol} market data") return None # Calculate technical indicators technical_indicators = self.technical_analyzer.calculate_indicators(df) if not technical_indicators: logger.warning(f"Failed to calculate {self.symbol} technical indicators") return None # Weighted technical signals weighted_signals = self.technical_analyzer.generate_weighted_signals(df) # DeepSeek AI analysis deepseek_analysis = self.deepseek.analyze_market(self.symbol, df, technical_indicators) # Technical signals technical_signals = self.technical_analyzer.generate_signals(df) return { 'deepseek': deepseek_analysis, 'technical_signals': technical_signals, 'weighted_signals': weighted_signals, 'technical_indicators': technical_indicators, 'current_price': df['close'].iloc[-1], 'market_data': df } except Exception as e: logger.error(f"{self.symbol} market analysis error: {e}") return None def make_enhanced_decision(self, analysis): """Enhanced trading decision""" if analysis is None: return {'action': 'HOLD', 'confidence': 'LOW', 'reason': 'Insufficient analysis data'} deepseek_signal = analysis['deepseek'] weighted_signals = analysis['weighted_signals'] technical_indicators = analysis['technical_indicators'] market_data = analysis['market_data'] # Build decision matrix decision_matrix = self._build_decision_matrix(deepseek_signal, weighted_signals, technical_indicators, market_data) return self._finalize_decision(decision_matrix) def _build_decision_matrix(self, ai_signal, weighted_signal, technical_indicators, market_data): """Build decision matrix""" matrix = { 'ai_weight': 0.6, # AI analysis weight 'technical_weight': 0.2, # Traditional technical indicator weight 'reversal_weight': 0.05, # Reversal signal weight 'support_resistance_weight': 0.1, # Support resistance weight 'market_weight': 0.05 # Market state weight } # AI signal scoring ai_score = self._score_ai_signal(ai_signal) if ai_signal else 0.5 # Technical signal scoring tech_score = self._score_technical_signal(weighted_signal) # Market state scoring market_score = self._score_market_condition(technical_indicators) # Reversal signal scoring reversal_signals = TechnicalAnalyzer.detect_reversal_patterns(market_data) reversal_score = 0.5 if reversal_signals: # Score based on reversal signal quantity and strength strong_signals = [s for s in reversal_signals if s['strength'] == 'STRONG'] reversal_score = 0.5 + (len(strong_signals) * 0.1) + (len(reversal_signals) * 0.05) reversal_score = min(0.9, reversal_score) # Support resistance scoring sr_levels = TechnicalAnalyzer.calculate_support_resistance(market_data) sr_score = 0.5 if sr_levels: # Near support level adds points, near resistance level subtracts points if sr_levels['current_vs_support'] > -2: # Very close to support sr_score = 0.7 elif sr_levels['current_vs_resistance'] < 2: # Very close to resistance sr_score = 0.3 # Comprehensive scoring total_score = ( ai_score * matrix['ai_weight'] + tech_score * matrix['technical_weight'] + reversal_score * matrix['reversal_weight'] + sr_score * matrix['support_resistance_weight'] + market_score * matrix['market_weight'] ) logger.info(f"[{self.symbol}] Decision matrix details: " f"AI score={ai_score:.3f}, technical score={tech_score:.3f}, " f"market score={market_score:.3f}, reversal score={reversal_score:.3f}, " f"support resistance score={sr_score:.3f}, total score={total_score:.3f}") # Determine action and confidence if total_score > 0.6: action = 'BUY' confidence = 'HIGH' if total_score > 0.75 else 'MEDIUM' elif total_score < 0.4: action = 'SELL' confidence = 'HIGH' if total_score < 0.25 else 'MEDIUM' else: action = 'HOLD' confidence = 'MEDIUM' if abs(total_score - 0.5) > 0.1 else 'LOW' return { 'total_score': total_score, 'action': action, 'confidence': confidence, 'components': { 'ai_score': ai_score, 'tech_score': tech_score, 'market_score': market_score, 'reversal_score': reversal_score, 'sr_score': sr_score } } def _score_ai_signal(self, ai_signal): """AI signal scoring""" if not ai_signal: logger.warning("AI signal is empty, using neutral score") return 0.5 action = ai_signal.get('action', 'HOLD') confidence = ai_signal.get('confidence', 'MEDIUM') # Correction: Redesign scoring logic to ensure score has differentiation if action == 'BUY': base_score = 0.7 # Buy base score elif action == 'SELL': base_score = 0.3 # Sell base score else: # HOLD base_score = 0.5 # Hold neutral score # Confidence adjustment - ensure final score is within reasonable range confidence_adjustments = { 'HIGH': 0.2, # High confidence +0.2 'MEDIUM': 0.0, # Medium confidence unchanged 'LOW': -0.2 # Low confidence -0.2 } adjustment = confidence_adjustments.get(confidence, 0.0) final_score = base_score + adjustment # Ensure within 0-1 range final_score = max(0.1, min(0.9, final_score)) logger.debug(f"AI signal scoring: action={action}, confidence={confidence}, " f"base={base_score}, adjustment={adjustment}, final={final_score}") return final_score def _score_technical_signal(self, weighted_signal): """Technical signal scoring""" if not weighted_signal: logger.warning("Technical signal is empty, using neutral score") return 0.5 action = weighted_signal.get('action', 'HOLD') score = weighted_signal.get('score', 0.5) signal_count = weighted_signal.get('signal_count', 0) # Adjust based on signal strength and quantity if action == 'BUY': # Buy signal: 0.5-1.0 range if score > 0.7 and signal_count >= 3: final_score = 0.8 elif score > 0.6: final_score = 0.7 else: final_score = 0.6 elif action == 'SELL': # Sell signal: 0.0-0.5 range if score < 0.3 and signal_count >= 3: final_score = 0.2 elif score < 0.4: final_score = 0.3 else: final_score = 0.4 else: # HOLD final_score = 0.5 logger.debug(f"Technical signal scoring: action={action}, score={score}, " f"signal_count={signal_count}, final={final_score}") return final_score def _score_market_condition(self, technical_indicators): """Market state scoring""" try: # Assess market state based on multiple indicators rsi = technical_indicators.get('rsi', 50) if hasattr(rsi, '__len__'): rsi = rsi.iloc[-1] trend_strength = technical_indicators.get('trend_strength', 0) volatility = technical_indicators.get('volatility', 0) # RSI scoring (30-70 is neutral) rsi_score = 0.5 if rsi < 30: # Oversold rsi_score = 0.8 elif rsi > 70: # Overbought rsi_score = 0.2 # Trend strength scoring trend_score = 0.5 + (trend_strength * 0.3) # Strong trend favors buying # Volatility scoring (medium volatility is best) vol_score = 0.7 if 0.3 < volatility < 0.6 else 0.5 # Comprehensive scoring return (rsi_score + trend_score + vol_score) / 3 except Exception as e: logger.error(f"Market state scoring error: {e}") return 0.5 def _finalize_decision(self, decision_matrix): """Final decision""" action = decision_matrix['action'] confidence = decision_matrix['confidence'] total_score = decision_matrix['total_score'] reason = f"Comprehensive score: {total_score:.3f} (AI: {decision_matrix['components']['ai_score']:.3f}, " reason += f"Technical: {decision_matrix['components']['tech_score']:.3f}, " reason += f"Market: {decision_matrix['components']['market_score']:.3f}," reason += f"Reversal: {decision_matrix['components']['reversal_score']:.3f}," reason += f"Support Resistance: {decision_matrix['components']['sr_score']:.3f})," return { 'action': action, 'confidence': confidence, 'reason': reason, 'total_score': total_score, 'source': 'Enhanced Decision Matrix' } def execute_trade(self, decision, current_price): """Execute trade""" if decision['action'] == 'HOLD': logger.info(f"[{self.symbol}] Decision is HOLD, not executing trade") return # Sync exchange position self.sync_with_exchange() actual_position = self.base_amount # Use synced position logger.info(f"[{self.symbol}] Current position: {self.base_amount:.10f}") # Only get needed total asset information total_assets, available_usdt, _ = self.risk_manager.calculate_total_assets() if decision['action'] == 'BUY': # Get market analysis for dynamic position calculation market_analysis = self.analyze_market() if market_analysis is None: logger.warning(f"[{self.symbol}] Unable to get market analysis, using base position") buy_amount = self.risk_manager.get_position_size(self.symbol, decision['confidence'], current_price) else: # Use dynamic position calculation buy_amount = self.risk_manager.get_dynamic_position_size( self.symbol, decision['confidence'], market_analysis['technical_indicators'], current_price ) if buy_amount == 0: logger.warning(f"[{self.symbol}] Calculated position size is 0, skipping buy") return # Get exchange minimum trade quantity for buy check min_sz, _ = self.api.get_instrument_info(self.symbol) if min_sz is None: min_sz = self.api.get_default_min_size(self.symbol) # Calculate base currency quantity to buy estimated_base_amount = buy_amount / current_price if current_price > 0 else 0 logger.info(f"[{self.symbol}] Pre-buy check: estimated quantity={estimated_base_amount:.10f}, " f"minimum trade quantity={min_sz}, available USDT=${available_usdt:.2f}, " f"buy amount=${buy_amount:.2f}") # Check if buy quantity meets exchange minimum requirement if estimated_base_amount < min_sz: logger.warning(f"[{self.symbol}] Estimated buy quantity {estimated_base_amount:.10f} less than minimum trade quantity {min_sz}, canceling buy") return # Check if quote currency balance is sufficient if available_usdt < buy_amount: logger.warning(f"[{self.symbol}] Quote currency balance insufficient: need ${buy_amount:.2f}, current ${available_usdt:.2f}") return logger.info(f"[{self.symbol}] Creating buy order: amount=${buy_amount:.2f}, price=${current_price:.2f}") order_id = self.api.create_order( symbol=self.symbol, side='buy', amount=buy_amount ) if order_id: # Wait for order completion order_status = self.api.wait_for_order_completion(self.symbol, order_id) if order_status is None: logger.error(f"[{self.symbol}] Order not completed") return # Get actual fill price and quantity fill_price = order_status['avgPx'] fill_amount = order_status['accFillSz'] # Update position status self.base_amount += fill_amount # Update average entry price (weighted average) if self.entry_price > 0: # If adding position, calculate weighted average price self.entry_price = (self.entry_price * (self.base_amount - fill_amount) + fill_price * fill_amount) / self.base_amount logger.info(f"[{self.symbol}] Position addition operation, updated average entry price: ${self.entry_price:.2f}") else: self.entry_price = fill_price logger.info(f"[{self.symbol}] Buy successful: quantity={fill_amount:.10f}, price=${fill_price:.2f}") logger.info(f"[{self.symbol}] Updated position: {self.symbol.split('-')[0]}={self.base_amount:.10f}") # Sync exchange position self.sync_with_exchange() # Set dynamic stop-loss trailing_percent = self.risk_manager.trailing_stop_percent self.db.set_dynamic_stop(self.symbol, fill_price, trailing_percent=trailing_percent) logger.info(f"[{self.symbol}] Set dynamic stop-loss: initial price=${fill_price:.2f}, trailing_percent={trailing_percent:.1%}") # Note: sync_with_exchange already saves position, no need to save again here self.risk_manager.increment_trade_count(self.symbol) else: logger.error(f"[{self.symbol}] Buy order creation failed") elif decision['action'] == 'SELL': if actual_position <= 0: logger.info(f"[{self.symbol}] Exchange actual position is 0, ignoring sell signal") # Update local status self.base_amount = 0 self.entry_price = 0 self.save_position() return # Prepare position information for AI analysis position_info = self._prepare_position_info(actual_position, current_price, total_assets) # Get market analysis data (for technical indicators) market_analysis = self.analyze_market() # Use DeepSeek to analyze sell proportion try: sell_proportion = self.deepseek.analyze_sell_proportion( self.symbol, position_info, market_analysis, current_price ) except Exception as e: logger.warning(f"[{self.symbol}] DeepSeek sell proportion analysis failed: {e}, using fallback decision") sell_proportion = self.deepseek._get_fallback_sell_proportion(position_info, decision['confidence']) # Calculate sell quantity based on AI suggestion sell_amount = actual_position * sell_proportion # Ensure sell quantity doesn't exceed actual position sell_amount = min(sell_amount, actual_position) logger.info(f"[{self.symbol}] AI suggested sell proportion: {sell_proportion:.1%}, calculated sell quantity: {sell_amount:.10f}") # Get exchange minimum trade quantity for sell check min_sz, _ = self.api.get_instrument_info(self.symbol) if min_sz is None: min_sz = self.api.get_default_min_size(self.symbol) # Check if sell quantity is less than exchange minimum trade quantity if sell_amount < min_sz: if sell_proportion < 1.0: # Partial sell logger.info(f"[{self.symbol}] Sell quantity {sell_amount:.10f} less than minimum trade quantity {min_sz}, ignoring this partial sell") return else: # Full sell logger.info(f"[{self.symbol}] Position quantity {sell_amount:.10f} less than minimum trade quantity {min_sz}, dust position, ignoring sell") return # Record sell decision details logger.info(f"[{self.symbol}] Final sell decision: proportion={sell_proportion:.1%}, quantity={sell_amount:.10f}, " f"reason={decision.get('reason', 'AI decision')}") # Execute sell order order_id = self.api.create_order( symbol=self.symbol, side='sell', amount=sell_amount ) if order_id: # Wait for order completion order_status = self.api.wait_for_order_completion(self.symbol, order_id) if order_status is None: logger.error(f"[{self.symbol}] Order not completed") return # Get actual fill price and quantity fill_price = order_status['avgPx'] fill_amount = order_status['accFillSz'] # Calculate profit profit = (fill_price - self.entry_price) * fill_amount profit_percent = (fill_price / self.entry_price - 1) * 100 if self.entry_price > 0 else 0 # Sync exchange position self.sync_with_exchange() logger.info(f"[{self.symbol}] Sell successful: quantity={fill_amount:.10f}, price=${fill_price:.2f}") logger.info(f"[{self.symbol}] Profit: ${profit:.2f} ({profit_percent:+.2f}%)") logger.info(f"[{self.symbol}] Updated position: {self.symbol.split('-')[0]}={self.base_amount:.10f}") # Save trade record self.db.save_trade_record(self.symbol, 'sell', fill_amount, fill_price, order_id) # If fully sold, reset status if self.base_amount <= 0: self.entry_price = 0 self.db.delete_dynamic_stop(self.symbol) logger.info(f"[{self.symbol}] Position cleared, removed dynamic stop-loss") else: # After partial sell, update dynamic stop-loss self.db.set_dynamic_stop(self.symbol, self.entry_price, trailing_percent=0.03, multiplier=2) logger.info(f"[{self.symbol}] After partial sell, reset dynamic stop-loss") # Note: sync_with_exchange already saves position, no need to save again here self.risk_manager.increment_trade_count(self.symbol) else: logger.error(f"[{self.symbol}] Sell order creation failed") def _prepare_position_info(self, actual_position, current_price, total_assets): """Prepare position information for AI analysis""" try: # Calculate position value position_value = actual_position * current_price # Calculate position ratio position_ratio = position_value / total_assets if total_assets > 0 else 0 # Calculate profit ratio profit_ratio = (current_price - self.entry_price) / self.entry_price if self.entry_price > 0 else 0 # Calculate distance to stop-loss/take-profit stop_loss_price = self.entry_price * (1 - self.risk_manager.stop_loss) if self.entry_price > 0 else 0 take_profit_price = self.entry_price * (1 + self.risk_manager.take_profit) if self.entry_price > 0 else 0 distance_to_stop_loss = (current_price - stop_loss_price) / current_price if current_price > 0 else 0 distance_to_take_profit = (take_profit_price - current_price) / current_price if current_price > 0 else 0 # Get market volatility market_data = self.api.get_market_data(self.symbol, '1H', limit=24) if market_data is not None and len(market_data) > 0: market_volatility = market_data['close'].pct_change().std() * 100 else: market_volatility = 0 # Get trend strength market_analysis = self.analyze_market() trend_strength = 0 if market_analysis and 'technical_indicators' in market_analysis: trend_strength = market_analysis['technical_indicators'].get('trend_strength', 0) position_info = { 'base_amount': actual_position, 'entry_price': self.entry_price, 'current_price': current_price, 'position_value': position_value, 'position_ratio': position_ratio, 'profit_ratio': profit_ratio, 'distance_to_stop_loss': distance_to_stop_loss, 'distance_to_take_profit': distance_to_take_profit, 'market_volatility': market_volatility, 'trend_strength': trend_strength, 'total_assets': total_assets } logger.debug(f"[{self.symbol}] Position analysis information: ratio={position_ratio:.2%}, profit={profit_ratio:+.2%}, " f"distance to stop-loss={distance_to_stop_loss:.2%}, volatility={market_volatility:.2f}%") return position_info except Exception as e: logger.error(f"[{self.symbol}] Error preparing position information: {e}") # Return basic position information return { 'base_amount': actual_position, 'entry_price': self.entry_price, 'current_price': current_price, 'position_value': actual_position * current_price, 'position_ratio': 0, 'profit_ratio': 0, 'distance_to_stop_loss': 0, 'distance_to_take_profit': 0, 'market_volatility': 0, 'trend_strength': 0, 'total_assets': total_assets } def start_dynamic_stop_monitor(self): """Start dynamic stop-loss monitoring""" if self.monitor_running and self.monitor_thread and self.monitor_thread.is_alive(): logger.info(f"[{self.symbol}] Dynamic stop-loss monitoring already running") return self.monitor_running = True self.monitor_thread = threading.Thread(target=self._dynamic_stop_monitor_loop, name=f"StopMonitor-{self.symbol}") self.monitor_thread.daemon = True self.monitor_thread.start() logger.info(f"[{self.symbol}] Dynamic stop-loss monitoring started") def stop_dynamic_stop_monitor(self): """Stop dynamic stop-loss monitoring""" self.monitor_running = False if self.monitor_thread and self.monitor_thread.is_alive(): self.monitor_thread.join(timeout=5) if self.monitor_thread.is_alive(): logger.warning(f"{self.symbol} dynamic stop-loss monitoring thread didn't exit normally") else: logger.info(f"{self.symbol} dynamic stop-loss monitoring stopped") def _dynamic_stop_monitor_loop(self): """Dynamic stop-loss monitoring loop""" while self.monitor_running: try: # Only monitor when there is position if self.base_amount > 0: self.check_dynamic_stops() # Monitor every 5 minutes time.sleep(self.monitor_interval) except Exception as e: logger.error(f"[{self.symbol}] Dynamic stop-loss monitoring error: {e}") time.sleep(60) # Wait 1 minute before continuing when error occurs def check_dynamic_stops(self): """Check dynamic stop-loss conditions""" try: # Get current price current_price = self.api.get_current_price(self.symbol) if not current_price: logger.warning(f"[{self.symbol}] Unable to get current price, skipping stop-loss check") return # Get dynamic stop-loss information stop_info = self.db.get_dynamic_stop(self.symbol) if not stop_info: logger.debug(f"[{self.symbol}] No dynamic stop-loss settings") return stop_loss_price = stop_info['current_stop_loss'] take_profit_price = stop_info['current_take_profit'] trailing_percent = stop_info['trailing_percent'] logger.debug(f"[{self.symbol}] Dynamic stop-loss check: current price=${current_price:.2f}, stop-loss=${stop_loss_price:.2f}, take-profit=${take_profit_price:.2f}") # Check stop-loss condition if current_price <= stop_loss_price: logger.warning(f"⚠️ {self.symbol} triggered dynamic stop-loss! Current price=${current_price:.2f} <= stop-loss price=${stop_loss_price:.2f}") self.execute_trade({ 'action': 'SELL', 'confidence': 'HIGH', 'reason': f'Dynamic stop-loss triggered: {current_price:.2f} <= {stop_loss_price:.2f}' }, current_price) return # Check take-profit condition if current_price >= take_profit_price: logger.info(f"🎯 {self.symbol} triggered dynamic take-profit! Current price=${current_price:.2f} >= take-profit price=${take_profit_price:.2f}") self.execute_trade({ 'action': 'SELL', 'confidence': 'HIGH', 'reason': f'Dynamic take-profit triggered: {current_price:.2f} >= {take_profit_price:.2f}' }, current_price) return # Update trailing stop-loss (only moves upward) - fix logic if stop_info and current_price > stop_info.get('highest_price', 0): new_stop_loss = self.db.update_dynamic_stop(self.symbol, current_price, trailing_percent=trailing_percent, multiplier=2) if new_stop_loss: logger.info(f"[{self.symbol}] Trailing stop-loss updated: new stop-loss price=${new_stop_loss:.2f}") except Exception as e: logger.error(f"[{self.symbol}] Error checking dynamic stop-loss: {e}") def run(self): """Run strategy (using enhanced decision)""" self.strategy_running = True while self.strategy_running: try: logger.info(f"\n=== {self.symbol} Strategy Auto Execution ===") # Market analysis analysis = self.analyze_market() if analysis is None: logger.warning("Analysis failed, waiting for next execution") time.sleep(self.config['interval']) continue # Make trading decision decision = self.make_enhanced_decision(analysis) logger.info(f"{self.symbol} Decision: {decision['action']} (Confidence: {decision['confidence']}, Score: {decision['total_score']:.3f})") if decision['reason']: logger.info(f"Reason: {decision['reason']}") # Execute trade self.execute_trade(decision, analysis['current_price']) # Risk management check self.check_risk_management(analysis['current_price']) time.sleep(self.config['interval']) except Exception as e: logger.error(f"Strategy execution error: {e}") if "Network" in str(e) or "Connection" in str(e): wait_time = min(300, self.config['interval'] * 2) # Network error wait longer else: wait_time = self.config['interval'] if self.strategy_running: time.sleep(wait_time) def stop_strategy(self): """Stop strategy running""" with threading.Lock(): # Add thread lock self.strategy_running = False logger.info(f"{self.symbol} strategy stop signal sent") def check_risk_management(self, current_price): """Check risk management (fixed stop-loss/take-profit)""" if self.base_amount == 0: return # Calculate profit/loss percentage pnl_pct = (current_price - self.entry_price) / self.entry_price if self.entry_price > 0 else 0 # Fixed stop-loss/take-profit check if pnl_pct <= -self.risk_manager.stop_loss: logger.warning(f"⚠️ {self.symbol} triggered fixed stop-loss ({pnl_pct:.2%})") self.execute_trade({ 'action': 'SELL', 'confidence': 'HIGH', 'reason': 'Fixed stop-loss triggered' }, current_price) elif pnl_pct >= self.risk_manager.take_profit: logger.info(f"🎯 {self.symbol} triggered fixed take-profit ({pnl_pct:.2%})") self.execute_trade({ 'action': 'SELL', 'confidence': 'HIGH', 'reason': 'Fixed take-profit triggered' }, current_price)