Ver Fonte

Increment bot version to 2.6.299 and simplify position tracking logic

- Updated BOT_VERSION to 2.6.299.
- Refined position tracking in PositionTracker by removing the database reconciliation method for missed position closures.
- Enhanced clarity in comments and logging for position change processing and handling of closed positions.
- Improved notification messages for position closures to reflect accurate PnL calculations.
Carles Sentis há 22 horas atrás
pai
commit
9c931fa2ab
2 ficheiros alterados com 49 adições e 100 exclusões
  1. 48 99
      src/monitoring/position_tracker.py
  2. 1 1
      trading_bot.py

+ 48 - 99
src/monitoring/position_tracker.py

@@ -81,88 +81,15 @@ class PositionTracker:
             previous_positions = self.current_positions.copy()
             await self._update_current_positions()
             
-            # Compare with previous positions (normal tracking)
+            # Compare with previous positions (simple exchange state tracking)
             await self._process_position_changes(previous_positions, self.current_positions)
             
-            # IMPORTANT: Also reconcile with database state to catch missed positions
-            await self._reconcile_database_with_exchange()
-            
             # Update database with current market data for open positions
             await self._update_database_market_data()
             
         except Exception as e:
             logger.error(f"Error checking position changes: {e}")
             
-    async def _reconcile_database_with_exchange(self):
-        """Reconcile database state with exchange state to catch missed position closures"""
-        try:
-            # Lazy load TradingStats if needed
-            if self.trading_stats is None:
-                from ..stats.trading_stats import TradingStats
-                self.trading_stats = TradingStats()
-            
-            # Get open trades from database
-            open_trades = self.trading_stats.get_open_positions()
-            logger.debug(f"🔍 Reconciling: Database shows {len(open_trades)} open positions, Exchange shows {len(self.current_positions)}")
-            
-            for trade in open_trades:
-                try:
-                    symbol = trade.get('symbol', '')
-                    if not symbol:
-                        continue
-                        
-                    # Extract token from symbol (e.g., "BTC/USDC:USDC" -> "BTC")  
-                    token = symbol.split('/')[0] if '/' in symbol else symbol
-                    
-                    # Check if this database position exists on the exchange
-                    if token not in self.current_positions:
-                        # Position exists in database but NOT on exchange = it was closed and we missed it!
-                        logger.warning(f"🔍 Found missed position closure: {symbol} exists in database but not on exchange")
-                        
-                        # We need to simulate the position closure
-                        # Get current market price to calculate exit PnL
-                        market_data = self.hl_client.get_market_data(symbol)
-                        if not market_data:
-                            logger.error(f"Could not get market data for {symbol} to process missed closure")
-                            continue
-                            
-                        current_price = float(market_data.get('ticker', {}).get('last', 0))
-                        entry_price = float(trade.get('entry_price', 0))
-                        size = float(trade.get('amount', 0))
-                        side = trade.get('side', '').lower()
-                        
-                        # Calculate PnL
-                        if side == "long":
-                            pnl = (current_price - entry_price) * size
-                        else:
-                            pnl = (entry_price - current_price) * size
-                            
-                        # Close the position in database
-                        await self._save_position_stats(symbol, side, size, entry_price, current_price, pnl)
-                        
-                        # Send notification about missed closure
-                        pnl_emoji = "🟢" if pnl >= 0 else "🔴"
-                        message = (
-                            f"{pnl_emoji} Position Closed (Missed)\n"
-                            f"Token: {token}\n"
-                            f"Side: {side.title()}\n"
-                            f"Size: {size:.4f}\n"
-                            f"Entry: ${entry_price:.4f}\n"
-                            f"Exit: ${current_price:.4f}\n"
-                            f"PnL: ${pnl:.3f}\n"
-                            f"⚠️ Closed between monitoring cycles"
-                        )
-                        
-                        await self.notification_manager.send_generic_notification(message)
-                        logger.info(f"📊 Processed missed position closure: {symbol} side={side} PnL=${pnl:.3f}")
-                        
-                except Exception as e:
-                    logger.warning(f"Error processing missed closure for trade {trade.get('trade_lifecycle_id', 'unknown')}: {e}")
-                    continue
-                    
-        except Exception as e:
-            logger.error(f"Error reconciling database with exchange: {e}")
-            
     async def _update_database_market_data(self):
         """Update database with current market data for open positions"""
         try:
