Explorar el Código

Enhance error handling and logging in HyperliquidClient and TradingEngine - Implemented a method to extract specific error messages from exceptions in HyperliquidClient, improving clarity in error logging. Updated TradingEngine to handle order placement responses more robustly, ensuring detailed error messages are logged and returned for better debugging and user feedback.

Carles Sentis hace 4 días
padre
commit
2218d5a8b5
Se han modificado 2 ficheros con 229 adiciones y 120 borrados
  1. 98 36
      src/clients/hyperliquid_client.py
  2. 131 84
      src/trading/trading_engine.py

+ 98 - 36
src/clients/hyperliquid_client.py

@@ -1,7 +1,9 @@
 import logging
-from typing import Optional, Dict, Any, List
+from typing import Optional, Dict, Any, List, Tuple
 from hyperliquid import HyperliquidSync
 from src.config.config import Config
+import re
+import json
 
 # Use existing logger setup (will be configured by main application)
 logger = logging.getLogger(__name__)
@@ -122,6 +124,44 @@ class HyperliquidClient:
             logger.warning(f"⚠️ Connection test failed: {e}")
             logger.warning("💡 This might be normal if you have no positions/balance")
     
+    def _extract_error_message(self, exception_obj: Exception) -> str:
+        """Extracts a more specific error message from a Hyperliquid exception."""
+        error_str = str(exception_obj)
+        
+        # Attempt to parse the JSON-like structure in the error string
+        # Example: hyperliquid {"status":"ok","response":{"type":"order","data":{"statuses":[{"error":"Insufficient margin..."}]}}}
+        try:
+            # Look for the start of the JSON part
+            json_match = re.search(r'{\s*"status":.*}', error_str)
+            if json_match:
+                json_str = json_match.group(0)
+                error_data = json.loads(json_str)
+                if isinstance(error_data, dict):
+                    response = error_data.get('response')
+                    if isinstance(response, dict):
+                        data = response.get('data')
+                        if isinstance(data, dict):
+                            statuses = data.get('statuses')
+                            if isinstance(statuses, list) and statuses:
+                                first_status = statuses[0]
+                                if isinstance(first_status, dict) and 'error' in first_status:
+                                    return str(first_status['error']) # Return the specific error
+        except (json.JSONDecodeError, AttributeError, TypeError, IndexError) as parse_error:
+            logger.debug(f"Could not parse detailed Hyperliquid error from string '{error_str}': {parse_error}")
+            
+        # Fallback: Check for common CCXT error types if the above fails or if it's a CCXT error
+        # (ccxt.base.errors.InsufficientFunds, ccxt.base.errors.ExchangeError etc.)
+        # These often have a message attribute or a more direct string representation.
+        if hasattr(exception_obj, 'message') and isinstance(exception_obj.message, str) and exception_obj.message:
+            return exception_obj.message
+            
+        # Generic fallback to the first 150 chars of the exception string
+        # Avoid returning the full "hyperliquid {..." string if parsing failed.
+        prefix_to_remove = "hyperliquid "
+        if error_str.startswith(prefix_to_remove):
+            return error_str[len(prefix_to_remove):].split(',')[0][:150] # Get a cleaner part
+        return error_str[:150]
+
     def get_balance(self) -> Optional[Dict[str, Any]]:
         """Get account balance."""
         try:
@@ -150,7 +190,8 @@ class HyperliquidClient:
             logger.info("✅ Successfully fetched balance")
             return balance
         except Exception as e:
-            logger.error(f"❌ Error fetching balance: {e}")
+            error_message = self._extract_error_message(e)
+            logger.error(f"❌ Error fetching balance: {error_message} (Full exception: {e})")
             logger.debug(f"💡 Attempted with params: {params}")
             return None
     
@@ -187,7 +228,8 @@ class HyperliquidClient:
             return None
             
         except Exception as e:
-            logger.error(f"❌ Error in alternative balance fetch: {e}")
+            error_message = self._extract_error_message(e)
+            logger.error(f"❌ Error in alternative balance fetch: {error_message} (Full exception: {e})")
             return None
     
     def get_positions(self, symbol: Optional[str] = None) -> Optional[List[Dict[str, Any]]]:
