#!/usr/bin/env python3 """ πŸ€– TELEGRAM BOT COMMANDS V2 - Interactive Bot Control Updated for python-telegram-bot 22.x (latest version) COMMANDS: /status - Bot Status, offene Positionen, Balance /pause - Trading pausieren (keine neuen Trades) /resume - Trading fortsetzen /close - ALLE offenen Positionen schließen (Emergency) /stats - Performance Statistiken (heute, Woche, gesamt) /balance - Aktueller Kontostand + Equity /help - Liste aller Commands """ import asyncio import threading import json import MetaTrader5 as mt5 from datetime import datetime, timedelta from typing import Optional, Dict import sqlite3 # New API imports for python-telegram-bot 22.x from telegram import Update from telegram.ext import Application, CommandHandler, ContextTypes # Import existing modules from telegram_notifier import load_telegram_config class TradingBotController: """ Controller fΓΌr Trading Bot - ermΓΆglicht Pause/Resume/Close """ def __init__(self): self.is_paused = False self.pause_reason = "" self.pause_timestamp = None def pause_trading(self, reason: str = "Manual pause via Telegram"): """Pausiert Trading (keine neuen Trades)""" self.is_paused = True self.pause_reason = reason self.pause_timestamp = datetime.now() return { 'success': True, 'message': f"βœ… Trading PAUSED\nReason: {reason}" } def resume_trading(self): """Aktiviert Trading wieder""" if not self.is_paused: return { 'success': False, 'message': "⚠️ Trading is not paused" } pause_duration = datetime.now() - self.pause_timestamp self.is_paused = False self.pause_reason = "" return { 'success': True, 'message': f"βœ… Trading RESUMED\nWas paused for: {pause_duration}" } def close_all_positions(self) -> Dict: """Schließt ALLE offenen Positionen (Emergency Exit)""" if not mt5.initialize(): return { 'success': False, 'message': "❌ MT5 connection failed" } positions = mt5.positions_get() if not positions: return { 'success': True, 'message': "ℹ️ No open positions to close" } closed_count = 0 failed_count = 0 total_profit = 0 for position in positions: # Prepare close request close_request = { "action": mt5.TRADE_ACTION_DEAL, "symbol": position.symbol, "volume": position.volume, "type": mt5.ORDER_TYPE_SELL if position.type == mt5.ORDER_TYPE_BUY else mt5.ORDER_TYPE_BUY, "position": position.ticket, "price": mt5.symbol_info_tick(position.symbol).bid if position.type == mt5.ORDER_TYPE_BUY else mt5.symbol_info_tick(position.symbol).ask, "deviation": 20, "magic": 234000, "comment": "Emergency close via Telegram", "type_time": mt5.ORDER_TIME_GTC, "type_filling": mt5.ORDER_FILLING_IOC, } # Execute close result = mt5.order_send(close_request) if result.retcode == mt5.TRADE_RETCODE_DONE: closed_count += 1 total_profit += position.profit else: failed_count += 1 message = f"🚨 *EMERGENCY CLOSE EXECUTED*\n\n" message += f"βœ… Closed: {closed_count} positions\n" if failed_count > 0: message += f"❌ Failed: {failed_count} positions\n" message += f"πŸ’° Total P&L: ${total_profit:.2f}" return { 'success': True, 'message': message, 'closed': closed_count, 'failed': failed_count, 'profit': total_profit } def get_status(self) -> Dict: """Holt aktuellen Bot Status""" if not mt5.initialize(): return { 'success': False, 'message': "❌ MT5 connection failed" } # Account Info account_info = mt5.account_info() # Open Positions positions = mt5.positions_get() # Calculate floating P&L floating_pl = sum(p.profit for p in positions) if positions else 0 # Build status message status_msg = "πŸ“Š *BOT STATUS*\n\n" # Trading Status if self.is_paused: status_msg += "⏸️ *Status:* PAUSED\n" status_msg += f"*Reason:* {self.pause_reason}\n" duration = datetime.now() - self.pause_timestamp status_msg += f"*Duration:* {duration}\n\n" else: status_msg += "βœ… *Status:* ACTIVE\n\n" # Account Info status_msg += "πŸ’° *ACCOUNT*\n" status_msg += f"Balance: ${account_info.balance:.2f}\n" status_msg += f"Equity: ${account_info.equity:.2f}\n" status_msg += f"Margin: ${account_info.margin:.2f}\n" status_msg += f"Free Margin: ${account_info.margin_free:.2f}\n\n" # Open Positions status_msg += f"πŸ“ˆ *POSITIONS*\n" status_msg += f"Open: {len(positions) if positions else 0}\n" status_msg += f"Floating P&L: ${floating_pl:.2f}\n\n" # Position Details if positions: status_msg += "*Open Trades:*\n" for i, p in enumerate(positions[:5], 1): # Max 5 positions type_emoji = "🟒" if p.type == mt5.ORDER_TYPE_BUY else "πŸ”΄" status_msg += f"{i}. {type_emoji} {p.symbol} | {p.volume} lots | ${p.profit:.2f}\n" if len(positions) > 5: status_msg += f"... and {len(positions) - 5} more\n" status_msg += f"\n⏰ {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} UTC" return { 'success': True, 'message': status_msg, 'is_paused': self.is_paused, 'positions': len(positions) if positions else 0, 'balance': account_info.balance, 'equity': account_info.equity } def get_balance(self) -> Dict: """Holt Balance + Equity Info""" if not mt5.initialize(): return { 'success': False, 'message': "❌ MT5 connection failed" } account_info = mt5.account_info() positions = mt5.positions_get() floating_pl = sum(p.profit for p in positions) if positions else 0 message = "πŸ’° *ACCOUNT BALANCE*\n\n" message += f"Balance: ${account_info.balance:.2f}\n" message += f"Equity: ${account_info.equity:.2f}\n" message += f"Floating P&L: ${floating_pl:.2f}\n\n" message += f"Margin Used: ${account_info.margin:.2f}\n" message += f"Free Margin: ${account_info.margin_free:.2f}\n" message += f"Margin Level: {account_info.margin_level:.2f}%\n\n" message += f"⏰ {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} UTC" return { 'success': True, 'message': message } class TelegramBotCommander: """ Telegram Bot Command Handler (python-telegram-bot 22.x) """ def __init__(self, config_file: str = "telegram_config.json"): """Initialize Telegram Bot Commander""" # Load config self.config = load_telegram_config() if not self.config or 'bot_token' not in self.config: raise ValueError("❌ Telegram config not found or invalid") self.bot_token = self.config['bot_token'] self.chat_id = self.config['chat_id'] # Controller self.controller = TradingBotController() # Database path self.db_path = "trading_bot.db" # Application (new API) self.application = None print("βœ… Telegram Bot Commander initialized") print(f"πŸ“± Bot Token: {self.bot_token[:20]}...") print(f"πŸ‘€ Chat ID: {self.chat_id}") async def start_async(self): """Start bot asynchronously (new API)""" # Create application (disable job queue to avoid timezone issues) self.application = ( Application.builder() .token(self.bot_token) .job_queue(None) # Disable job queue - fixes timezone error .build() ) # Add command handlers self.application.add_handler(CommandHandler("start", self.cmd_start)) self.application.add_handler(CommandHandler("help", self.cmd_help)) self.application.add_handler(CommandHandler("status", self.cmd_status)) self.application.add_handler(CommandHandler("pause", self.cmd_pause)) self.application.add_handler(CommandHandler("resume", self.cmd_resume)) self.application.add_handler(CommandHandler("close", self.cmd_close)) self.application.add_handler(CommandHandler("balance", self.cmd_balance)) self.application.add_handler(CommandHandler("stats", self.cmd_stats)) print("βœ… Command handlers registered") print("πŸš€ Starting Telegram Bot...") print("πŸ“± Send /help to see available commands") # Send startup message await self.application.bot.send_message( chat_id=self.chat_id, text="πŸ€– *Telegram Bot Commander STARTED*\n\n" "βœ… Bot is now listening for commands\n" "Send /help for available commands", parse_mode='Markdown' ) # Initialize and start polling await self.application.initialize() await self.application.start() await self.application.updater.start_polling() print("βœ… Bot is running") def start_background(self): """Start bot in background thread""" def run_async_loop(): try: # Create new event loop for this thread loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) # Run bot loop.run_until_complete(self.start_async()) # Keep loop running loop.run_forever() except Exception as e: print(f"❌ Bot thread error: {e}") import traceback traceback.print_exc() thread = threading.Thread(target=run_async_loop, daemon=True) thread.start() print("βœ… Telegram Bot running in background") return thread # ========================================== # COMMAND HANDLERS (async in new API) # ========================================== async def cmd_start(self, update: Update, context: ContextTypes.DEFAULT_TYPE): """Handle /start command""" message = ( "πŸ€– *Trading Bot Commander*\n\n" "Welcome! I can help you control your trading bot remotely.\n\n" "Send /help to see available commands." ) await update.message.reply_text(message, parse_mode='Markdown') async def cmd_help(self, update: Update, context: ContextTypes.DEFAULT_TYPE): """Handle /help command""" message = """ πŸ“š *Available Commands* *Bot Control:* /status - Bot status, positions, balance /pause - Pause trading (no new trades) /resume - Resume trading /close - Close ALL positions (emergency) *Information:* /balance - Account balance & equity /stats - Performance statistics /help - Show this help *Safety Features:* ⚠️ /close requires confirmation βœ… /pause is instant πŸ”’ Only authorized user can use commands """ await update.message.reply_text(message, parse_mode='Markdown') async def cmd_status(self, update: Update, context: ContextTypes.DEFAULT_TYPE): """Handle /status command""" result = self.controller.get_status() await update.message.reply_text(result['message'], parse_mode='Markdown') async def cmd_pause(self, update: Update, context: ContextTypes.DEFAULT_TYPE): """Handle /pause command""" result = self.controller.pause_trading("Manual pause via Telegram") await update.message.reply_text(result['message'], parse_mode='Markdown') async def cmd_resume(self, update: Update, context: ContextTypes.DEFAULT_TYPE): """Handle /resume command""" result = self.controller.resume_trading() await update.message.reply_text(result['message'], parse_mode='Markdown') async def cmd_close(self, update: Update, context: ContextTypes.DEFAULT_TYPE): """Handle /close command""" # Safety check - require confirmation args = context.args if not args or args[0].lower() != 'confirm': message = ( "⚠️ *EMERGENCY CLOSE*\n\n" "This will close ALL open positions!\n\n" "To confirm, send:\n" "`/close confirm`" ) await update.message.reply_text(message, parse_mode='Markdown') return # Execute close await update.message.reply_text("🚨 Closing all positions...", parse_mode='Markdown') result = self.controller.close_all_positions() await update.message.reply_text(result['message'], parse_mode='Markdown') async def cmd_balance(self, update: Update, context: ContextTypes.DEFAULT_TYPE): """Handle /balance command""" result = self.controller.get_balance() await update.message.reply_text(result['message'], parse_mode='Markdown') async def cmd_stats(self, update: Update, context: ContextTypes.DEFAULT_TYPE): """Handle /stats command""" stats = self._get_performance_stats() await update.message.reply_text(stats, parse_mode='Markdown') # ========================================== # HELPER FUNCTIONS # ========================================== def _get_performance_stats(self) -> str: """Get performance statistics from database""" try: conn = sqlite3.connect(self.db_path) cursor = conn.cursor() # Today's stats today_stats = cursor.execute(""" SELECT COUNT(*) as trades, SUM(CASE WHEN net_profit > 0 THEN 1 ELSE 0 END) as wins, SUM(CASE WHEN net_profit < 0 THEN 1 ELSE 0 END) as losses, ROUND(SUM(net_profit), 2) as profit FROM trades WHERE DATE(entry_time) = DATE('now') """).fetchone() # This week's stats week_stats = cursor.execute(""" SELECT COUNT(*) as trades, SUM(CASE WHEN net_profit > 0 THEN 1 ELSE 0 END) as wins, SUM(CASE WHEN net_profit < 0 THEN 1 ELSE 0 END) as losses, ROUND(SUM(net_profit), 2) as profit FROM trades WHERE entry_time >= datetime('now', '-7 days') """).fetchone() # Overall stats overall_stats = cursor.execute(""" SELECT COUNT(*) as trades, SUM(CASE WHEN net_profit > 0 THEN 1 ELSE 0 END) as wins, SUM(CASE WHEN net_profit < 0 THEN 1 ELSE 0 END) as losses, ROUND(SUM(net_profit), 2) as profit, ROUND(AVG(net_profit), 2) as avg_profit FROM trades WHERE net_profit IS NOT NULL """).fetchone() conn.close() # Build message message = "πŸ“Š *PERFORMANCE STATISTICS*\n\n" # Today message += "πŸ“… *TODAY*\n" if today_stats[0] > 0: today_wr = (today_stats[1] / today_stats[0] * 100) if today_stats[0] > 0 else 0 message += f"Trades: {today_stats[0]} | WR: {today_wr:.1f}%\n" message += f"Profit: ${today_stats[3]:.2f}\n\n" else: message += "No trades today\n\n" # This Week message += "πŸ“ˆ *THIS WEEK (7 days)*\n" if week_stats[0] > 0: week_wr = (week_stats[1] / week_stats[0] * 100) if week_stats[0] > 0 else 0 message += f"Trades: {week_stats[0]} | WR: {week_wr:.1f}%\n" message += f"Wins: {week_stats[1]} | Losses: {week_stats[2]}\n" message += f"Profit: ${week_stats[3]:.2f}\n\n" else: message += "No trades this week\n\n" # Overall message += "🎯 *OVERALL*\n" if overall_stats[0] > 0: overall_wr = (overall_stats[1] / overall_stats[0] * 100) if overall_stats[0] > 0 else 0 message += f"Total Trades: {overall_stats[0]}\n" message += f"Win Rate: {overall_wr:.1f}%\n" message += f"Total Profit: ${overall_stats[3]:.2f}\n" message += f"Avg/Trade: ${overall_stats[4]:.2f}\n" else: message += "No trade history\n" message += f"\n⏰ {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} UTC" return message except Exception as e: return f"❌ Error fetching stats: {e}" # ========================================== # GLOBAL CONTROLLER INSTANCE # ========================================== _global_controller = None def get_bot_controller() -> TradingBotController: """Get global bot controller instance""" global _global_controller if _global_controller is None: _global_controller = TradingBotController() return _global_controller # ========================================== # MAIN # ========================================== if __name__ == "__main__": print("="*70) print("πŸ€– TELEGRAM BOT COMMANDER V2 - Starting") print("="*70) try: commander = TelegramBotCommander() asyncio.run(commander.start_async()) # Keep running asyncio.get_event_loop().run_forever() except KeyboardInterrupt: print("\n⏸️ Bot stopped by user") except Exception as e: print(f"❌ Error: {e}") import traceback traceback.print_exc()