Browse Source

Add leverage management methods to HyperliquidClient and update trading_commands for improved leverage handling

- Introduced `set_leverage` and `fetch_leverage` methods in `HyperliquidClient` for managing leverage settings.
- Enhanced `leverage_command` in `trading_commands.py` to support both fetching and setting leverage with improved command parsing and user feedback.
- Updated error handling and logging for better clarity and robustness in leverage operations.
Carles Sentis 2 days ago
parent
commit
1371234ae6
4 changed files with 107 additions and 29 deletions
  1. 33 1
      src/clients/hyperliquid_client.py
  2. 55 23
      src/commands/trading_commands.py
  3. 18 4
      src/trading/trading_engine.py
  4. 1 1
      trading_bot.py

+ 33 - 1
src/clients/hyperliquid_client.py

@@ -618,4 +618,36 @@ class HyperliquidClient:
             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 
+            return None
+
+    def set_leverage(self, leverage: int, symbol: str, params: Optional[Dict] = None) -> Tuple[Optional[Dict[str, Any]], Optional[str]]:
+        """Set leverage for a symbol."""
+        try:
+            if not self.sync_client:
+                logger.error("❌ Client not initialized")
+                return None, "Client not initialized"
+
+            # CCXT's setLeverage usually requires the symbol
+            response = self.sync_client.setLeverage(leverage, symbol, params=params)
+            logger.info(f"✅ Successfully set leverage to {leverage}x for {symbol}.")
+            return response, None
+        except Exception as e:
+            error_message = self._extract_error_message(e)
+            logger.error(f"❌ Error setting leverage for {symbol}: {error_message}")
+            return None, error_message
+
+    def fetch_leverage(self, symbol: str, params: Optional[Dict] = None) -> Tuple[Optional[Dict[str, Any]], Optional[str]]:
+        """Fetch leverage for a symbol."""
+        try:
+            if not self.sync_client:
+                logger.error("❌ Client not initialized")
+                return None, "Client not initialized"
+
+            # CCXT's fetchLeverage usually requires the symbol
+            leverage_data = self.sync_client.fetchLeverage(symbol, params=params if params else {})
+            logger.info(f"✅ Successfully fetched leverage for {symbol}.")
+            return leverage_data, None
+        except Exception as e:
+            error_message = self._extract_error_message(e)
+            logger.error(f"❌ Error fetching leverage for {symbol}: {error_message}")
+            return None, error_message 

+ 55 - 23
src/commands/trading_commands.py

@@ -910,40 +910,72 @@ This action cannot be undone.
             await query.edit_message_text(text="Leverage adjustment cancelled.")
     
     async def leverage_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
-        """Command to set leverage for a symbol. Format: /leverage [token] [leverage]"""
+        """
+        Command to set or get leverage for a symbol.
+        Format:
+        /leverage [token] -> get leverage
+        /leverage [token] [leverage] -> set leverage
+        """
         chat_id = update.effective_chat.id
         try:
             parts = update.message.text.split()
-            if len(parts) != 3:
-                await self.notification_manager.send_generic_notification(
-                    "Format: /leverage [token] [leverage_value]", chat_id=chat_id
-                )
-                return
+            num_args = len(parts)
 
-            token = normalize_token_case(parts[1])
-            leverage = int(parts[2])
-            
-            if leverage <= 0:
+            if num_args not in [2, 3]:
                 await self.notification_manager.send_generic_notification(
-                    "Leverage must be a positive number.", chat_id=chat_id
+                    "Format: /leverage [token] OR /leverage [token] [leverage_value]", chat_id=chat_id
                 )
                 return
 
+            token = normalize_token_case(parts[1])
             symbol = f"{token}/USDC:USDC"
 
-            # Confirmation dialog
-            keyboard = [
-                [
-                    InlineKeyboardButton("✅ Yes, Set Leverage", callback_data=f"leverage_confirm_yes_{token}_{leverage}"),
-                    InlineKeyboardButton("❌ No, Cancel", callback_data="leverage_confirm_no"),
+            # Case 1: Get leverage
+            if num_args == 2:
+                await update.message.reply_text(f"⏳ Fetching leverage for {token}...")
+                
+                leverage_data, error = await self.trading_engine.get_leverage(symbol)
+                if error:
+                    await update.message.reply_text(f"❌ Could not fetch leverage: {error}")
+                    return
+
+                market_data = await self.trading_engine.get_market_data(symbol)
+                max_leverage = "N/A"
+                if market_data and market_data.get('ticker', {}).get('info', {}):
+                    max_leverage = market_data['ticker']['info'].get('maxLeverage', 'N/A')
+
+                current_leverage = leverage_data.get('leverage', 'N/A') if leverage_data else 'N/A'
+
+                message = (
+                    f"ℹ️ <b>Leverage for {token}</b>\n\n"
+                    f"Current Leverage: <b>{current_leverage}x</b>\n"
+                    f"Max Leverage: <b>{max_leverage}x</b>"
+                )
+                await update.message.reply_html(message)
+
+            # Case 2: Set leverage
+            elif num_args == 3:
+                leverage = int(parts[2])
+                if leverage <= 0:
+                    await self.notification_manager.send_generic_notification(
+                        "Leverage must be a positive number.", chat_id=chat_id
+                    )
+                    return
+
+                # Confirmation dialog
+                keyboard = [
+                    [
+                        InlineKeyboardButton("✅ Yes, Set Leverage", callback_data=f"leverage_confirm_yes_{token}_{leverage}"),
+                        InlineKeyboardButton("❌ No, Cancel", callback_data="leverage_confirm_no"),
+                    ]
                 ]
-            ]
-            reply_markup = InlineKeyboardMarkup(keyboard)
-            
-            await update.message.reply_html(
-                f"Are you sure you want to set leverage for <b>{token}</b> to <b>{leverage}x</b>?",
-                reply_markup=reply_markup,
-            )
+                reply_markup = InlineKeyboardMarkup(keyboard)
+                
+                await update.message.reply_html(
+                    f"Are you sure you want to set leverage for <b>{token}</b> to <b>{leverage}x</b>?",
+                    reply_markup=reply_markup,
+                )
+
         except (IndexError, ValueError):
             await self.notification_manager.send_generic_notification(
                 "Invalid format. Use: /leverage [token] [leverage_value]", chat_id=chat_id

+ 18 - 4
src/trading/trading_engine.py

@@ -1062,11 +1062,21 @@ class TradingEngine:
         return order_data is not None # If found, it was a bot-managed order
     
     def get_stats(self) -> TradingStats:
-        """Get trading statistics object."""
-        return self.stats 
+        """Returns the TradingStats instance."""
+        return self.stats
+
+    async def get_leverage(self, symbol: str):
+        """Gets leverage for a symbol."""
+        if not self.client:
+            logger.error("Trading client not initialized, cannot get leverage.")
+            return None, "Trading client not initialized."
+        
+        return self.client.fetch_leverage(symbol)
 
     async def execute_triggered_stop_order(self, original_trigger_order_db_id: int) -> Dict[str, Any]:
-        """Executes an actual stop order on the exchange after its trigger condition was met."""
+        """
+        Executes a stop-loss order that has been triggered.
+        """
         if not self.stats:
             return {"success": False, "error": "TradingStats not available."}
 
@@ -1217,8 +1227,12 @@ class TradingEngine:
             logger.info(f"Setting leverage for {symbol} to {leverage}x")
             
             # The client's set_leverage method handles the interaction
-            response = self.client.set_leverage(leverage, symbol, params=params)
+            response, error = self.client.set_leverage(leverage, symbol, params=params)
             
+            if error:
+                logger.error(f"❌ Error setting leverage for {symbol}: {error}", exc_info=True)
+                return {"success": False, "error": error}
+
             logger.info(f"Successfully set leverage for {symbol} to {leverage}x. Response: {response}")
             return {"success": True, "data": response}
         except Exception as e:

+ 1 - 1
trading_bot.py

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