core.py 20 KB

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