ソースを参照

Enhance position management with live market data integration

- Updated PositionsCommands to retrieve live exchange data for positions, improving accuracy in ROE and mark price calculations.
- Implemented a new method in PositionTracker to update the database with current market data for open positions, ensuring consistency with live data.
- Refactored RiskManager to clarify the handling of ROE thresholds, emphasizing the distinction between profit and loss scenarios.
- Improved error handling and logging throughout the position management process for better traceability.
Carles Sentis 18 時間 前
コミット
07be533f9b

+ 32 - 7
src/commands/info/positions.py

@@ -28,8 +28,16 @@ class PositionsCommands(InfoCommandsBase):
                 await self._reply(update, "📭 No open positions\n\n💡 Use /long or /short to open a position")
                 return
 
-            # Get current exchange orders for stop loss detection
+            # Get current exchange data for live ROE and mark prices
+            exchange_positions = self.trading_engine.get_positions() or []
             exchange_orders = self.trading_engine.get_orders() or []
+            
+            # Create lookup for exchange data by symbol
+            exchange_data_by_symbol = {}
+            for ex_pos in exchange_positions:
+                symbol_key = ex_pos.get('coin', '')
+                if symbol_key:
+                    exchange_data_by_symbol[symbol_key] = ex_pos
 
             # Initialize totals
             total_position_value = 0.0
@@ -91,13 +99,21 @@ class PositionsCommands(InfoCommandsBase):
                             logger.warning(f"Could not parse position_opened_at: {position_opened_at_str} for {symbol}")
                             duration_str = "Error"
                     
-                    # Get price data with defaults
+                    # Get price data with defaults - prioritize live exchange data
                     mark_price = entry_price  # Default to entry price
-                    if position_trade.get('mark_price') is not None:
+                    
+                    # Try to get live mark price from exchange first
+                    if exchange_data and exchange_data.get('markPrice') is not None:
+                        try:
+                            mark_price = float(exchange_data['markPrice'])
+                        except (ValueError, TypeError):
+                            logger.warning(f"Could not convert exchange mark_price for {symbol}")
+                    # Fallback to database mark price
+                    elif position_trade.get('mark_price') is not None:
                         try:
                             mark_price = float(position_trade['mark_price'])
                         except (ValueError, TypeError):
-                            logger.warning(f"Could not convert mark_price for {symbol}")
+                            logger.warning(f"Could not convert database mark_price for {symbol}")
                     
                     # Calculate unrealized PnL
                     unrealized_pnl = 0.0
@@ -107,13 +123,22 @@ class PositionsCommands(InfoCommandsBase):
                         except (ValueError, TypeError):
                             logger.warning(f"Could not convert unrealized_pnl for {symbol}")
                     
-                    # Get ROE from database
+                    # Get ROE from live exchange data (much more accurate)
                     roe_percentage = 0.0
-                    if position_trade.get('roe_percentage') is not None:
+                    exchange_data = exchange_data_by_symbol.get(base_asset)
+                    if exchange_data and exchange_data.get('returnOnEquity') is not None:
+                        try:
+                            # Convert from decimal (0.118) to percentage (11.8%)
+                            roe_percentage = float(exchange_data['returnOnEquity']) * 100
+                        except (ValueError, TypeError):
+                            logger.warning(f"Could not convert exchange ROE for {symbol}")
+                    
+                    # Fallback to database ROE if exchange data not available
+                    if roe_percentage == 0.0 and position_trade.get('roe_percentage') is not None:
                         try:
                             roe_percentage = float(position_trade['roe_percentage'])
                         except (ValueError, TypeError):
-                            logger.warning(f"Could not convert roe_percentage for {symbol}")
+                            logger.warning(f"Could not convert database roe_percentage for {symbol}")
 
                     # Add to totals
                     individual_position_value = 0.0

+ 47 - 0
src/monitoring/position_tracker.py

@@ -62,9 +62,56 @@ class PositionTracker:
             # Compare with previous positions
             await self._process_position_changes(previous_positions, self.current_positions)
             
+            # 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 _update_database_market_data(self):
+        """Update database with current market data for open positions"""
+        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()
+            
+            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
+                    
+                    # Find corresponding exchange position
+                    if token in self.current_positions:
+                        pos_data = self.current_positions[token]
+                        
+                        # Convert exchange ROE from decimal to percentage
+                        roe_percentage = pos_data['return_on_equity'] * 100
+                        
+                        # Update database with live market data
+                        self.trading_stats.update_trade_market_data(
+                            trade_lifecycle_id=trade['trade_lifecycle_id'],
+                            current_position_size=abs(pos_data['size']),
+                            unrealized_pnl=pos_data['unrealized_pnl'],
+                            roe_percentage=roe_percentage,
+                            margin_used=pos_data['margin_used'],
+                            leverage=pos_data['max_leverage']
+                        )
+                        
+                except Exception as e:
+                    logger.warning(f"Error updating market data for trade {trade.get('trade_lifecycle_id', 'unknown')}: {e}")
+                    continue
+                    
+        except Exception as e:
+            logger.error(f"Error updating database market data: {e}")
+            
     async def _update_current_positions(self):
         """Update current positions from exchange"""
         try:

+ 3 - 2
src/monitoring/risk_manager.py

@@ -21,7 +21,8 @@ class RiskManager:
         self.is_running = False
         
         # Risk thresholds from config (convert percentage to decimal)
-        self.hard_exit_roe = -(config.STOP_LOSS_PERCENTAGE / 100.0)  # 10.0% -> -0.10 ROE
+        # Note: Exchange ROE is positive for profits, negative for losses
+        self.hard_exit_roe = -(config.STOP_LOSS_PERCENTAGE / 100.0)  # 10.0% -> -0.10 ROE (loss threshold)
         
     async def start(self):
         """Start risk manager"""
@@ -64,7 +65,7 @@ class RiskManager:
                     symbol = position.get('coin', '')
                     roe = float(position.get('returnOnEquity', '0'))
                     
-                    # Check if ROE exceeds hard exit threshold
+                    # Check if ROE exceeds hard exit threshold (negative ROE = loss)
                     if roe <= self.hard_exit_roe:
                         positions_to_close.append({
                             'symbol': symbol,

+ 1 - 1
trading_bot.py

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