#!/usr/bin/env python3 """ Hyperliquid Manual Trading Bot - Main Launcher A robust launcher with error handling, auto-restart, and telegram notifications. This is the ONLY file you need to run - it handles everything. """ import sys import os import asyncio import logging import traceback import signal import time from datetime import datetime from pathlib import Path # Add src directory to Python path sys.path.insert(0, str(Path(__file__).parent / "src")) try: from config import Config from telegram_bot import TelegramTradingBot from trading_stats import TradingStats except ImportError as e: print(f"āŒ Import error: {e}") print("šŸ’” Make sure you're in the correct directory and dependencies are installed") sys.exit(1) # Global variables for graceful shutdown bot_instance = None is_shutting_down = False class BotManager: """Manages the trading bot with error handling and auto-restart.""" def __init__(self): self.bot = None self.restart_count = 0 self.max_restarts = 10 self.restart_delay = 5 # seconds self.error_log_file = "logs/bot_errors.log" self.setup_logging() # Ensure logs directory exists os.makedirs("logs", exist_ok=True) def setup_logging(self): """Set up comprehensive logging.""" # Create logs directory os.makedirs("logs", exist_ok=True) # Set up file logging log_file = f"logs/trading_bot_{datetime.now().strftime('%Y%m%d')}.log" logging.basicConfig( level=getattr(logging, Config.LOG_LEVEL if hasattr(Config, 'LOG_LEVEL') else 'INFO'), format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler(log_file), logging.StreamHandler(sys.stdout) ] ) self.logger = logging.getLogger(__name__) self.logger.info(f"Logging initialized - Log file: {log_file}") def print_banner(self): """Print startup banner.""" banner = f""" ╔══════════════════════════════════════════════════════════════╗ ā•‘ šŸ“± HYPERLIQUID TRADING BOT ā•‘ ā•‘ Single Command Launcher ā•‘ ╠══════════════════════════════════════════════════════════════╣ ā•‘ ā•‘ ā•‘ šŸ¤– Manual phone control via Telegram ā•‘ ā•‘ šŸ“Š Comprehensive trading statistics ā•‘ ā•‘ šŸ›”ļø Auto-restart & error notifications ā•‘ ā•‘ šŸ’¾ Persistent data between restarts ā•‘ ā•‘ šŸ“± Professional mobile interface ā•‘ ā•‘ ā•‘ ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā• šŸš€ Starting at: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} šŸ“ Working directory: {os.getcwd()} 🌐 Network: {'Testnet' if Config.HYPERLIQUID_TESTNET else '🚨 MAINNET 🚨'} """ print(banner) def validate_configuration(self): """Validate bot configuration.""" self.logger.info("šŸ” Validating configuration...") missing_config = [] if not hasattr(Config, 'HYPERLIQUID_PRIVATE_KEY') or not Config.HYPERLIQUID_PRIVATE_KEY: missing_config.append("HYPERLIQUID_PRIVATE_KEY") if not hasattr(Config, 'TELEGRAM_BOT_TOKEN') or not Config.TELEGRAM_BOT_TOKEN: missing_config.append("TELEGRAM_BOT_TOKEN") if not hasattr(Config, 'TELEGRAM_CHAT_ID') or not Config.TELEGRAM_CHAT_ID: missing_config.append("TELEGRAM_CHAT_ID") if not hasattr(Config, 'TELEGRAM_ENABLED') or not Config.TELEGRAM_ENABLED: missing_config.append("TELEGRAM_ENABLED (must be true)") if missing_config: error_msg = f"āŒ Missing configuration: {', '.join(missing_config)}" self.logger.error(error_msg) print(f"\n{error_msg}") print("\nšŸ’” Setup steps:") print("1. Copy config: cp config/env.example .env") print("2. Get Telegram setup: python utils/get_telegram_chat_id.py") print("3. Edit .env with your details") print("4. See: SETUP_GUIDE.md for detailed instructions") return False self.logger.info("āœ… Configuration validation passed") return True def check_stats_persistence(self): """Check and report on statistics persistence.""" stats_file = "trading_stats.json" if os.path.exists(stats_file): try: stats = TradingStats() basic_stats = stats.get_basic_stats() self.logger.info(f"šŸ“Š Existing stats found - {basic_stats['total_trades']} trades since {basic_stats.get('start_date', 'unknown')}") return True except Exception as e: self.logger.warning(f"āš ļø Stats file exists but couldn't load: {e}") return False else: self.logger.info("šŸ“Š No existing stats - will create new tracking from launch") return False async def send_error_notification(self, error_message: str, error_details: str = None): """Send error notification via Telegram.""" try: if hasattr(Config, 'TELEGRAM_BOT_TOKEN') and hasattr(Config, 'TELEGRAM_CHAT_ID'): from telegram import Bot bot = Bot(token=Config.TELEGRAM_BOT_TOKEN) notification = f""" 🚨 Trading Bot Error ā° Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} āŒ Error: {error_message} šŸ”„ Status: Attempting restart... šŸ“Š Restart Count: {self.restart_count}/{self.max_restarts} {f'Details: {error_details[:500]}...' if error_details else ''} """ await bot.send_message( chat_id=Config.TELEGRAM_CHAT_ID, text=notification.strip(), parse_mode='HTML' ) self.logger.info("šŸ“± Error notification sent via Telegram") except Exception as e: self.logger.error(f"Failed to send error notification: {e}") async def send_startup_notification(self): """Send startup notification via Telegram.""" try: if self.bot and hasattr(self.bot, 'send_message'): message = f""" 🟢 Trading Bot Started Successfully ā° Started: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} 🌐 Network: {'Testnet' if Config.HYPERLIQUID_TESTNET else 'Mainnet'} šŸ“Š Symbol: {Config.DEFAULT_TRADING_SYMBOL} šŸ’° Default Amount: {Config.DEFAULT_TRADE_AMOUNT} šŸ”„ Restart #: {self.restart_count} šŸ“± Bot is ready for manual trading! Send /start for quick actions. """ await self.bot.send_message(message.strip()) except Exception as e: self.logger.error(f"Failed to send startup notification: {e}") async def run_bot(self): """Run the main bot with error handling.""" try: self.logger.info("šŸ¤– Creating TelegramTradingBot instance...") self.bot = TelegramTradingBot() self.logger.info("šŸ“Š Checking statistics persistence...") self.check_stats_persistence() self.logger.info("šŸš€ Starting Telegram bot...") # Send startup notification await self.send_startup_notification() # Run the bot await self.bot.run() except KeyboardInterrupt: self.logger.info("šŸ‘‹ Bot stopped by user (Ctrl+C)") raise # Re-raise to handle gracefully except Exception as e: self.logger.error(f"āŒ Bot error: {e}") # Get detailed error info error_details = traceback.format_exc() self.logger.error(f"Error details:\n{error_details}") # Log to error file with open(self.error_log_file, 'a') as f: f.write(f"\n{datetime.now().isoformat()} - {e}\n{error_details}\n{'='*50}\n") # Send error notification await self.send_error_notification(str(e), error_details) raise # Re-raise for restart logic async def run_with_restart(self): """Run bot with auto-restart capability.""" global is_shutting_down while not is_shutting_down and self.restart_count < self.max_restarts: try: if self.restart_count > 0: self.logger.info(f"šŸ”„ Restarting bot (attempt {self.restart_count + 1}/{self.max_restarts})") self.logger.info(f"ā³ Waiting {self.restart_delay} seconds before restart...") await asyncio.sleep(self.restart_delay) await self.run_bot() # If we get here, bot exited normally break except KeyboardInterrupt: self.logger.info("šŸ‘‹ Graceful shutdown requested") is_shutting_down = True break except Exception as e: self.restart_count += 1 self.logger.error(f"šŸ’„ Bot crashed: {e}") if self.restart_count >= self.max_restarts: self.logger.error(f"āŒ Max restart attempts ({self.max_restarts}) reached. Giving up.") # Send final error notification await self.send_error_notification( f"Bot failed permanently after {self.max_restarts} restart attempts", f"Last error: {str(e)}" ) break # Exponential backoff for restart delay self.restart_delay = min(self.restart_delay * 1.5, 60) async def start(self): """Main entry point.""" try: self.print_banner() if not self.validate_configuration(): return False self.logger.info("šŸŽÆ All systems ready - starting trading bot...") print("šŸ’” Send /start to your Telegram bot for quick actions") print("šŸ›‘ Press Ctrl+C to stop\n") await self.run_with_restart() return True except Exception as e: self.logger.error(f"āŒ Fatal error in bot manager: {e}") print(f"\nšŸ’„ Fatal error: {e}") return False def signal_handler(signum, frame): """Handle shutdown signals gracefully.""" global is_shutting_down, bot_instance print("\nšŸ›‘ Shutdown signal received...") is_shutting_down = True if bot_instance: try: asyncio.create_task(bot_instance.send_error_notification("Bot shutdown requested", "Graceful shutdown")) except: pass def main(): """Main function.""" global bot_instance # Set up signal handlers for graceful shutdown signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) try: bot_manager = BotManager() bot_instance = bot_manager # Run the bot success = asyncio.run(bot_manager.start()) if success: print("\nāœ… Bot session completed successfully") else: print("\nāŒ Bot session failed") sys.exit(1) except KeyboardInterrupt: print("\nšŸ‘‹ Bot stopped by user") except Exception as e: print(f"\nšŸ’„ Unexpected error: {e}") sys.exit(1) finally: print("šŸ“Š Your trading statistics have been saved") print("šŸ”„ Run again anytime to continue tracking!") if __name__ == "__main__": main()