Przeglądaj źródła

Enhance stop loss management in MarketMonitor - Updated orphaned stop loss cleanup logic to consider parent order statuses, improving accuracy in identifying and cancelling orphaned stop losses. Enhanced logging to provide clearer reasons for cancellations and updated documentation for the cleanup method to reflect new behavior.

Carles Sentis 4 dni temu
rodzic
commit
3849a02442
2 zmienionych plików z 39 dodań i 7 usunięć
  1. 2 1
      src/commands/info_commands.py
  2. 37 6
      src/monitoring/market_monitor.py

+ 2 - 1
src/commands/info_commands.py

@@ -226,7 +226,8 @@ class InfoCommands:
                                     if linked_sls:
                                         sl_order = linked_sls[0]  # Should only be one
                                         sl_price = sl_order.get('price', 0)
-                                        orders_text += f"   🛑 Pending SL: ${sl_price:,.2f} (activates when filled)\n"
+                                        sl_side = sl_order.get('side', '').upper()
+                                        orders_text += f"   🛑 Pending SL: {sl_side} @ ${sl_price:,.2f} (activates when filled)\n"
                         
                         orders_text += "\n"
                 

+ 37 - 6
src/monitoring/market_monitor.py

@@ -774,7 +774,7 @@ class MarketMonitor:
             logger.error(f"❌ Error in automatic risk management check: {e}", exc_info=True)
 
     async def _cleanup_orphaned_stop_losses(self):
-        """Clean up pending stop losses that no longer have corresponding positions."""
+        """Clean up pending stop losses that no longer have corresponding positions OR whose parent orders have been cancelled/failed."""
         try:
             stats = self.trading_engine.get_stats()
             if not stats:
@@ -804,9 +804,40 @@ class MarketMonitor:
             for sl_order in pending_stop_losses:
                 symbol = sl_order.get('symbol')
                 order_db_id = sl_order.get('id')
+                parent_bot_ref_id = sl_order.get('parent_bot_order_ref_id')
                 
-                if symbol not in position_symbols:
-                    # This stop loss has no corresponding position - cancel it
+                should_cancel = False
+                cancel_reason = ""
+                
+                # Check if parent order exists and its status
+                if parent_bot_ref_id:
+                    parent_order = stats.get_order_by_bot_ref_id(parent_bot_ref_id)
+                    if parent_order:
+                        parent_status = parent_order.get('status', '').lower()
+                        
+                        # Cancel if parent order failed, was cancelled, or disappeared
+                        if parent_status in ['failed_submission', 'failed_submission_no_data', 'cancelled_manually', 
+                                           'cancelled_externally', 'disappeared_from_exchange']:
+                            should_cancel = True
+                            cancel_reason = f"parent order {parent_status}"
+                        elif parent_status == 'filled':
+                            # Parent order filled but no position - position might have been closed externally
+                            if symbol not in position_symbols:
+                                should_cancel = True
+                                cancel_reason = "parent filled but position no longer exists"
+                        # If parent is still 'open', 'submitted', or 'partially_filled', keep the stop loss
+                    else:
+                        # Parent order not found in DB - this is truly orphaned
+                        should_cancel = True
+                        cancel_reason = "parent order not found in database"
+                else:
+                    # No parent reference - fallback to old logic (position-based check)
+                    if symbol not in position_symbols:
+                        should_cancel = True
+                        cancel_reason = "no position exists and no parent reference"
+                
+                if should_cancel:
+                    # Cancel this orphaned stop loss
                     success = stats.update_order_status(
                         order_db_id=order_db_id,
                         new_status='cancelled_orphaned_no_position'
@@ -815,7 +846,7 @@ class MarketMonitor:
                     if success:
                         orphaned_count += 1
                         token = symbol.split('/')[0] if '/' in symbol else symbol.split(':')[0]
-                        logger.info(f"🧹 Cancelled orphaned stop loss for {token} (Order DB ID: {order_db_id}) - no position exists")
+                        logger.info(f"🧹 Cancelled orphaned stop loss for {token} (Order DB ID: {order_db_id}) - {cancel_reason}")
 
             if orphaned_count > 0:
                 logger.info(f"🧹 Cleanup completed: Cancelled {orphaned_count} orphaned stop loss orders")
@@ -824,9 +855,9 @@ class MarketMonitor:
                     await self.notification_manager.send_generic_notification(
                         f"🧹 <b>Cleanup Completed</b>\n\n"
                         f"Cancelled {orphaned_count} orphaned stop loss order(s)\n"
-                        f"Reason: No corresponding positions found\n"
+                        f"Reason: Parent orders cancelled/failed or positions closed externally\n"
                         f"Time: {datetime.now().strftime('%H:%M:%S')}\n\n"
-                        f"💡 This automatic cleanup ensures stop losses stay synchronized with actual positions."
+                        f"💡 This automatic cleanup ensures stop losses stay synchronized with actual orders and positions."
                     )
 
         except Exception as e: