Browse Source

Enhance stop loss order logic in TradingEngine - Implemented smart stop loss functionality to determine order type (market or limit) based on current market price relative to stop price. Improved logging for order placement and error handling, ensuring clarity in execution status and enhancing overall stop loss management.

Carles Sentis 2 tuần trước cách đây
mục cha
commit
14ae0d889a
1 tập tin đã thay đổi với 69 bổ sung18 xóa
  1. 69 18
      src/trading/trading_engine.py

+ 69 - 18
src/trading/trading_engine.py

@@ -753,7 +753,7 @@ class TradingEngine:
         # Side of the actual SL order to be placed (e.g., if trigger was 'sell', actual order is 'sell')
         sl_order_side = trigger_order_details.get('side') 
         amount = trigger_order_details.get('amount_requested')
-        stop_price = trigger_order_details.get('price') # This was the trigger price, now becomes the limit price for the SL
+        stop_price = trigger_order_details.get('price') # This was the trigger price
         parent_bot_ref_id_of_trigger = trigger_order_details.get('bot_order_ref_id') # The ref ID of the trigger order itself
 
         if not all([symbol, sl_order_side, amount, stop_price]):
@@ -761,11 +761,44 @@ class TradingEngine:
             logger.error(msg)
             return {"success": False, "error": msg}
 
-        # This logic is very similar to execute_stop_loss_order or execute_take_profit_order
-        # It places a new limit order that acts as the Stop Loss.
-        order_type_for_actual_sl = 'limit' 
+        # 🧠 SMART STOP LOSS LOGIC: Check if price has moved beyond stop loss
+        # Get current market price to determine order type
+        current_price = None
+        try:
+            market_data = self.get_market_data(symbol)
+            if market_data and market_data.get('ticker'):
+                current_price = float(market_data['ticker'].get('last', 0))
+        except Exception as price_error:
+            logger.warning(f"Could not fetch current price for {symbol}: {price_error}")
+
+        # Determine if we need market order (price moved beyond stop) or limit order (normal case)
+        use_market_order = False
+        order_type_for_actual_sl = 'limit'  # Default to limit
+        order_price = stop_price  # Default to stop price
+
+        if current_price and current_price > 0:
+            if sl_order_side.lower() == 'buy':
+                # SHORT position stop loss (BUY to close)
+                # If current price > stop price, use market order (price moved beyond stop)
+                if current_price > stop_price:
+                    use_market_order = True
+                    logger.warning(f"🚨 SHORT SL: Price ${current_price:.4f} > Stop ${stop_price:.4f} - Using MARKET order for immediate execution")
+                else:
+                    logger.info(f"📊 SHORT SL: Price ${current_price:.4f} ≤ Stop ${stop_price:.4f} - Using LIMIT order at stop price")
+            elif sl_order_side.lower() == 'sell':
+                # LONG position stop loss (SELL to close)
+                # If current price < stop price, use market order (price moved beyond stop)
+                if current_price < stop_price:
+                    use_market_order = True
+                    logger.warning(f"🚨 LONG SL: Price ${current_price:.4f} < Stop ${stop_price:.4f} - Using MARKET order for immediate execution")
+                else:
+                    logger.info(f"📊 LONG SL: Price ${current_price:.4f} ≥ Stop ${stop_price:.4f} - Using LIMIT order at stop price")
 
-        # 1. Generate a new bot_order_ref_id for this actual SL limit order
+        if use_market_order:
+            order_type_for_actual_sl = 'market'
+            order_price = None  # Market orders don't have a specific price
+
+        # 1. Generate a new bot_order_ref_id for this actual SL order
         actual_sl_bot_order_ref_id = uuid.uuid4().hex
         # We can link this actual SL order back to the trigger order that spawned it.
         actual_sl_order_db_id = self.stats.record_order_placed(
@@ -773,7 +806,7 @@ class TradingEngine:
             side=sl_order_side, 
             order_type=order_type_for_actual_sl,
             amount_requested=amount, 
-            price=stop_price, 
+            price=order_price,  # None for market, stop_price for limit
             bot_order_ref_id=actual_sl_bot_order_ref_id, 
             status='pending_submission',
             parent_bot_order_ref_id=parent_bot_ref_id_of_trigger # Linking actual SL to its trigger order
@@ -784,35 +817,50 @@ class TradingEngine:
             logger.error(msg)
             return {"success": False, "error": msg}
 
-        # 2. Place the actual SL limit order on the exchange
-        logger.info(f"Placing ACTUAL SL ORDER (LIMIT {sl_order_side.upper()}) from trigger {original_trigger_order_db_id}. New BotRef: {actual_sl_bot_order_ref_id}, Amount: {amount}, Price: ${stop_price}")
-        exchange_order_data, error_msg = self.client.place_limit_order(symbol, sl_order_side, amount, stop_price)
+        # 2. Place the actual SL order on the exchange
+        if use_market_order:
+            logger.info(f"🚨 Placing ACTUAL SL ORDER (MARKET {sl_order_side.upper()}) from trigger {original_trigger_order_db_id}. New BotRef: {actual_sl_bot_order_ref_id}, Amount: {amount}, Trigger was: ${stop_price}")
+            exchange_order_data, error_msg = self.client.place_market_order(symbol, sl_order_side, amount)
+        else:
+            logger.info(f"📊 Placing ACTUAL SL ORDER (LIMIT {sl_order_side.upper()}) from trigger {original_trigger_order_db_id}. New BotRef: {actual_sl_bot_order_ref_id}, Amount: {amount}, Price: ${stop_price}")
+            exchange_order_data, error_msg = self.client.place_limit_order(symbol, sl_order_side, amount, stop_price)
 
         if error_msg:
-            logger.error(f"Actual SL order placement failed for {symbol} (BotRef {actual_sl_bot_order_ref_id}): {error_msg}")
+            order_type_desc = "market" if use_market_order else "limit"
+            logger.error(f"Actual SL {order_type_desc} order placement failed for {symbol} (BotRef {actual_sl_bot_order_ref_id}): {error_msg}")
             self.stats.update_order_status(order_db_id=actual_sl_order_db_id, new_status='failed_submission', bot_order_ref_id=actual_sl_bot_order_ref_id)
-            return {"success": False, "error": f"Actual SL order placement failed: {error_msg}"}
+            return {"success": False, "error": f"Actual SL {order_type_desc} order placement failed: {error_msg}"}
         if not exchange_order_data:
-            logger.error(f"Actual SL order placement call failed for {symbol} (BotRef {actual_sl_bot_order_ref_id}). No data/error from client.")
+            order_type_desc = "market" if use_market_order else "limit"
+            logger.error(f"Actual SL {order_type_desc} order placement call failed for {symbol} (BotRef {actual_sl_bot_order_ref_id}). No data/error from client.")
             self.stats.update_order_status(order_db_id=actual_sl_order_db_id, new_status='failed_submission_no_data', bot_order_ref_id=actual_sl_bot_order_ref_id)
-            return {"success": False, "error": "Actual SL order placement failed at client level."}
+            return {"success": False, "error": f"Actual SL {order_type_desc} order placement failed at client level."}
 
         exchange_oid = exchange_order_data.get('id')
 
-        # 3. Update the actual SL order in DB with exchange_order_id and status 'open'
+        # 3. Update the actual SL order in DB with exchange_order_id and appropriate status
         if exchange_oid:
+            # Market orders are 'submitted' until filled, limit orders are 'open' until triggered/filled
+            new_status = 'submitted' if use_market_order else 'open'
             self.stats.update_order_status(
                 order_db_id=actual_sl_order_db_id, 
                 exchange_order_id=exchange_oid, 
-                new_status='open',
+                new_status=new_status,
                 bot_order_ref_id=actual_sl_bot_order_ref_id
             )
         else:
-            logger.warning(f"No exchange_order_id received for actual SL order (BotRef {actual_sl_bot_order_ref_id}).")
+            order_type_desc = "market" if use_market_order else "limit"
+            logger.warning(f"No exchange_order_id received for actual SL {order_type_desc} order (BotRef {actual_sl_bot_order_ref_id}).")
+
+        success_message = f"Actual Stop Loss {'MARKET' if use_market_order else 'LIMIT'} order placed successfully"
+        if use_market_order:
+            success_message += " for immediate execution (price moved beyond stop level)."
+        else:
+            success_message += f" at ${stop_price}."
 
         return {
             "success": True,
-            "message": "Actual Stop Loss order placed successfully.",
+            "message": success_message,
             "placed_sl_order_details": {
                 "bot_order_ref_id": actual_sl_bot_order_ref_id,
                 "exchange_order_id": exchange_oid,
@@ -821,6 +869,9 @@ class TradingEngine:
                 "side": sl_order_side,
                 "type": order_type_for_actual_sl,
                 "amount_requested": amount,
-                "price_requested": stop_price
+                "price_requested": order_price,
+                "original_trigger_price": stop_price,
+                "current_market_price": current_price,
+                "used_market_order": use_market_order
             }
         }