Browse Source

Enhance stop loss handling in InfoCommands for improved risk management.

- Integrated checks for pending stop loss activations from both the old and new trade lifecycle systems, ensuring comprehensive monitoring of stop loss conditions.
- Updated the display logic to present pending stop loss activations clearly, including details on lifecycle IDs and activation conditions.
- Removed redundant fetching of stats to streamline performance and improve code clarity.
Carles Sentis 1 week ago
parent
commit
ae696f4a2c
2 changed files with 68 additions and 38 deletions
  1. 67 37
      src/commands/info_commands.py
  2. 1 1
      trading_bot.py

+ 67 - 37
src/commands/info_commands.py

@@ -402,10 +402,6 @@ class InfoCommands:
         if not self._is_authorized(update):
             return
         
-        # Fetch all exchange orders once to use throughout the command if needed by other parts
-        # For this specific change, we'll use it inside the loop, but good practice to fetch once.
-        # self._cached_all_exchange_orders = self.trading_engine.get_orders() or [] 
-        
         reply_method = None
         if update.callback_query:
             reply_method = update.callback_query.message.reply_text
@@ -418,6 +414,7 @@ class InfoCommands:
         
         try:
             orders = self.trading_engine.get_orders()
+            stats = self.trading_engine.get_stats()
             
             if orders is not None:
                 if len(orders) > 0:
@@ -460,8 +457,9 @@ class InfoCommands:
                             orders_text += f"   {side_emoji} {side} {formatter.format_amount(amount, symbol)} @ {formatter.format_price_with_symbol(price, symbol)}\n"
                             orders_text += f"   📋 Type: {order_type} | ID: {exchange_order_id}\n"
                             
-                            stats = self.trading_engine.get_stats()
+                            # Check for pending SL in the trade lifecycle system
                             if stats:
+                                # First check the old system for conceptual pending SLs
                                 order_in_db = stats.get_order_by_exchange_id(exchange_order_id)
                                 if order_in_db:
                                     bot_ref_id = order_in_db.get('bot_order_ref_id')
@@ -480,15 +478,26 @@ class InfoCommands:
                                             orders_text += f"   ⏳ Pending SL Activation: {sl_conceptual_side} at {formatter.format_price_with_symbol(sl_price, symbol)}\n"
                                             orders_text += f"      (Activates after main order fills)\n"
                                             displayed_sl_parent_refs.add(bot_ref_id)
+                                
+                                # Also check for pending SL in trade lifecycle (new system)
+                                lifecycle_manager = stats.trade_lifecycle_manager
+                                if lifecycle_manager:
+                                    pending_trade = lifecycle_manager.get_lifecycle_by_entry_order_id(exchange_order_id, status='pending')
+                                    if pending_trade and pending_trade.get('stop_loss_price'):
+                                        sl_price = pending_trade['stop_loss_price']
+                                        entry_side = pending_trade.get('side', '').lower()
+                                        sl_side = 'SELL' if entry_side == 'buy' else 'BUY'
+                                        
+                                        orders_text += f"   ⏳ Pending SL: {sl_side} at {formatter.format_price_with_symbol(sl_price, symbol)}\n"
+                                        orders_text += f"      (Activates when order fills)\n"
                             
                             orders_text += "\n"
                     
                     orders_text += f"💼 <b>Total Open Exchange Orders:</b> {len(orders)}\n"
                     
