core.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  1. #!/usr/bin/env python3
  2. """
  3. Core Telegram Bot - Handles only bot setup, authentication, and basic messaging.
  4. """
  5. import asyncio
  6. import logging
  7. import telegram # Import telegram to check version
  8. from datetime import datetime
  9. from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
  10. from telegram.ext import Application, ContextTypes, CommandHandler, CallbackQueryHandler, MessageHandler, filters
  11. from src.config.config import Config
  12. from src.trading.trading_engine import TradingEngine
  13. from src.monitoring.market_monitor import MarketMonitor
  14. from src.notifications.notification_manager import NotificationManager
  15. from src.commands.trading_commands import TradingCommands
  16. from src.commands.info_commands import InfoCommands
  17. from src.commands.management_commands import ManagementCommands
  18. logger = logging.getLogger(__name__)
  19. class TelegramTradingBot:
  20. """Core Telegram bot handling only authentication, messaging, and command routing."""
  21. def __init__(self):
  22. """Initialize the core bot with minimal responsibilities."""
  23. # Core bot attributes
  24. self.application = None
  25. self.version = "Unknown"
  26. # Initialize subsystems
  27. self.trading_engine = TradingEngine()
  28. self.notification_manager = NotificationManager()
  29. self.market_monitor = MarketMonitor(self.trading_engine, self.notification_manager)
  30. # Initialize command handlers
  31. self.info_commands = InfoCommands(self.trading_engine)
  32. self.management_commands = ManagementCommands(self.trading_engine, self.market_monitor)
  33. # Pass info and management command handlers to TradingCommands
  34. self.trading_commands = TradingCommands(self.trading_engine, self.notification_manager,
  35. info_commands_handler=self.info_commands,
  36. management_commands_handler=self.management_commands)
  37. def is_authorized(self, chat_id: str) -> bool:
  38. """Check if the chat ID is authorized to use the bot."""
  39. authorized = str(chat_id) == str(Config.TELEGRAM_CHAT_ID)
  40. logger.info(f"Authorization check: Incoming chat_id='{chat_id}', Configured TELEGRAM_CHAT_ID='{Config.TELEGRAM_CHAT_ID}', Authorized={authorized}")
  41. return authorized
  42. async def send_message(self, text: str, parse_mode: str = 'HTML') -> None:
  43. """Send a message to the authorized chat."""
  44. if self.application and Config.TELEGRAM_CHAT_ID:
  45. try:
  46. await self.application.bot.send_message(
  47. chat_id=Config.TELEGRAM_CHAT_ID,
  48. text=text,
  49. parse_mode=parse_mode
  50. )
  51. except Exception as e:
  52. logger.error(f"Failed to send message: {e}")
  53. def setup_handlers(self):
  54. """Set up command handlers for the bot."""
  55. if not self.application:
  56. return
  57. # Basic bot commands (directly on application for v20.x)
  58. self.application.add_handler(CommandHandler("start", self.start_command))
  59. self.application.add_handler(CommandHandler("help", self.help_command))
  60. # Trading commands
  61. self.application.add_handler(CommandHandler("long", self.trading_commands.long_command))
  62. self.application.add_handler(CommandHandler("short", self.trading_commands.short_command))
  63. self.application.add_handler(CommandHandler("exit", self.trading_commands.exit_command))
  64. self.application.add_handler(CommandHandler("sl", self.trading_commands.sl_command))
  65. self.application.add_handler(CommandHandler("tp", self.trading_commands.tp_command))
  66. self.application.add_handler(CommandHandler("coo", self.trading_commands.coo_command))
  67. # Info commands
  68. self.application.add_handler(CommandHandler("balance", self.info_commands.balance_command))
  69. self.application.add_handler(CommandHandler("positions", self.info_commands.positions_command))
  70. self.application.add_handler(CommandHandler("orders", self.info_commands.orders_command))
  71. self.application.add_handler(CommandHandler("stats", self.info_commands.stats_command))
  72. self.application.add_handler(CommandHandler("trades", self.info_commands.trades_command))
  73. self.application.add_handler(CommandHandler("cycles", self.info_commands.cycles_command))
  74. self.application.add_handler(CommandHandler("market", self.info_commands.market_command))
  75. self.application.add_handler(CommandHandler("price", self.info_commands.price_command))
  76. self.application.add_handler(CommandHandler("performance", self.info_commands.performance_command))
  77. self.application.add_handler(CommandHandler("daily", self.info_commands.daily_command))
  78. self.application.add_handler(CommandHandler("weekly", self.info_commands.weekly_command))
  79. self.application.add_handler(CommandHandler("monthly", self.info_commands.monthly_command))
  80. self.application.add_handler(CommandHandler("risk", self.info_commands.risk_command))
  81. self.application.add_handler(CommandHandler("balance_adjustments", self.info_commands.balance_adjustments_command))
  82. self.application.add_handler(CommandHandler("commands", self.info_commands.commands_command))
  83. self.application.add_handler(CommandHandler("c", self.info_commands.commands_command)) # Alias
  84. # Management commands
  85. self.application.add_handler(CommandHandler("monitoring", self.management_commands.monitoring_command))
  86. self.application.add_handler(CommandHandler("alarm", self.management_commands.alarm_command))
  87. self.application.add_handler(CommandHandler("logs", self.management_commands.logs_command))
  88. self.application.add_handler(CommandHandler("debug", self.management_commands.debug_command))
  89. self.application.add_handler(CommandHandler("version", self.management_commands.version_command))
  90. self.application.add_handler(CommandHandler("keyboard", self.management_commands.keyboard_command))
  91. # Callback and message handlers
  92. self.application.add_handler(CallbackQueryHandler(self.trading_commands.button_callback))
  93. async def start_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
  94. """Handle the /start command."""
  95. logger.info(f"/start command triggered by chat_id: {update.effective_chat.id}")
  96. logger.debug(f"Full Update object in start_command: {update}")
  97. chat_id = update.effective_chat.id
  98. if not self.is_authorized(chat_id):
  99. logger.warning(f"Unauthorized access attempt by chat_id: {chat_id} in /start.")
  100. try:
  101. await context.bot.send_message(chat_id=chat_id, text="❌ Unauthorized access.")
  102. except Exception as e:
  103. logger.error(f"Error sending unauthorized message in /start: {e}")
  104. return
  105. # Determine risk management and stop loss details from Config
  106. risk_enabled = getattr(Config, 'RISK_MANAGEMENT_ENABLED', False)
  107. stop_loss_percentage = getattr(Config, 'STOP_LOSS_PERCENTAGE', 0)
  108. bot_heartbeat = getattr(Config, 'BOT_HEARTBEAT_SECONDS', 10)
  109. welcome_text = f"""
  110. 🤖 <b>Welcome to Hyperliquid Trading Bot v{self.version}</b>
  111. 📱 <b>Quick Actions:</b>
  112. • Trading: /long {Config.DEFAULT_TRADING_TOKEN} 100 or /short {Config.DEFAULT_TRADING_TOKEN} 50
  113. • Exit: /exit {Config.DEFAULT_TRADING_TOKEN} (closes position)
  114. • Info: /balance, /positions, /orders
  115. 📊 <b>Market Data:</b>
  116. • /market - Detailed market overview
  117. • /price - Quick price check
  118. <b>⚡ Quick Commands:</b>
  119. • /balance - Account balance
  120. • /positions - Open positions
  121. • /orders - Active orders
  122. • /market - Market data & prices
  123. <b>🚀 Trading:</b>
  124. • /long {Config.DEFAULT_TRADING_TOKEN} 100 - Long position
  125. • /long {Config.DEFAULT_TRADING_TOKEN} 100 45000 - Limit order
  126. • /long {Config.DEFAULT_TRADING_TOKEN} 100 sl:44000 - With stop loss
  127. • /short {Config.DEFAULT_TRADING_TOKEN} 50 - Short position
  128. • /short {Config.DEFAULT_TRADING_TOKEN} 50 3500 sl:3600 - With stop loss
  129. • /exit {Config.DEFAULT_TRADING_TOKEN} - Close position
  130. • /coo {Config.DEFAULT_TRADING_TOKEN} - Cancel open orders
  131. <b>🛡️ Risk Management:</b>
  132. • Enabled: {'✅ Yes' if risk_enabled else '❌ No'}
  133. • Auto Stop Loss: {stop_loss_percentage}%
  134. • Order Stop Loss: Use sl:price parameter
  135. • /sl {Config.DEFAULT_TRADING_TOKEN} 44000 - Manual stop loss
  136. • /tp {Config.DEFAULT_TRADING_TOKEN} 50000 - Take profit order
  137. <b>📈 Performance & Analytics:</b>
  138. • /stats - Complete trading statistics
  139. • /performance - Token performance ranking & detailed stats
  140. • /daily - Daily performance (last 10 days)
  141. • /weekly - Weekly performance (last 10 weeks)
  142. • /monthly - Monthly performance (last 10 months)
  143. • /risk - Sharpe ratio, drawdown, VaR
  144. • /version - Bot version & system information
  145. • /trades - Recent trade history
  146. <b>🔔 Price Alerts:</b>
  147. • /alarm - List all active alarms
  148. • /alarm {Config.DEFAULT_TRADING_TOKEN} 50000 - Set alarm for {Config.DEFAULT_TRADING_TOKEN} at $50,000
  149. • /alarm {Config.DEFAULT_TRADING_TOKEN} - Show all {Config.DEFAULT_TRADING_TOKEN} alarms
  150. • /alarm 3 - Remove alarm ID 3
  151. <b>🔄 Automatic Monitoring:</b>
  152. • Real-time order fill alerts
  153. • Position opened/closed notifications
  154. • P&L calculations on trade closure
  155. • Price alarm triggers
  156. • External trade detection & sync
  157. • Auto stats synchronization
  158. • Automatic stop loss placement
  159. • {bot_heartbeat}-second monitoring interval
  160. <b>📊 Universal Trade Tracking:</b>
  161. • Bot trades: Full logging & notifications
  162. • Platform trades: Auto-detected & synced
  163. • Mobile app trades: Monitored & recorded
  164. • API trades: Tracked & included in stats
  165. Type /help for detailed command information.
  166. <b>🔄 Order Monitoring:</b>
  167. • /monitoring - View monitoring status
  168. • /logs - View log file statistics and cleanup
  169. <b>⚙️ Configuration:</b>
  170. • Default Token: {Config.DEFAULT_TRADING_TOKEN}
  171. • Network: {'Testnet' if Config.HYPERLIQUID_TESTNET else 'Mainnet'}
  172. <b>🛡️ Safety Features:</b>
  173. • All trades logged automatically
  174. • Comprehensive performance tracking
  175. • Real-time balance monitoring
  176. • Risk metrics calculation
  177. • Automatic stop loss protection
  178. <b>📱 Mobile Optimized:</b>
  179. • Quick action buttons via /commands
  180. • Instant notifications
  181. • Clean, readable layout
  182. <b>💡 Quick Access:</b>
  183. • /commands or /c - One-tap button menu for all commands
  184. For support, contact your bot administrator.
  185. """
  186. logger.debug(f"In /start, update.message is: {update.message}, update.effective_chat.id is: {chat_id}")
  187. try:
  188. await context.bot.send_message(chat_id=chat_id, text=welcome_text, parse_mode='HTML')
  189. except Exception as e:
  190. logger.error(f"Error sending welcome message in /start: {e}")
  191. async def help_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
  192. """Handle the /help command."""
  193. logger.info(f"/help command triggered by chat_id: {update.effective_chat.id}")
  194. logger.debug(f"Full Update object in help_command: {update}")
  195. chat_id = update.effective_chat.id
  196. if not self.is_authorized(chat_id):
  197. logger.warning(f"Unauthorized access attempt by chat_id: {chat_id} in /help.")
  198. try:
  199. await context.bot.send_message(chat_id=chat_id, text="❌ Unauthorized access.")
  200. except Exception as e:
  201. logger.error(f"Error sending unauthorized message in /help: {e}")
  202. return
  203. help_text = """
  204. 📖 <b>Complete Command Reference</b>
  205. 🔄 <b>Trading Commands:</b>
  206. • /long [token] [USDC] [price] [sl:price] - Open long position
  207. • /short [token] [USDC] [price] [sl:price] - Open short position
  208. • /exit [token] - Close position (market order)
  209. • /sl [token] [price] - Set stop loss order
  210. • /tp [token] [price] - Set take profit order
  211. • /coo [token] - Cancel all open orders
  212. 📊 <b>Account Info:</b>
  213. • /balance - Account balance and equity
  214. • /positions - Open positions with P&L
  215. • /orders - Active orders
  216. • /trades - Recent trade history
  217. • /stats - Comprehensive trading statistics
  218. 📈 <b>Market Data:</b>
  219. • /market [token] - Market data and orderbook
  220. • /price [token] - Quick price check
  221. ⚙️ <b>Management:</b>
  222. • /monitoring - View/toggle order monitoring
  223. • /alarm [token] [price] - Set price alerts
  224. • /logs - View recent bot logs
  225. • /debug - Bot internal state (troubleshooting)
  226. For support or issues, check the logs or contact the administrator.
  227. """
  228. logger.debug(f"In /help, update.message is: {update.message}, update.effective_chat.id is: {chat_id}")
  229. try:
  230. await context.bot.send_message(chat_id=chat_id, text=help_text, parse_mode='HTML')
  231. except Exception as e:
  232. logger.error(f"Error sending help message in /help: {e}")
  233. async def run(self):
  234. """Run the Telegram bot with manual initialization and shutdown (v20.x style)."""
  235. if not Config.TELEGRAM_BOT_TOKEN:
  236. logger.error("❌ TELEGRAM_BOT_TOKEN not configured")
  237. return
  238. if not Config.TELEGRAM_CHAT_ID:
  239. logger.error("❌ TELEGRAM_CHAT_ID not configured")
  240. return
  241. logger.info(f"🔧 Using python-telegram-bot version: {telegram.__version__} (Running in v20.x style)")
  242. # Create application
  243. self.application = Application.builder().token(Config.TELEGRAM_BOT_TOKEN).build()
  244. # Connect notification manager to the bot application
  245. self.notification_manager.set_bot_application(self.application)
  246. # Set up handlers
  247. self.setup_handlers()
  248. keep_running_future = asyncio.Future() # Future to keep the bot alive
  249. try:
  250. logger.info("🚀 Initializing bot application (v20.x style)...")
  251. await self.application.initialize()
  252. logger.info(f"🚀 Starting Telegram trading bot v{self.version} (v20.x style)...")
  253. await self.send_message(
  254. f"🤖 <b>Manual Trading Bot v{self.version} Started (v20.x style)</b>\n\n"
  255. f"✅ Connected to Hyperliquid {('Testnet' if Config.HYPERLIQUID_TESTNET else 'Mainnet')}\n"
  256. f"📊 Default Symbol: {Config.DEFAULT_TRADING_TOKEN}\n"
  257. f"🔄 All systems ready!\n\n"
  258. "Use /start for quick actions or /help for all commands."
  259. )
  260. await self.market_monitor.start()
  261. logger.info("▶️ Starting PTB application's internal tasks (update processing, job queue).")
  262. await self.application.start() # This is non-blocking and starts the Application's processing loop.
  263. if self.application.updater:
  264. logger.info(f"▶️ Activating PTB updater to fetch updates (drop_pending_updates={Config.TELEGRAM_DROP_PENDING_UPDATES}).")
  265. # updater.start_polling is an async method that starts fetching updates and populates the application's update_queue.
  266. # It needs to run as a background task, managed by the existing event loop.
  267. # We don't await it directly here if we want other code (like keep_running_future) to proceed.
  268. # However, for graceful shutdown, we'll manage its lifecycle.
  269. # For this structure, we expect the main script to keep the event loop running.
  270. await self.application.updater.start_polling(drop_pending_updates=Config.TELEGRAM_DROP_PENDING_UPDATES)
  271. else:
  272. logger.error("❌ Critical: Application updater is not initialized. Bot cannot receive Telegram updates.")
  273. # If updater is critical, we might want to stop here or raise an error.
  274. # For now, we'll let keep_running_future potentially handle the stop.
  275. if not keep_running_future.done():
  276. keep_running_future.set_exception(RuntimeError("Updater not available"))
  277. logger.info("✅ Bot is initialized and updater is polling. Awaiting stop signal via keep_running_future or Ctrl+C.")
  278. await keep_running_future
  279. except (KeyboardInterrupt, SystemExit) as e: # Added SystemExit here
  280. logger.info(f"🛑 Bot run interrupted by {type(e).__name__}. Initiating shutdown (v20.x style)...")
  281. if not keep_running_future.done():
  282. keep_running_future.set_exception(e if isinstance(e, SystemExit) else KeyboardInterrupt())
  283. except asyncio.CancelledError:
  284. logger.info("🛑 Bot run task cancelled. Initiating shutdown (v20.x style)...")
  285. if not keep_running_future.done():
  286. keep_running_future.cancel()
  287. except Exception as e:
  288. logger.error(f"❌ Unhandled error in bot run loop (v20.x style): {e}", exc_info=True)
  289. if not keep_running_future.done():
  290. keep_running_future.set_exception(e)
  291. finally:
  292. logger.info("🔌 Starting graceful shutdown sequence in TelegramTradingBot.run (v20.x style)...")
  293. try:
  294. logger.info("Stopping market monitor...")
  295. await self.market_monitor.stop()
  296. logger.info("Market monitor stopped.")
  297. if self.application:
  298. # Stop the updater first if it's running
  299. if self.application.updater and self.application.updater.running:
  300. logger.info("Stopping PTB updater polling...")
  301. await self.application.updater.stop()
  302. logger.info("PTB updater polling stopped.")
  303. # Then stop the application's own processing
  304. if self.application.running: # Check if application was started
  305. logger.info("Stopping PTB application components (handlers, job queue)...")
  306. await self.application.stop()
  307. logger.info("PTB application components stopped.")
  308. # Finally, shutdown the application
  309. logger.info("Shutting down PTB application (bot, persistence, etc.)...")
  310. await self.application.shutdown()
  311. logger.info("PTB application shut down.")
  312. else:
  313. logger.warning("Application object was None during shutdown in TelegramTradingBot.run.")
  314. logger.info("✅ Graceful shutdown sequence in TelegramTradingBot.run (v20.x style) complete.")
  315. except Exception as e:
  316. logger.error(f"💥 Error during shutdown sequence in TelegramTradingBot.run (v20.x style): {e}", exc_info=True)