import os from dotenv import load_dotenv from typing import Optional import logging # Load environment variables from .env file load_dotenv() logger = logging.getLogger(__name__) class Config: """Configuration class for the Hyperliquid trading bot.""" # Hyperliquid API Configuration HYPERLIQUID_PRIVATE_KEY: Optional[str] = os.getenv('HYPERLIQUID_PRIVATE_KEY') HYPERLIQUID_SECRET_KEY: Optional[str] = os.getenv('HYPERLIQUID_SECRET_KEY') HYPERLIQUID_WALLET_ADDRESS: Optional[str] = os.getenv('HYPERLIQUID_WALLET_ADDRESS') HYPERLIQUID_TESTNET: bool = os.getenv('HYPERLIQUID_TESTNET', 'true').lower() == 'true' # Trading Bot Configuration DEFAULT_TRADING_TOKEN: str = os.getenv('DEFAULT_TRADING_TOKEN', 'BTC') RISK_MANAGEMENT_ENABLED: bool = os.getenv('RISK_MANAGEMENT_ENABLED', 'true').lower() == 'true' STOP_LOSS_PERCENTAGE: float = float(os.getenv('STOP_LOSS_PERCENTAGE', '10.0')) # Telegram Bot Configuration TELEGRAM_BOT_TOKEN: Optional[str] = os.getenv('TELEGRAM_BOT_TOKEN') TELEGRAM_CHAT_ID: Optional[str] = os.getenv('TELEGRAM_CHAT_ID') TELEGRAM_ENABLED: bool = os.getenv('TELEGRAM_ENABLED', 'true').lower() == 'true' # Bot monitoring configuration BOT_HEARTBEAT_SECONDS = int(os.getenv('BOT_HEARTBEAT_SECONDS', '30')) # Logging LOG_LEVEL: str = os.getenv('LOG_LEVEL', 'INFO') # Log file configuration LOG_TO_FILE: bool = os.getenv('LOG_TO_FILE', 'true').lower() == 'true' LOG_FILE_PATH: str = os.getenv('LOG_FILE_PATH', 'logs/trading_bot.log') LOG_MAX_SIZE_MB: int = int(os.getenv('LOG_MAX_SIZE_MB', '10')) LOG_BACKUP_COUNT: int = int(os.getenv('LOG_BACKUP_COUNT', '5')) LOG_ROTATION_TYPE: str = os.getenv('LOG_ROTATION_TYPE', 'size') # 'size' or 'time' LOG_ROTATION_INTERVAL: str = os.getenv('LOG_ROTATION_INTERVAL', 'midnight') # for time rotation @classmethod def validate(cls) -> bool: """Validate all configuration settings.""" is_valid = True # Validate Hyperliquid settings if not cls.HYPERLIQUID_PRIVATE_KEY: logger.error("❌ HYPERLIQUID_PRIVATE_KEY is required") is_valid = False # Validate wallet address or private key format if not cls.HYPERLIQUID_WALLET_ADDRESS and cls.HYPERLIQUID_PRIVATE_KEY: # Check if private key looks like an address if not cls.HYPERLIQUID_PRIVATE_KEY.startswith('0x') and len(cls.HYPERLIQUID_PRIVATE_KEY) < 40: logger.warning("⚠️ HYPERLIQUID_WALLET_ADDRESS not set - will attempt to use private key as address") elif len(cls.HYPERLIQUID_PRIVATE_KEY) < 64: logger.warning("⚠️ HYPERLIQUID_WALLET_ADDRESS not set and private key appears short - consider setting explicit wallet address") # Validate Telegram settings (if enabled) if cls.TELEGRAM_ENABLED: if not cls.TELEGRAM_BOT_TOKEN: logger.error("❌ TELEGRAM_BOT_TOKEN is required when Telegram is enabled") is_valid = False if not cls.TELEGRAM_CHAT_ID: logger.error("❌ TELEGRAM_CHAT_ID is required when Telegram is enabled") is_valid = False # Validate heartbeat interval if cls.BOT_HEARTBEAT_SECONDS < 5: logger.error("❌ BOT_HEARTBEAT_SECONDS must be at least 5 seconds to avoid rate limiting") is_valid = False elif cls.BOT_HEARTBEAT_SECONDS > 600: logger.warning("⚠️ BOT_HEARTBEAT_SECONDS is very high (>10 minutes), monitoring may be slow") # Validate trading settings if cls.DEFAULT_TRADING_TOKEN == '': logger.error("❌ DEFAULT_TRADING_TOKEN must be set") is_valid = False # Validate logging settings if cls.LOG_MAX_SIZE_MB <= 0: logger.error("❌ LOG_MAX_SIZE_MB must be positive") is_valid = False if cls.LOG_BACKUP_COUNT < 0: logger.error("❌ LOG_BACKUP_COUNT must be non-negative") is_valid = False if cls.LOG_ROTATION_TYPE not in ['size', 'time']: logger.error("❌ LOG_ROTATION_TYPE must be 'size' or 'time'") is_valid = False return is_valid @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 # Hyperliquid CCXT implementation expects 'privateKey' and 'walletAddress' credentials if cls.HYPERLIQUID_PRIVATE_KEY: config['privateKey'] = cls.HYPERLIQUID_PRIVATE_KEY config['apiKey'] = cls.HYPERLIQUID_PRIVATE_KEY # Keep for backward compatibility if cls.HYPERLIQUID_WALLET_ADDRESS: config['walletAddress'] = cls.HYPERLIQUID_WALLET_ADDRESS elif cls.HYPERLIQUID_PRIVATE_KEY: # If no explicit wallet address, try to use the private key as address # (This might work if the private key is actually the wallet address) wallet_addr = cls.HYPERLIQUID_PRIVATE_KEY if not wallet_addr.startswith('0x'): wallet_addr = f"0x{wallet_addr}" config['walletAddress'] = wallet_addr if cls.HYPERLIQUID_SECRET_KEY: config['secret'] = cls.HYPERLIQUID_SECRET_KEY return config @classmethod def print_config(cls): """Print current configuration (hiding sensitive data).""" logger.info("🔧 Configuration:") logger.info(f" 📡 Hyperliquid Testnet: {cls.HYPERLIQUID_TESTNET}") logger.info(f" 🔑 Private Key: {'✅ Set' if cls.HYPERLIQUID_PRIVATE_KEY else '❌ Missing'}") logger.info(f" 🏠 Wallet Address: {'✅ Set' if cls.HYPERLIQUID_WALLET_ADDRESS else '⚠️ Will derive from private key'}") logger.info(f" 🔐 Secret Key: {'✅ Set' if cls.HYPERLIQUID_SECRET_KEY else '⚠️ Missing (may be optional)'}") logger.info(f" 📱 Telegram: {'✅ Enabled' if cls.TELEGRAM_ENABLED else '❌ Disabled'}") logger.info(f" 🔍 Log Level: {cls.LOG_LEVEL}") logger.info(f" 💾 Log to File: {cls.LOG_TO_FILE}") logger.info(f" ⏱️ Heartbeat: {cls.BOT_HEARTBEAT_SECONDS}s") logger.info(f" 💰 DEFAULT_TRADING_TOKEN: {cls.DEFAULT_TRADING_TOKEN}") logger.info(f" 🧮 RISK_MANAGEMENT_ENABLED: {cls.RISK_MANAGEMENT_ENABLED}") logger.info(f" 🔄 STOP_LOSS_PERCENTAGE: {cls.STOP_LOSS_PERCENTAGE}%") logger.info(f" 🤖 TELEGRAM_BOT_TOKEN: {'✅ Set' if cls.TELEGRAM_BOT_TOKEN else '❌ Not Set'}") logger.info(f" 💬 TELEGRAM_CHAT_ID: {'✅ Set' if cls.TELEGRAM_CHAT_ID else '❌ Not Set'}") logger.info(f" 🔍 LOG_FILE_PATH: {cls.LOG_FILE_PATH}") logger.info(f" 🔄 LOG_MAX_SIZE_MB: {cls.LOG_MAX_SIZE_MB} MB") logger.info(f" 🔄 LOG_BACKUP_COUNT: {cls.LOG_BACKUP_COUNT}") logger.info(f" 🔄 LOG_ROTATION_TYPE: {cls.LOG_ROTATION_TYPE}") logger.info(f" 🔄 LOG_ROTATION_INTERVAL: {cls.LOG_ROTATION_INTERVAL}")