فهرست منبع

Refactor OrdersCommands and PositionTracker for improved error handling and data processing

- Updated OrdersCommands to handle missing order attributes more gracefully by using default values.
- Enhanced PositionTracker to retain last known positions during API failures and exceptions, improving reliability.
- Added detailed logging for position changes, including thresholds for notifications based on token characteristics and market price changes.
Carles Sentis 1 هفته پیش
والد
کامیت
ad3ef3d5dc
3فایلهای تغییر یافته به همراه98 افزوده شده و 12 حذف شده
  1. 5 5
      src/commands/info/orders.py
  2. 92 6
      src/monitoring/position_tracker.py
  3. 1 1
      trading_bot.py

+ 5 - 5
src/commands/info/orders.py

@@ -51,17 +51,17 @@ class OrdersCommands(InfoCommandsBase):
                         entry_side = order.get('side', 'unknown').upper()
                         order_type = "STOP (PENDING)"
                         side = "SELL" if entry_side == "BUY" else "BUY"
-                        price = float(order.get('stop_loss_price', 0))
-                        amount = float(order.get('amount', 0)) # Amount from the entry order
+                        price = float(order.get('stop_loss_price') or 0)
+                        amount = float(order.get('amount') or 0) # Amount from the entry order
                         status = f"Awaiting {order.get('status', '').upper()} Entry" # e.g. Awaiting PENDING Entry
                         order_id = order.get('trade_lifecycle_id', 'unknown')
                     else:
                         # This is a regular database order
                         order_type = order.get('type', 'unknown').upper()
                         side = order.get('side', 'unknown').upper()
-                        price = float(order.get('price', 0))
-                        amount_requested = float(order.get('amount_requested', 0))
-                        amount_filled = float(order.get('amount_filled', 0))
+                        price = float(order.get('price') or 0)
+                        amount_requested = float(order.get('amount_requested') or 0)
+                        amount_filled = float(order.get('amount_filled') or 0)
                         amount = amount_requested - amount_filled # Show remaining amount
                         status = order.get('status', 'unknown').upper()
                         order_id = order.get('exchange_order_id') or order.get('bot_order_ref_id', 'unknown')

+ 92 - 6
src/monitoring/position_tracker.py

@@ -243,7 +243,12 @@ class PositionTracker:
             
             if not positions:
                 logger.warning("📊 No positions returned from exchange - this might be wrong if you have open positions!")
-                self.current_positions = {}
+                # Don't clear positions during API failures - keep last known state to avoid false "position opened" notifications
+                if not self.current_positions:
+                    # Only clear if we truly have no tracked positions (e.g., first startup)
+                    self.current_positions = {}
+                else:
+                    logger.info(f"📊 Keeping last known positions during API failure: {list(self.current_positions.keys())}")
                 return
                 
             logger.info(f"📊 Raw positions data from exchange: {len(positions)} positions")
@@ -287,11 +292,25 @@ class PositionTracker:
                             'return_on_equity': float(position_data.get('returnOnEquity', '0'))
                         }
             
+            # Check if we're recovering from API failure
+            had_positions_before = len(self.current_positions) > 0
+            getting_positions_now = len(new_positions) > 0
+            
+            if had_positions_before and not getting_positions_now:
+                logger.info("📊 All positions appear to have been closed")
+            elif not had_positions_before and getting_positions_now:
+                logger.info(f"📊 New positions detected: {list(new_positions.keys())}")
+            elif had_positions_before and getting_positions_now:
+                logger.debug(f"✅ Updated current positions: {len(new_positions)} open positions ({list(new_positions.keys())})")
+            else:
+                logger.debug(f"✅ Updated current positions: {len(new_positions)} open positions ({list(new_positions.keys()) if new_positions else 'none'})")
+            
             self.current_positions = new_positions
-            logger.debug(f"✅ Updated current positions: {len(new_positions)} open positions ({list(new_positions.keys()) if new_positions else 'none'})")
             
         except Exception as e:
             logger.error(f"❌ Error updating current positions: {e}", exc_info=True)
+            # Don't clear positions on exception - keep last known state
+            logger.info(f"📊 Keeping last known positions during error: {list(self.current_positions.keys()) if self.current_positions else 'none'}")
             
     async def _process_position_changes(self, previous: Dict, current: Dict):
         """Process changes between previous and current positions"""
@@ -437,22 +456,89 @@ class PositionTracker:
                 
             # Check if position size changed significantly
             size_change = abs(curr_size) - abs(prev_size)
