Parcourir la source

Refactor Telegram bot command responses and authorization handling - Updated command methods to utilize context-based message sending for unauthorized access notifications. Enhanced logging for command execution and error handling, improving user feedback and traceability during bot operations. Streamlined command usage messages for clarity and consistency across trading and management commands.

Carles Sentis il y a 5 jours
Parent
commit
c9bc3dbf2e
4 fichiers modifiés avec 242 ajouts et 228 suppressions
  1. 31 8
      src/bot/core.py
  2. 90 76
      src/commands/info_commands.py
  3. 50 79
      src/commands/management_commands.py
  4. 71 65
      src/commands/trading_commands.py

+ 31 - 8
src/bot/core.py

@@ -108,8 +108,15 @@ class TelegramTradingBot:
     async def start_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
         """Handle the /start command."""
         logger.info(f"/start command triggered by chat_id: {update.effective_chat.id}")
-        if not self.is_authorized(update.effective_chat.id):
-            await update.message.reply_text("❌ Unauthorized access.")
+        logger.debug(f"Full Update object in start_command: {update}")
+
+        chat_id = update.effective_chat.id
+        if not self.is_authorized(chat_id):
+            logger.warning(f"Unauthorized access attempt by chat_id: {chat_id} in /start.")
+            try:
+                await context.bot.send_message(chat_id=chat_id, text="❌ Unauthorized access.")
+            except Exception as e:
+                logger.error(f"Error sending unauthorized message in /start: {e}")
             return
         
         # Determine risk management and stop loss details from Config
@@ -211,12 +218,24 @@ Type /help for detailed command information.
 For support, contact your bot administrator.
         """
         
-        await update.message.reply_text(welcome_text, parse_mode='HTML')
+        logger.debug(f"In /start, update.message is: {update.message}, update.effective_chat.id is: {chat_id}")
+        try:
+            await context.bot.send_message(chat_id=chat_id, text=welcome_text, parse_mode='HTML')
+        except Exception as e:
+            logger.error(f"Error sending welcome message in /start: {e}")
     
     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.")
+        logger.info(f"/help command triggered by chat_id: {update.effective_chat.id}")
+        logger.debug(f"Full Update object in help_command: {update}")
+
+        chat_id = update.effective_chat.id
+        if not self.is_authorized(chat_id):
+            logger.warning(f"Unauthorized access attempt by chat_id: {chat_id} in /help.")
+            try:
+                await context.bot.send_message(chat_id=chat_id, text="❌ Unauthorized access.")
+            except Exception as e:
+                logger.error(f"Error sending unauthorized message in /help: {e}")
             return
         
         help_text = """
@@ -250,7 +269,11 @@ For support, contact your bot administrator.
 For support or issues, check the logs or contact the administrator.
         """
         
-        await update.message.reply_text(help_text, parse_mode='HTML')
+        logger.debug(f"In /help, update.message is: {update.message}, update.effective_chat.id is: {chat_id}")
+        try:
+            await context.bot.send_message(chat_id=chat_id, text=help_text, parse_mode='HTML')
+        except Exception as e:
+            logger.error(f"Error sending help message in /help: {e}")
     
     async def run(self):
         """Run the Telegram bot with manual initialization and shutdown."""
@@ -265,7 +288,7 @@ For support or issues, check the logs or contact the administrator.
         # logger.info(f"🔧 Using python-telegram-bot version: {telegram.__version__}") # NO LONGER NEEDED
 
         # Create application
-        self.application = Application.builder().token(Config.TELEGRAM_BOT_TOKEN).build()
+        self.application = Application.builder().token(Config.TELEGRAM_BOT_TOKEN).drop_pending_updates(True).build()
         
         # Connect notification manager to the bot application
         self.notification_manager.set_bot_application(self.application)
@@ -277,7 +300,7 @@ For support or issues, check the logs or contact the administrator.
 
         try:
             logger.info("🚀 Initializing bot application...")
-            await self.application.initialize(drop_pending_updates=True)
+            await self.application.initialize()
             
             logger.info(f"🚀 Starting Telegram trading bot v{self.version}...")
             

+ 90 - 76
src/commands/info_commands.py

@@ -26,8 +26,9 @@ class InfoCommands:
     
     async def balance_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
         """Handle the /balance command."""
-        if not self._is_authorized(update.effective_chat.id):
-            await update.message.reply_text("❌ Unauthorized access.")
+        chat_id = update.effective_chat.id
+        if not self._is_authorized(chat_id):
+            await context.bot.send_message(chat_id=chat_id, text="❌ Unauthorized access.")
             return
         
         balance = self.trading_engine.get_balance()
@@ -93,14 +94,15 @@ class InfoCommands:
                 balance_text += f"   💵 Initial: ${initial_balance:,.2f}\n"
                 balance_text += f"   {pnl_emoji} P&L: ${pnl:,.2f} ({pnl_percent:+.2f}%)\n"
             
-            await update.message.reply_text(balance_text, parse_mode='HTML')
+            await context.bot.send_message(chat_id=chat_id, text=balance_text, parse_mode='HTML')
         else:
-            await update.message.reply_text("❌ Could not fetch balance information")
+            await context.bot.send_message(chat_id=chat_id, text="❌ Could not fetch balance information")
     
     async def positions_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
         """Handle the /positions command."""
-        if not self._is_authorized(update.effective_chat.id):
-            await update.message.reply_text("❌ Unauthorized access.")
+        chat_id = update.effective_chat.id
+        if not self._is_authorized(chat_id):
+            await context.bot.send_message(chat_id=chat_id, text="❌ Unauthorized access.")
             return
         
         positions = self.trading_engine.get_positions()
@@ -168,14 +170,15 @@ class InfoCommands:
                 positions_text += "📭 No open positions\n\n"
                 positions_text += "💡 Use /long or /short to open a position"
             
-            await update.message.reply_text(positions_text, parse_mode='HTML')
+            await context.bot.send_message(chat_id=chat_id, text=positions_text, parse_mode='HTML')
         else:
-            await update.message.reply_text("❌ Could not fetch positions")
+            await context.bot.send_message(chat_id=chat_id, text="❌ Could not fetch positions")
     
     async def orders_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
         """Handle the /orders command."""
-        if not self._is_authorized(update.effective_chat.id):
-            await update.message.reply_text("❌ Unauthorized access.")
+        chat_id = update.effective_chat.id
+        if not self._is_authorized(chat_id):
+            await context.bot.send_message(chat_id=chat_id, text="❌ Unauthorized access.")
             return
         
         orders = self.trading_engine.get_orders()
@@ -216,14 +219,15 @@ class InfoCommands:
                 orders_text += "📭 No open orders\n\n"
                 orders_text += "💡 Use /long, /short, /sl, or /tp to create orders"
             
-            await update.message.reply_text(orders_text, parse_mode='HTML')
+            await context.bot.send_message(chat_id=chat_id, text=orders_text, parse_mode='HTML')
         else:
-            await update.message.reply_text("❌ Could not fetch orders")
+            await context.bot.send_message(chat_id=chat_id, text="❌ Could not fetch orders")
     
     async def stats_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
         """Handle the /stats command."""
-        if not self._is_authorized(update.effective_chat.id):
-            await update.message.reply_text("❌ Unauthorized access.")
+        chat_id = update.effective_chat.id
+        if not self._is_authorized(chat_id):
+            await context.bot.send_message(chat_id=chat_id, text="❌ Unauthorized access.")
             return
         
         # Get current balance for stats
@@ -235,25 +239,26 @@ class InfoCommands:
         stats = self.trading_engine.get_stats()
         if stats:
             stats_message = stats.format_stats_message(current_balance)
-            await update.message.reply_text(stats_message, parse_mode='HTML')
+            await context.bot.send_message(chat_id=chat_id, text=stats_message, parse_mode='HTML')
         else:
-            await update.message.reply_text("❌ Could not load trading statistics")
+            await context.bot.send_message(chat_id=chat_id, text="❌ Could not load trading statistics")
     
     async def trades_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
         """Handle the /trades command."""
-        if not self._is_authorized(update.effective_chat.id):
-            await update.message.reply_text("❌ Unauthorized access.")
+        chat_id = update.effective_chat.id
+        if not self._is_authorized(chat_id):
+            await context.bot.send_message(chat_id=chat_id, text="❌ Unauthorized access.")
             return
         
         stats = self.trading_engine.get_stats()
         if not stats:
-            await update.message.reply_text("❌ Could not load trading statistics")
+            await context.bot.send_message(chat_id=chat_id, text="❌ Could not load trading statistics")
             return
         
         recent_trades = stats.get_recent_trades(10)
         
         if not recent_trades:
-            await update.message.reply_text("📝 No trades recorded yet.")
+            await context.bot.send_message(chat_id=chat_id, text="📝 No trades recorded yet.")
             return
         
         trades_text = "🔄 <b>Recent Trades</b>\n\n"
@@ -266,12 +271,13 @@ class InfoCommands:
             trades_text += f"   💰 ${trade['price']:,.2f} | 💵 ${trade['value']:,.2f}\n"
             trades_text += f"   📅 {timestamp}\n\n"
         
-        await update.message.reply_text(trades_text, parse_mode='HTML')
+        await context.bot.send_message(chat_id=chat_id, text=trades_text, parse_mode='HTML')
     
     async def market_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
         """Handle the /market command."""
-        if not self._is_authorized(update.effective_chat.id):
-            await update.message.reply_text("❌ Unauthorized access.")
+        chat_id = update.effective_chat.id
+        if not self._is_authorized(chat_id):
+            await context.bot.send_message(chat_id=chat_id, text="❌ Unauthorized access.")
             return
         
         # Get token from arguments or use default
@@ -316,14 +322,15 @@ class InfoCommands:
 ⏰ <b>Last Updated:</b> {datetime.now().strftime('%H:%M:%S')}
             """
             
