core.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  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. import signal
  12. from src.config.config import Config
  13. from src.trading.trading_engine import TradingEngine
  14. from src.monitoring.monitoring_coordinator import MonitoringCoordinator
  15. from src.notifications.notification_manager import NotificationManager
  16. from src.commands.trading_commands import TradingCommands
  17. from src.commands.management_commands import ManagementCommands
  18. from src.commands.info.balance import BalanceCommands
  19. from src.commands.info.positions import PositionsCommands
  20. from src.commands.info.orders import OrdersCommands
  21. from src.commands.info.stats import StatsCommands
  22. from src.commands.info.trades import TradesCommands
  23. from src.commands.info.market import MarketCommands
  24. from src.commands.info.performance import PerformanceCommands
  25. from src.commands.info.daily import DailyCommands
  26. from src.commands.info.weekly import WeeklyCommands
  27. from src.commands.info.monthly import MonthlyCommands
  28. from src.commands.info.risk import RiskCommands
  29. from src.commands.info.price import PriceCommands
  30. from src.commands.info.balance_adjustments import BalanceAdjustmentsCommands
  31. from src.commands.info.commands import CommandsInfo
  32. from src.commands.copy_trading_commands import CopyTradingCommands
  33. logger = logging.getLogger(__name__)
  34. class TelegramTradingBot:
  35. """Core Telegram bot handling only authentication, messaging, and command routing."""
  36. def __init__(self):
  37. """Initialize the core bot with minimal responsibilities."""
  38. # Core bot attributes
  39. self.application = None
  40. self.version = "Unknown"
  41. # Initialize subsystems
  42. self.trading_engine = TradingEngine()
  43. self.notification_manager = NotificationManager()
  44. self.monitoring_coordinator = MonitoringCoordinator(
  45. self.trading_engine.client,
  46. self.notification_manager,
  47. Config
  48. )
  49. # Initialize command handlers
  50. self.management_commands = ManagementCommands(self.trading_engine, self.monitoring_coordinator)
  51. # Instantiate new info command classes
  52. self.balance_cmds = BalanceCommands(self.trading_engine, self.notification_manager)
  53. self.positions_cmds = PositionsCommands(self.trading_engine, self.notification_manager)
  54. self.orders_cmds = OrdersCommands(self.trading_engine, self.notification_manager)
  55. self.stats_cmds = StatsCommands(self.trading_engine, self.notification_manager)
  56. self.trades_cmds = TradesCommands(self.trading_engine, self.notification_manager)
  57. self.market_cmds = MarketCommands(self.trading_engine, self.notification_manager)
  58. self.performance_cmds = PerformanceCommands(self.trading_engine, self.notification_manager)
  59. self.daily_cmds = DailyCommands(self.trading_engine, self.notification_manager)
  60. self.weekly_cmds = WeeklyCommands(self.trading_engine, self.notification_manager)
  61. self.monthly_cmds = MonthlyCommands(self.trading_engine, self.notification_manager)
  62. self.risk_cmds = RiskCommands(self.trading_engine, self.notification_manager)
  63. self.price_cmds = PriceCommands(self.trading_engine, self.notification_manager)
  64. self.balance_adjustments_cmds = BalanceAdjustmentsCommands(self.trading_engine, self.notification_manager)
  65. self.commands_cmds = CommandsInfo(self.trading_engine, self.notification_manager)
  66. self.copy_trading_cmds = CopyTradingCommands(self.monitoring_coordinator)
  67. # Create a class to hold all info commands
  68. class InfoCommandsHandler:
  69. def __init__(self, bot):
  70. self.bot = bot
  71. self.balance_command = bot.balance_cmds.balance_command
  72. self.positions_command = bot.positions_cmds.positions_command
  73. self.orders_command = bot.orders_cmds.orders_command
  74. self.stats_command = bot.stats_cmds.stats_command
  75. self.price_command = bot.price_cmds.price_command
  76. self.market_command = bot.market_cmds.market_command
  77. self.performance_command = bot.performance_cmds.performance_command
  78. self.daily_command = bot.daily_cmds.daily_command
  79. self.weekly_command = bot.weekly_cmds.weekly_command
  80. self.monthly_command = bot.monthly_cmds.monthly_command
  81. self.trades_command = bot.trades_cmds.trades_command
  82. self.risk_command = bot.risk_cmds.risk_command
  83. self.info_commands = InfoCommandsHandler(self)
  84. # Pass info and management command handlers to TradingCommands
  85. self.trading_commands = TradingCommands(
  86. self.trading_engine,
  87. self.notification_manager,
  88. monitoring_coordinator=self.monitoring_coordinator,
  89. management_commands_handler=self.management_commands,
  90. info_commands_handler=self.info_commands
  91. )
  92. def is_authorized(self, chat_id: str) -> bool:
  93. """Check if the chat ID is authorized to use the bot."""
  94. authorized = str(chat_id) == str(Config.TELEGRAM_CHAT_ID)
  95. logger.info(f"Authorization check: Incoming chat_id='{chat_id}', Configured TELEGRAM_CHAT_ID='{Config.TELEGRAM_CHAT_ID}', Authorized={authorized}")
  96. return authorized
  97. async def send_message(self, text: str, parse_mode: str = 'HTML') -> None:
  98. """Send a message to the authorized chat."""
  99. if self.application and Config.TELEGRAM_CHAT_ID:
  100. try:
  101. await self.application.bot.send_message(
  102. chat_id=Config.TELEGRAM_CHAT_ID,
  103. text=text,
  104. parse_mode=parse_mode
  105. )
  106. except Exception as e:
  107. logger.error(f"Failed to send message: {e}")
  108. def setup_handlers(self):
  109. """Set up command handlers for the bot."""
  110. if not self.application:
  111. return
  112. # Basic bot commands (directly on application for v20.x)
  113. self.application.add_handler(CommandHandler("start", self.start_command))
  114. self.application.add_handler(CommandHandler("help", self.help_command))
  115. # Trading commands
  116. self.application.add_handler(CommandHandler("long", self.trading_commands.long_command))
  117. self.application.add_handler(CommandHandler("short", self.trading_commands.short_command))
  118. self.application.add_handler(CommandHandler("exit", self.trading_commands.exit_command))
  119. self.application.add_handler(CommandHandler("sl", self.trading_commands.sl_command))
  120. self.application.add_handler(CommandHandler("tp", self.trading_commands.tp_command))
  121. self.application.add_handler(CommandHandler("coo", self.trading_commands.coo_command))
  122. self.application.add_handler(CommandHandler("leverage", self.trading_commands.leverage_command))
  123. # Info commands
  124. self.application.add_handler(CommandHandler("balance", self.balance_cmds.balance_command))
  125. self.application.add_handler(CommandHandler("positions", self.positions_cmds.positions_command))
  126. self.application.add_handler(CommandHandler("orders", self.orders_cmds.orders_command))
  127. self.application.add_handler(CommandHandler("stats", self.stats_cmds.stats_command))
  128. self.application.add_handler(CommandHandler("trades", self.trades_cmds.trades_command))
  129. self.application.add_handler(CommandHandler("market", self.market_cmds.market_command))
  130. self.application.add_handler(CommandHandler("price", self.price_cmds.price_command))
  131. self.application.add_handler(CommandHandler("performance", self.performance_cmds.performance_command))
  132. self.application.add_handler(CommandHandler("daily", self.daily_cmds.daily_command))
  133. self.application.add_handler(CommandHandler("weekly", self.weekly_cmds.weekly_command))
  134. self.application.add_handler(CommandHandler("monthly", self.monthly_cmds.monthly_command))
  135. self.application.add_handler(CommandHandler("risk", self.risk_cmds.risk_command))
  136. self.application.add_handler(CommandHandler("balance_adjustments", self.balance_adjustments_cmds.balance_adjustments_command))
  137. self.application.add_handler(CommandHandler("commands", self.commands_cmds.commands_command))
  138. # Copy trading commands
  139. self.application.add_handler(CommandHandler("copy_status", self.copy_trading_cmds.copy_status_command))
  140. self.application.add_handler(CommandHandler("copy_start", self.copy_trading_cmds.copy_start_command))
  141. self.application.add_handler(CommandHandler("copy_stop", self.copy_trading_cmds.copy_stop_command))
  142. self.application.add_handler(CommandHandler("c", self.commands_cmds.commands_command)) # Alias
  143. # Management commands
  144. self.application.add_handler(CommandHandler("monitoring", self.management_commands.monitoring_command))
  145. self.application.add_handler(CommandHandler("alarm", self.management_commands.alarm_command))
  146. self.application.add_handler(CommandHandler("logs", self.management_commands.logs_command))
  147. self.application.add_handler(CommandHandler("debug", self.management_commands.debug_command))
  148. self.application.add_handler(CommandHandler("version", self.management_commands.version_command))
  149. self.application.add_handler(CommandHandler("keyboard", self.management_commands.keyboard_command))
  150. self.application.add_handler(CommandHandler("sync", self.management_commands.sync_command))
  151. self.application.add_handler(CommandHandler("sync_status", self.management_commands.sync_status_command))
  152. self.application.add_handler(CommandHandler("deposit", self.management_commands.deposit_command))
  153. self.application.add_handler(CommandHandler("withdrawal", self.management_commands.withdrawal_command))
  154. # Callback and message handlers
  155. self.application.add_handler(CallbackQueryHandler(self.trading_commands.button_callback))
  156. self.application.add_handler(MessageHandler(
  157. filters.Regex(r'^(LONG|SHORT|EXIT|SL|TP|LEVERAGE|BALANCE|POSITIONS|ORDERS|STATS|MARKET|PERFORMANCE|DAILY|WEEKLY|MONTHLY|RISK|ALARM|MONITORING|LOGS|DEBUG|VERSION|COMMANDS|KEYBOARD|COO|COPY_STATUS|COPY_START|COPY_STOP)'),
  158. self.handle_keyboard_command
  159. ))
  160. async def handle_keyboard_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
  161. """Handles commands sent via the main keyboard."""
  162. command_text = update.message.text.upper()
  163. logger.info(f"Keyboard command received: {command_text}")
  164. # Map keyboard text to the appropriate command method
  165. command_map = {
  166. "LONG": self.trading_commands.long_command,
  167. "SHORT": self.trading_commands.short_command,
  168. "EXIT": self.trading_commands.exit_command,
  169. "SL": self.trading_commands.sl_command,
  170. "TP": self.trading_commands.tp_command,
  171. "LEVERAGE": self.trading_commands.leverage_command,
  172. "COO": self.trading_commands.coo_command,
  173. "BALANCE": self.info_commands.balance_command,
  174. "POSITIONS": self.info_commands.positions_command,
  175. "ORDERS": self.info_commands.orders_command,
  176. "STATS": self.info_commands.stats_command,
  177. "MARKET": self.info_commands.market_command,
  178. "PERFORMANCE": self.info_commands.performance_command,
  179. "DAILY": self.info_commands.daily_command,
  180. "WEEKLY": self.info_commands.weekly_command,
  181. "MONTHLY": self.info_commands.monthly_command,
  182. "RISK": self.info_commands.risk_command,
  183. "COMMANDS": self.commands_cmds.commands_command,
  184. "ALARM": self.management_commands.alarm_command,
  185. "MONITORING": self.management_commands.monitoring_command,
  186. "LOGS": self.management_commands.logs_command,
  187. "DEBUG": self.management_commands.debug_command,
  188. "VERSION": self.management_commands.version_command,
  189. "KEYBOARD": self.management_commands.keyboard_command,
  190. "COPY_STATUS": self.copy_trading_cmds.copy_status_command,
  191. "COPY_START": self.copy_trading_cmds.copy_start_command,
  192. "COPY_STOP": self.copy_trading_cmds.copy_stop_command,
  193. }
  194. command_func = command_map.get(command_text)
  195. if command_func:
  196. # We need to simulate a command call, so we'll prepend "/"
  197. # to the message text to make it look like a real command.
  198. original_text = update.message.text
  199. update.message.text = f"/{original_text.lower()}"
  200. await command_func(update, context)
  201. else:
  202. logger.warning(f"Unknown keyboard command: {command_text}")
  203. await self.notification_manager.send_generic_notification(
  204. f"Unknown command: {command_text}", chat_id=update.effective_chat.id
  205. )
  206. async def start_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
  207. """Handle the /start command."""
  208. logger.info(f"/start command triggered by chat_id: {update.effective_chat.id}")
  209. logger.debug(f"Full Update object in start_command: {update}")
  210. chat_id = update.effective_chat.id
  211. logger.info(f"🔍 Authorization check: chat_id={chat_id}, expected={Config.TELEGRAM_CHAT_ID}")
  212. if not self.is_authorized(chat_id):
  213. logger.warning(f"Unauthorized access attempt by chat_id: {chat_id} in /start.")
  214. try:
  215. await context.bot.send_message(chat_id=chat_id, text="❌ Unauthorized access.")
  216. except Exception as e:
  217. logger.error(f"Error sending unauthorized message in /start: {e}")
  218. return
  219. # Determine risk management and stop loss details from Config
  220. risk_enabled = getattr(Config, 'RISK_MANAGEMENT_ENABLED', False)
  221. stop_loss_percentage = getattr(Config, 'STOP_LOSS_PERCENTAGE', 0)
  222. bot_heartbeat = getattr(Config, 'BOT_HEARTBEAT_SECONDS', 10)
  223. welcome_text = f"""
  224. 🤖 <b>Welcome to Hyperliquid Trading Bot v{self.version}</b>
  225. 📱 <b>Quick Actions:</b>
  226. • Trading: /long {Config.DEFAULT_TRADING_TOKEN} 100 or /short {Config.DEFAULT_TRADING_TOKEN} 50
  227. • Exit: /exit {Config.DEFAULT_TRADING_TOKEN} (closes position)
  228. • Info: /balance, /positions, /orders
  229. 📊 <b>Market Data:</b>
  230. • /market - Detailed market overview
  231. • /price - Quick price check
  232. <b>⚡ Quick Commands:</b>
  233. • /balance - Account balance
  234. • /positions - Open positions
  235. • /orders - Active orders
  236. • /market - Market data & prices
  237. <b>🚀 Trading:</b>
  238. • /long {Config.DEFAULT_TRADING_TOKEN} 100 - Long position
  239. • /long {Config.DEFAULT_TRADING_TOKEN} 100 45000 - Limit order
  240. • /long {Config.DEFAULT_TRADING_TOKEN} 100 sl:44000 - With stop loss
  241. • /short {Config.DEFAULT_TRADING_TOKEN} 50 - Short position
  242. • /short {Config.DEFAULT_TRADING_TOKEN} 50 3500 sl:3600 - With stop loss
  243. • /exit {Config.DEFAULT_TRADING_TOKEN} - Close position
  244. • /coo {Config.DEFAULT_TRADING_TOKEN} - Cancel open orders
  245. <b>🛡️ Risk Management:</b>
  246. • Enabled: {'✅ Yes' if risk_enabled else '❌ No'}
  247. • Auto Stop Loss: {stop_loss_percentage}% ROE
  248. • Order Stop Loss: Use sl:price parameter
  249. • /sl {Config.DEFAULT_TRADING_TOKEN} 44000 - Manual stop loss
  250. • /tp {Config.DEFAULT_TRADING_TOKEN} 50000 - Take profit order
  251. • /leverage {Config.DEFAULT_TRADING_TOKEN} 10 - Set leverage to 10x
  252. <b>📈 Performance & Analytics:</b>
  253. • /stats - Complete trading statistics
  254. • /performance - Token performance ranking & detailed stats
  255. • /daily - Daily performance (last 10 days)
  256. • /weekly - Weekly performance (last 10 weeks)
  257. • /monthly - Monthly performance (last 10 months)
  258. • /risk - Sharpe ratio, drawdown, VaR
  259. • /version - Bot version & system information
  260. • /trades - Recent trade history
  261. <b>🔔 Price Alerts:</b>
  262. • /alarm - List all active alarms
  263. • /alarm {Config.DEFAULT_TRADING_TOKEN} 50000 - Set alarm for {Config.DEFAULT_TRADING_TOKEN} at $50,000
  264. • /alarm {Config.DEFAULT_TRADING_TOKEN} - Show all {Config.DEFAULT_TRADING_TOKEN} alarms
  265. • /alarm 3 - Remove alarm ID 3
  266. <b>🔄 Copy Trading:</b>
  267. • /copy_status - View copy trading status & session info
  268. • /copy_start [address] - Start copying a target trader
  269. • /copy_stop - Stop copy trading (preserves session)
  270. <b>🔄 Automatic Monitoring:</b>
  271. • Real-time order fill alerts
  272. • Position opened/closed notifications
  273. • P&L calculations on trade closure
  274. • Price alarm triggers
  275. • External trade detection & sync
  276. • Auto stats synchronization
  277. • Automatic stop loss placement
  278. • {bot_heartbeat}-second monitoring interval
  279. <b>📊 Universal Trade Tracking:</b>
  280. • Bot trades: Full logging & notifications
  281. • Platform trades: Auto-detected & synced
  282. • Mobile app trades: Monitored & recorded
  283. • API trades: Tracked & included in stats
  284. Type /help for detailed command information.
  285. <b>🔄 Order Monitoring:</b>
  286. • /monitoring - View monitoring status
  287. • /logs - View log file statistics and cleanup
  288. • /sync force - Resync bot with exchange (clears local data)
  289. <b>⚙️ Configuration:</b>
  290. • Default Token: {Config.DEFAULT_TRADING_TOKEN}
  291. • Network: {'Testnet' if Config.HYPERLIQUID_TESTNET else 'Mainnet'}
  292. <b>🛡️ Safety Features:</b>
  293. • All trades logged automatically
  294. • Comprehensive performance tracking
  295. • Real-time balance monitoring
  296. • Risk metrics calculation
  297. • Automatic stop loss protection
  298. <b>📱 Mobile Optimized:</b>
  299. • Quick action buttons via /commands
  300. • Instant notifications
  301. • Clean, readable layout
  302. <b>💡 Quick Access:</b>
  303. • /commands or /c - One-tap button menu for all commands
  304. For support, contact your bot administrator.
  305. """
  306. logger.debug(f"In /start, update.message is: {update.message}, update.effective_chat.id is: {chat_id}")
  307. try:
  308. await context.bot.send_message(chat_id=chat_id, text=welcome_text, parse_mode='HTML')
  309. except Exception as e:
  310. logger.error(f"Error sending welcome message in /start: {e}")
  311. async def help_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
  312. """Handle the /help command, providing detailed command information."""
  313. logger.info(f"/help command triggered by chat_id: {update.effective_chat.id}")
  314. logger.debug(f"Full Update object in help_command: {update}")
  315. chat_id = update.effective_chat.id
  316. if not self.is_authorized(chat_id):
  317. logger.warning(f"Unauthorized access attempt by chat_id: {chat_id} in /help.")
  318. try:
  319. await context.bot.send_message(chat_id=chat_id, text="❌ Unauthorized access.")
  320. except Exception as e:
  321. logger.error(f"Error sending unauthorized message in /help: {e}")
  322. return
  323. help_text = """
  324. 📖 <b>Complete Command Reference</b>
  325. 🔄 <b>Trading Commands:</b>
  326. • /long [token] [USDC] [price] [sl:price] - Open long position
  327. • /short [token] [USDC] [price] [sl:price] - Open short position
  328. • /exit [token] - Close position (market order)
  329. • /sl [token] [price] - Set stop loss order
  330. • /tp [token] [price] - Set take profit order
  331. • /coo [token] - Cancel all open orders
  332. • /leverage [token] [leverage] - Set leverage
  333. 📊 <b>Account Info:</b>
  334. • /balance - Account balance and equity
  335. • /positions - Open positions with P&L
  336. • /orders - Active orders
  337. • /trades - Recent trade history
  338. • /stats - Comprehensive trading statistics
  339. 📈 <b>Market Data:</b>
  340. • /market [token] - Market data and orderbook
  341. • /price [token] - Quick price check
  342. ⚙️ <b>Management:</b>
  343. • /monitoring - View/toggle order monitoring
  344. • /alarm [token] [price] - Set price alerts
  345. • /logs - View recent bot logs
  346. • /debug - Bot internal state (troubleshooting)
  347. • /sync force - Clear local data and resync with exchange
  348. 🔄 <b>Copy Trading:</b>
  349. • /copy_status - View copy trading status & configuration
  350. • /copy_start [address] - Start copying a target trader's positions
  351. • /copy_stop - Stop copy trading (session data preserved)
  352. For support or issues, check the logs or contact the administrator.
  353. """
  354. logger.debug(f"In /help, update.message is: {update.message}, update.effective_chat.id is: {chat_id}")
  355. try:
  356. await context.bot.send_message(chat_id=chat_id, text=help_text, parse_mode='HTML')
  357. except Exception as e:
  358. logger.error(f"Error sending help message in /help: {e}")
  359. async def run(self):
  360. """Run the bot, including all setup and the main polling loop."""
  361. logger.info(f"Starting bot version {self.version}...")
  362. # Perform async initialization for components that need it
  363. await self.trading_engine.async_init()
  364. # Initialize Telegram Application
  365. self.application = Application.builder().token(Config.TELEGRAM_BOT_TOKEN).build()
  366. # Pass application to notification manager
  367. self.notification_manager.set_bot_application(self.application)
  368. # Set up handlers
  369. self.setup_handlers()
  370. logger.info("🚀 Initializing bot application (v20.x style)...")
  371. await self.application.initialize()
  372. logger.info(f"🚀 Starting Telegram trading bot v{self.version} (v20.x style)...")
  373. await self.send_message(
  374. f"🤖 <b>Manual Trading Bot v{self.version} Started (v20.x style)</b>\n\n"
  375. f"✅ Connected to Hyperliquid {('Testnet' if Config.HYPERLIQUID_TESTNET else 'Mainnet')}\n"
  376. f"📊 Default Symbol: {Config.DEFAULT_TRADING_TOKEN}\n"
  377. f"🔄 All systems ready!\n\n"
  378. "Use /start for quick actions or /help for all commands."
  379. )
  380. await self.monitoring_coordinator.start()
  381. logger.info("▶️ Starting PTB application's internal tasks (update processing, job queue).")
  382. await self.application.start()
  383. await self.application.updater.start_polling(drop_pending_updates=Config.TELEGRAM_DROP_PENDING_UPDATES)
  384. logger.info("✅ Bot is initialized and polling. Awaiting stop signal or Ctrl+C.")
  385. # Create a future to keep the bot running
  386. stop_future = asyncio.Future()
  387. # Set up signal handlers
  388. loop = asyncio.get_running_loop()
  389. for sig in (signal.SIGINT, signal.SIGTERM):
  390. loop.add_signal_handler(sig, lambda: stop_future.set_result(None))
  391. try:
  392. # Keep the bot running until stop_future is set
  393. await stop_future
  394. except asyncio.CancelledError:
  395. logger.info("🛑 Bot run task cancelled. Initiating shutdown (v20.x style)...")
  396. finally:
  397. logger.info("🔌 Starting graceful shutdown sequence in TelegramTradingBot.run (v20.x style)...")
  398. try:
  399. logger.info("Stopping monitoring coordinator...")
  400. await self.monitoring_coordinator.stop()
  401. logger.info("Monitoring coordinator stopped.")
  402. if self.application:
  403. # Stop the updater first
  404. if self.application.updater and self.application.updater.running:
  405. logger.info("Stopping PTB updater...")
  406. await self.application.updater.stop()
  407. logger.info("PTB updater stopped.")
  408. # Then stop the application's own processing
  409. if self.application.running:
  410. logger.info("Stopping PTB application components (handlers, job queue)...")
  411. await self.application.stop()
  412. logger.info("PTB application components stopped.")
  413. # Finally, shutdown the application
  414. logger.info("Shutting down PTB application (bot, persistence, etc.)...")
  415. await self.application.shutdown()
  416. logger.info("PTB application shut down.")
  417. else:
  418. logger.warning("Application object was None during shutdown in TelegramTradingBot.run.")
  419. logger.info("✅ Graceful shutdown sequence in TelegramTradingBot.run (v20.x style) complete.")
  420. except Exception as e:
  421. logger.error(f"💥 Error during shutdown sequence in TelegramTradingBot.run (v20.x style): {e}", exc_info=True)