core.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  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. from datetime import datetime
  8. from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
  9. from telegram.ext import Application, ContextTypes, CommandHandler, CallbackQueryHandler, MessageHandler, filters
  10. from src.config.config import Config
  11. from src.trading.trading_engine import TradingEngine
  12. from src.monitoring.market_monitor import MarketMonitor
  13. from src.notifications.notification_manager import NotificationManager
  14. from src.commands.trading_commands import TradingCommands
  15. from src.commands.info_commands import InfoCommands
  16. from src.commands.management_commands import ManagementCommands
  17. logger = logging.getLogger(__name__)
  18. class TelegramTradingBot:
  19. """Core Telegram bot handling only authentication, messaging, and command routing."""
  20. def __init__(self):
  21. """Initialize the core bot with minimal responsibilities."""
  22. # Core bot attributes
  23. self.application = None
  24. self.version = "Unknown"
  25. # Initialize subsystems
  26. self.trading_engine = TradingEngine()
  27. self.market_monitor = MarketMonitor(self.trading_engine)
  28. self.notification_manager = NotificationManager()
  29. # Connect notification manager to market monitor
  30. self.market_monitor.set_notification_manager(self.notification_manager)
  31. # Initialize command handlers
  32. self.trading_commands = TradingCommands(self.trading_engine, self.notification_manager)
  33. self.info_commands = InfoCommands(self.trading_engine)
  34. self.management_commands = ManagementCommands(self.trading_engine, self.market_monitor)
  35. def is_authorized(self, chat_id: str) -> bool:
  36. """Check if the chat ID is authorized to use the bot."""
  37. return str(chat_id) == str(Config.TELEGRAM_CHAT_ID)
  38. async def send_message(self, text: str, parse_mode: str = 'HTML') -> None:
  39. """Send a message to the authorized chat."""
  40. if self.application and Config.TELEGRAM_CHAT_ID:
  41. try:
  42. await self.application.bot.send_message(
  43. chat_id=Config.TELEGRAM_CHAT_ID,
  44. text=text,
  45. parse_mode=parse_mode
  46. )
  47. except Exception as e:
  48. logger.error(f"Failed to send message: {e}")
  49. def setup_handlers(self):
  50. """Set up command handlers for the bot."""
  51. if not self.application:
  52. return
  53. # Basic bot commands
  54. self.application.add_handler(CommandHandler("start", self.start_command))
  55. self.application.add_handler(CommandHandler("help", self.help_command))
  56. # Trading commands
  57. self.application.add_handler(CommandHandler("long", self.trading_commands.long_command))
  58. self.application.add_handler(CommandHandler("short", self.trading_commands.short_command))
  59. self.application.add_handler(CommandHandler("exit", self.trading_commands.exit_command))
  60. self.application.add_handler(CommandHandler("sl", self.trading_commands.sl_command))
  61. self.application.add_handler(CommandHandler("tp", self.trading_commands.tp_command))
  62. self.application.add_handler(CommandHandler("coo", self.trading_commands.coo_command))
  63. # Info commands
  64. self.application.add_handler(CommandHandler("balance", self.info_commands.balance_command))
  65. self.application.add_handler(CommandHandler("positions", self.info_commands.positions_command))
  66. self.application.add_handler(CommandHandler("orders", self.info_commands.orders_command))
  67. self.application.add_handler(CommandHandler("stats", self.info_commands.stats_command))
  68. self.application.add_handler(CommandHandler("trades", self.info_commands.trades_command))
  69. self.application.add_handler(CommandHandler("market", self.info_commands.market_command))
  70. self.application.add_handler(CommandHandler("price", self.info_commands.price_command))
  71. self.application.add_handler(CommandHandler("performance", self.info_commands.performance_command))
  72. self.application.add_handler(CommandHandler("daily", self.info_commands.daily_command))
  73. self.application.add_handler(CommandHandler("weekly", self.info_commands.weekly_command))
  74. self.application.add_handler(CommandHandler("monthly", self.info_commands.monthly_command))
  75. self.application.add_handler(CommandHandler("risk", self.info_commands.risk_command))
  76. self.application.add_handler(CommandHandler("balance_adjustments", self.info_commands.balance_adjustments_command))
  77. self.application.add_handler(CommandHandler("commands", self.info_commands.commands_command))
  78. self.application.add_handler(CommandHandler("c", self.info_commands.commands_command)) # Alias
  79. # Management commands
  80. self.application.add_handler(CommandHandler("monitoring", self.management_commands.monitoring_command))
  81. self.application.add_handler(CommandHandler("alarm", self.management_commands.alarm_command))
  82. self.application.add_handler(CommandHandler("logs", self.management_commands.logs_command))
  83. self.application.add_handler(CommandHandler("debug", self.management_commands.debug_command))
  84. self.application.add_handler(CommandHandler("version", self.management_commands.version_command))
  85. self.application.add_handler(CommandHandler("keyboard", self.management_commands.keyboard_command))
  86. # Callback and message handlers
  87. self.application.add_handler(CallbackQueryHandler(self.trading_commands.button_callback))
  88. self.application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, self.handle_keyboard_message))
  89. async def start_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
  90. """Handle the /start command."""
  91. if not self.is_authorized(update.effective_chat.id):
  92. await update.message.reply_text("❌ Unauthorized access.")
  93. return
  94. welcome_text = f"""
  95. 🤖 <b>Manual Trading Bot v{self.version}</b>
  96. 🚀 <b>Welcome to your personal trading assistant!</b>
  97. 📈 <b>Trading Commands:</b>
  98. • /long [token] [amount] - Open long position
  99. • /short [token] [amount] - Open short position
  100. • /exit [token] - Close position
  101. • /sl [token] [price] - Set stop loss
  102. • /tp [token] [price] - Set take profit
  103. 📊 <b>Info Commands:</b>
  104. • /balance - Check account balance
  105. • /positions - View open positions
  106. • /stats - Trading statistics
  107. • /market [token] - Market data
  108. ⚙️ <b>Management:</b>
  109. • /monitoring - Toggle monitoring
  110. • /alarm - Set price alerts
  111. • /help - Full command list
  112. 🔗 <b>Network:</b> {Config.HYPERLIQUID_TESTNET and "Testnet" or "Mainnet"}
  113. 📊 <b>Default Token:</b> {Config.DEFAULT_TRADING_TOKEN}
  114. Ready to trade! Use the commands above or tap /help for more options.
  115. """
  116. await update.message.reply_text(welcome_text, parse_mode='HTML')
  117. async def help_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
  118. """Handle the /help command."""
  119. if not self.is_authorized(update.effective_chat.id):
  120. await update.message.reply_text("❌ Unauthorized access.")
  121. return
  122. help_text = """
  123. 📖 <b>Complete Command Reference</b>
  124. 🔄 <b>Trading Commands:</b>
  125. • /long [token] [USDC] [price] [sl:price] - Open long position
  126. • /short [token] [USDC] [price] [sl:price] - Open short position
  127. • /exit [token] - Close position (market order)
  128. • /sl [token] [price] - Set stop loss order
  129. • /tp [token] [price] - Set take profit order
  130. • /coo [token] - Cancel all open orders
  131. 📊 <b>Account Info:</b>
  132. • /balance - Account balance and equity
  133. • /positions - Open positions with P&L
  134. • /orders - Active orders
  135. • /trades - Recent trade history
  136. • /stats - Comprehensive trading statistics
  137. 📈 <b>Market Data:</b>
  138. • /market [token] - Market data and orderbook
  139. • /price [token] - Quick price check
  140. ⚙️ <b>Management:</b>
  141. • /monitoring - View/toggle order monitoring
  142. • /alarm [token] [price] - Set price alerts
  143. • /logs - View recent bot logs
  144. • /debug - Bot internal state (troubleshooting)
  145. For support or issues, check the logs or contact the administrator.
  146. """
  147. await update.message.reply_text(help_text, parse_mode='HTML')
  148. async def handle_keyboard_message(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
  149. """Handle messages from custom keyboard buttons."""
  150. if not self.is_authorized(update.effective_chat.id):
  151. await update.message.reply_text("❌ Unauthorized access.")
  152. return
  153. # For now, just ignore keyboard messages or implement basic ones
  154. message_text = update.message.text.lower()
  155. # Basic keyboard shortcuts
  156. if message_text in ['balance', 'positions', 'orders', 'stats']:
  157. # Route to appropriate command
  158. if message_text == 'balance':
  159. await self.info_commands.balance_command(update, context)
  160. elif message_text == 'positions':
  161. await self.info_commands.positions_command(update, context)
  162. elif message_text == 'orders':
  163. await self.info_commands.orders_command(update, context)
  164. elif message_text == 'stats':
  165. await self.info_commands.stats_command(update, context)
  166. async def run(self):
  167. """Run the Telegram bot."""
  168. if not Config.TELEGRAM_BOT_TOKEN:
  169. logger.error("❌ TELEGRAM_BOT_TOKEN not configured")
  170. return
  171. if not Config.TELEGRAM_CHAT_ID:
  172. logger.error("❌ TELEGRAM_CHAT_ID not configured")
  173. return
  174. try:
  175. # Create application
  176. self.application = Application.builder().token(Config.TELEGRAM_BOT_TOKEN).build()
  177. # Connect notification manager to the bot application
  178. self.notification_manager.set_bot_application(self.application)
  179. # Set up handlers
  180. self.setup_handlers()
  181. logger.info("🚀 Starting Telegram trading bot...")
  182. # Initialize the application
  183. await self.application.initialize()
  184. # Send startup notification
  185. await self.send_message(
  186. f"🤖 <b>Manual Trading Bot v{self.version} Started</b>\n\n"
  187. f"✅ Connected to Hyperliquid {'Testnet' if Config.HYPERLIQUID_TESTNET else 'Mainnet'}\n"
  188. f"📊 Default Symbol: {Config.DEFAULT_TRADING_TOKEN}\n"
  189. f"🔄 All systems ready!\n\n"
  190. "Use /start for quick actions or /help for all commands."
  191. )
  192. # Start the application
  193. await self.application.start()
  194. # Start subsystems
  195. await self.market_monitor.start()
  196. # Start polling for updates
  197. logger.info("🔄 Starting update polling...")
  198. last_update_id = 0
  199. while True:
  200. try:
  201. updates = await self.application.bot.get_updates(
  202. offset=last_update_id + 1,
  203. timeout=30,
  204. allowed_updates=None
  205. )
  206. for update in updates:
  207. last_update_id = update.update_id
  208. await self.application.process_update(update)
  209. except Exception as e:
  210. logger.error(f"Error processing updates: {e}")
  211. await asyncio.sleep(5)
  212. except asyncio.CancelledError:
  213. logger.info("🛑 Bot polling cancelled")
  214. raise
  215. except Exception as e:
  216. logger.error(f"❌ Error in telegram bot: {e}")
  217. raise
  218. finally:
  219. # Clean shutdown
  220. try:
  221. await self.market_monitor.stop()
  222. if self.application:
  223. await self.application.stop()
  224. await self.application.shutdown()
  225. except Exception as e:
  226. logger.error(f"Error during shutdown: {e}")