-            await update.message.reply_text(market_text.strip(), parse_mode='HTML')
+            await context.bot.send_message(chat_id=chat_id, text=market_text.strip(), parse_mode='HTML')
         else:
-            await update.message.reply_text(f"❌ Could not fetch market data for {token}")
+            await context.bot.send_message(chat_id=chat_id, text=f"❌ Could not fetch market data for {token}")
     
     async def price_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
         """Handle the /price command."""
-        if not self._is_authorized(update.effective_chat.id):
-            await update.message.reply_text("❌ Unauthorized access.")
+        chat_id = update.effective_chat.id
+        if not self._is_authorized(chat_id):
+            await context.bot.send_message(chat_id=chat_id, text="❌ Unauthorized access.")
             return
         
         # Get token from arguments or use default
@@ -353,14 +360,15 @@ class InfoCommands:
 ⏰ {datetime.now().strftime('%H:%M:%S')}
             """
             
-            await update.message.reply_text(price_text.strip(), parse_mode='HTML')
+            await context.bot.send_message(chat_id=chat_id, text=price_text.strip(), parse_mode='HTML')
         else:
-            await update.message.reply_text(f"❌ Could not fetch price for {token}")
+            await context.bot.send_message(chat_id=chat_id, text=f"❌ Could not fetch price for {token}")
     
     async def performance_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
         """Handle the /performance command to show token performance ranking or detailed stats."""
-        if not self._is_authorized(update.effective_chat.id):
-            await update.message.reply_text("❌ Unauthorized access.")
+        chat_id = update.effective_chat.id
+        if not self._is_authorized(chat_id):
+            await context.bot.send_message(chat_id=chat_id, text="❌ Unauthorized access.")
             return
         
         try:
@@ -368,27 +376,27 @@ class InfoCommands:
             if context.args and len(context.args) >= 1:
                 # Detailed performance for specific token
                 token = context.args[0].upper()
-                await self._show_token_performance(update, token)
+                await self._show_token_performance(chat_id, token, context)
             else:
                 # Show token performance ranking
-                await self._show_performance_ranking(update)
+                await self._show_performance_ranking(chat_id, context)
                 
         except Exception as e:
             error_message = f"❌ Error processing performance command: {str(e)}"
-            await update.message.reply_text(error_message)
+            await context.bot.send_message(chat_id=chat_id, text=error_message)
             logger.error(f"Error in performance command: {e}")
 
-    async def _show_performance_ranking(self, update: Update):
+    async def _show_performance_ranking(self, chat_id: str, context: ContextTypes.DEFAULT_TYPE):
         """Show token performance ranking (compressed view)."""
         stats = self.trading_engine.get_stats()
         if not stats:
-            await update.message.reply_text("❌ Could not load trading statistics")
+            await context.bot.send_message(chat_id=chat_id, text="❌ Could not load trading statistics")
             return
             
         token_performance = stats.get_token_performance()
         
         if not token_performance:
-            await update.message.reply_text(
+            await context.bot.send_message(chat_id=chat_id, text=
                 "📊 <b>Token Performance</b>\n\n"
                 "📭 No trading data available yet.\n\n"
                 "💡 Performance tracking starts after your first completed trades.\n"
@@ -444,20 +452,20 @@ class InfoCommands:
         
         performance_text += f"💡 <b>Usage:</b> <code>/performance BTC</code> for detailed {Config.DEFAULT_TRADING_TOKEN} stats"
         
-        await update.message.reply_text(performance_text.strip(), parse_mode='HTML')
+        await context.bot.send_message(chat_id=chat_id, text=performance_text.strip(), parse_mode='HTML')
 
-    async def _show_token_performance(self, update: Update, token: str):
+    async def _show_token_performance(self, chat_id: str, token: str, context: ContextTypes.DEFAULT_TYPE):
         """Show detailed performance for a specific token."""
         stats = self.trading_engine.get_stats()
         if not stats:
-            await update.message.reply_text("❌ Could not load trading statistics")
+            await context.bot.send_message(chat_id=chat_id, text="❌ Could not load trading statistics")
             return
             
         token_stats = stats.get_token_detailed_stats(token)
         
         # Check if token has any data
         if token_stats.get('total_trades', 0) == 0:
-            await update.message.reply_text(
+            await context.bot.send_message(chat_id=chat_id, text=
                 f"📊 <b>{token} Performance</b>\n\n"
                 f"📭 No trading history found for {token}.\n\n"
                 f"💡 Start trading {token} with:\n"
@@ -470,7 +478,7 @@ class InfoCommands:
         
         # Check if there's a message (no completed trades)
         if 'message' in token_stats and token_stats.get('completed_trades', 0) == 0:
-            await update.message.reply_text(
+            await context.bot.send_message(chat_id=chat_id, text=
                 f"📊 <b>{token} Performance</b>\n\n"
                 f"{token_stats['message']}\n\n"
                 f"📈 <b>Current Activity:</b>\n"
@@ -525,24 +533,25 @@ class InfoCommands:
         
         performance_text += f"\n🔄 Use <code>/performance</code> to see all token rankings"
         
-        await update.message.reply_text(performance_text.strip(), parse_mode='HTML')
+        await context.bot.send_message(chat_id=chat_id, text=performance_text.strip(), parse_mode='HTML')
 
     async def daily_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
         """Handle the /daily command to show daily performance stats."""
-        if not self._is_authorized(update.effective_chat.id):
-            await update.message.reply_text("❌ Unauthorized access.")
+        chat_id = update.effective_chat.id
+        if not self._is_authorized(chat_id):
+            await context.bot.send_message(chat_id=chat_id, text="❌ Unauthorized access.")
             return
         
         try:
             stats = self.trading_engine.get_stats()
             if not stats:
-                await update.message.reply_text("❌ Could not load trading statistics")
+                await context.bot.send_message(chat_id=chat_id, text="❌ Could not load trading statistics")
                 return
                 
             daily_stats = stats.get_daily_stats(10)
             
             if not daily_stats:
-                await update.message.reply_text(
+                await context.bot.send_message(chat_id=chat_id, text=
                     "📅 <b>Daily Performance</b>\n\n"
                     "📭 No daily performance data available yet.\n\n"
                     "💡 Daily stats are calculated from completed trades.\n"
@@ -584,29 +593,30 @@ class InfoCommands:
                 daily_text += f"   📈 Avg Daily P&L: ${avg_daily_pnl:,.2f}\n"
                 daily_text += f"   🔄 Total Trades: {total_trades}\n"
             
-            await update.message.reply_text(daily_text.strip(), parse_mode='HTML')
+            await context.bot.send_message(chat_id=chat_id, text=daily_text.strip(), parse_mode='HTML')
                 
         except Exception as e:
             error_message = f"❌ Error processing daily command: {str(e)}"
-            await update.message.reply_text(error_message)
+            await context.bot.send_message(chat_id=chat_id, text=error_message)
             logger.error(f"Error in daily command: {e}")
     
     async def weekly_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
         """Handle the /weekly command to show weekly performance stats."""
-        if not self._is_authorized(update.effective_chat.id):
-            await update.message.reply_text("❌ Unauthorized access.")
+        chat_id = update.effective_chat.id
+        if not self._is_authorized(chat_id):
+            await context.bot.send_message(chat_id=chat_id, text="❌ Unauthorized access.")
             return
         
         try:
             stats = self.trading_engine.get_stats()
             if not stats:
-                await update.message.reply_text("❌ Could not load trading statistics")
+                await context.bot.send_message(chat_id=chat_id, text="❌ Could not load trading statistics")
                 return
                 
             weekly_stats = stats.get_weekly_stats(10)
             
             if not weekly_stats:
-                await update.message.reply_text(
+                await context.bot.send_message(chat_id=chat_id, text=
                     "📊 <b>Weekly Performance</b>\n\n"
                     "📭 No weekly performance data available yet.\n\n"
                     "💡 Weekly stats are calculated from completed trades.\n"
@@ -650,29 +660,30 @@ class InfoCommands:
                 weekly_text += f"   📭 No completed trades in the last 10 weeks\n"
                 weekly_text += f"   💡 Start trading to see weekly performance!"
             
-            await update.message.reply_text(weekly_text.strip(), parse_mode='HTML')
+            await context.bot.send_message(chat_id=chat_id, text=weekly_text.strip(), parse_mode='HTML')
             
         except Exception as e:
             error_message = f"❌ Error processing weekly command: {str(e)}"
-            await update.message.reply_text(error_message)
+            await context.bot.send_message(chat_id=chat_id, text=error_message)
             logger.error(f"Error in weekly command: {e}")
 
     async def monthly_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
         """Handle the /monthly command to show monthly performance stats."""
-        if not self._is_authorized(update.effective_chat.id):
-            await update.message.reply_text("❌ Unauthorized access.")
+        chat_id = update.effective_chat.id
+        if not self._is_authorized(chat_id):
+            await context.bot.send_message(chat_id=chat_id, text="❌ Unauthorized access.")
             return
         
         try:
             stats = self.trading_engine.get_stats()
             if not stats:
-                await update.message.reply_text("❌ Could not load trading statistics")
+                await context.bot.send_message(chat_id=chat_id, text="❌ Could not load trading statistics")
                 return
                 
             monthly_stats = stats.get_monthly_stats(10)
             
             if not monthly_stats:
-                await update.message.reply_text(
+                await context.bot.send_message(chat_id=chat_id, text=
                     "📆 <b>Monthly Performance</b>\n\n"
                     "📭 No monthly performance data available yet.\n\n"
                     "💡 Monthly stats are calculated from completed trades.\n"
@@ -716,17 +727,18 @@ class InfoCommands:
                 monthly_text += f"   📭 No completed trades in the last 10 months\n"
                 monthly_text += f"   💡 Start trading to see monthly performance!"
             
-            await update.message.reply_text(monthly_text.strip(), parse_mode='HTML')
+            await context.bot.send_message(chat_id=chat_id, text=monthly_text.strip(), parse_mode='HTML')
             
         except Exception as e:
             error_message = f"❌ Error processing monthly command: {str(e)}"
-            await update.message.reply_text(error_message)
+            await context.bot.send_message(chat_id=chat_id, text=error_message)
             logger.error(f"Error in monthly command: {e}")
     
     async def risk_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
         """Handle the /risk command to show advanced risk metrics."""
-        if not self._is_authorized(update.effective_chat.id):
-            await update.message.reply_text("❌ Unauthorized access.")
+        chat_id = update.effective_chat.id
+        if not self._is_authorized(chat_id):
+            await context.bot.send_message(chat_id=chat_id, text="❌ Unauthorized access.")
             return
         
         try:
@@ -739,7 +751,7 @@ class InfoCommands:
             # Get risk metrics and basic stats
             stats = self.trading_engine.get_stats()
             if not stats:
-                await update.message.reply_text("❌ Could not load trading statistics")
+                await context.bot.send_message(chat_id=chat_id, text="❌ Could not load trading statistics")
                 return
                 
             risk_metrics = stats.get_risk_metrics()
@@ -747,7 +759,7 @@ class InfoCommands:
             
             # Check if we have enough data for risk calculations
             if basic_stats['completed_trades'] < 2:
-                await update.message.reply_text(
+                await context.bot.send_message(chat_id=chat_id, text=
                     "📊 <b>Risk Analysis</b>\n\n"
                     "📭 <b>Insufficient Data</b>\n\n"
                     f"• Current completed trades: {basic_stats['completed_trades']}\n"
@@ -834,23 +846,24 @@ class InfoCommands:
 🔄 Use /stats for trading performance metrics
             """
             
