Quellcode durchsuchen

Enhance InfoCommands and MarketMonitor with notification manager integration - Updated InfoCommands to accept a notification manager for sending alerts on auto-synced positions. Improved MarketMonitor to immediately check for orphaned positions on startup and send notifications with detailed risk metrics. Enhanced logging for better traceability of auto-sync actions and notifications.

Carles Sentis vor 4 Tagen
Ursprung
Commit
b35c45a3cc
3 geänderte Dateien mit 188 neuen und 29 gelöschten Zeilen
  1. 1 1
      src/bot/core.py
  2. 44 2
      src/commands/info_commands.py
  3. 143 26
      src/monitoring/market_monitor.py

+ 1 - 1
src/bot/core.py

@@ -38,7 +38,7 @@ class TelegramTradingBot:
         self.trading_engine.set_market_monitor(self.market_monitor)
         
         # Initialize command handlers
-        self.info_commands = InfoCommands(self.trading_engine)
+        self.info_commands = InfoCommands(self.trading_engine, self.notification_manager)
         self.management_commands = ManagementCommands(self.trading_engine, self.market_monitor)
         # Pass info and management command handlers to TradingCommands
         self.trading_commands = TradingCommands(self.trading_engine, self.notification_manager, 

+ 44 - 2
src/commands/info_commands.py

@@ -17,9 +17,10 @@ logger = logging.getLogger(__name__)
 class InfoCommands:
     """Handles all information-related Telegram commands."""
     
-    def __init__(self, trading_engine):
-        """Initialize with trading engine."""
+    def __init__(self, trading_engine, notification_manager=None):
+        """Initialize with trading engine and notification manager."""
         self.trading_engine = trading_engine
+        self.notification_manager = notification_manager
     
     def _is_authorized(self, chat_id: str) -> bool:
         """Check if the chat ID is authorized."""
@@ -173,6 +174,47 @@ class InfoCommands:
                         if success:
                             synced_positions.append(symbol)
                             logger.info(f"✅ Successfully synced orphaned position for {symbol}")
+                            
+                            # 🆕 Send immediate notification for auto-synced position
+                            token = symbol.split('/')[0] if '/' in symbol else symbol
+                            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))
+                            pnl_percentage = float(exchange_pos.get('percentage', 0))
+                            
+                            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: ${entry_price:,.4f}\n"
+                                f"💵 Value: ${position_value:,.2f}\n"
+                                f"{pnl_emoji} P&L: ${unrealized_pnl:,.2f} ({pnl_percentage:+.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"\n📍 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"
+                            )
+                            
+                            # Send notification via trading engine's notification manager
+                            if self.notification_manager:
+                                try:
+                                    await self.notification_manager.send_generic_notification(notification_text)
+                                    logger.info(f"📤 Sent auto-sync notification for {symbol}")
+                                except Exception as e:
+                                    logger.error(f"❌ Failed to send auto-sync notification: {e}")
+                            else:
+                                logger.warning(f"⚠️ No notification manager available for auto-sync notification")
                         else:
                             logger.error(f"❌ Failed to sync orphaned position for {symbol}")
                     else:

+ 143 - 26
src/monitoring/market_monitor.py

@@ -54,6 +54,7 @@ class MarketMonitor:
     async def start(self):
         """Start the market monitor."""
         if self._monitoring_active:
+            logger.warning("Market monitor is already active")
             return
         
         self._monitoring_active = True
@@ -62,7 +63,7 @@ class MarketMonitor:
         # Initialize tracking
         await self._initialize_tracking()
         
-        # Start monitoring task
+        # Start the monitoring loop
         self._monitor_task = asyncio.create_task(self._monitor_loop())
     
     async def stop(self):
@@ -136,29 +137,36 @@ class MarketMonitor:
     async def _initialize_tracking(self):
         """Initialize order and position tracking."""
         try:
-            # Get current open orders to initialize tracking
-            orders = self.trading_engine.get_orders()
-            if orders:
+            # Initialize order tracking
+            try:
+                orders = self.trading_engine.get_orders() or []
                 self.last_known_orders = {order.get('id') for order in orders if order.get('id')}
-                logger.info(f"📋 Initialized tracking with {len(self.last_known_orders)} open orders")
-            
-            # Get current positions for P&L tracking
-            positions = self.trading_engine.get_positions()
-            if positions:
-                for position in positions:
-                    symbol = position.get('symbol')
-                    contracts = float(position.get('contracts', 0))
-                    entry_price = float(position.get('entryPx', 0))
-                    
-                    if symbol and contracts != 0:
-                        self.last_known_positions[symbol] = {
-                            'contracts': contracts,
-                            'entry_price': entry_price
-                        }
-                logger.info(f"📊 Initialized tracking with {len(self.last_known_positions)} positions")
+                logger.info(f"📋 Initialized tracking with {len(orders)} open orders")
+            except Exception as e:
+                logger.error(f"❌ Failed to initialize order tracking: {e}")
+                self.last_known_orders = set()
+
+            # Initialize position tracking
+            try:
+                positions = self.trading_engine.get_positions() or []
+                self.last_known_positions = {
+                    pos.get('symbol'): pos for pos in positions 
+                    if pos.get('symbol') and abs(float(pos.get('contracts', 0))) > 0
+                }
+                logger.info(f"📊 Initialized tracking with {len(positions)} positions")
                 
+                # 🆕 IMMEDIATE AUTO-SYNC: Check for orphaned positions right after initialization
+                if positions:
+                    await self._immediate_startup_auto_sync()
+                    
+            except Exception as e:
+                logger.error(f"❌ Failed to initialize position tracking: {e}")
+                self.last_known_positions = {}
+
         except Exception as e:
-            logger.error(f"❌ Error initializing tracking: {e}")
+            logger.error(f"❌ Failed to initialize tracking: {e}")
+            self.last_known_orders = set()
+            self.last_known_positions = {}
     
     async def _monitor_loop(self):
         """Main monitoring loop that runs every BOT_HEARTBEAT_SECONDS."""
@@ -1628,10 +1636,10 @@ class MarketMonitor:
                         if lifecycle_id:
                             # Update to position_opened status
                             success = stats.update_trade_position_opened(
-                                lifecycle_id=lifecycle_id,
-                                entry_price=entry_price,
-                                entry_amount=abs(contracts),
-                                exchange_fill_id=f"external_fill_{int(datetime.now().timestamp())}"
+                                lifecycle_id, 
+                                entry_price, 
+                                abs(contracts),
+                                f"external_fill_{int(datetime.now().timestamp())}"
                             )
                             
                             if success:
@@ -1728,4 +1736,113 @@ class MarketMonitor:
             logger.info(f"🧹 _handle_orphaned_position deprecated: use _auto_sync_orphaned_positions instead")
 
         except Exception as e:
-            logger.error(f"❌ Error handling orphaned position: {e}", exc_info=True) 
+            logger.error(f"❌ Error handling orphaned position: {e}", exc_info=True) 
+
+    async def _immediate_startup_auto_sync(self):
+        """🆕 Immediately check for and sync orphaned positions on startup."""
+        try:
+            logger.info("🔍 Checking for orphaned positions on startup...")
+            
+            stats = self.trading_engine.get_stats()
+            if not stats:
+                logger.warning("⚠️ TradingStats not available for startup auto-sync")
+                return
+
+            # Get fresh exchange positions
+            exchange_positions = self.trading_engine.get_positions() or []
+            synced_count = 0
+
+            for exchange_pos in exchange_positions:
+                symbol = exchange_pos.get('symbol')
+                contracts = float(exchange_pos.get('contracts', 0))
+                
+                if symbol and abs(contracts) > 0:
+                    # Check if we have a trade lifecycle record for this position
+                    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('entryPrice', 0))
+                        position_side = 'long' if contracts > 0 else 'short'
+                        order_side = 'buy' if contracts > 0 else 'sell'
+                        
+                        logger.info(f"🔄 STARTUP: Auto-syncing orphaned position: {symbol} {position_side} {abs(contracts)} @ ${entry_price} (exchange data)")
+                        
+                        # Create trade lifecycle for external position
+                        lifecycle_id = stats.create_trade_lifecycle(
+                            symbol=symbol,
+                            side=order_side,
+                            entry_order_id=f"startup_sync_{int(datetime.now().timestamp())}",
+                            trade_type='external'
+                        )
+                        
+                        if lifecycle_id:
+                            # Update to position_opened status
+                            success = stats.update_trade_position_opened(
+                                lifecycle_id, 
+                                entry_price, 
+                                abs(contracts),
+                                f"startup_fill_{int(datetime.now().timestamp())}"
+                            )
+                            
+                            if success:
+                                synced_count += 1
+                                logger.info(f"✅ STARTUP: Successfully synced orphaned position for {symbol}")
+                                
+                                # 🆕 Send immediate notification
+                                await self._send_startup_auto_sync_notification(exchange_pos, symbol, position_side, abs(contracts), entry_price)
+                            else:
+                                logger.error(f"❌ STARTUP: Failed to sync orphaned position for {symbol}")
+                        else:
+                            logger.error(f"❌ STARTUP: Failed to create lifecycle for orphaned position {symbol}")
+
+            if synced_count > 0:
+                logger.info(f"🎉 STARTUP: Auto-synced {synced_count} orphaned position(s) and sent notifications")
+            else:
+                logger.info("✅ STARTUP: No orphaned positions found - all positions already tracked")
+                
+        except Exception as e:
+            logger.error(f"❌ Error in startup auto-sync: {e}")
+
+    async def _send_startup_auto_sync_notification(self, exchange_pos, symbol, position_side, contracts, entry_price):
+        """Send notification for positions auto-synced on startup."""
+        try:
+            if not self.notification_manager:
+                logger.warning("⚠️ No notification manager available for startup auto-sync notification")
+                return
+
+            token = symbol.split('/')[0] if '/' in symbol else symbol
+            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))
+            pnl_percentage = float(exchange_pos.get('percentage', 0))
+            
+            pnl_emoji = "🟢" if unrealized_pnl >= 0 else "🔴"
+            notification_text = (
+                f"🚨 <b>Bot Startup: Position Auto-Synced</b>\n\n"
+                f"🎯 Token: {token}\n"
+                f"📈 Direction: {position_side.upper()}\n"
+                f"📏 Size: {contracts:.6f} {token}\n"
+                f"💰 Entry: ${entry_price:,.4f}\n"
+                f"💵 Value: ${position_value:,.2f}\n"
+                f"{pnl_emoji} P&L: ${unrealized_pnl:,.2f} ({pnl_percentage:+.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"\n📍 <b>Discovered on bot startup</b>\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)
+            logger.info(f"📤 STARTUP: Sent auto-sync notification for {symbol}")
+            
+        except Exception as e:
+            logger.error(f"❌ STARTUP: Failed to send auto-sync notification for {symbol}: {e}")