123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331 |
- #!/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"""
- 🚨 <b>Trading Bot Error</b>
- ⏰ <b>Time:</b> {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
- ❌ <b>Error:</b> {error_message}
- 🔄 <b>Status:</b> Attempting restart...
- 📊 <b>Restart Count:</b> {self.restart_count}/{self.max_restarts}
- {f'<b>Details:</b> <code>{error_details[:500]}...</code>' 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"""
- 🟢 <b>Trading Bot Started Successfully</b>
- ⏰ <b>Started:</b> {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
- 🌐 <b>Network:</b> {'Testnet' if Config.HYPERLIQUID_TESTNET else 'Mainnet'}
- 📊 <b>Symbol:</b> {Config.DEFAULT_TRADING_SYMBOL}
- 💰 <b>Default Amount:</b> {Config.DEFAULT_TRADE_AMOUNT}
- 🔄 <b>Restart #:</b> {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()
|