#!/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()