Sfoglia il codice sorgente

Update Hyperliquid client for CCXT compatibility - Add secret key support, CCXT-style initialization, enhanced authentication and parameters

Carles Sentis 6 giorni fa
parent
commit
2528e41235
3 ha cambiato i file con 140 aggiunte e 19 eliminazioni
  1. 27 1
      config/env.example
  2. 26 0
      src/config.py
  3. 87 18
      src/hyperliquid_client.py

+ 27 - 1
config/env.example

@@ -1,19 +1,45 @@
-# Hyperliquid API Configuration
+# ========================================
+# Hyperliquid API Configuration (CCXT Style)
+# ========================================
+# Your Hyperliquid private key (required)
 HYPERLIQUID_PRIVATE_KEY=your_private_key_here
+
+# Your Hyperliquid secret key (may be optional depending on implementation)
+# Some CCXT exchanges require both apiKey and secret, others only one
+HYPERLIQUID_SECRET_KEY=your_secret_key_here
+
+# Network selection: true for testnet (safe), false for mainnet (real money!)
 HYPERLIQUID_TESTNET=true
 
+# ========================================
 # Trading Bot Configuration
+# ========================================
+# Default symbol to trade (Hyperliquid format)
 DEFAULT_TRADING_SYMBOL=BTC/USDC:USDC
+
+# Default trade amount (in base currency)
 DEFAULT_TRADE_AMOUNT=0.001
+
+# Risk management settings
 RISK_MANAGEMENT_ENABLED=true
 MAX_POSITION_SIZE=100
 STOP_LOSS_PERCENTAGE=2.0
 TAKE_PROFIT_PERCENTAGE=5.0
 
+# ========================================
 # Telegram Bot Configuration
+# ========================================
+# Get these from @BotFather in Telegram
 TELEGRAM_BOT_TOKEN=your_telegram_bot_token_here
+
+# Get this by running: python utils/get_telegram_chat_id.py
 TELEGRAM_CHAT_ID=your_chat_id_here
+
+# Enable/disable Telegram integration
 TELEGRAM_ENABLED=true
 
+# ========================================
 # Logging
+# ========================================
+# Options: DEBUG, INFO, WARNING, ERROR
 LOG_LEVEL=INFO 

+ 26 - 0
src/config.py

@@ -10,6 +10,7 @@ class Config:
     
     # Hyperliquid API Configuration
     HYPERLIQUID_PRIVATE_KEY: Optional[str] = os.getenv('HYPERLIQUID_PRIVATE_KEY')
+    HYPERLIQUID_SECRET_KEY: Optional[str] = os.getenv('HYPERLIQUID_SECRET_KEY')
     HYPERLIQUID_TESTNET: bool = os.getenv('HYPERLIQUID_TESTNET', 'true').lower() == 'true'
     
     # Trading Bot Configuration
@@ -35,6 +36,9 @@ class Config:
             print("❌ HYPERLIQUID_PRIVATE_KEY is required")
             return False
         
+        # Note: SECRET_KEY might be optional depending on Hyperliquid implementation
+        # Some CCXT exchanges require both apiKey and secret, others only one
+        
         if cls.TELEGRAM_ENABLED and not cls.TELEGRAM_BOT_TOKEN:
             print("❌ TELEGRAM_BOT_TOKEN is required when TELEGRAM_ENABLED=true")
             return False
@@ -46,6 +50,27 @@ class Config:
         print("✅ Configuration is valid")
         return True
     
+    @classmethod
+    def get_hyperliquid_config(cls) -> dict:
+        """Get Hyperliquid configuration in CCXT format."""
+        config = {
+            'testnet': cls.HYPERLIQUID_TESTNET,
+            'sandbox': cls.HYPERLIQUID_TESTNET,  # CCXT uses sandbox for testnet
+        }
+        
+        # Add authentication if available
+        if cls.HYPERLIQUID_PRIVATE_KEY:
+            config['apiKey'] = cls.HYPERLIQUID_PRIVATE_KEY
+            
+        if cls.HYPERLIQUID_SECRET_KEY:
+            config['secret'] = cls.HYPERLIQUID_SECRET_KEY
+            
+        # Legacy support for private_key parameter
+        if cls.HYPERLIQUID_PRIVATE_KEY:
+            config['private_key'] = cls.HYPERLIQUID_PRIVATE_KEY
+            
+        return config
+    
     @classmethod
     def print_config(cls):
         """Print current configuration (hiding sensitive data)."""