-            await update.message.reply_text(risk_text.strip(), parse_mode='HTML')
+            await context.bot.send_message(chat_id=chat_id, text=risk_text.strip(), parse_mode='HTML')
             
         except Exception as e:
             error_message = f"❌ Error processing risk command: {str(e)}"
-            await update.message.reply_text(error_message)
+            await context.bot.send_message(chat_id=chat_id, text=error_message)
             logger.error(f"Error in risk command: {e}")
     
     async def balance_adjustments_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
         """Handle the /balance_adjustments command to show deposit/withdrawal history."""
-        if not self._is_authorized(update.effective_chat.id):
-            await update.message.reply_text("❌ Unauthorized access.")
+        chat_id = update.effective_chat.id
+        if not self._is_authorized(chat_id):
+            await context.bot.send_message(chat_id=chat_id, text="❌ Unauthorized access.")
             return
         
         try:
             stats = self.trading_engine.get_stats()
             if not stats:
-                await update.message.reply_text("❌ Could not load trading statistics")
+                await context.bot.send_message(chat_id=chat_id, text="❌ Could not load trading statistics")
                 return
             
             # Get balance adjustments summary
@@ -860,7 +873,7 @@ class InfoCommands:
             all_adjustments = stats.data.get('balance_adjustments', [])
             
             if not all_adjustments:
-                await update.message.reply_text(
+                await context.bot.send_message(chat_id=chat_id, text=
                     "💰 <b>Balance Adjustments</b>\n\n"
                     "📭 No deposits or withdrawals detected yet.\n\n"
                     "💡 The bot automatically monitors for deposits and withdrawals\n"
@@ -915,17 +928,18 @@ class InfoCommands:
 ⏰ <b>Last Check:</b> {adjustments_summary['last_adjustment'][:16] if adjustments_summary['last_adjustment'] else 'Never'}
             """
             
-            await update.message.reply_text(adjustments_text.strip(), parse_mode='HTML')
+            await context.bot.send_message(chat_id=chat_id, text=adjustments_text.strip(), parse_mode='HTML')
             
         except Exception as e:
             error_message = f"❌ Error processing balance adjustments command: {str(e)}"
-            await update.message.reply_text(error_message)
+            await context.bot.send_message(chat_id=chat_id, text=error_message)
             logger.error(f"Error in balance_adjustments command: {e}")
     
     async def commands_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
         """Handle the /commands and /c command with quick action buttons."""
-        if not self._is_authorized(update.effective_chat.id):
-            await update.message.reply_text("❌ Unauthorized access.")
+        chat_id = update.effective_chat.id
+        if not self._is_authorized(chat_id):
+            await context.bot.send_message(chat_id=chat_id, text="❌ Unauthorized access.")
             return
         
         commands_text = """
@@ -973,4 +987,4 @@ Tap any button below for instant access to bot functions:
         ]
         reply_markup = InlineKeyboardMarkup(keyboard)
         
-        await update.message.reply_text(commands_text, parse_mode='HTML', reply_markup=reply_markup) 
+        await context.bot.send_message(chat_id=chat_id, text=commands_text, parse_mode='HTML', reply_markup=reply_markup) 

+ 50 - 79
src/commands/management_commands.py

@@ -8,7 +8,7 @@ import os
 import platform
 import sys
 from datetime import datetime, timedelta
-from telegram import Update
+from telegram import Update, ReplyKeyboardMarkup, KeyboardButton, InlineKeyboardMarkup
 from telegram.ext import ContextTypes
 import json
 
@@ -32,8 +32,9 @@ class ManagementCommands:
     
     async def monitoring_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
         """Handle the /monitoring command."""
-        if not self._is_authorized(update.effective_chat.id):
-            await update.message.reply_text("❌ Unauthorized access.")
+        chat_id = update.effective_chat.id
+        if not self._is_authorized(chat_id):
+            await context.bot.send_message(chat_id=chat_id, text="❌ Unauthorized access.")
             return
         
         # Get alarm statistics
@@ -105,12 +106,13 @@ class ManagementCommands:
             for token, count in alarm_stats['token_breakdown'].items():
                 status_text += f"• {token}: {count} alarm{'s' if count != 1 else ''}\n"
         
-        await update.message.reply_text(status_text.strip(), parse_mode='HTML')
+        await context.bot.send_message(chat_id=chat_id, text=status_text.strip(), parse_mode='HTML')
     
     async def alarm_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
         """Handle the /alarm command."""
-        if not self._is_authorized(update.effective_chat.id):
-            await update.message.reply_text("❌ Unauthorized access.")
+        chat_id = update.effective_chat.id
+        if not self._is_authorized(chat_id):
+            await context.bot.send_message(chat_id=chat_id, text="❌ Unauthorized access.")
             return
         
         try:
@@ -118,7 +120,7 @@ class ManagementCommands:
                 # No arguments - list all alarms
                 alarms = self.alarm_manager.get_all_active_alarms()
                 message = self.alarm_manager.format_alarm_list(alarms)
-                await update.message.reply_text(message, parse_mode='HTML')
+                await context.bot.send_message(chat_id=chat_id, text=message, parse_mode='HTML')
                 return
             
             elif len(context.args) == 1:
@@ -129,16 +131,16 @@ class ManagementCommands:
                     alarm_id = int(arg)
                     # Remove alarm by ID
                     if self.alarm_manager.remove_alarm(alarm_id):
-                        await update.message.reply_text(f"✅ Alarm ID {alarm_id} has been removed.")
+                        await context.bot.send_message(chat_id=chat_id, text=f"✅ Alarm ID {alarm_id} has been removed.")
                     else:
-                        await update.message.reply_text(f"❌ Alarm ID {alarm_id} not found.")
+                        await context.bot.send_message(chat_id=chat_id, text=f"❌ Alarm ID {alarm_id} not found.")
                     return
                 except ValueError:
                     # Not a number, treat as token
                     token = arg.upper()
                     alarms = self.alarm_manager.get_alarms_by_token(token)
                     message = self.alarm_manager.format_alarm_list(alarms, f"{token} Price Alarms")
-                    await update.message.reply_text(message, parse_mode='HTML')
+                    await context.bot.send_message(chat_id=chat_id, text=message, parse_mode='HTML')
                     return
             
             elif len(context.args) == 2:
@@ -151,12 +153,12 @@ class ManagementCommands:
                 market_data = self.trading_engine.get_market_data(symbol)
                 
                 if not market_data or not market_data.get('ticker'):
-                    await update.message.reply_text(f"❌ Could not fetch current price for {token}")
+                    await context.bot.send_message(chat_id=chat_id, text=f"❌ Could not fetch current price for {token}")
                     return
                 
                 current_price = float(market_data['ticker'].get('last', 0))
                 if current_price <= 0:
-                    await update.message.reply_text(f"❌ Invalid current price for {token}")
+                    await context.bot.send_message(chat_id=chat_id, text=f"❌ Invalid current price for {token}")
                     return
                 
                 # Create the alarm
@@ -189,44 +191,44 @@ Will trigger when {token} price moves {alarm['direction']} ${target_price:,.2f}
 💡 The alarm will be checked every {Config.BOT_HEARTBEAT_SECONDS} seconds and you'll receive a notification when triggered.
                 """
                 
-                await update.message.reply_text(message.strip(), parse_mode='HTML')
+                await context.bot.send_message(chat_id=chat_id, text=message.strip(), parse_mode='HTML')
                 
             else:
                 # Too many arguments
-                await update.message.reply_text(
+                await context.bot.send_message(chat_id=chat_id, text=(
                     "❌ Invalid usage. Examples:\n\n"
                     "• <code>/alarm</code> - List all alarms\n"
                     "• <code>/alarm BTC</code> - List BTC alarms\n"
                     "• <code>/alarm BTC 50000</code> - Set alarm for BTC at $50,000\n"
-                    "• <code>/alarm 3</code> - Remove alarm ID 3",
-                    parse_mode='HTML'
-                )
+                    "• <code>/alarm 3</code> - Remove alarm ID 3"
+                ), parse_mode='HTML')
                 
         except ValueError:
-            await update.message.reply_text("❌ Invalid price format. Please use numbers only.")
+            await context.bot.send_message(chat_id=chat_id, text="❌ Invalid price format. Please use numbers only.")
         except Exception as e:
             error_message = f"❌ Error processing alarm command: {str(e)}"
-            await update.message.reply_text(error_message)
+            await context.bot.send_message(chat_id=chat_id, text=error_message)
             logger.error(f"Error in alarm command: {e}")
     
     async def logs_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
         """Handle the /logs command."""
-        if not self._is_authorized(update.effective_chat.id):
-            await update.message.reply_text("❌ Unauthorized access.")
+        chat_id = update.effective_chat.id
+        if not self._is_authorized(chat_id):
+            await context.bot.send_message(chat_id=chat_id, text="❌ Unauthorized access.")
             return
         
         try:
             logs_dir = "logs"
             
             if not os.path.exists(logs_dir):
-                await update.message.reply_text("📜 No logs directory found.")
+                await context.bot.send_message(chat_id=chat_id, text="📜 No logs directory found.")
                 return
             
             # Get log files
             log_files = [f for f in os.listdir(logs_dir) if f.endswith('.log')]
             
             if not log_files:
-                await update.message.reply_text("📜 No log files found.")
+                await context.bot.send_message(chat_id=chat_id, text="📜 No log files found.")
                 return
             
             # Handle cleanup command
@@ -236,7 +238,7 @@ Will trigger when {token} price moves {alarm['direction']} ${target_price:,.2f}
                     try:
                         days_to_keep = int(context.args[1])
                     except ValueError:
-                        await update.message.reply_text("❌ Invalid number of days. Using default (30 days).")
+                        await context.bot.send_message(chat_id=chat_id, text="❌ Invalid number of days. Using default (30 days).")
                 
                 # Clean up old log files
                 cutoff_date = datetime.now() - timedelta(days=days_to_keep)
@@ -254,19 +256,13 @@ Will trigger when {token} price moves {alarm['direction']} ${target_price:,.2f}
                         cleaned_files += 1
                         total_size_cleaned += file_size
                 
-                cleanup_message = f"""
-🧹 <b>Log Cleanup Complete</b>
-
-📊 <b>Cleanup Results:</b>
-• Files Removed: {cleaned_files}
-• Size Freed: {total_size_cleaned / 1024 / 1024:.2f} MB
-• Cutoff Date: {cutoff_date.strftime('%Y-%m-%d')}
-• Days Kept: {days_to_keep}
-
-✅ <b>Status:</b> Cleanup completed successfully
-                """
-                
-                await update.message.reply_text(cleanup_message.strip(), parse_mode='HTML')
+                size_cleaned_mb = total_size_cleaned / (1024 * 1024)
+                await context.bot.send_message(chat_id=chat_id, text=
+                    f"🧹 Log cleanup complete.\n"
+                    f"• Files older than {days_to_keep} days removed.\n"
+                    f"• Total files deleted: {cleaned_files}\n"
+                    f"• Total size cleaned: {size_cleaned_mb:.2f} MB"
+                )
                 return
             
             # Show log statistics
@@ -327,17 +323,18 @@ Will trigger when {token} price moves {alarm['direction']} ${target_price:,.2f}
 • DEBUG: Detailed debugging (if enabled)
             """
             
-            await update.message.reply_text(logs_message.strip(), parse_mode='HTML')
+            await context.bot.send_message(chat_id=chat_id, text=logs_message.strip(), parse_mode='HTML')
             
         except Exception as e:
             error_message = f"❌ Error processing logs command: {str(e)}"
-            await update.message.reply_text(error_message)
+            await context.bot.send_message(chat_id=chat_id, text=error_message)
             logger.error(f"Error in logs command: {e}")
     
     async def debug_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
         """Handle the /debug command."""
-        if not self._is_authorized(update.effective_chat.id):
-            await update.message.reply_text("❌ Unauthorized access.")
+        chat_id = update.effective_chat.id
+        if not self._is_authorized(chat_id):
+            await context.bot.send_message(chat_id=chat_id, text="❌ Unauthorized access.")
             return
         
         try:
@@ -397,16 +394,17 @@ Will trigger when {token} price moves {alarm['direction']} ${target_price:,.2f}
             except Exception as e:
                 debug_info += f"\n💰 <b>Balance:</b> Error fetching ({str(e)})\n"
             
-            await update.message.reply_text(debug_info.strip(), parse_mode='HTML')
+            await context.bot.send_message(chat_id=chat_id, text=debug_info.strip(), parse_mode='HTML')
             
         except Exception as e:
             logger.error(f"❌ Error in debug command: {e}")
-            await update.message.reply_text(f"❌ Debug error: {e}")
+            await context.bot.send_message(chat_id=chat_id, text=f"❌ Debug error: {e}")
     
     async def version_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
         """Handle the /version command."""
-        if not self._is_authorized(update.effective_chat.id):
-            await update.message.reply_text("❌ Unauthorized access.")
+        chat_id = update.effective_chat.id
+        if not self._is_authorized(chat_id):
+            await context.bot.send_message(chat_id=chat_id, text="❌ Unauthorized access.")
             return
         
         try:
@@ -459,17 +457,18 @@ Will trigger when {token} price moves {alarm['direction']} ${target_price:,.2f}
 ⏰ <b>Current Time:</b> {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
             """
             
-            await update.message.reply_text(version_text.strip(), parse_mode='HTML')
+            await context.bot.send_message(chat_id=chat_id, text=version_text.strip(), parse_mode='HTML')
             
         except Exception as e:
             error_message = f"❌ Error processing version command: {str(e)}"
-            await update.message.reply_text(error_message)
+            await context.bot.send_message(chat_id=chat_id, text=error_message)
             logger.error(f"Error in version command: {e}")
     
     async def keyboard_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
         """Handle the /keyboard command to enable/show custom keyboard."""
-        if not self._is_authorized(update.effective_chat.id):
-            await update.message.reply_text("❌ Unauthorized access.")
+        chat_id = update.effective_chat.id
+        if not self._is_authorized(chat_id):
+            await context.bot.send_message(chat_id=chat_id, text="❌ Unauthorized access.")
             return
         
         # Check if custom keyboard is enabled in config
@@ -477,8 +476,6 @@ Will trigger when {token} price moves {alarm['direction']} ${target_price:,.2f}
         
         if keyboard_enabled:
             # Create a simple reply keyboard with common commands
-            from telegram import ReplyKeyboardMarkup, KeyboardButton
-            
             keyboard = [
                 [KeyboardButton("/balance"), KeyboardButton("/positions")],
                 [KeyboardButton("/orders"), KeyboardButton("/stats")],
@@ -493,32 +490,6 @@ Will trigger when {token} price moves {alarm['direction']} ${target_price:,.2f}
                 selective=True
             )
             
-            await update.message.reply_text(
-                "⌨️ <b>Custom Keyboard Activated!</b>\n\n"
-                "🎯 <b>Your quick buttons are now ready:</b>\n"
-                "• /balance - Account balance\n"
-                "• /positions - Open positions\n"
-                "• /orders - Active orders\n"
-                "• /stats - Trading statistics\n"
-                "• /daily - Daily performance\n"
-                "• /performance - Performance stats\n"
-                "• /help - Help guide\n"
-                "• /commands - Command menu\n\n"
-                "💡 <b>How to use:</b>\n"
-                "Tap any button below to send the command instantly!\n\n"
-                "🔧 These buttons will stay at the bottom of your chat.",
-                parse_mode='HTML',
-                reply_markup=reply_markup
-            )
+            await context.bot.send_message(chat_id=chat_id, text="⌨️ <b>Custom Keyboard Activated!</b>\n\n🎯 <b>Your quick buttons are now ready:</b>\n• /balance - Account balance\n• /positions - Open positions\n• /orders - Active orders\n• /stats - Trading statistics\n• /daily - Daily performance\n• /performance - Performance stats\n• /help - Help guide\n• /commands - Command menu\n\n💡 <b>How to use:</b>\nTap any button below to send the command instantly!\n\n🔧 These buttons will stay at the bottom of your chat.", reply_markup=reply_markup, parse_mode='HTML')
         else:
-            await update.message.reply_text(
-                "❌ <b>Custom Keyboard Disabled</b>\n\n"
-                "🔧 <b>To enable:</b>\n"
-                "• Set TELEGRAM_CUSTOM_KEYBOARD_ENABLED=true in your .env file\n"
-                "• Restart the bot\n"
-                "• Run /keyboard again\n\n"
-                f"📋 <b>Current config:</b>\n"
-                f"• Enabled: {keyboard_enabled}\n"
-                f"• Layout: {getattr(Config, 'TELEGRAM_CUSTOM_KEYBOARD_LAYOUT', 'default')}",
-                parse_mode='HTML'
-            ) 
+            await context.bot.send_message(chat_id=chat_id, text="❌ <b>Custom Keyboard Disabled</b>\n\n🔧 <b>To enable:</b>\n• Set TELEGRAM_CUSTOM_KEYBOARD_ENABLED=true in your .env file\n• Restart the bot\n• Run /keyboard again\n\n📋 <b>Current config:</b>\n• Enabled: {keyboard_enabled}\n• Layout: {getattr(Config, 'TELEGRAM_CUSTOM_KEYBOARD_LAYOUT', 'default')}", parse_mode='HTML') 

+ 71 - 65
src/commands/trading_commands.py

@@ -26,20 +26,21 @@ class TradingCommands:
     
     async def long_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
         """Handle the /long command for opening long positions."""
-        if not self._is_authorized(update.effective_chat.id):
-            await update.message.reply_text("❌ Unauthorized access.")
+        chat_id = update.effective_chat.id
+        if not self._is_authorized(chat_id):
+            await context.bot.send_message(chat_id=chat_id, text="❌ Unauthorized access.")
             return
         
         try:
             if not context.args or len(context.args) < 2:
-                await update.message.reply_text(
+                await context.bot.send_message(chat_id=chat_id, text=(
                     "❌ Usage: /long [token] [USDC amount] [price (optional)] [sl:price (optional)]\n"
                     "Examples:\n"
                     "• /long BTC 100 - Market order\n"
                     "• /long BTC 100 45000 - Limit order at $45,000\n"
                     "• /long BTC 100 sl:44000 - Market order with stop loss at $44,000\n"
                     "• /long BTC 100 45000 sl:44000 - Limit order at $45,000 with stop loss at $44,000"
-                )
+                ))
                 return
             
             token = context.args[0].upper()
@@ -56,25 +57,25 @@ class TradingCommands:
                     try:
                         stop_loss_price = float(arg[3:])  # Remove 'sl:' prefix
                     except ValueError:
-                        await update.message.reply_text("❌ Invalid stop loss price format. Use sl:price (e.g., sl:44000)")
+                        await context.bot.send_message(chat_id=chat_id, text="❌ Invalid stop loss price format. Use sl:price (e.g., sl:44000)")
                         return
                 elif limit_price is None:
                     # First non-sl parameter is the limit price
                     try:
                         limit_price = float(arg)
                     except ValueError:
-                        await update.message.reply_text("❌ Invalid limit price format. Please use numbers only.")
+                        await context.bot.send_message(chat_id=chat_id, text="❌ Invalid limit price format. Please use numbers only.")
                         return
             
             # Get current market price
             market_data = self.trading_engine.get_market_data(f"{token}/USDC:USDC")
             if not market_data:
-                await update.message.reply_text(f"❌ Could not fetch market data for {token}")
+                await context.bot.send_message(chat_id=chat_id, text=f"❌ Could not fetch market data for {token}")
                 return
             
             current_price = float(market_data['ticker'].get('last', 0))
             if current_price <= 0:
-                await update.message.reply_text(f"❌ Invalid current price for {token}")
+                await context.bot.send_message(chat_id=chat_id, text=f"❌ Invalid current price for {token}")
                 return
             
             # Determine order type and price
@@ -89,13 +90,13 @@ class TradingCommands:
             
             # Validate stop loss for long positions
             if stop_loss_price and stop_loss_price >= price:
-                await update.message.reply_text(
+                await context.bot.send_message(chat_id=chat_id, text=(
                     f"⚠️ Stop loss price should be BELOW entry price for long positions\n\n"
                     f"📊 Your order:\n"
                     f"• Entry Price: ${price:,.2f}\n"
                     f"• Stop Loss: ${stop_loss_price:,.2f} ❌\n\n"
                     f"💡 Try a lower stop loss like: sl:{price * 0.95:.0f}"
-                )
+                ))
                 return
             
             # Create confirmation message
@@ -131,30 +132,31 @@ This will {"place a limit buy order" if limit_price else "execute a market buy o
             ]
             reply_markup = InlineKeyboardMarkup(keyboard)
             
-            await update.message.reply_text(confirmation_text, parse_mode='HTML', reply_markup=reply_markup)
+            await context.bot.send_message(chat_id=chat_id, text=confirmation_text, parse_mode='HTML', reply_markup=reply_markup)
             
         except ValueError as e:
-            await update.message.reply_text(f"❌ Invalid input format: {e}")
+            await context.bot.send_message(chat_id=chat_id, text=f"❌ Invalid input format: {e}")
         except Exception as e:
-            await update.message.reply_text(f"❌ Error processing long command: {e}")
+            await context.bot.send_message(chat_id=chat_id, text=f"❌ Error processing long command: {e}")
             logger.error(f"Error in long command: {e}")
     
     async def short_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
         """Handle the /short command for opening short positions."""
-        if not self._is_authorized(update.effective_chat.id):
-            await update.message.reply_text("❌ Unauthorized access.")
+        chat_id = update.effective_chat.id
+        if not self._is_authorized(chat_id):
+            await context.bot.send_message(chat_id=chat_id, text="❌ Unauthorized access.")
             return
         
         try:
             if not context.args or len(context.args) < 2:
-                await update.message.reply_text(
+                await context.bot.send_message(chat_id=chat_id, text=(
                     "❌ Usage: /short [token] [USDC amount] [price (optional)] [sl:price (optional)]\n"
                     "Examples:\n"
                     "• /short BTC 100 - Market order\n"
                     "• /short BTC 100 45000 - Limit order at $45,000\n"
                     "• /short BTC 100 sl:46000 - Market order with stop loss at $46,000\n"
                     "• /short BTC 100 45000 sl:46000 - Limit order at $45,000 with stop loss at $46,000"
-                )
+                ))
                 return
             
             token = context.args[0].upper()
@@ -169,24 +171,24 @@ This will {"place a limit buy order" if limit_price else "execute a market buy o
                     try:
                         stop_loss_price = float(arg[3:])
                     except ValueError:
-                        await update.message.reply_text("❌ Invalid stop loss price format. Use sl:price (e.g., sl:46000)")
+                        await context.bot.send_message(chat_id=chat_id, text="❌ Invalid stop loss price format. Use sl:price (e.g., sl:46000)")
                         return
                 elif limit_price is None:
                     try:
                         limit_price = float(arg)
                     except ValueError:
-                        await update.message.reply_text("❌ Invalid limit price format. Please use numbers only.")
+                        await context.bot.send_message(chat_id=chat_id, text="❌ Invalid limit price format. Please use numbers only.")
                         return
             
             # Get current market price
             market_data = self.trading_engine.get_market_data(f"{token}/USDC:USDC")
             if not market_data:
-                await update.message.reply_text(f"❌ Could not fetch market data for {token}")
+                await context.bot.send_message(chat_id=chat_id, text=f"❌ Could not fetch market data for {token}")
                 return
             
             current_price = float(market_data['ticker'].get('last', 0))
             if current_price <= 0:
-                await update.message.reply_text(f"❌ Invalid current price for {token}")
+                await context.bot.send_message(chat_id=chat_id, text=f"❌ Invalid current price for {token}")
                 return
             
             # Determine order type and price
@@ -201,13 +203,13 @@ This will {"place a limit buy order" if limit_price else "execute a market buy o
             
             # Validate stop loss for short positions
             if stop_loss_price and stop_loss_price <= price:
-                await update.message.reply_text(
+                await context.bot.send_message(chat_id=chat_id, text=(
                     f"⚠️ Stop loss price should be ABOVE entry price for short positions\n\n"
                     f"📊 Your order:\n"
                     f"• Entry Price: ${price:,.2f}\n"
                     f"• Stop Loss: ${stop_loss_price:,.2f} ❌\n\n"
                     f"💡 Try a higher stop loss like: sl:{price * 1.05:.0f}"
-                )
+                ))
                 return
             
             # Create confirmation message
@@ -243,27 +245,28 @@ This will {"place a limit sell order" if limit_price else "execute a market sell
             ]
             reply_markup = InlineKeyboardMarkup(keyboard)
             
-            await update.message.reply_text(confirmation_text, parse_mode='HTML', reply_markup=reply_markup)
+            await context.bot.send_message(chat_id=chat_id, text=confirmation_text, parse_mode='HTML', reply_markup=reply_markup)
             
         except ValueError as e:
-            await update.message.reply_text(f"❌ Invalid input format: {e}")
+            await context.bot.send_message(chat_id=chat_id, text=f"❌ Invalid input format: {e}")
         except Exception as e:
-            await update.message.reply_text(f"❌ Error processing short command: {e}")
+            await context.bot.send_message(chat_id=chat_id, text=f"❌ Error processing short command: {e}")
             logger.error(f"Error in short command: {e}")
     
     async def exit_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
         """Handle the /exit command for closing positions."""
-        if not self._is_authorized(update.effective_chat.id):
-            await update.message.reply_text("❌ Unauthorized access.")
+        chat_id = update.effective_chat.id
+        if not self._is_authorized(chat_id):
+            await context.bot.send_message(chat_id=chat_id, text="❌ Unauthorized access.")
             return
         
         try:
             if not context.args or len(context.args) < 1:
-                await update.message.reply_text(
+                await context.bot.send_message(chat_id=chat_id, text=(
                     "❌ Usage: /exit [token]\n"
                     "Example: /exit BTC\n\n"
                     "This closes your entire position for the specified token."
-                )
+                ))
                 return
             
             token = context.args[0].upper()
@@ -271,7 +274,7 @@ This will {"place a limit sell order" if limit_price else "execute a market sell
             # Find the position
             position = self.trading_engine.find_position(token)
             if not position:
-                await update.message.reply_text(f"📭 No open position found for {token}")
+                await context.bot.send_message(chat_id=chat_id, text=f"📭 No open position found for {token}")
                 return
             
             # Get position details
@@ -282,7 +285,7 @@ This will {"place a limit sell order" if limit_price else "execute a market sell
             # Get current market price
             market_data = self.trading_engine.get_market_data(f"{token}/USDC:USDC")
             if not market_data:
-                await update.message.reply_text(f"❌ Could not fetch current price for {token}")
+                await context.bot.send_message(chat_id=chat_id, text=f"❌ Could not fetch current price for {token}")
                 return
             
             current_price = float(market_data['ticker'].get('last', 0))
@@ -320,25 +323,26 @@ This will {"place a limit sell order" if limit_price else "execute a market sell
             ]
             reply_markup = InlineKeyboardMarkup(keyboard)
             
-            await update.message.reply_text(confirmation_text, parse_mode='HTML', reply_markup=reply_markup)
+            await context.bot.send_message(chat_id=chat_id, text=confirmation_text, parse_mode='HTML', reply_markup=reply_markup)
             
         except Exception as e:
-            await update.message.reply_text(f"❌ Error processing exit command: {e}")
+            await context.bot.send_message(chat_id=chat_id, text=f"❌ Error processing exit command: {e}")
             logger.error(f"Error in exit command: {e}")
     
     async def sl_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
         """Handle the /sl (stop loss) command."""
-        if not self._is_authorized(update.effective_chat.id):
-            await update.message.reply_text("❌ Unauthorized access.")
+        chat_id = update.effective_chat.id
+        if not self._is_authorized(chat_id):
+            await context.bot.send_message(chat_id=chat_id, text="❌ Unauthorized access.")
             return
         
         try:
             if not context.args or len(context.args) < 2:
-                await update.message.reply_text(
+                await context.bot.send_message(chat_id=chat_id, text=(
                     "❌ Usage: /sl [token] [price]\n"
                     "Example: /sl BTC 44000\n\n"
                     "This sets a stop loss order for your existing position."
-                )
+                ))
                 return
             
             token = context.args[0].upper()
@@ -347,7 +351,7 @@ This will {"place a limit sell order" if limit_price else "execute a market sell
             # Find the position
             position = self.trading_engine.find_position(token)
             if not position:
-                await update.message.reply_text(f"📭 No open position found for {token}")
+                await context.bot.send_message(chat_id=chat_id, text=f"📭 No open position found for {token}")
                 return
             
             # Get position details
@@ -356,22 +360,22 @@ This will {"place a limit sell order" if limit_price else "execute a market sell
             
             # Validate stop loss price based on position direction
             if position_type == "LONG" and stop_price >= entry_price:
-                await update.message.reply_text(
+                await context.bot.send_message(chat_id=chat_id, text=(
                     f"⚠️ Stop loss price should be BELOW entry price for long positions\n\n"
                     f"📊 Your {token} LONG position:\n"
                     f"• Entry Price: ${entry_price:,.2f}\n"
                     f"• Stop Price: ${stop_price:,.2f} ❌\n\n"
                     f"💡 Try a lower price like: /sl {token} {entry_price * 0.95:.0f}"
-                )
+                ))
                 return
             elif position_type == "SHORT" and stop_price <= entry_price:
-                await update.message.reply_text(
+                await context.bot.send_message(chat_id=chat_id, text=(
                     f"⚠️ Stop loss price should be ABOVE entry price for short positions\n\n"
                     f"📊 Your {token} SHORT position:\n"
                     f"• Entry Price: ${entry_price:,.2f}\n"
                     f"• Stop Price: ${stop_price:,.2f} ❌\n\n"
                     f"💡 Try a higher price like: /sl {token} {entry_price * 1.05:.0f}"
-                )
+                ))
                 return
             
             # Get current market price
@@ -418,27 +422,28 @@ This will place a limit {exit_side} order at ${stop_price:,.2f} to protect your
             ]
             reply_markup = InlineKeyboardMarkup(keyboard)
             
-            await update.message.reply_text(confirmation_text, parse_mode='HTML', reply_markup=reply_markup)
+            await context.bot.send_message(chat_id=chat_id, text=confirmation_text, parse_mode='HTML', reply_markup=reply_markup)
             
         except ValueError:
-            await update.message.reply_text("❌ Invalid price format. Please use numbers only.")
+            await context.bot.send_message(chat_id=chat_id, text="❌ Invalid price format. Please use numbers only.")
         except Exception as e:
-            await update.message.reply_text(f"❌ Error processing stop loss command: {e}")
+            await context.bot.send_message(chat_id=chat_id, text=f"❌ Error processing stop loss command: {e}")
             logger.error(f"Error in sl command: {e}")
     
     async def tp_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
         """Handle the /tp (take profit) command."""
-        if not self._is_authorized(update.effective_chat.id):
-            await update.message.reply_text("❌ Unauthorized access.")
+        chat_id = update.effective_chat.id
+        if not self._is_authorized(chat_id):
+            await context.bot.send_message(chat_id=chat_id, text="❌ Unauthorized access.")
             return
         
         try:
             if not context.args or len(context.args) < 2:
-                await update.message.reply_text(
+                await context.bot.send_message(chat_id=chat_id, text=(
                     "❌ Usage: /tp [token] [price]\n"
                     "Example: /tp BTC 50000\n\n"
                     "This sets a take profit order for your existing position."
-                )
+                ))
                 return
             
             token = context.args[0].upper()
@@ -447,7 +452,7 @@ This will place a limit {exit_side} order at ${stop_price:,.2f} to protect your
             # Find the position
             position = self.trading_engine.find_position(token)
             if not position:
-                await update.message.reply_text(f"📭 No open position found for {token}")
+                await context.bot.send_message(chat_id=chat_id, text=f"📭 No open position found for {token}")
                 return
             
             # Get position details
@@ -456,22 +461,22 @@ This will place a limit {exit_side} order at ${stop_price:,.2f} to protect your
             
             # Validate take profit price based on position direction
             if position_type == "LONG" and tp_price <= entry_price:
-                await update.message.reply_text(
+                await context.bot.send_message(chat_id=chat_id, text=(
                     f"⚠️ Take profit price should be ABOVE entry price for long positions\n\n"
                     f"📊 Your {token} LONG position:\n"
                     f"• Entry Price: ${entry_price:,.2f}\n"
                     f"• Take Profit: ${tp_price:,.2f} ❌\n\n"
                     f"💡 Try a higher price like: /tp {token} {entry_price * 1.05:.0f}"
-                )
+                ))
                 return
             elif position_type == "SHORT" and tp_price >= entry_price:
-                await update.message.reply_text(
+                await context.bot.send_message(chat_id=chat_id, text=(
                     f"⚠️ Take profit price should be BELOW entry price for short positions\n\n"
-                    f"📊 Your {token} SHORT position:\n"
+                    f"�� Your {token} SHORT position:\n"
                     f"• Entry Price: ${entry_price:,.2f}\n"
                     f"• Take Profit: ${tp_price:,.2f} ❌\n\n"
                     f"💡 Try a lower price like: /tp {token} {entry_price * 0.95:.0f}"
-                )
+                ))
                 return
             
             # Get current market price
@@ -518,27 +523,28 @@ This will place a limit {exit_side} order at ${tp_price:,.2f} to secure profits
             ]
             reply_markup = InlineKeyboardMarkup(keyboard)
             
-            await update.message.reply_text(confirmation_text, parse_mode='HTML', reply_markup=reply_markup)
+            await context.bot.send_message(chat_id=chat_id, text=confirmation_text, parse_mode='HTML', reply_markup=reply_markup)
             
         except ValueError:
-            await update.message.reply_text("❌ Invalid price format. Please use numbers only.")
+            await context.bot.send_message(chat_id=chat_id, text="❌ Invalid price format. Please use numbers only.")
         except Exception as e:
-            await update.message.reply_text(f"❌ Error processing take profit command: {e}")
+            await context.bot.send_message(chat_id=chat_id, text=f"❌ Error processing take profit command: {e}")
             logger.error(f"Error in tp command: {e}")
     
     async def coo_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
         """Handle the /coo (cancel all orders) command."""
-        if not self._is_authorized(update.effective_chat.id):
-            await update.message.reply_text("❌ Unauthorized access.")
+        chat_id = update.effective_chat.id
+        if not self._is_authorized(chat_id):
+            await context.bot.send_message(chat_id=chat_id, text="❌ Unauthorized access.")
             return
         
         try:
             if not context.args or len(context.args) < 1:
-                await update.message.reply_text(
+                await context.bot.send_message(chat_id=chat_id, text=(
                     "❌ Usage: /coo [token]\n"
                     "Example: /coo BTC\n\n"
                     "This cancels all open orders for the specified token."
-                )
+                ))
                 return
             
             token = context.args[0].upper()
@@ -566,10 +572,10 @@ This action cannot be undone.
             ]
             reply_markup = InlineKeyboardMarkup(keyboard)
             
-            await update.message.reply_text(confirmation_text, parse_mode='HTML', reply_markup=reply_markup)
+            await context.bot.send_message(chat_id=chat_id, text=confirmation_text, parse_mode='HTML', reply_markup=reply_markup)
             
         except Exception as e:
-            await update.message.reply_text(f"❌ Error processing cancel orders command: {e}")
+            await context.bot.send_message(chat_id=chat_id, text=f"❌ Error processing cancel orders command: {e}")
             logger.error(f"Error in coo command: {e}")
     
     async def button_callback(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: