瀏覽代碼

Enhance orphaned position handling in InfoCommands and MarketMonitor - Updated logic to utilize exchange data for entry prices, improving accuracy in position management. Added detailed notifications with exchange-provided risk metrics, including unrealized P&L, leverage, and liquidation prices. Enhanced logging for better traceability of auto-sync actions and fallback mechanisms.

Carles Sentis 4 天之前
父節點
當前提交
a210658664
共有 2 個文件被更改,包括 101 次插入43 次删除
  1. 64 26
      src/commands/info_commands.py
  2. 37 17
      src/monitoring/market_monitor.py

+ 64 - 26
src/commands/info_commands.py

@@ -125,17 +125,18 @@ class InfoCommands:
                 existing_trade = stats.get_trade_by_symbol_and_status(symbol, 'position_opened')
                 
                 if not existing_trade:
-                    # 🚨 ORPHANED POSITION: Auto-create trade lifecycle record
-                    entry_price = float(exchange_pos.get('entryPx', 0))
+                    # 🚨 ORPHANED POSITION: Auto-create trade lifecycle record using exchange data
+                    entry_price = float(exchange_pos.get('entryPrice', 0))
                     position_side = 'long' if contracts > 0 else 'short'
                     order_side = 'buy' if contracts > 0 else 'sell'
                     
-                    # 🔧 FIX: If entry price is 0, try to estimate it
-                    if entry_price <= 0:
+                    # ✅ Use exchange data - no need to estimate!
+                    if entry_price > 0:
+                        logger.info(f"🔄 Auto-syncing orphaned position: {symbol} {position_side} {abs(contracts)} @ ${entry_price} (exchange data)")
+                    else:
+                        # Fallback only if exchange truly doesn't provide entry price
                         entry_price = await self._estimate_entry_price_for_orphaned_position(symbol, contracts)
