Răsfoiți Sursa

Refactor order execution logic in TradingEngine - Updated the execute_long_order and execute_short_order methods to improve handling of limit and market orders. Enhanced error handling for price validation and order placement, ensuring robustness against invalid inputs and improving logging for better traceability of order processing.

Carles Sentis 4 zile în urmă
părinte
comite
bb8b71a7ef
1 a modificat fișierele cu 135 adăugiri și 106 ștergeri
  1. 135 106
      src/trading/trading_engine.py

+ 135 - 106
src/trading/trading_engine.py

@@ -125,9 +125,8 @@ class TradingEngine:
                 return "SHORT", "buy", abs(contracts)
     
     async def execute_long_order(self, token: str, usdc_amount: float, 
-                                price: Optional[float] = None, 
+                                limit_price_arg: Optional[float] = None,
                                 stop_loss_price: Optional[float] = None) -> Dict[str, Any]:
-        """Execute a long order."""
         symbol = f"{token}/USDC:USDC"
         
         try:
@@ -137,138 +136,168 @@ class TradingEngine:
             
             # Get market data for price validation
             market_data = self.get_market_data(symbol)
-            if not market_data:
+            if not market_data or not market_data.get('ticker'):
                 return {"success": False, "error": f"Could not fetch market data for {token}"}
             
-            current_price = float(market_data['ticker'].get('last', 0))
+            current_price = float(market_data['ticker'].get('last', 0.0) or 0.0)
             if current_price <= 0:
-                return {"success": False, "error": f"Invalid current price for {token}"}
+                # Allow trading if current_price is 0 but a valid limit_price_arg is provided
+                if not (limit_price_arg and limit_price_arg > 0):
+                    return {"success": False, "error": f"Invalid current price ({current_price}) for {token} and no valid limit price provided."}
             
-            # Calculate token amount
-            if price:
-                # Limit order - use specified price
-                token_amount = usdc_amount / price
+            order_placement_price: float
+            token_amount: float
+
+            if limit_price_arg is not None:
+                # Limit order intent
+                if limit_price_arg <= 0:
+                    return {"success": False, "error": "Limit price must be positive."}
+                order_placement_price = limit_price_arg
+                token_amount = usdc_amount / order_placement_price
+                logger.info(f"Placing LIMIT BUY order for {token_amount:.6f} {symbol} at ${order_placement_price:,.2f}")
+                order = self.client.place_limit_order(symbol, 'buy', token_amount, order_placement_price)
             else:
-                # Market order - use current price
-                token_amount = usdc_amount / current_price
-                price = current_price
-            
-            # Execute the order
-            if price == current_price:
-                # Market order
+                # Market order intent
+                if current_price <= 0: # Re-check specifically for market order if current_price was initially 0
+                    return {"success": False, "error": f"Cannot place market order for {token} due to invalid current price: {current_price}"}
+                order_placement_price = current_price 
+                token_amount = usdc_amount / order_placement_price
+                logger.info(f"Placing MARKET BUY order for {token_amount:.6f} {symbol} (approx. price ${order_placement_price:,.2f})")
                 order = self.client.place_market_order(symbol, 'buy', token_amount)
-            else:
-                # Limit order
-                order = self.client.place_limit_order(symbol, 'buy', token_amount, price)
             
-            if order:
-                # Track the order
-                order_id = order.get('id', 'N/A')
-                actual_price = order.get('average', price)
-                
-                if order_id != 'N/A':
-                    self.bot_trade_ids.add(order_id)
-                
-                # Record in stats
-                action_type = self.stats.record_trade_with_enhanced_tracking(
-                    symbol, 'buy', token_amount, actual_price, order_id, "bot"
-                )
-                
-                # Handle stop loss if specified
-                if stop_loss_price and order_id != 'N/A':
-                    self.pending_stop_losses[order_id] = {
-                        'token': token,
-                        'stop_price': stop_loss_price,
-                        'side': 'sell',  # Exit side for long position
-                        'amount': token_amount,
-                        'order_type': 'stop_loss'
-                    }
-                
-                self._save_state()
-                
-                return {
-                    "success": True,
-                    "order": order,
-                    "action_type": action_type,
-                    "token_amount": token_amount,
-                    "actual_price": actual_price,
-                    "stop_loss_pending": stop_loss_price is not None
+            if not order:
+                logger.error(f"Order placement call failed for {symbol}. Client returned None or empty.")
+                return {"success": False, "error": "Order placement failed at client level (no order object)."}
+            
+            order_id = order.get('id', 'N/A')
+            order_avg_fill_price = order.get('average') 
+
+            # For stats, use average fill price if available, otherwise the price order was placed at.
+            final_price_for_stats = order_avg_fill_price if order_avg_fill_price is not None else order_placement_price
+
+            if final_price_for_stats is None: 
+                logger.critical(f"CRITICAL: final_price_for_stats is None for order {order_id}. Order: {order}, Placement Price: {order_placement_price}, Avg Fill: {order_avg_fill_price}")
+                return {"success": False, "error": "Critical: Price for stats recording became None."}
+
+            logger.info(f"Order {order_id} for {symbol} processing. AvgFill: {order_avg_fill_price}, PlacementPrice: {order_placement_price}, StatsPrice: {final_price_for_stats}")
+            
+            if order_id != 'N/A':
+                self.bot_trade_ids.add(order_id)
+            
+            # Record in stats
+            action_type = self.stats.record_trade_with_enhanced_tracking(
+                symbol, 'buy', token_amount, final_price_for_stats, order_id, "bot"
+            )
+            
+            # Handle stop loss if specified
+            if stop_loss_price and order_id != 'N/A':
+                self.pending_stop_losses[order_id] = {
+                    'token': token,
+                    'stop_price': stop_loss_price,
+                    'side': 'sell', 
+                    'amount': token_amount,
+                    'order_type': 'stop_loss'
                 }
-            else:
-                return {"success": False, "error": "Order execution failed"}
-                
+            
+            self._save_state()
+            
+            return {
+                "success": True,
+                "order": order,
+                "action_type": action_type,
+                "token_amount": token_amount,
+                "actual_price": final_price_for_stats,
+                "stop_loss_pending": stop_loss_price is not None
+            }
+        except ZeroDivisionError as e:
+            logger.error(f"Error executing long order due to ZeroDivisionError (likely price issue): {e}. LimitArg: {limit_price_arg}, CurrentPrice: {current_price if 'current_price' in locals() else 'N/A'}")
+            return {"success": False, "error": f"Math error (division by zero), check prices: {e}"}
         except Exception as e:
-            logger.error(f"Error executing long order: {e}")
+            logger.error(f"Error executing long order: {e}", exc_info=True)
             return {"success": False, "error": str(e)}
     
     async def execute_short_order(self, token: str, usdc_amount: float, 
-                                 price: Optional[float] = None, 
+                                 limit_price_arg: Optional[float] = None,
                                  stop_loss_price: Optional[float] = None) -> Dict[str, Any]:
-        """Execute a short order."""
         symbol = f"{token}/USDC:USDC"
         
         try:
-            # Similar to long order but with sell side
             if usdc_amount <= 0:
                 return {"success": False, "error": "Invalid USDC amount"}
             
             market_data = self.get_market_data(symbol)
-            if not market_data:
+            if not market_data or not market_data.get('ticker'):
                 return {"success": False, "error": f"Could not fetch market data for {token}"}
             
-            current_price = float(market_data['ticker'].get('last', 0))
+            current_price = float(market_data['ticker'].get('last', 0.0) or 0.0)
             if current_price <= 0:
-                return {"success": False, "error": f"Invalid current price for {token}"}
-            
-            # Calculate token amount
-            if price:
-                token_amount = usdc_amount / price
+                if not (limit_price_arg and limit_price_arg > 0):
+                    return {"success": False, "error": f"Invalid current price ({current_price}) for {token} and no valid limit price provided."}
+
+            order_placement_price: float
+            token_amount: float
+
+            if limit_price_arg is not None:
+                if limit_price_arg <= 0:
+                    return {"success": False, "error": "Limit price must be positive."}
+                order_placement_price = limit_price_arg
+                token_amount = usdc_amount / order_placement_price
+                logger.info(f"Placing LIMIT SELL order for {token_amount:.6f} {symbol} at ${order_placement_price:,.2f}")
+                order = self.client.place_limit_order(symbol, 'sell', token_amount, order_placement_price)
             else:
-                token_amount = usdc_amount / current_price
-                price = current_price
-            
-            # Execute the order
-            if price == current_price:
+                if current_price <= 0:
+                     return {"success": False, "error": f"Cannot place market order for {token} due to invalid current price: {current_price}"}
+                order_placement_price = current_price
+                token_amount = usdc_amount / order_placement_price
+                logger.info(f"Placing MARKET SELL order for {token_amount:.6f} {symbol} (approx. price ${order_placement_price:,.2f})")
                 order = self.client.place_market_order(symbol, 'sell', token_amount)
-            else:
-                order = self.client.place_limit_order(symbol, 'sell', token_amount, price)
             
-            if order:
-                order_id = order.get('id', 'N/A')
-                actual_price = order.get('average', price)
-                
-                if order_id != 'N/A':
-                    self.bot_trade_ids.add(order_id)
-                
-                action_type = self.stats.record_trade_with_enhanced_tracking(
-                    symbol, 'sell', token_amount, actual_price, order_id, "bot"
-                )
-                
-                # Handle stop loss if specified
-                if stop_loss_price and order_id != 'N/A':
-                    self.pending_stop_losses[order_id] = {
-                        'token': token,
-                        'stop_price': stop_loss_price,
-                        'side': 'buy',  # Exit side for short position
-                        'amount': token_amount,
-                        'order_type': 'stop_loss'
-                    }
-                
-                self._save_state()
-                
-                return {
-                    "success": True,
-                    "order": order,
-                    "action_type": action_type,
-                    "token_amount": token_amount,
-                    "actual_price": actual_price,
-                    "stop_loss_pending": stop_loss_price is not None
+            if not order:
+                logger.error(f"Order placement call failed for {symbol}. Client returned None or empty.")
+                return {"success": False, "error": "Order placement failed at client level (no order object)."}
+
+            order_id = order.get('id', 'N/A')
+            order_avg_fill_price = order.get('average')
+            
+            final_price_for_stats = order_avg_fill_price if order_avg_fill_price is not None else order_placement_price
+
+            if final_price_for_stats is None:
+                logger.critical(f"CRITICAL: final_price_for_stats is None for order {order_id}. Order: {order}, Placement Price: {order_placement_price}, Avg Fill: {order_avg_fill_price}")
+                return {"success": False, "error": "Critical: Price for stats recording became None."}
+
+            logger.info(f"Order {order_id} for {symbol} processing. AvgFill: {order_avg_fill_price}, PlacementPrice: {order_placement_price}, StatsPrice: {final_price_for_stats}")
+
+            if order_id != 'N/A':
+                self.bot_trade_ids.add(order_id)
+            
+            action_type = self.stats.record_trade_with_enhanced_tracking(
+                symbol, 'sell', token_amount, final_price_for_stats, order_id, "bot"
+            )
+            
+            if stop_loss_price and order_id != 'N/A':
+                self.pending_stop_losses[order_id] = {
+                    'token': token,
+                    'stop_price': stop_loss_price,
+                    'side': 'buy', # Exit side for short
+                    'amount': token_amount,
+                    'order_type': 'stop_loss'
                 }
-            else:
-                return {"success": False, "error": "Order execution failed"}
-                
+            
+            self._save_state()
+            
+            return {
+                "success": True,
+                "order": order,
+                "action_type": action_type,
+                "token_amount": token_amount,
+                "actual_price": final_price_for_stats,
+                "stop_loss_pending": stop_loss_price is not None
+            }
+        except ZeroDivisionError as e:
+            logger.error(f"Error executing short order due to ZeroDivisionError (likely price issue): {e}. LimitArg: {limit_price_arg}, CurrentPrice: {current_price if 'current_price' in locals() else 'N/A'}")
+            return {"success": False, "error": f"Math error (division by zero), check prices: {e}"}
         except Exception as e:
-            logger.error(f"Error executing short order: {e}")
+            logger.error(f"Error executing short order: {e}", exc_info=True)
             return {"success": False, "error": str(e)}
     
     async def execute_exit_order(self, token: str) -> Dict[str, Any]: