Browse Source

Refactor trading commands to support multiple stop loss prefixes and enhance order handling. Introduce a new method for retrieving pending stop losses from trade lifecycles, improving order visibility and clarity in the orders command. Update order formatting for better user experience.

Carles Sentis 3 days ago
parent
commit
b72dc27cd5

+ 20 - 17
src/commands/info/orders.py

@@ -21,12 +21,12 @@ class OrdersCommands(InfoCommandsBase):
 
             # Get pending stop loss orders from the database
             stats = self.trading_engine.get_stats()
-            pending_sl_orders = []
+            pending_sl_lifecycles = []
             if stats:
-                pending_sl_orders = stats.get_orders_by_status('pending_trigger')
+                pending_sl_lifecycles = stats.get_pending_stop_losses_from_lifecycles()
             
             # Combine both lists
-            all_orders = open_orders + pending_sl_orders
+            all_orders = open_orders + pending_sl_lifecycles
 
             if not all_orders:
                 await self._reply(update, "📭 No open or pending orders")
@@ -37,19 +37,22 @@ class OrdersCommands(InfoCommandsBase):
 
             for order in all_orders:
                 try:
-                    is_pending_sl = order.get('status') == 'pending_trigger'
-
+                    is_pending_sl = 'trade_lifecycle_id' in order and order.get('stop_loss_price') is not None
+                    
                     symbol = order.get('symbol', 'unknown')
                     base_asset = symbol.split('/')[0] if '/' in symbol else symbol.split(':')[0]
                     
                     if is_pending_sl:
+                        # This is a pending SL from a trade lifecycle
+                        entry_side = order.get('side', 'unknown').upper()
                         order_type = "STOP (PENDING)"
-                        side = order.get('side', 'unknown').upper()
-                        price = float(order.get('price', 0)) # This is the trigger price for pending SL
-                        amount = float(order.get('amount_requested', 0))
-                        status = "PENDING TRIGGER"
-                        order_id = order.get('bot_order_ref_id', 'unknown')
+                        side = "SELL" if entry_side == "BUY" else "BUY"
+                        price = float(order.get('stop_loss_price', 0))
+                        amount = float(order.get('amount', 0)) # Amount from the entry order
+                        status = f"Awaiting {order.get('status', '').upper()} Entry" # e.g. Awaiting PENDING Entry
+                        order_id = order.get('trade_lifecycle_id', 'unknown')
                     else:
+                        # This is a regular exchange order
                         order_type = order.get('type', 'unknown').upper()
                         side = order.get('side', 'unknown').upper()
                         price = float(order.get('price', 0))
@@ -60,20 +63,20 @@ class OrdersCommands(InfoCommandsBase):
                         order_id = order.get('id', 'unknown')
 
                     # Skip fully filled orders
-                    if amount <= 0:
+                    if amount <= 0 and not is_pending_sl:
                         continue
                     
                     # Format order details
                     formatter = self._get_formatter()
                     price_str = await formatter.format_price_with_symbol(price, base_asset)
-                    amount_str = await formatter.format_amount(amount, base_asset)
+                    amount_str = await formatter.format_amount(amount, base_asset) if amount > 0 else "N/A"
 
                     # Order header
                     side_emoji = "🟢" if side == "BUY" else "🔴"
                     orders_text += f"{side_emoji} <b>{base_asset} {side} {order_type}</b>\n"
-                    orders_text += f"   Status: {status}\n"
-                    orders_text += f"   📏 Amount: {amount_str} {base_asset}\n"
-                    orders_text += f"   💰 {'Trigger Price' if is_pending_sl else 'Price'}: {price_str}\n"
+                    orders_text += f"   Status: {status.replace('_', ' ')}\n"
+                    orders_text += f"   📏 Amount: {amount_str}\n"
+                    orders_text += f"   💰 {'Trigger Price' if 'STOP' in order_type else 'Price'}: {price_str}\n"
 
                     # Add order type specific info for exchange orders
                     if not is_pending_sl and (order_type == "STOP_LOSS" or order_type == "TAKE_PROFIT"):
@@ -83,8 +86,8 @@ class OrdersCommands(InfoCommandsBase):
                             orders_text += f"   🎯 Trigger: {trigger_price_str}\n"
                     
                     # Add order ID
-                    id_label = "Bot Ref ID" if is_pending_sl else "Order ID"
-                    orders_text += f"   🆔 {id_label}: {order_id[:12]}\n\n"
+                    id_label = "Lifecycle ID" if is_pending_sl else "Order ID"
+                    orders_text += f"   🆔 {id_label}: {str(order_id)[:12]}\n\n"
 
                 except Exception as e:
                     logger.error(f"Error processing order {order.get('symbol', 'unknown')}: {e}", exc_info=True)

+ 5 - 3
src/commands/trading_commands.py

@@ -69,7 +69,8 @@ class TradingCommands:
                 if arg.startswith('sl:'):
                     # Stop loss parameter
                     try:
-                        stop_loss_price = float(arg[3:])  # Remove 'sl:' prefix
+                        prefix_len = 3 # Length of 'sl:' or 'sp:'
+                        stop_loss_price = float(arg[prefix_len:])
                     except ValueError:
                         await context.bot.send_message(chat_id=chat_id, text="❌ Invalid stop loss price format. Use sl:price (e.g., sl:44000)")
                         return
@@ -189,9 +190,10 @@ This will {"place a limit buy order" if limit_price else "execute a market buy o
             stop_loss_price = None
             
             for i, arg in enumerate(context.args[2:], 2):
-                if arg.startswith('sl:'):
+                if arg.startswith('sl:') or arg.startswith('sp:'):
                     try:
-                        stop_loss_price = float(arg[3:])
+                        prefix_len = 3 # Length of 'sl:' or 'sp:'
+                        stop_loss_price = float(arg[prefix_len:])
                     except ValueError:
                         await context.bot.send_message(chat_id=chat_id, text="❌ Invalid stop loss price format. Use sl:price (e.g., sl:46000)")
                         return

+ 18 - 0
src/stats/trade_lifecycle_manager.py

@@ -246,8 +246,26 @@ class TradeLifecycleManager:
         query = "SELECT * FROM trades WHERE take_profit_order_id = ? AND status = ? LIMIT 1"
         return self.db._fetchone_query(query, (tp_exchange_order_id, status))
     
+    def get_pending_stop_losses_from_lifecycles(self) -> List[Dict[str, Any]]:
+        """
+        Get trade lifecycles that have a stop loss price defined but no exchange order ID for it yet.
+        This represents pending stop losses for both unfilled limit orders and open positions
+        where the SL has not been placed by the market monitor yet.
+        """
+        query = """
+            SELECT * FROM trades 
+            WHERE 
+                status IN ('pending', 'position_opened') 
+                AND stop_loss_price IS NOT NULL 
+                AND stop_loss_order_id IS NULL
+            ORDER BY timestamp DESC
+        """
+        return self.db._fetch_query(query)
+    
     def get_pending_stop_loss_activations(self) -> List[Dict[str, Any]]:
         """Get open positions that need stop loss activation."""
+        # This query finds trades that have been open for more than a minute 
+        # without a stop loss order linked, suggesting the activation might have failed.
         query = """
             SELECT * FROM trades 
             WHERE status = 'position_opened' 

+ 5 - 1
src/stats/trading_stats.py

@@ -254,8 +254,12 @@ class TradingStats:
         """Get lifecycle by take profit order ID."""
         return self.trade_manager.get_lifecycle_by_tp_order_id(tp_exchange_order_id, status)
     
+    def get_pending_stop_losses_from_lifecycles(self) -> List[Dict[str, Any]]:
+        """Get pending stop losses from trade lifecycles."""
+        return self.trade_manager.get_pending_stop_losses_from_lifecycles()
+    
     def get_pending_stop_loss_activations(self) -> List[Dict[str, Any]]:
-        """Get pending stop loss activations."""
+        """Get open positions that need stop loss activation."""
         return self.trade_manager.get_pending_stop_loss_activations()
     
     def cleanup_old_cancelled_trades(self, days_old: int = 7) -> int:

+ 1 - 1
trading_bot.py

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