-                    # Now, check for any other pending_sl_activation orders whose parents are not in the open list
-                    stats_for_orphan_check = self.trading_engine.get_stats() # Get stats again if not already available
-                    if stats_for_orphan_check:
-                        all_pending_sl_activations = stats_for_orphan_check.get_orders_by_status(
+                    # Check for orphaned pending SLs from old system
+                    if stats:
+                        all_pending_sl_activations = stats.get_orders_by_status(
                             status='pending_activation',
                             order_type_filter='pending_sl_activation'
                         )
@@ -526,36 +535,57 @@ class InfoCommands:
                 else:
                     orders_text = "📋 <b>Open Orders (0)</b>\n\n"
                     orders_text += "📭 No open orders\n\n"
-                    # Check for purely conceptual pending SLs even if no exchange orders are open
-                    stats_for_empty_check = self.trading_engine.get_stats()
-                    if stats_for_empty_check:
-                        all_pending_sl_activations_empty = stats_for_empty_check.get_orders_by_status(
-                            status='pending_activation',
-                            order_type_filter='pending_sl_activation'
-                        )
-                        if all_pending_sl_activations_empty:
-                            orders_text += "\n" 
-                            orders_text += "⏳ <b>Pending SL Activations (Entry Order Assumed Filled/Closed)</b>\n\n"
-                            formatter_for_empty = get_formatter() # Ensure formatter is available
-
-                            orphaned_sls_by_symbol_group_empty = {}
-                            for sl_data_empty in all_pending_sl_activations_empty:
-                                sl_symbol_raw_empty = sl_data_empty.get('symbol', '')
-                                sl_symbol_display_key_empty = sl_symbol_raw_empty.replace('/USDC:USDC', '')
-                                if sl_symbol_display_key_empty not in orphaned_sls_by_symbol_group_empty:
-                                    orphaned_sls_by_symbol_group_empty[sl_symbol_display_key_empty] = []
-                                orphaned_sls_by_symbol_group_empty[sl_symbol_display_key_empty].append(sl_data_empty)
+                    
+                    # Check for pending SLs from trade lifecycle even if no exchange orders
+                    if stats and stats.trade_lifecycle_manager:
+                        pending_sl_trades = stats.trade_lifecycle_manager.get_pending_stop_loss_activations()
+                        
+                        if pending_sl_trades:
+                            orders_text += "\n⏳ <b>Pending Stop Loss Activations</b>\n\n"
+                            formatter_for_empty = get_formatter()
                             
-                            for sl_sym_key_empty, sl_list_items_empty in orphaned_sls_by_symbol_group_empty.items():
-                                orders_text += f"📊 <b>{sl_sym_key_empty}</b>\n"
-                                for sl_item_empty in sl_list_items_empty:
-                                    sl_price_val_empty = sl_item_empty.get('price', 0)
-                                    sl_side_val_empty = sl_item_empty.get('side', '').upper()
-                                    orders_text += f"   ⏳ Pending SL: {sl_side_val_empty} at {formatter_for_empty.format_price_with_symbol(sl_price_val_empty, sl_sym_key_empty)}\n"
-                                    orders_text += f"      (Awaiting activation by bot)\n\n"
-                            orders_text += f"📦 <b>Total Pending Activations (Entry Filled):</b> {len(all_pending_sl_activations_empty)}\n"
+                            for trade in pending_sl_trades:
+                                symbol_raw = trade.get('symbol', '')
+                                symbol_display = symbol_raw.replace('/USDC:USDC', '')
+                                sl_price = trade.get('stop_loss_price', 0)
+                                entry_side = trade.get('side', '').lower()
+                                sl_side = 'SELL' if entry_side == 'buy' else 'BUY'
+                                lifecycle_id = trade.get('trade_lifecycle_id', '')[:8]
+                                
+                                orders_text += f"📊 <b>{symbol_display}</b>\n"
+                                orders_text += f"   ⏳ Pending SL: {sl_side} at {formatter_for_empty.format_price_with_symbol(sl_price, symbol_display)}\n"
+                                orders_text += f"      (Position opened, awaiting SL activation)\n"
+                                orders_text += f"      Lifecycle: {lifecycle_id}\n\n"
+                            
+                            orders_text += f"📦 <b>Total Pending SL Activations:</b> {len(pending_sl_trades)}\n"
                         else:
-                             orders_text += "💡 Use /long, /short, /sl, or /tp to create orders" # Original message if no pending SLs either
+                            # Check for purely conceptual pending SLs from old system
+                            all_pending_sl_activations_empty = stats.get_orders_by_status(
+                                status='pending_activation',
+                                order_type_filter='pending_sl_activation'
+                            )
+                            if all_pending_sl_activations_empty:
+                                orders_text += "\n⏳ <b>Pending SL Activations (Entry Order Assumed Filled/Closed)</b>\n\n"
+                                formatter_for_empty = get_formatter()
+
+                                orphaned_sls_by_symbol_group_empty = {}
+                                for sl_data_empty in all_pending_sl_activations_empty:
+                                    sl_symbol_raw_empty = sl_data_empty.get('symbol', '')
+                                    sl_symbol_display_key_empty = sl_symbol_raw_empty.replace('/USDC:USDC', '')
+                                    if sl_symbol_display_key_empty not in orphaned_sls_by_symbol_group_empty:
+                                        orphaned_sls_by_symbol_group_empty[sl_symbol_display_key_empty] = []
+                                    orphaned_sls_by_symbol_group_empty[sl_symbol_display_key_empty].append(sl_data_empty)
+                                
+                                for sl_sym_key_empty, sl_list_items_empty in orphaned_sls_by_symbol_group_empty.items():
+                                    orders_text += f"📊 <b>{sl_sym_key_empty}</b>\n"
+                                    for sl_item_empty in sl_list_items_empty:
+                                        sl_price_val_empty = sl_item_empty.get('price', 0)
+                                        sl_side_val_empty = sl_item_empty.get('side', '').upper()
+                                        orders_text += f"   ⏳ Pending SL: {sl_side_val_empty} at {formatter_for_empty.format_price_with_symbol(sl_price_val_empty, sl_sym_key_empty)}\n"
+                                        orders_text += f"      (Awaiting activation by bot)\n\n"
+                                orders_text += f"📦 <b>Total Pending Activations (Entry Filled):</b> {len(all_pending_sl_activations_empty)}\n"
+                            else:
+                                orders_text += "💡 Use /long, /short, /sl, or /tp to create orders"
                     else:
                         orders_text += "💡 Use /long, /short, /sl, or /tp to create orders"
                 

+ 1 - 1
trading_bot.py

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