@@ -315,45 +242,67 @@ class PositionTracker:
             logger.error(f"Error handling position opened for {symbol}: {e}")
             
     async def _handle_position_closed(self, symbol: str, position: Dict):
-        """Handle position closed - save stats to database"""
+        """Handle position closed - find and close the corresponding database trade"""
         try:
-            # Construct full symbol format for market data (symbol here is just token name like "BTC")
+            # Lazy load TradingStats if needed
+            if self.trading_stats is None:
+                from ..stats.trading_stats import TradingStats
+                self.trading_stats = TradingStats()
+            
+            # Construct full symbol format (symbol here is just token name like "BTC")
             full_symbol = f"{symbol}/USDC:USDC"
             
-            # Get current market price for PnL calculation
+            # Find the open trade in database for this symbol
+            open_trade = self.trading_stats.get_trade_by_symbol_and_status(full_symbol, 'position_opened')
+            
+            if not open_trade:
+                logger.warning(f"No open trade found in database for {full_symbol} - position was closed on exchange but no database record")
+                return
+                
+            lifecycle_id = open_trade['trade_lifecycle_id']
+            entry_price = position['entry_px']
+            size = abs(position['size'])
+            side = "Long" if position['size'] > 0 else "Short"
+            
+            # Get current market price for exit calculation
             market_data = self.hl_client.get_market_data(full_symbol)
             if not market_data:
                 logger.error(f"Could not get market data for {full_symbol}")
                 return
                 
             current_price = float(market_data.get('ticker', {}).get('last', 0))
-            entry_price = position['entry_px']
-            size = abs(position['size'])
-            side = "Long" if position['size'] > 0 else "Short"
             
-            # Calculate PnL
+            # Calculate realized PnL
             if side == "Long":
-                pnl = (current_price - entry_price) * size
+                realized_pnl = (current_price - entry_price) * size
             else:
-                pnl = (entry_price - current_price) * size
-                
-            # Save to database with full symbol format
-            await self._save_position_stats(full_symbol, side, size, entry_price, current_price, pnl)
-            
-            # Send notification
-            pnl_emoji = "🟢" if pnl >= 0 else "🔴"
-            message = (
-                f"{pnl_emoji} Position Closed\n"
-                f"Token: {symbol}\n"
-                f"Side: {side}\n"
-                f"Size: {size:.4f}\n"
-                f"Entry: ${entry_price:.4f}\n"
-                f"Exit: ${current_price:.4f}\n"
-                f"PnL: ${pnl:.3f}"
+                realized_pnl = (entry_price - current_price) * size
+            
+            # Close the trade in database
+            success = await self.trading_stats.update_trade_position_closed(
+                lifecycle_id=lifecycle_id,
+                exit_price=current_price,
+                realized_pnl=realized_pnl,
+                exchange_fill_id="position_tracker_detected_closure"
             )
             
-            await self.notification_manager.send_generic_notification(message)
-            logger.info(f"Position closed: {symbol} {side} PnL: ${pnl:.3f}")
+            if success:
+                # Send clean notification
+                pnl_emoji = "🟢" if realized_pnl >= 0 else "🔴"
+                message = (
+                    f"{pnl_emoji} Position Closed\n"
+                    f"Token: {symbol}\n"
+                    f"Side: {side}\n"
+                    f"Size: {size:.4f}\n"
+                    f"Entry: ${entry_price:.4f}\n"
+                    f"Exit: ${current_price:.4f}\n"
+                    f"PnL: ${realized_pnl:.3f}"
+                )
+                
+                await self.notification_manager.send_generic_notification(message)
+                logger.info(f"Position closed: {symbol} {side} PnL: ${realized_pnl:.3f}")
+            else:
+                logger.error(f"Failed to close trade {lifecycle_id} for {symbol}")
             
         except Exception as e:
             logger.error(f"Error handling position closed 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.298"
+BOT_VERSION = "2.6.299"
 
 # Add src directory to Python path
 sys.path.insert(0, str(Path(__file__).parent / "src"))