@@ -208,7 +250,8 @@ class HyperliquidClient:
             logger.info(f"✅ Successfully fetched positions for {symbol or 'all symbols'}")
             return positions
         except Exception as e:
-            logger.error(f"❌ Error fetching positions: {e}")
+            error_message = self._extract_error_message(e)
+            logger.error(f"❌ Error fetching positions: {error_message} (Full exception: {e})")
             logger.debug(f"💡 Attempted with params: {params}")
             return None
     
@@ -231,12 +274,15 @@ class HyperliquidClient:
             logger.info(f"✅ Successfully fetched market data for {symbol}")
             return market_data
         except Exception as e:
-            logger.error(f"❌ Error fetching market data for {symbol}: {e}")
+            error_message = self._extract_error_message(e)
+            logger.error(f"❌ Error fetching market data for {symbol}: {error_message} (Full exception: {e})")
             return None
     
-    def place_limit_order(self, symbol: str, side: str, amount: float, price: float, params: Optional[Dict] = None) -> Optional[Dict[str, Any]]:
+    def place_limit_order(self, symbol: str, side: str, amount: float, price: float, params: Optional[Dict] = None) -> Tuple[Optional[Dict[str, Any]], Optional[str]]:
         """
         Place a limit order with CCXT-style parameters.
+        Returns a tuple: (order_object, error_message_string).
+        Error_message_string is None on success. Order_object is None on failure.
         
         Args:
             symbol: Trading symbol (e.g., 'BTC/USDC:USDC')
@@ -248,7 +294,7 @@ class HyperliquidClient:
         try:
             if not self.sync_client:
                 logger.error("❌ Client not initialized")
-                return None
+                return None, "Client not initialized"
             
             # CCXT-style order creation
             order_params = params or {}
@@ -257,15 +303,18 @@ class HyperliquidClient:
             logger.info(f"✅ Successfully placed {side} limit order for {amount} {symbol} at ${price}")
             logger.debug(f"📄 Order details: {order}")
             
-            return order
+            return order, None
         except Exception as e:
-            logger.error(f"❌ Error placing limit order: {e}")
-            return None
+            error_message = self._extract_error_message(e)
+            logger.error(f"❌ Error placing limit order: {error_message} (Full exception: {e})")
+            return None, error_message
     
-    def place_market_order(self, symbol: str, side: str, amount: float, params: Optional[Dict] = None) -> Optional[Dict[str, Any]]:
+    def place_market_order(self, symbol: str, side: str, amount: float, params: Optional[Dict] = None) -> Tuple[Optional[Dict[str, Any]], Optional[str]]:
         """
         Place a market order with CCXT-style parameters.
-        
+        Returns a tuple: (order_object, error_message_string).
+        Error_message_string is None on success. Order_object is None on failure.
+
         Args:
             symbol: Trading symbol (e.g., 'BTC/USDC:USDC')
             side: 'buy' or 'sell'
@@ -275,18 +324,18 @@ class HyperliquidClient:
         try:
             if not self.sync_client:
                 logger.error("❌ Client not initialized")
-                return None
+                return None, "Client not initialized"
             
             # Get current market price for slippage calculation
             ticker = self.sync_client.fetch_ticker(symbol)
             if not ticker:
                 logger.error(f"❌ Could not fetch ticker for {symbol}")
-                return None
+                return None, f"Could not fetch ticker for {symbol}"
                 
             current_price = ticker.get('last')
             if not current_price:
                 logger.error(f"❌ Could not get current price for {symbol}")
-                return None
+                return None, f"Could not get current price for {symbol}"
             
             # Apply slippage tolerance (default 0.5% for market orders)
             slippage_percent = 0.5  # 0.5% slippage
@@ -308,10 +357,11 @@ class HyperliquidClient:
             logger.info(f"✅ Successfully placed {side} market order for {amount} {symbol}")
             logger.debug(f"📄 Order details: {order}")
             
-            return order
+            return order, None
         except Exception as e:
-            logger.error(f"❌ Error placing market order: {e}")
-            return None
+            error_message = self._extract_error_message(e)
+            logger.error(f"❌ Error placing market order: {error_message} (Full exception: {e})")
+            return None, error_message
     
     def get_open_orders(self, symbol: Optional[str] = None) -> Optional[List[Dict[str, Any]]]:
         """Get open orders."""
@@ -331,7 +381,8 @@ class HyperliquidClient:
             logger.info(f"✅ Successfully fetched open orders for {symbol or 'all symbols'}")
             return orders
         except Exception as e:
-            logger.error(f"❌ Error fetching open orders: {e}")
+            error_message = self._extract_error_message(e)
+            logger.error(f"❌ Error fetching open orders: {error_message} (Full exception: {e})")
             logger.debug(f"💡 Attempted with params: {params}")
             return None
     
@@ -348,7 +399,8 @@ class HyperliquidClient:
             logger.info(f"✅ Successfully cancelled order {order_id}")
             return True
         except Exception as e:
-            logger.error(f"❌ Error cancelling order {order_id}: {e}")
+            error_message = self._extract_error_message(e)
+            logger.error(f"❌ Error cancelling order {order_id}: {error_message} (Full exception: {e})")
             return False
     
     def get_recent_trades(self, symbol: str, limit: int = 10) -> Optional[List[Dict[str, Any]]]:
@@ -362,7 +414,8 @@ class HyperliquidClient:
             logger.info(f"✅ Successfully fetched {len(trades)} recent trades for {symbol}")
             return trades
         except Exception as e:
-            logger.error(f"❌ Error fetching recent trades for {symbol}: {e}")
+            error_message = self._extract_error_message(e)
+            logger.error(f"❌ Error fetching recent trades for {symbol}: {error_message} (Full exception: {e})")
             return None
     
     def get_trading_fee(self, symbol: str) -> Optional[Dict[str, Any]]:
@@ -376,7 +429,8 @@ class HyperliquidClient:
             logger.info(f"✅ Successfully fetched trading fee for {symbol}")
             return fee
         except Exception as e:
-            logger.error(f"❌ Error fetching trading fee for {symbol}: {e}")
+            error_message = self._extract_error_message(e)
+            logger.error(f"❌ Error fetching trading fee for {symbol}: {error_message} (Full exception: {e})")
             return None
     
     def get_markets(self) -> Optional[Dict[str, Any]]:
@@ -390,13 +444,16 @@ class HyperliquidClient:
             logger.info(f"✅ Successfully loaded {len(markets)} markets")
             return markets
         except Exception as e:
-            logger.error(f"❌ Error loading markets: {e}")
+            error_message = self._extract_error_message(e)
+            logger.error(f"❌ Error loading markets: {error_message} (Full exception: {e})")
             return None
     
-    def place_stop_loss_order(self, symbol: str, side: str, amount: float, price: float, params: Optional[Dict] = None) -> Optional[Dict[str, Any]]:
+    def place_stop_loss_order(self, symbol: str, side: str, amount: float, price: float, params: Optional[Dict] = None) -> Tuple[Optional[Dict[str, Any]], Optional[str]]:
         """
         Place a stop loss order (implemented as a limit order).
-        
+        Returns a tuple: (order_object, error_message_string).
+        Error_message_string is None on success. Order_object is None on failure.
+
         Args:
             symbol: Trading symbol (e.g., 'BTC/USDC:USDC')
             side: 'buy' or 'sell'
@@ -407,7 +464,7 @@ class HyperliquidClient:
         try:
             if not self.sync_client:
                 logger.error("❌ Client not initialized")
-                return None
+                return None, "Client not initialized"
             
             # Stop loss orders are implemented as limit orders
             # They will be filled when the market price reaches the stop price
@@ -421,15 +478,18 @@ class HyperliquidClient:
             logger.info(f"✅ Successfully placed stop loss order for {amount} {symbol} at ${price}")
             logger.debug(f"📄 Stop loss order details: {order}")
             
-            return order
+            return order, None
         except Exception as e:
-            logger.error(f"❌ Error placing stop loss order: {e}")
-            return None
+            error_message = self._extract_error_message(e)
+            logger.error(f"❌ Error placing stop loss order: {error_message} (Full exception: {e})")
+            return None, error_message
     
-    def place_take_profit_order(self, symbol: str, side: str, amount: float, price: float, params: Optional[Dict] = None) -> Optional[Dict[str, Any]]:
+    def place_take_profit_order(self, symbol: str, side: str, amount: float, price: float, params: Optional[Dict] = None) -> Tuple[Optional[Dict[str, Any]], Optional[str]]:
         """
         Place a take profit order (implemented as a limit order).