@@ -60,5 +85,6 @@ class Config:
         print(f"  TELEGRAM_ENABLED: {cls.TELEGRAM_ENABLED}")
         print(f"  LOG_LEVEL: {cls.LOG_LEVEL}")
         print(f"  PRIVATE_KEY: {'✅ Set' if cls.HYPERLIQUID_PRIVATE_KEY else '❌ Not Set'}")
+        print(f"  SECRET_KEY: {'✅ Set' if cls.HYPERLIQUID_SECRET_KEY else '❌ Not Set'}")
         print(f"  TELEGRAM_BOT_TOKEN: {'✅ Set' if cls.TELEGRAM_BOT_TOKEN else '❌ Not Set'}")
         print(f"  TELEGRAM_CHAT_ID: {'✅ Set' if cls.TELEGRAM_CHAT_ID else '❌ Not Set'}") 

+ 87 - 18
src/hyperliquid_client.py

@@ -11,32 +11,73 @@ logger = logging.getLogger(__name__)
 class HyperliquidClient:
     """Wrapper class for Hyperliquid API client with enhanced functionality."""
     
-    def __init__(self, use_testnet: bool = True):
+    def __init__(self, use_testnet: bool = None):
         """
-        Initialize the Hyperliquid client.
+        Initialize the Hyperliquid client with CCXT-style configuration.
         
         Args:
-            use_testnet: Whether to use testnet (default: True for safety)
+            use_testnet: Whether to use testnet (default: from Config.HYPERLIQUID_TESTNET)
         """
+        # Use config value if not explicitly provided
+        if use_testnet is None:
+            use_testnet = Config.HYPERLIQUID_TESTNET
+            
         self.use_testnet = use_testnet
-        self.config = {
-            'private_key': Config.HYPERLIQUID_PRIVATE_KEY,
-            'testnet': use_testnet
-        }
         
-        # Initialize sync client
+        # Get CCXT-style configuration
+        self.config = Config.get_hyperliquid_config()
+        
+        # Override testnet setting if provided
+        if use_testnet is not None:
+            self.config['testnet'] = use_testnet
+            self.config['sandbox'] = use_testnet
+        
+        # Initialize clients
         self.sync_client = None
         self.async_client = None
         
         if Config.HYPERLIQUID_PRIVATE_KEY:
             try:
+                # Initialize with CCXT-style parameters
+                logger.info(f"🔧 Initializing Hyperliquid client with config: {self._safe_config_log()}")
+                
+                # Try different initialization patterns for CCXT compatibility
                 self.sync_client = HyperliquidSync(self.config)
-                logger.info(f"✅ Hyperliquid client initialized (testnet: {use_testnet})")
+                
+                logger.info(f"✅ Hyperliquid client initialized successfully")
+                logger.info(f"🌐 Network: {'Testnet' if use_testnet else '🚨 MAINNET 🚨'}")
+                
+                # Test the connection
+                self._test_connection()
+                
             except Exception as e:
                 logger.error(f"❌ Failed to initialize Hyperliquid client: {e}")
+                logger.error(f"💡 Config used: {self._safe_config_log()}")
+                raise
         else:
             logger.warning("⚠️ No private key provided - client will have limited functionality")
     
+    def _safe_config_log(self) -> Dict[str, Any]:
+        """Return config with sensitive data masked for logging."""
+        safe_config = self.config.copy()
+        if 'apiKey' in safe_config and safe_config['apiKey']:
+            safe_config['apiKey'] = f"{safe_config['apiKey'][:8]}..."
+        if 'secret' in safe_config and safe_config['secret']:
+            safe_config['secret'] = f"{safe_config['secret'][:8]}..."
+        if 'private_key' in safe_config and safe_config['private_key']:
+            safe_config['private_key'] = f"{safe_config['private_key'][:8]}..."
+        return safe_config
+    
+    def _test_connection(self):
+        """Test the connection to verify credentials."""
+        try:
+            # Try to fetch balance to test authentication
+            balance = self.sync_client.fetch_balance()
+            logger.info(f"🔗 Connection test successful")
+        except Exception as e:
+            logger.warning(f"⚠️ Connection test failed: {e}")
+            logger.warning("💡 This might be normal if you have no positions/balance")
+    
     def get_balance(self) -> Optional[Dict[str, Any]]:
         """Get account balance."""
         try:
@@ -87,44 +128,56 @@ class HyperliquidClient:
             logger.error(f"❌ Error fetching market data for {symbol}: {e}")
             return None
     
-    def place_limit_order(self, symbol: str, side: str, amount: float, price: float) -> Optional[Dict[str, Any]]:
+    def place_limit_order(self, symbol: str, side: str, amount: float, price: float, params: Optional[Dict] = None) -> Optional[Dict[str, Any]]:
         """
-        Place a limit order.
+        Place a limit order with CCXT-style parameters.
         
         Args:
             symbol: Trading symbol (e.g., 'BTC/USDC:USDC')
             side: 'buy' or 'sell'
             amount: Order amount
             price: Order price
+            params: Additional parameters for CCXT compatibility
         """
         try:
             if not self.sync_client:
                 logger.error("❌ Client not initialized")
                 return None
             
-            order = self.sync_client.create_limit_order(symbol, side, amount, price)
+            # CCXT-style order creation
+            order_params = params or {}
+            order = self.sync_client.create_limit_order(symbol, side, amount, price, params=order_params)
+            
             logger.info(f"✅ Successfully placed {side} limit order for {amount} {symbol} at ${price}")
+            logger.debug(f"📄 Order details: {order}")
+            
             return order
         except Exception as e:
             logger.error(f"❌ Error placing limit order: {e}")
             return None
     
-    def place_market_order(self, symbol: str, side: str, amount: float) -> Optional[Dict[str, Any]]:
+    def place_market_order(self, symbol: str, side: str, amount: float, params: Optional[Dict] = None) -> Optional[Dict[str, Any]]:
         """
-        Place a market order.
+        Place a market order with CCXT-style parameters.
         
         Args:
             symbol: Trading symbol (e.g., 'BTC/USDC:USDC')
             side: 'buy' or 'sell'
             amount: Order amount
+            params: Additional parameters for CCXT compatibility
         """
         try:
             if not self.sync_client:
                 logger.error("❌ Client not initialized")
                 return None
             
-            order = self.sync_client.create_market_order(symbol, side, amount)
+            # CCXT-style order creation
+            order_params = params or {}
+            order = self.sync_client.create_market_order(symbol, side, amount, params=order_params)
+            
             logger.info(f"✅ Successfully placed {side} market order for {amount} {symbol}")
+            logger.debug(f"📄 Order details: {order}")
+            
             return order
         except Exception as e:
             logger.error(f"❌ Error placing market order: {e}")
@@ -144,14 +197,16 @@ class HyperliquidClient:
             logger.error(f"❌ Error fetching open orders: {e}")
             return None
     
-    def cancel_order(self, order_id: str, symbol: str) -> bool:
-        """Cancel an order."""
+    def cancel_order(self, order_id: str, symbol: str, params: Optional[Dict] = None) -> bool:
+        """Cancel an order with CCXT-style parameters."""
         try:
             if not self.sync_client:
                 logger.error("❌ Client not initialized")
                 return False
             
-            result = self.sync_client.cancel_order(order_id, symbol)
+            cancel_params = params or {}
+            result = self.sync_client.cancel_order(order_id, symbol, params=cancel_params)
+            
             logger.info(f"✅ Successfully cancelled order {order_id}")
             return True
         except Exception as e:
@@ -184,4 +239,18 @@ class HyperliquidClient:
             return fee
         except Exception as e:
             logger.error(f"❌ Error fetching trading fee for {symbol}: {e}")
+            return None
+    
+    def get_markets(self) -> Optional[Dict[str, Any]]:
+        """Get available markets/symbols."""
+        try:
+            if not self.sync_client:
+                logger.error("❌ Client not initialized")
+                return None
+            
+            markets = self.sync_client.load_markets()
+            logger.info(f"✅ Successfully loaded {len(markets)} markets")
+            return markets
+        except Exception as e:
+            logger.error(f"❌ Error loading markets: {e}")
             return None