trading_bot.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. #!/usr/bin/env python3
  2. """
  3. Hyperliquid Manual Trading Bot - Main Launcher
  4. A robust launcher with error handling, auto-restart, and telegram notifications.
  5. This is the ONLY file you need to run - it handles everything.
  6. """
  7. import sys
  8. import os
  9. import asyncio
  10. import logging
  11. import traceback
  12. import signal
  13. import time
  14. from datetime import datetime
  15. from pathlib import Path
  16. # Add src directory to Python path
  17. sys.path.insert(0, str(Path(__file__).parent / "src"))
  18. try:
  19. from config import Config
  20. from telegram_bot import TelegramTradingBot
  21. from trading_stats import TradingStats
  22. except ImportError as e:
  23. print(f"โŒ Import error: {e}")
  24. print("๐Ÿ’ก Make sure you're in the correct directory and dependencies are installed")
  25. sys.exit(1)
  26. # Global variables for graceful shutdown
  27. bot_instance = None
  28. is_shutting_down = False
  29. class BotManager:
  30. """Manages the trading bot with error handling and auto-restart."""
  31. def __init__(self):
  32. self.bot = None
  33. self.restart_count = 0
  34. self.max_restarts = 10
  35. self.restart_delay = 5 # seconds
  36. self.error_log_file = "logs/bot_errors.log"
  37. self.setup_logging()
  38. # Ensure logs directory exists
  39. os.makedirs("logs", exist_ok=True)
  40. def setup_logging(self):
  41. """Set up comprehensive logging."""
  42. # Create logs directory
  43. os.makedirs("logs", exist_ok=True)
  44. # Set up file logging
  45. log_file = f"logs/trading_bot_{datetime.now().strftime('%Y%m%d')}.log"
  46. logging.basicConfig(
  47. level=getattr(logging, Config.LOG_LEVEL if hasattr(Config, 'LOG_LEVEL') else 'INFO'),
  48. format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
  49. handlers=[
  50. logging.FileHandler(log_file),
  51. logging.StreamHandler(sys.stdout)
  52. ]
  53. )
  54. self.logger = logging.getLogger(__name__)
  55. self.logger.info(f"Logging initialized - Log file: {log_file}")
  56. def print_banner(self):
  57. """Print startup banner."""
  58. banner = f"""
  59. โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—
  60. โ•‘ ๐Ÿ“ฑ HYPERLIQUID TRADING BOT โ•‘
  61. โ•‘ Single Command Launcher โ•‘
  62. โ• โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฃ
  63. โ•‘ โ•‘
  64. โ•‘ ๐Ÿค– Manual phone control via Telegram โ•‘
  65. โ•‘ ๐Ÿ“Š Comprehensive trading statistics โ•‘
  66. โ•‘ ๐Ÿ›ก๏ธ Auto-restart & error notifications โ•‘
  67. โ•‘ ๐Ÿ’พ Persistent data between restarts โ•‘
  68. โ•‘ ๐Ÿ“ฑ Professional mobile interface โ•‘
  69. โ•‘ โ•‘
  70. โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
  71. ๐Ÿš€ Starting at: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
  72. ๐Ÿ“ Working directory: {os.getcwd()}
  73. ๐ŸŒ Network: {'Testnet' if Config.HYPERLIQUID_TESTNET else '๐Ÿšจ MAINNET ๐Ÿšจ'}
  74. """
  75. print(banner)
  76. def validate_configuration(self):
  77. """Validate bot configuration."""
  78. self.logger.info("๐Ÿ” Validating configuration...")
  79. missing_config = []
  80. if not hasattr(Config, 'HYPERLIQUID_PRIVATE_KEY') or not Config.HYPERLIQUID_PRIVATE_KEY:
  81. missing_config.append("HYPERLIQUID_PRIVATE_KEY")
  82. if not hasattr(Config, 'TELEGRAM_BOT_TOKEN') or not Config.TELEGRAM_BOT_TOKEN:
  83. missing_config.append("TELEGRAM_BOT_TOKEN")
  84. if not hasattr(Config, 'TELEGRAM_CHAT_ID') or not Config.TELEGRAM_CHAT_ID:
  85. missing_config.append("TELEGRAM_CHAT_ID")
  86. if not hasattr(Config, 'TELEGRAM_ENABLED') or not Config.TELEGRAM_ENABLED:
  87. missing_config.append("TELEGRAM_ENABLED (must be true)")
  88. if missing_config:
  89. error_msg = f"โŒ Missing configuration: {', '.join(missing_config)}"
  90. self.logger.error(error_msg)
  91. print(f"\n{error_msg}")
  92. print("\n๐Ÿ’ก Setup steps:")
  93. print("1. Copy config: cp config/env.example .env")
  94. print("2. Get Telegram setup: python utils/get_telegram_chat_id.py")
  95. print("3. Edit .env with your details")
  96. print("4. See: SETUP_GUIDE.md for detailed instructions")
  97. return False
  98. self.logger.info("โœ… Configuration validation passed")
  99. return True
  100. def check_stats_persistence(self):
  101. """Check and report on statistics persistence."""
  102. stats_file = "trading_stats.json"
  103. if os.path.exists(stats_file):
  104. try:
  105. stats = TradingStats()
  106. basic_stats = stats.get_basic_stats()
  107. self.logger.info(f"๐Ÿ“Š Existing stats found - {basic_stats['total_trades']} trades since {basic_stats.get('start_date', 'unknown')}")
  108. return True
  109. except Exception as e:
  110. self.logger.warning(f"โš ๏ธ Stats file exists but couldn't load: {e}")
  111. return False
  112. else:
  113. self.logger.info("๐Ÿ“Š No existing stats - will create new tracking from launch")
  114. return False
  115. async def send_error_notification(self, error_message: str, error_details: str = None):
  116. """Send error notification via Telegram."""
  117. try:
  118. if hasattr(Config, 'TELEGRAM_BOT_TOKEN') and hasattr(Config, 'TELEGRAM_CHAT_ID'):
  119. from telegram import Bot
  120. bot = Bot(token=Config.TELEGRAM_BOT_TOKEN)
  121. notification = f"""
  122. ๐Ÿšจ <b>Trading Bot Error</b>
  123. โฐ <b>Time:</b> {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
  124. โŒ <b>Error:</b> {error_message}
  125. ๐Ÿ”„ <b>Status:</b> Attempting restart...
  126. ๐Ÿ“Š <b>Restart Count:</b> {self.restart_count}/{self.max_restarts}
  127. {f'<b>Details:</b> <code>{error_details[:500]}...</code>' if error_details else ''}
  128. """
  129. await bot.send_message(
  130. chat_id=Config.TELEGRAM_CHAT_ID,
  131. text=notification.strip(),
  132. parse_mode='HTML'
  133. )
  134. self.logger.info("๐Ÿ“ฑ Error notification sent via Telegram")
  135. except Exception as e:
  136. self.logger.error(f"Failed to send error notification: {e}")
  137. async def send_startup_notification(self):
  138. """Send startup notification via Telegram."""
  139. try:
  140. if self.bot and hasattr(self.bot, 'send_message'):
  141. message = f"""
  142. ๐ŸŸข <b>Trading Bot Started Successfully</b>
  143. โฐ <b>Started:</b> {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
  144. ๐ŸŒ <b>Network:</b> {'Testnet' if Config.HYPERLIQUID_TESTNET else 'Mainnet'}
  145. ๐Ÿ“Š <b>Symbol:</b> {Config.DEFAULT_TRADING_SYMBOL}
  146. ๐Ÿ’ฐ <b>Default Amount:</b> {Config.DEFAULT_TRADE_AMOUNT}
  147. ๐Ÿ”„ <b>Restart #:</b> {self.restart_count}
  148. ๐Ÿ“ฑ Bot is ready for manual trading!
  149. Send /start for quick actions.
  150. """
  151. await self.bot.send_message(message.strip())
  152. except Exception as e:
  153. self.logger.error(f"Failed to send startup notification: {e}")
  154. async def run_bot(self):
  155. """Run the main bot with error handling."""
  156. try:
  157. self.logger.info("๐Ÿค– Creating TelegramTradingBot instance...")
  158. self.bot = TelegramTradingBot()
  159. self.logger.info("๐Ÿ“Š Checking statistics persistence...")
  160. self.check_stats_persistence()
  161. self.logger.info("๐Ÿš€ Starting Telegram bot...")
  162. # Send startup notification
  163. await self.send_startup_notification()
  164. # Run the bot
  165. await self.bot.run()
  166. except KeyboardInterrupt:
  167. self.logger.info("๐Ÿ‘‹ Bot stopped by user (Ctrl+C)")
  168. raise # Re-raise to handle gracefully
  169. except Exception as e:
  170. self.logger.error(f"โŒ Bot error: {e}")
  171. # Get detailed error info
  172. error_details = traceback.format_exc()
  173. self.logger.error(f"Error details:\n{error_details}")
  174. # Log to error file
  175. with open(self.error_log_file, 'a') as f:
  176. f.write(f"\n{datetime.now().isoformat()} - {e}\n{error_details}\n{'='*50}\n")
  177. # Send error notification
  178. await self.send_error_notification(str(e), error_details)
  179. raise # Re-raise for restart logic
  180. async def run_with_restart(self):
  181. """Run bot with auto-restart capability."""
  182. global is_shutting_down
  183. while not is_shutting_down and self.restart_count < self.max_restarts:
  184. try:
  185. if self.restart_count > 0:
  186. self.logger.info(f"๐Ÿ”„ Restarting bot (attempt {self.restart_count + 1}/{self.max_restarts})")
  187. self.logger.info(f"โณ Waiting {self.restart_delay} seconds before restart...")
  188. await asyncio.sleep(self.restart_delay)
  189. await self.run_bot()
  190. # If we get here, bot exited normally
  191. break
  192. except KeyboardInterrupt:
  193. self.logger.info("๐Ÿ‘‹ Graceful shutdown requested")
  194. is_shutting_down = True
  195. break
  196. except Exception as e:
  197. self.restart_count += 1
  198. self.logger.error(f"๐Ÿ’ฅ Bot crashed: {e}")
  199. if self.restart_count >= self.max_restarts:
  200. self.logger.error(f"โŒ Max restart attempts ({self.max_restarts}) reached. Giving up.")
  201. # Send final error notification
  202. await self.send_error_notification(
  203. f"Bot failed permanently after {self.max_restarts} restart attempts",
  204. f"Last error: {str(e)}"
  205. )
  206. break
  207. # Exponential backoff for restart delay
  208. self.restart_delay = min(self.restart_delay * 1.5, 60)
  209. async def start(self):
  210. """Main entry point."""
  211. try:
  212. self.print_banner()
  213. if not self.validate_configuration():
  214. return False
  215. self.logger.info("๐ŸŽฏ All systems ready - starting trading bot...")
  216. print("๐Ÿ’ก Send /start to your Telegram bot for quick actions")
  217. print("๐Ÿ›‘ Press Ctrl+C to stop\n")
  218. await self.run_with_restart()
  219. return True
  220. except Exception as e:
  221. self.logger.error(f"โŒ Fatal error in bot manager: {e}")
  222. print(f"\n๐Ÿ’ฅ Fatal error: {e}")
  223. return False
  224. def signal_handler(signum, frame):
  225. """Handle shutdown signals gracefully."""
  226. global is_shutting_down, bot_instance
  227. print("\n๐Ÿ›‘ Shutdown signal received...")
  228. is_shutting_down = True
  229. if bot_instance:
  230. try:
  231. asyncio.create_task(bot_instance.send_error_notification("Bot shutdown requested", "Graceful shutdown"))
  232. except:
  233. pass
  234. def main():
  235. """Main function."""
  236. global bot_instance
  237. # Set up signal handlers for graceful shutdown
  238. signal.signal(signal.SIGINT, signal_handler)
  239. signal.signal(signal.SIGTERM, signal_handler)
  240. try:
  241. bot_manager = BotManager()
  242. bot_instance = bot_manager
  243. # Run the bot
  244. success = asyncio.run(bot_manager.start())
  245. if success:
  246. print("\nโœ… Bot session completed successfully")
  247. else:
  248. print("\nโŒ Bot session failed")
  249. sys.exit(1)
  250. except KeyboardInterrupt:
  251. print("\n๐Ÿ‘‹ Bot stopped by user")
  252. except Exception as e:
  253. print(f"\n๐Ÿ’ฅ Unexpected error: {e}")
  254. sys.exit(1)
  255. finally:
  256. print("๐Ÿ“Š Your trading statistics have been saved")
  257. print("๐Ÿ”„ Run again anytime to continue tracking!")
  258. if __name__ == "__main__":
  259. main()