-            if abs(size_change) > 0.001:  # Threshold to avoid noise
+            
+            # Get current market price for more accurate value calculation
+            try:
+                full_symbol = f"{symbol}/USDC:USDC"
+                market_data = self.hl_client.get_market_data(full_symbol)
+                current_market_price = float(market_data.get('ticker', {}).get('last', current['entry_px'])) if market_data else current['entry_px']
+            except Exception:
+                current_market_price = current['entry_px']  # Fallback to entry price
+            
+            # Calculate change value using current market price
+            change_value = abs(size_change) * current_market_price
+            
+            # Get formatter to determine token category and appropriate thresholds
+            try:
+                from src.utils.token_display_formatter import get_formatter
+                formatter = get_formatter()
+                
+                # Use the existing token classification system to determine threshold
+                price_decimals = await formatter.get_token_price_decimal_places(symbol)
+                amount_decimals = await formatter.get_token_amount_decimal_places(symbol)
+                
+                # Determine quantity threshold based on token characteristics
+                # Higher precision tokens (like BTC, ETH) need smaller quantity thresholds
+                if price_decimals <= 2:  # Major tokens like BTC, ETH (high value)
+                    quantity_threshold = 0.0001
+                elif price_decimals <= 4:  # Mid-tier tokens 
+                    quantity_threshold = 0.001
+                else:  # Lower-value tokens (meme coins, etc.)
+                    quantity_threshold = 0.01
+                    
+                # Also set minimum value threshold based on token category
+                min_value_threshold = 1.0  # Minimum $1 change for any token
+                
+            except Exception as e:
+                logger.debug(f"Could not get token formatting info for {symbol}, using defaults: {e}")
+                quantity_threshold = 0.001
+                min_value_threshold = 1.0
+                price_decimals = 4  # Default for fallback logging
+            
+            # Trigger notification if either:
+            # 1. Quantity change exceeds token-specific threshold, OR
+            # 2. Value change exceeds minimum value threshold
+            should_notify = (abs(size_change) > quantity_threshold or 
+                           change_value > min_value_threshold)
+            
+            if should_notify:
                 
                 change_type = "Increased" if size_change > 0 else "Decreased"
                 side = "Long" if curr_size > 0 else "Short"
                 
+                # Use formatter for consistent display
+                try:
+                    formatted_new_size = await formatter.format_amount(abs(curr_size), symbol)
+                    formatted_change = await formatter.format_amount(abs(size_change), symbol)
+                    formatted_value_change = await formatter.format_price_with_symbol(change_value)
+                    formatted_current_price = await formatter.format_price_with_symbol(current_market_price, symbol)
+                except Exception:
+                    # Fallback formatting
+                    formatted_new_size = f"{abs(curr_size):.4f}"
+                    formatted_change = f"{abs(size_change):.4f}"
+                    formatted_value_change = f"${change_value:.2f}"
+                    formatted_current_price = f"${current_market_price:.4f}"
+                
                 message = (
                     f"🔄 Position {change_type}\n"
                     f"Token: {symbol}\n"
                     f"Side: {side}\n"
-                    f"New Size: {abs(curr_size):.4f}\n"
-                    f"Change: {'+' if size_change > 0 else ''}{size_change:.4f}\n\n"
+                    f"New Size: {formatted_new_size}\n"
+                    f"Change: {'+' if size_change > 0 else ''}{formatted_change}\n"
+                    f"Value Change: {formatted_value_change}\n"
+                    f"Current Price: {formatted_current_price}\n\n"
                     f"💡 Use /positions to see current positions"
                 )
                 
                 await self.notification_manager.send_generic_notification(message)
-                logger.info(f"Position changed: {symbol} {change_type} by {size_change:.4f}")
+                logger.info(f"Position changed: {symbol} {change_type} by {size_change:.6f} (${change_value:.2f}) "
+                          f"threshold: {quantity_threshold} qty or ${min_value_threshold} value")
+            else:
+                # Log when changes don't meet threshold (debug level to avoid spam)
+                logger.debug(f"Position size changed for {symbol} but below notification threshold: "
+                           f"{size_change:.6f} quantity (${change_value:.2f} value), "
+                           f"thresholds: {quantity_threshold} qty or ${min_value_threshold} value "
+                           f"(price_decimals: {price_decimals if 'price_decimals' in locals() else 'unknown'})")
                 
         except Exception as e:
             logger.error(f"Error handling position change for {symbol}: {e}")

+ 1 - 1
trading_bot.py

@@ -14,7 +14,7 @@ from datetime import datetime
 from pathlib import Path
 
 # Bot version
-BOT_VERSION = "2.6.306"
+BOT_VERSION = "2.6.307"
 
 # Add src directory to Python path
 sys.path.insert(0, str(Path(__file__).parent / "src"))