core.py 23 KB

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