-        
+        Returns a tuple: (order_object, error_message_string).
+        Error_message_string is None on success. Order_object is None on failure.
+
         Args:
             symbol: Trading symbol (e.g., 'BTC/USDC:USDC')
             side: 'buy' or 'sell'
@@ -440,7 +500,7 @@ class HyperliquidClient:
         try:
             if not self.sync_client:
                 logger.error("❌ Client not initialized")
-                return None
+                return None, "Client not initialized"
             
             # Take profit orders are implemented as limit orders
             # They will be filled when the market price reaches the target price
@@ -454,10 +514,11 @@ class HyperliquidClient:
             logger.info(f"✅ Successfully placed take profit order for {amount} {symbol} at ${price}")
             logger.debug(f"📄 Take profit order details: {order}")
             
-            return order
+            return order, None
         except Exception as e:
-            logger.error(f"❌ Error placing take profit order: {e}")
-            return None
+            error_message = self._extract_error_message(e)
+            logger.error(f"❌ Error placing take profit order: {error_message} (Full exception: {e})")
+            return None, error_message
 
     def get_recent_fills(self, limit: int = 100) -> Optional[List[Dict[str, Any]]]:
         """
@@ -523,6 +584,7 @@ class HyperliquidClient:
                 return result
                 
         except Exception as e:
-            logger.error(f"❌ Error fetching recent fills: {e}")
+            error_message = self._extract_error_message(e)
+            logger.error(f"❌ Error fetching recent fills: {error_message} (Full exception: {e})")
             logger.debug(f"💡 Attempted with params: {params}")
             return None 

+ 131 - 84
src/trading/trading_engine.py

@@ -155,7 +155,7 @@ class TradingEngine:
                 order_placement_price = limit_price_arg
                 token_amount = usdc_amount / order_placement_price
                 logger.info(f"Placing LIMIT BUY order for {token_amount:.6f} {symbol} at ${order_placement_price:,.2f}")
-                order = self.client.place_limit_order(symbol, 'buy', token_amount, order_placement_price)
+                order_data, error_msg = self.client.place_limit_order(symbol, 'buy', token_amount, order_placement_price)
             else:
                 # Market order intent
                 if current_price <= 0: # Re-check specifically for market order if current_price was initially 0
@@ -163,20 +163,23 @@ class TradingEngine:
                 order_placement_price = current_price 
                 token_amount = usdc_amount / order_placement_price
                 logger.info(f"Placing MARKET BUY order for {token_amount:.6f} {symbol} (approx. price ${order_placement_price:,.2f})")
-                order = self.client.place_market_order(symbol, 'buy', token_amount)
+                order_data, error_msg = self.client.place_market_order(symbol, 'buy', token_amount)
             
-            if not order:
-                logger.error(f"Order placement call failed for {symbol}. Client returned None or empty.")
-                return {"success": False, "error": "Order placement failed at client level (no order object)."}
+            if error_msg:
+                logger.error(f"Order placement failed for {symbol}: {error_msg}")
+                return {"success": False, "error": f"Order placement failed: {error_msg}"}
+            if not order_data:
+                logger.error(f"Order placement call failed for {symbol}. Client returned no data and no error.")
+                return {"success": False, "error": "Order placement failed at client level (no order object or error)."}
             
-            order_id = order.get('id', 'N/A')
-            order_avg_fill_price = order.get('average') 
+            order_id = order_data.get('id', 'N/A')
+            order_avg_fill_price = order_data.get('average') 
 
             # For stats, use average fill price if available, otherwise the price order was placed at.
             final_price_for_stats = order_avg_fill_price if order_avg_fill_price is not None else order_placement_price
 
             if final_price_for_stats is None: 
-                logger.critical(f"CRITICAL: final_price_for_stats is None for order {order_id}. Order: {order}, Placement Price: {order_placement_price}, Avg Fill: {order_avg_fill_price}")
+                logger.critical(f"CRITICAL: final_price_for_stats is None for order {order_id}. Order: {order_data}, Placement Price: {order_placement_price}, Avg Fill: {order_avg_fill_price}")
                 return {"success": False, "error": "Critical: Price for stats recording became None."}
 
             logger.info(f"Order {order_id} for {symbol} processing. AvgFill: {order_avg_fill_price}, PlacementPrice: {order_placement_price}, StatsPrice: {final_price_for_stats}")
@@ -203,7 +206,7 @@ class TradingEngine:
             
             return {
                 "success": True,
-                "order": order,
+                "order": order_data,
                 "action_type": action_type,
                 "token_amount": token_amount,
                 "actual_price": final_price_for_stats,
@@ -243,26 +246,29 @@ class TradingEngine:
                 order_placement_price = limit_price_arg
                 token_amount = usdc_amount / order_placement_price
                 logger.info(f"Placing LIMIT SELL order for {token_amount:.6f} {symbol} at ${order_placement_price:,.2f}")
-                order = self.client.place_limit_order(symbol, 'sell', token_amount, order_placement_price)
+                order_data, error_msg = self.client.place_limit_order(symbol, 'sell', token_amount, order_placement_price)
             else:
                 if current_price <= 0:
                      return {"success": False, "error": f"Cannot place market order for {token} due to invalid current price: {current_price}"}
                 order_placement_price = current_price
                 token_amount = usdc_amount / order_placement_price
                 logger.info(f"Placing MARKET SELL order for {token_amount:.6f} {symbol} (approx. price ${order_placement_price:,.2f})")
-                order = self.client.place_market_order(symbol, 'sell', token_amount)
+                order_data, error_msg = self.client.place_market_order(symbol, 'sell', token_amount)
             
-            if not order:
-                logger.error(f"Order placement call failed for {symbol}. Client returned None or empty.")
-                return {"success": False, "error": "Order placement failed at client level (no order object)."}
+            if error_msg:
+                logger.error(f"Order placement failed for {symbol}: {error_msg}")
+                return {"success": False, "error": f"Order placement failed: {error_msg}"}
+            if not order_data:
+                logger.error(f"Order placement call failed for {symbol}. Client returned no data and no error.")
+                return {"success": False, "error": "Order placement failed at client level (no order object or error)."}
 
-            order_id = order.get('id', 'N/A')
-            order_avg_fill_price = order.get('average')
+            order_id = order_data.get('id', 'N/A')
+            order_avg_fill_price = order_data.get('average')
             
             final_price_for_stats = order_avg_fill_price if order_avg_fill_price is not None else order_placement_price
 
             if final_price_for_stats is None:
-                logger.critical(f"CRITICAL: final_price_for_stats is None for order {order_id}. Order: {order}, Placement Price: {order_placement_price}, Avg Fill: {order_avg_fill_price}")
+                logger.critical(f"CRITICAL: final_price_for_stats is None for order {order_id}. Order: {order_data}, Placement Price: {order_placement_price}, Avg Fill: {order_avg_fill_price}")
                 return {"success": False, "error": "Critical: Price for stats recording became None."}
 
             logger.info(f"Order {order_id} for {symbol} processing. AvgFill: {order_avg_fill_price}, PlacementPrice: {order_placement_price}, StatsPrice: {final_price_for_stats}")
@@ -287,7 +293,7 @@ class TradingEngine:
             
             return {
                 "success": True,
-                "order": order,
+                "order": order_data,
                 "action_type": action_type,
                 "token_amount": token_amount,
                 "actual_price": final_price_for_stats,
@@ -311,32 +317,42 @@ class TradingEngine:
             position_type, exit_side, contracts = self.get_position_direction(position)
             
             # Execute market order to close position
-            order = self.client.place_market_order(symbol, exit_side, contracts)
+            order_data, error_msg = self.client.place_market_order(symbol, exit_side, contracts)
             
-            if order:
-                order_id = order.get('id', 'N/A')
-                actual_price = order.get('average', 0)
-                
-                if order_id != 'N/A':
-                    self.bot_trade_ids.add(order_id)
-                
-                action_type = self.stats.record_trade_with_enhanced_tracking(
-                    symbol, exit_side, contracts, actual_price, order_id, "bot"
-                )
-                
-                self._save_state()
-                
-                return {
-                    "success": True,
-                    "order": order,
-                    "action_type": action_type,
-                    "position_type": position_type,
-                    "contracts": contracts,
-                    "actual_price": actual_price
-                }
-            else:
-                return {"success": False, "error": "Exit order execution failed"}
-                
+            if error_msg:
+                logger.error(f"Exit order execution failed for {symbol}: {error_msg}")
+                return {"success": False, "error": f"Exit order execution failed: {error_msg}"}
+            if not order_data:
+                logger.error(f"Exit order execution call failed for {symbol}. Client returned no data and no error.")
+                return {"success": False, "error": "Exit order execution failed (no order object or error)."}
+            
+            # If we reached here, order_data is not None
+            order_id = order_data.get('id', 'N/A')
+            actual_price = order_data.get('average', 0) # Fallback to 0 if 'average' is missing
+            
+            if actual_price is None: # Explicitly check for None if 'average' can return it
+                ticker_data = self.get_market_data(symbol)
+                current_market_price = float(ticker_data['ticker'].get('last', 0.0) or 0.0) if ticker_data and ticker_data.get('ticker') else 0.0
+                actual_price = current_market_price # Use current market price as a fallback
+                logger.warning(f"Order {order_id} for {symbol} had no average fill price. Using current market price ${actual_price:.2f} for stats.")
+            
+            if order_id != 'N/A':
+                self.bot_trade_ids.add(order_id)
+            
+            action_type = self.stats.record_trade_with_enhanced_tracking(
+                symbol, exit_side, contracts, actual_price, order_id, "bot"
+            )
+            
+            self._save_state()
+            
+            return {
+                "success": True,
+                "order": order_data,
+                "action_type": action_type,
+                "position_type": position_type,
+                "contracts": contracts,
+                "actual_price": actual_price
+            }
         except Exception as e:
             logger.error(f"Error executing exit order: {e}")
             return {"success": False, "error": str(e)}
@@ -359,26 +375,30 @@ class TradingEngine:
                 return {"success": False, "error": "Stop loss price should be above entry price for short positions"}
             
             # Place limit order at stop loss price
-            order = self.client.place_limit_order(symbol, exit_side, contracts, stop_price)
+            order_data, error_msg = self.client.place_limit_order(symbol, exit_side, contracts, stop_price)
             
-            if order:
-                order_id = order.get('id', 'N/A')
-                
-                if order_id != 'N/A':
-                    self.bot_trade_ids.add(order_id)
-                
-                self._save_state()
-                
-                return {
-                    "success": True,
-                    "order": order,
-                    "position_type": position_type,
-                    "contracts": contracts,
-                    "stop_price": stop_price
-                }
-            else:
-                return {"success": False, "error": "Stop loss order placement failed"}
-                
+            if error_msg:
+                logger.error(f"Stop loss order placement failed for {symbol}: {error_msg}")
+                return {"success": False, "error": f"Stop loss order placement failed: {error_msg}"}
+            if not order_data:
+                logger.error(f"Stop loss order placement call failed for {symbol}. Client returned no data and no error.")
+                return {"success": False, "error": "Stop loss order placement failed (no order object or error)."}
+
+            # If we reached here, order_data is not None
+            order_id = order_data.get('id', 'N/A')
+            
+            if order_id != 'N/A':
+                self.bot_trade_ids.add(order_id)
+            
+            self._save_state()
+            
+            return {
+                "success": True,
+                "order": order_data,
+                "position_type": position_type,
+                "contracts": contracts,
+                "stop_price": stop_price
+            }
         except Exception as e:
             logger.error(f"Error executing stop loss order: {e}")
             return {"success": False, "error": str(e)}
@@ -401,26 +421,30 @@ class TradingEngine:
                 return {"success": False, "error": "Take profit price should be below entry price for short positions"}
             
             # Place limit order at take profit price
-            order = self.client.place_limit_order(symbol, exit_side, contracts, profit_price)
+            order_data, error_msg = self.client.place_limit_order(symbol, exit_side, contracts, profit_price)
             
-            if order:
-                order_id = order.get('id', 'N/A')
-                
-                if order_id != 'N/A':
-                    self.bot_trade_ids.add(order_id)
-                
-                self._save_state()
-                
-                return {
-                    "success": True,
-                    "order": order,
-                    "position_type": position_type,
-                    "contracts": contracts,
-                    "profit_price": profit_price
-                }
-            else:
-                return {"success": False, "error": "Take profit order placement failed"}
-                
+            if error_msg:
+                logger.error(f"Take profit order placement failed for {symbol}: {error_msg}")
+                return {"success": False, "error": f"Take profit order placement failed: {error_msg}"}
+            if not order_data:
+                logger.error(f"Take profit order placement call failed for {symbol}. Client returned no data and no error.")
+                return {"success": False, "error": "Take profit order placement failed (no order object or error)."}
+
+            # If we reached here, order_data is not None
+            order_id = order_data.get('id', 'N/A')
+            
+            if order_id != 'N/A':
+                self.bot_trade_ids.add(order_id)
+            
+            self._save_state()
+            
+            return {
+                "success": True,
+                "order": order_data,
+                "position_type": position_type,
+                "contracts": contracts,
+                "profit_price": profit_price
+            }
         except Exception as e:
             logger.error(f"Error executing take profit order: {e}")
             return {"success": False, "error": str(e)}
@@ -429,15 +453,38 @@ class TradingEngine:
         """Cancel all orders for a token."""
         try:
             symbol = f"{token}/USDC:USDC"
+            # Assuming self.client.cancel_all_orders either succeeds and returns data or raises an exception
+            # If it can return (None, error_msg) like other order methods, this would need adjustment
+            # For now, sticking to its likely current CCXT-like behavior.
+            # If it was changed in hyperliquid_client.py, this needs to be updated.
+            # Let's assume cancel_all_orders was NOT changed to return a tuple for now.
+            # It usually returns specific data from CCXT or raises an error.
+            
+            # If self.client.cancel_all_orders was modified to return (data, error_msg):
+            # data, error_msg = self.client.cancel_all_orders(symbol)
+            # if error_msg:
+            #     logger.error(f"Error cancelling orders for {symbol}: {error_msg}")
+            #     return {"success": False, "error": f"Failed to cancel orders: {error_msg}"}
+            # return {"success": True, "result": data}
+            
+            # Sticking to original assumption based on typical CCXT cancel_all_orders:
             result = self.client.cancel_all_orders(symbol)
+            # CCXT cancel_all_orders often returns a list of cancelled order structures or similar.
+            # If it fails, it typically raises an exception handled by the generic catch block below.
             
+            logger.info(f"Attempted to cancel all orders for {symbol}. Result: {result}")
             return {
                 "success": True,
-                "result": result
+                "result": result # This might be a list of order dicts, or specific response from API
             }
         except Exception as e:
-            logger.error(f"Error cancelling orders: {e}")
-            return {"success": False, "error": str(e)}
+            # If client.cancel_all_orders raises an Exception that is caught here,
+            # we can use the _extract_error_message if it's available from client.
+            error_message = str(e)
+            if hasattr(self.client, '_extract_error_message'):
+                error_message = self.client._extract_error_message(e)
+            logger.error(f"Error cancelling orders for {token}: {error_message}", exc_info=True)
+            return {"success": False, "error": f"Failed to cancel orders: {error_message}"}
     
     # Alias methods for consistency with command handlers
     async def execute_sl_order(self, token: str, stop_price: float) -> Dict[str, Any]: