Selaa lähdekoodia

Refactor ROE handling in PositionMonitor to utilize stored heartbeat data

- Updated PositionMonitor to retrieve the last known ROE from lifecycle data instead of directly from exchange data, improving reliability.
- Enhanced error handling and logging for ROE parsing and availability checks.
- Modified position closure handling to check for duplicate processing and improved exit price estimation from market data.
- Updated logging messages for clarity and consistency during position reconciliation.
Carles Sentis 1 päivä sitten
vanhempi
sitoutus
114b16e1f4
2 muutettua tiedostoa jossa 42 lisäystä ja 28 poistoa
  1. 41 27
      src/monitoring/position_monitor.py
  2. 1 1
      trading_bot.py

+ 41 - 27
src/monitoring/position_monitor.py

@@ -207,22 +207,18 @@ class PositionMonitor:
                 pnl_emoji = "🟢" if realized_pnl and realized_pnl >= 0 else "🔴"
                 pnl_text = f"{await formatter.format_price_with_symbol(realized_pnl)}" if realized_pnl is not None else "N/A"
                 
-                # Get ROE directly from exchange data
-                info_data = existing_lc.get('info', {})
-                position_info = info_data.get('position', {})
-                roe_raw = position_info.get('returnOnEquity')  # Changed from 'percentage' to 'returnOnEquity'
-                
-                if roe_raw is not None:
+                # Use the last known ROE from heartbeat data (stored in lifecycle)
+                stored_roe = existing_lc.get('roe_percentage')
+                if stored_roe is not None:
                     try:
-                        # The exchange provides ROE as a decimal (e.g., -0.326 for -32.6%)
-                        # We need to multiply by 100 and keep the sign
-                        roe = float(roe_raw) * 100
+                        roe = float(stored_roe)
                         roe_text = f" ({roe:+.2f}%)"
+                        logger.debug(f"Using stored ROE from heartbeat for {full_symbol}: {roe:+.2f}%")
                     except (ValueError, TypeError):
-                        logger.warning(f"Could not parse ROE value: {roe_raw} for {full_symbol}")
+                        logger.warning(f"Could not parse stored ROE value: {stored_roe} for {full_symbol}")
                         roe_text = ""
                 else:
-                    logger.warning(f"No ROE data available from exchange for {full_symbol}")
+                    logger.debug(f"No stored ROE available for {full_symbol}")
                     roe_text = ""
                 
                 message = f"""
@@ -970,19 +966,32 @@ class PositionMonitor:
             logger.error(f"❌ Error handling position opened for {symbol}: {e}")
     
     async def _handle_position_closed(self, symbol: str, db_pos: Dict, stats, timestamp: datetime):
-        """Handle position closure detection."""
+        """Handle position closed during reconciliation."""
         try:
             lifecycle_id = db_pos['trade_lifecycle_id']
             entry_price = db_pos.get('entry_price', 0)
             position_side = db_pos.get('position_side')
             size = db_pos.get('current_position_size', 0)
             
-            # Estimate exit price (could be improved with recent fills)
-            market_data = await self.trading_engine.get_market_data(symbol)
-            exit_price = entry_price  # Fallback
-            if market_data and market_data.get('ticker'):
-                exit_price = float(market_data['ticker'].get('last', exit_price))
+            # Get the latest lifecycle status to check if it was already closed
+            latest_lifecycle = stats.get_trade_by_lifecycle_id(lifecycle_id)
+            if latest_lifecycle and latest_lifecycle.get('status') == 'position_closed':
+                logger.info(f"ℹ️ Position for {symbol} already marked as closed in lifecycle {lifecycle_id[:8]}. Skipping duplicate close processing.")
+                return
             
+            # Estimate exit price from market data
+            exit_price = 0
+            try:
+                market_data = await self.trading_engine.get_symbol_data(symbol)
+                if market_data and 'markPrice' in market_data:
+                    exit_price = float(market_data['markPrice'])
+                else:
+                    logger.warning(f"⚠️ Could not get exit price for {symbol} - using entry price")
+                    exit_price = entry_price
+            except Exception as e:
+                logger.warning(f"⚠️ Error fetching market data for {symbol}: {e} - using entry price")
+                exit_price = entry_price
+
             # Calculate realized PnL
             realized_pnl = 0
             if position_side == 'long':
@@ -1018,6 +1027,7 @@ class PositionMonitor:
                     'exit_price': exit_price,
                     'realized_pnl': realized_pnl,
                     'timestamp': timestamp,
+                    'lifecycle_id': lifecycle_id,  # Pass lifecycle_id to get stored ROE
                     'info': exchange_pos.get('info', {}) if exchange_pos else {}
                 })
                 
@@ -1176,22 +1186,26 @@ class PositionMonitor:
                 pnl = details['realized_pnl']
                 pnl_emoji = "🟢" if pnl >= 0 else "🔴"
                 
-                # Get ROE directly from exchange data
-                info_data = details.get('info', {})
-                position_info = info_data.get('position', {})
-                roe_raw = position_info.get('returnOnEquity')  # Changed from 'percentage' to 'returnOnEquity'
+                # Use the last known ROE from heartbeat data (stored in lifecycle)
+                stored_roe = None
+                lifecycle_id = details.get('lifecycle_id')
+                if lifecycle_id:
+                    stats = self.trading_engine.get_stats()
+                    if stats:
+                        lifecycle = stats.get_trade_by_lifecycle_id(lifecycle_id)
+                        if lifecycle:
+                            stored_roe = lifecycle.get('roe_percentage')
                 
-                if roe_raw is not None:
+                if stored_roe is not None:
                     try:
-                        # The exchange provides ROE as a decimal (e.g., -0.326 for -32.6%)
-                        # We need to multiply by 100 and keep the sign
-                        roe = float(roe_raw) * 100
+                        roe = float(stored_roe)
                         roe_text = f"({roe:+.2f}%)"
+                        logger.debug(f"Using stored ROE from heartbeat for reconciled {symbol}: {roe:+.2f}%")
                     except (ValueError, TypeError):
-                        logger.warning(f"Could not parse ROE value: {roe_raw} for {symbol}")
+                        logger.warning(f"Could not parse stored ROE value: {stored_roe} for {symbol}")
                         roe_text = ""
                 else:
-                    logger.warning(f"No ROE data available from exchange for {symbol}")
+                    logger.debug(f"No stored ROE available for reconciled {symbol}")
                     roe_text = ""
                 
                 message = f"""🎯 <b>Position Closed (Reconciled)</b>

+ 1 - 1
trading_bot.py

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