123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279 |
- #!/usr/bin/env python3
- """
- Core Telegram Bot - Handles only bot setup, authentication, and basic messaging.
- """
- import asyncio
- import logging
- from datetime import datetime
- from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
- from telegram.ext import Application, ContextTypes, CommandHandler, CallbackQueryHandler, MessageHandler, filters
- from src.config.config import Config
- from src.trading.trading_engine import TradingEngine
- from src.monitoring.market_monitor import MarketMonitor
- from src.notifications.notification_manager import NotificationManager
- from src.commands.trading_commands import TradingCommands
- from src.commands.info_commands import InfoCommands
- from src.commands.management_commands import ManagementCommands
- logger = logging.getLogger(__name__)
- class TelegramTradingBot:
- """Core Telegram bot handling only authentication, messaging, and command routing."""
-
- def __init__(self):
- """Initialize the core bot with minimal responsibilities."""
- # Core bot attributes
- self.application = None
- self.version = "Unknown"
-
- # Initialize subsystems
- self.trading_engine = TradingEngine()
- self.market_monitor = MarketMonitor(self.trading_engine)
- self.notification_manager = NotificationManager()
-
- # Connect notification manager to market monitor
- self.market_monitor.set_notification_manager(self.notification_manager)
-
- # Initialize command handlers
- self.trading_commands = TradingCommands(self.trading_engine, self.notification_manager)
- self.info_commands = InfoCommands(self.trading_engine)
- self.management_commands = ManagementCommands(self.trading_engine, self.market_monitor)
-
- def is_authorized(self, chat_id: str) -> bool:
- """Check if the chat ID is authorized to use the bot."""
- return str(chat_id) == str(Config.TELEGRAM_CHAT_ID)
-
- async def send_message(self, text: str, parse_mode: str = 'HTML') -> None:
- """Send a message to the authorized chat."""
- if self.application and Config.TELEGRAM_CHAT_ID:
- try:
- await self.application.bot.send_message(
- chat_id=Config.TELEGRAM_CHAT_ID,
- text=text,
- parse_mode=parse_mode
- )
- except Exception as e:
- logger.error(f"Failed to send message: {e}")
-
- def setup_handlers(self):
- """Set up command handlers for the bot."""
- if not self.application:
- return
-
- # Basic bot commands
- self.application.add_handler(CommandHandler("start", self.start_command))
- self.application.add_handler(CommandHandler("help", self.help_command))
-
- # Trading commands
- self.application.add_handler(CommandHandler("long", self.trading_commands.long_command))
- self.application.add_handler(CommandHandler("short", self.trading_commands.short_command))
- self.application.add_handler(CommandHandler("exit", self.trading_commands.exit_command))
- self.application.add_handler(CommandHandler("sl", self.trading_commands.sl_command))
- self.application.add_handler(CommandHandler("tp", self.trading_commands.tp_command))
- self.application.add_handler(CommandHandler("coo", self.trading_commands.coo_command))
-
- # Info commands
- self.application.add_handler(CommandHandler("balance", self.info_commands.balance_command))
- self.application.add_handler(CommandHandler("positions", self.info_commands.positions_command))
- self.application.add_handler(CommandHandler("orders", self.info_commands.orders_command))
- self.application.add_handler(CommandHandler("stats", self.info_commands.stats_command))
- self.application.add_handler(CommandHandler("trades", self.info_commands.trades_command))
- self.application.add_handler(CommandHandler("market", self.info_commands.market_command))
- self.application.add_handler(CommandHandler("price", self.info_commands.price_command))
- self.application.add_handler(CommandHandler("performance", self.info_commands.performance_command))
- self.application.add_handler(CommandHandler("daily", self.info_commands.daily_command))
- self.application.add_handler(CommandHandler("weekly", self.info_commands.weekly_command))
- self.application.add_handler(CommandHandler("monthly", self.info_commands.monthly_command))
- self.application.add_handler(CommandHandler("risk", self.info_commands.risk_command))
- self.application.add_handler(CommandHandler("balance_adjustments", self.info_commands.balance_adjustments_command))
- self.application.add_handler(CommandHandler("commands", self.info_commands.commands_command))
- self.application.add_handler(CommandHandler("c", self.info_commands.commands_command)) # Alias
-
- # Management commands
- self.application.add_handler(CommandHandler("monitoring", self.management_commands.monitoring_command))
- self.application.add_handler(CommandHandler("alarm", self.management_commands.alarm_command))
- self.application.add_handler(CommandHandler("logs", self.management_commands.logs_command))
- self.application.add_handler(CommandHandler("debug", self.management_commands.debug_command))
- self.application.add_handler(CommandHandler("version", self.management_commands.version_command))
- self.application.add_handler(CommandHandler("keyboard", self.management_commands.keyboard_command))
-
- # Callback and message handlers
- self.application.add_handler(CallbackQueryHandler(self.trading_commands.button_callback))
- self.application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, self.handle_keyboard_message))
-
- async def start_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
- """Handle the /start command."""
- if not self.is_authorized(update.effective_chat.id):
- await update.message.reply_text("❌ Unauthorized access.")
- return
-
- welcome_text = f"""
- 🤖 <b>Manual Trading Bot v{self.version}</b>
- 🚀 <b>Welcome to your personal trading assistant!</b>
- 📈 <b>Trading Commands:</b>
- • /long [token] [amount] - Open long position
- • /short [token] [amount] - Open short position
- • /exit [token] - Close position
- • /sl [token] [price] - Set stop loss
- • /tp [token] [price] - Set take profit
- 📊 <b>Info Commands:</b>
- • /balance - Check account balance
- • /positions - View open positions
- • /stats - Trading statistics
- • /market [token] - Market data
- ⚙️ <b>Management:</b>
- • /monitoring - Toggle monitoring
- • /alarm - Set price alerts
- • /help - Full command list
- 🔗 <b>Network:</b> {Config.HYPERLIQUID_TESTNET and "Testnet" or "Mainnet"}
- 📊 <b>Default Token:</b> {Config.DEFAULT_TRADING_TOKEN}
- Ready to trade! Use the commands above or tap /help for more options.
- """
-
- await update.message.reply_text(welcome_text, parse_mode='HTML')
-
- async def help_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
- """Handle the /help command."""
- if not self.is_authorized(update.effective_chat.id):
- await update.message.reply_text("❌ Unauthorized access.")
- return
-
- help_text = """
- 📖 <b>Complete Command Reference</b>
- 🔄 <b>Trading Commands:</b>
- • /long [token] [USDC] [price] [sl:price] - Open long position
- • /short [token] [USDC] [price] [sl:price] - Open short position
- • /exit [token] - Close position (market order)
- • /sl [token] [price] - Set stop loss order
- • /tp [token] [price] - Set take profit order
- • /coo [token] - Cancel all open orders
- 📊 <b>Account Info:</b>
- • /balance - Account balance and equity
- • /positions - Open positions with P&L
- • /orders - Active orders
- • /trades - Recent trade history
- • /stats - Comprehensive trading statistics
- 📈 <b>Market Data:</b>
- • /market [token] - Market data and orderbook
- • /price [token] - Quick price check
- ⚙️ <b>Management:</b>
- • /monitoring - View/toggle order monitoring
- • /alarm [token] [price] - Set price alerts
- • /logs - View recent bot logs
- • /debug - Bot internal state (troubleshooting)
- For support or issues, check the logs or contact the administrator.
- """
-
- await update.message.reply_text(help_text, parse_mode='HTML')
-
- async def handle_keyboard_message(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
- """Handle messages from custom keyboard buttons."""
- if not self.is_authorized(update.effective_chat.id):
- await update.message.reply_text("❌ Unauthorized access.")
- return
-
- # For now, just ignore keyboard messages or implement basic ones
- message_text = update.message.text.lower()
-
- # Basic keyboard shortcuts
- if message_text in ['balance', 'positions', 'orders', 'stats']:
- # Route to appropriate command
- if message_text == 'balance':
- await self.info_commands.balance_command(update, context)
- elif message_text == 'positions':
- await self.info_commands.positions_command(update, context)
- elif message_text == 'orders':
- await self.info_commands.orders_command(update, context)
- elif message_text == 'stats':
- await self.info_commands.stats_command(update, context)
-
- async def run(self):
- """Run the Telegram bot."""
- if not Config.TELEGRAM_BOT_TOKEN:
- logger.error("❌ TELEGRAM_BOT_TOKEN not configured")
- return
-
- if not Config.TELEGRAM_CHAT_ID:
- logger.error("❌ TELEGRAM_CHAT_ID not configured")
- return
-
- try:
- # Create application
- self.application = Application.builder().token(Config.TELEGRAM_BOT_TOKEN).build()
-
- # Connect notification manager to the bot application
- self.notification_manager.set_bot_application(self.application)
-
- # Set up handlers
- self.setup_handlers()
-
- logger.info("🚀 Starting Telegram trading bot...")
-
- # Initialize the application
- await self.application.initialize()
-
- # Send startup notification
- await self.send_message(
- f"🤖 <b>Manual Trading Bot v{self.version} Started</b>\n\n"
- f"✅ Connected to Hyperliquid {'Testnet' if Config.HYPERLIQUID_TESTNET else 'Mainnet'}\n"
- f"📊 Default Symbol: {Config.DEFAULT_TRADING_TOKEN}\n"
- f"🔄 All systems ready!\n\n"
- "Use /start for quick actions or /help for all commands."
- )
-
- # Start the application
- await self.application.start()
-
- # Start subsystems
- await self.market_monitor.start()
-
- # Start polling for updates
- logger.info("🔄 Starting update polling...")
-
- last_update_id = 0
- while True:
- try:
- updates = await self.application.bot.get_updates(
- offset=last_update_id + 1,
- timeout=30,
- allowed_updates=None
- )
-
- for update in updates:
- last_update_id = update.update_id
- await self.application.process_update(update)
-
- except Exception as e:
- logger.error(f"Error processing updates: {e}")
- await asyncio.sleep(5)
-
- except asyncio.CancelledError:
- logger.info("🛑 Bot polling cancelled")
- raise
-
- except Exception as e:
- logger.error(f"❌ Error in telegram bot: {e}")
- raise
-
- finally:
- # Clean shutdown
- try:
- await self.market_monitor.stop()
- if self.application:
- await self.application.stop()
- await self.application.shutdown()
- except Exception as e:
- logger.error(f"Error during shutdown: {e}")
|