-                        logger.info(f"🔄 Estimated entry price for orphaned {symbol}: ${entry_price:.4f}")
-                    
-                    logger.info(f"🔄 Auto-syncing orphaned position: {symbol} {position_side} {abs(contracts)} @ ${entry_price}")
+                        logger.warning(f"🔄 Auto-syncing orphaned position: {symbol} {position_side} {abs(contracts)} @ ${entry_price} (estimated)")
                     
                     # Create trade lifecycle for external position
                     lifecycle_id = stats.create_trade_lifecycle(
@@ -176,32 +177,61 @@ class InfoCommands:
             total_unrealized = 0
             total_position_value = 0
             
+            # Also get fresh exchange data for display
+            fresh_exchange_positions = self.trading_engine.get_positions() or []
+            exchange_data_map = {pos.get('symbol'): pos for pos in fresh_exchange_positions}
+            
             for position_trade in open_positions:
                 symbol = position_trade['symbol']
                 token = symbol.split('/')[0] if '/' in symbol else symbol
                 position_side = position_trade['position_side']  # 'long' or 'short'
                 entry_price = position_trade['entry_price']
                 current_amount = position_trade['current_position_size']
-                unrealized_pnl = position_trade.get('unrealized_pnl', 0)
                 trade_type = position_trade.get('trade_type', 'manual')
                 
-                # Get current market price
-                mark_price = entry_price  # Fallback
-                try:
-                    market_data = self.trading_engine.get_market_data(symbol)
-                    if market_data and market_data.get('ticker'):
-                        mark_price = float(market_data['ticker'].get('last', entry_price))
-                        
-                        # Update unrealized PnL with current price
-                        if position_side == 'long':
-                            unrealized_pnl = current_amount * (mark_price - entry_price)
-                        else:  # Short position
-                            unrealized_pnl = current_amount * (entry_price - mark_price)
-                except:
-                    pass  # Use entry price as fallback
+                # 🆕 Use fresh exchange data if available (most accurate)
+                exchange_pos = exchange_data_map.get(symbol)
+                if exchange_pos:
+                    # Use exchange's official data
+                    unrealized_pnl = float(exchange_pos.get('unrealizedPnl', 0))
+                    mark_price = float(exchange_pos.get('markPrice') or 0)
+                    position_value = float(exchange_pos.get('notional', 0))
+                    liquidation_price = float(exchange_pos.get('liquidationPrice', 0))
+                    margin_used = float(exchange_pos.get('initialMargin', 0))
+                    leverage = float(exchange_pos.get('leverage', 1))
+                    pnl_percentage = float(exchange_pos.get('percentage', 0))
+                    
+                    # Get mark price from market data if not in position data
+                    if mark_price <= 0:
+                        try:
+                            market_data = self.trading_engine.get_market_data(symbol)
+                            if market_data and market_data.get('ticker'):
+                                mark_price = float(market_data['ticker'].get('last', entry_price))
+                        except:
+                            mark_price = entry_price  # Fallback
+                else:
+                    # Fallback to our calculation if exchange data unavailable
+                    unrealized_pnl = position_trade.get('unrealized_pnl', 0)
+                    mark_price = entry_price  # Fallback
+                    try:
+                        market_data = self.trading_engine.get_market_data(symbol)
+                        if market_data and market_data.get('ticker'):
+                            mark_price = float(market_data['ticker'].get('last', entry_price))
+                            
+                            # Calculate unrealized PnL with current price
+                            if position_side == 'long':
+                                unrealized_pnl = current_amount * (mark_price - entry_price)
+                            else:  # Short position
+                                unrealized_pnl = current_amount * (entry_price - mark_price)
+                    except:
+                        pass  # Use entry price as fallback
+                    
+                    position_value = abs(current_amount) * mark_price
+                    liquidation_price = None
+                    margin_used = None
+                    leverage = None
+                    pnl_percentage = (unrealized_pnl / position_value * 100) if position_value > 0 else 0
                 
-                # Calculate position value
-                position_value = abs(current_amount) * mark_price
                 total_position_value += position_value
                 total_unrealized += unrealized_pnl
                 
@@ -214,7 +244,6 @@ class InfoCommands:
                     direction = "SHORT"
                 
                 pnl_emoji = "🟢" if unrealized_pnl >= 0 else "🔴"
-                pnl_percent = (unrealized_pnl / position_value * 100) if position_value > 0 else 0
                 
                 # Format prices with proper precision for this token
                 formatter = get_formatter()
@@ -233,7 +262,16 @@ class InfoCommands:
                 positions_text += f"   💰 Entry: {entry_price_str}\n"
                 positions_text += f"   📊 Mark: {mark_price_str}\n"
                 positions_text += f"   💵 Value: ${position_value:,.2f}\n"
-                positions_text += f"   {pnl_emoji} P&L: ${unrealized_pnl:,.2f} ({pnl_percent:+.2f}%)\n"
+                positions_text += f"   {pnl_emoji} P&L: ${unrealized_pnl:,.2f} ({pnl_percentage:+.2f}%)\n"
+                
+                # Show exchange-provided risk data if available
+                if leverage:
+                    positions_text += f"   ⚡ Leverage: {leverage:.1f}x\n"
+                if margin_used:
+                    positions_text += f"   💳 Margin: ${margin_used:,.2f}\n"
+                if liquidation_price:
+                    liq_price_str = formatter.format_price_with_symbol(liquidation_price, token)
+                    positions_text += f"   ⚠️ Liquidation: {liq_price_str}\n"
                 
                 # Show stop loss if linked
                 if position_trade.get('stop_loss_price'):

+ 37 - 17
src/monitoring/market_monitor.py

@@ -1542,17 +1542,24 @@ class MarketMonitor:
                     
                     if not existing_trade:
                         # 🚨 ORPHANED POSITION: Auto-create trade lifecycle record
-                        entry_price = float(exchange_pos.get('entryPx', 0))
+                        entry_price = float(exchange_pos.get('entryPrice', 0))
                         position_side = 'long' if contracts > 0 else 'short'
                         order_side = 'buy' if contracts > 0 else 'sell'
                         token = symbol.split('/')[0] if '/' in symbol else symbol
                         
-                        # 🔧 FIX: If entry price is 0, try to estimate it
-                        if entry_price <= 0:
+                        # ✅ Use exchange data - no need to estimate!
+                        if entry_price > 0:
+                            logger.info(f"🔄 AUTO-SYNC: Orphaned position detected - {symbol} {position_side} {abs(contracts)} @ ${entry_price} (exchange data)")
+                        else:
+                            # Fallback only if exchange truly doesn't provide entry price
                             entry_price = await self._estimate_entry_price_for_orphaned_position(symbol, contracts)
-                            logger.info(f"🔄 AUTO-SYNC: Estimated entry price for orphaned {symbol}: ${entry_price:.4f}")
+                            logger.warning(f"🔄 AUTO-SYNC: Orphaned position detected - {symbol} {position_side} {abs(contracts)} @ ${entry_price} (estimated)")
                         
-                        logger.warning(f"🔄 AUTO-SYNC: Orphaned position detected - {symbol} {position_side} {abs(contracts)} @ ${entry_price}")
+                        # Get additional exchange data for notification
+                        unrealized_pnl = float(exchange_pos.get('unrealizedPnl', 0))
+                        position_value = float(exchange_pos.get('notional', 0))
+                        liquidation_price = float(exchange_pos.get('liquidationPrice', 0))
+                        leverage = float(exchange_pos.get('leverage', 1))
                         
                         # Create trade lifecycle for external position
                         lifecycle_id = stats.create_trade_lifecycle(
@@ -1575,19 +1582,32 @@ class MarketMonitor:
                                 synced_count += 1
                                 logger.info(f"✅ AUTO-SYNC: Successfully synced orphaned position for {symbol}")
                                 
-                                # Send notification about auto-sync
+                                # Enhanced notification with exchange data
+                                pnl_emoji = "🟢" if unrealized_pnl >= 0 else "🔴"
+                                notification_text = (
+                                    f"🔄 <b>Position Auto-Synced</b>\n\n"
+                                    f"Token: {token}\n"
+                                    f"Direction: {position_side.upper()}\n"
+                                    f"Size: {abs(contracts):.6f} {token}\n"
+                                    f"Entry Price: ${entry_price:,.4f}\n"
+                                    f"Position Value: ${position_value:,.2f}\n"
+                                    f"{pnl_emoji} P&L: ${unrealized_pnl:,.2f}\n"
+                                )
+                                
+                                if leverage > 1:
+                                    notification_text += f"⚡ Leverage: {leverage:.1f}x\n"
+                                if liquidation_price > 0:
+                                    notification_text += f"⚠️ Liquidation: ${liquidation_price:,.2f}\n"
+                                
+                                notification_text += (
+                                    f"Reason: Position opened outside bot\n"
+                                    f"Time: {datetime.now().strftime('%H:%M:%S')}\n\n"
+                                    f"✅ Position now tracked in bot\n"
+                                    f"💡 Use /sl {token} [price] to set stop loss"
+                                )
+                                
                                 if self.notification_manager:
-                                    await self.notification_manager.send_generic_notification(
-                                        f"🔄 <b>Position Auto-Synced</b>\n\n"
-                                        f"Token: {token}\n"
-                                        f"Direction: {position_side.upper()}\n"
-                                        f"Size: {abs(contracts):.6f} {token}\n"
-                                        f"Entry Price: ${entry_price:,.4f}\n"
-                                        f"Reason: Position opened outside bot\n"
-                                        f"Time: {datetime.now().strftime('%H:%M:%S')}\n\n"
-                                        f"✅ Position now tracked in bot\n"
-                                        f"💡 Use /sl {token} [price] to set stop loss"
-                                    )
+                                    await self.notification_manager.send_generic_notification(notification_text)
                             else:
                                 logger.error(f"❌ AUTO-SYNC: Failed to sync orphaned position for {symbol}")
                         else: