#!/usr/bin/env python3 """ đŸ–Ĩī¸ Trading Bot GUI - Tkinter Application Professional Desktop App fÃŧr Trading Bot V1.9 """ import tkinter as tk from tkinter import ttk, scrolledtext, messagebox import MetaTrader5 as mt import threading import queue from datetime import datetime import logging from apscheduler.schedulers.background import BackgroundScheduler # Trading Bot Imports from infrastructure_patch import TradingInfrastructure, create_scheduled_reports from position_monitor import PositionMonitor from session_filter_patch import ( create_session_filtered_check, SESSION_WHITELIST_CONFIG, is_session_allowed ) from drawdown_protection import create_protected_trading_check, print_protection_status from adaptive_rhythm_manager import AdaptiveRhythmManager from execute_trade import execute_trade_v2_adaptive # Setup Logging logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s' ) logger = logging.getLogger(__name__) class TradingBotGUI: """ Main Trading Bot GUI Application """ def __init__(self, root): self.root = root self.root.title("🤖 Trading Bot V1.9 - Control Center") self.root.geometry("1200x800") # Bot State self.bot_running = False self.scheduler = None self.infra = None self.position_monitor = None self.rhythm_manager = None self.drawdown_protection = None self.trading_check = None # MT5 Config self.symbol = "XAUUSD" self.strategy_name = "TradingBot_V1.9" self.max_positions = 1 # GUI Queue for thread-safe updates self.gui_queue = queue.Queue() # Build GUI self.create_widgets() # Start GUI update loop self.process_queue() def create_widgets(self): """Create all GUI widgets""" # ========================================== # TITLE BAR # ========================================== title_frame = tk.Frame(self.root, bg="#2c3e50", height=60) title_frame.pack(fill=tk.X) title_label = tk.Label( title_frame, text="🤖 Trading Bot V1.9 - Control Center", font=("Arial", 20, "bold"), bg="#2c3e50", fg="white" ) title_label.pack(pady=15) # ========================================== # MAIN CONTAINER # ========================================== main_frame = tk.Frame(self.root) main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) # Left Panel (Controls) left_frame = tk.Frame(main_frame, width=400) left_frame.pack(side=tk.LEFT, fill=tk.BOTH, padx=(0, 5)) # Right Panel (Logs & Status) right_frame = tk.Frame(main_frame) right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=(5, 0)) # ========================================== # LEFT PANEL: CONTROLS # ========================================== # Connection Status self.create_connection_panel(left_frame) # Bot Controls self.create_bot_controls(left_frame) # Session Filter Config self.create_session_config(left_frame) # Drawdown Protection Status self.create_drawdown_status(left_frame) # Manual Controls self.create_manual_controls(left_frame) # ========================================== # RIGHT PANEL: STATUS & LOGS # ========================================== # Trading Status self.create_trading_status(right_frame) # Logs self.create_log_panel(right_frame) def create_connection_panel(self, parent): """MT5 Connection Status Panel""" frame = tk.LabelFrame(parent, text="📡 MT5 Connection", font=("Arial", 10, "bold")) frame.pack(fill=tk.X, pady=5) # Status Label self.connection_status = tk.Label( frame, text="❌ Not Connected", font=("Arial", 12), fg="red" ) self.connection_status.pack(pady=10) # Connect Button self.connect_btn = tk.Button( frame, text="Connect to MT5", command=self.connect_mt5, bg="#3498db", fg="white", font=("Arial", 10, "bold"), cursor="hand2" ) self.connect_btn.pack(pady=5) # Account Info self.account_info = tk.Label(frame, text="", font=("Arial", 9)) self.account_info.pack(pady=5) def create_bot_controls(self, parent): """Bot Start/Stop Controls""" frame = tk.LabelFrame(parent, text="🤖 Bot Controls", font=("Arial", 10, "bold")) frame.pack(fill=tk.X, pady=5) # Bot Status self.bot_status_label = tk.Label( frame, text="â¸ī¸ Bot Stopped", font=("Arial", 12, "bold"), fg="orange" ) self.bot_status_label.pack(pady=10) # Control Buttons btn_frame = tk.Frame(frame) btn_frame.pack(pady=5) self.start_btn = tk.Button( btn_frame, text="â–ļī¸ Start Bot", command=self.start_bot, bg="#27ae60", fg="white", font=("Arial", 10, "bold"), width=12, cursor="hand2" ) self.start_btn.pack(side=tk.LEFT, padx=5) self.stop_btn = tk.Button( btn_frame, text="âšī¸ Stop Bot", command=self.stop_bot, bg="#e74c3c", fg="white", font=("Arial", 10, "bold"), width=12, cursor="hand2", state=tk.DISABLED ) self.stop_btn.pack(side=tk.LEFT, padx=5) def create_session_config(self, parent): """Session Filter Configuration""" frame = tk.LabelFrame(parent, text="📊 Session Filter", font=("Arial", 10, "bold")) frame.pack(fill=tk.X, pady=5) # Session Checkboxes self.session_vars = {} sessions = ['asian', 'london', 'overlap', 'ny'] for session in sessions: var = tk.BooleanVar(value=SESSION_WHITELIST_CONFIG['enabled_sessions'][session]) self.session_vars[session] = var cb = tk.Checkbutton( frame, text=f"{session.upper()}", variable=var, font=("Arial", 9), command=lambda s=session: self.update_session_config(s) ) cb.pack(anchor=tk.W, padx=20, pady=2) # Confidence Threshold conf_frame = tk.Frame(frame) conf_frame.pack(fill=tk.X, padx=20, pady=5) tk.Label(conf_frame, text="Confidence:", font=("Arial", 9)).pack(side=tk.LEFT) self.confidence_var = tk.IntVar(value=SESSION_WHITELIST_CONFIG['base_confidence']) self.confidence_slider = tk.Scale( conf_frame, from_=50, to=90, orient=tk.HORIZONTAL, variable=self.confidence_var, command=self.update_confidence ) self.confidence_slider.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5) self.confidence_label = tk.Label( conf_frame, text=f"{self.confidence_var.get()}%", font=("Arial", 9, "bold") ) self.confidence_label.pack(side=tk.LEFT) def create_drawdown_status(self, parent): """Drawdown Protection Status""" frame = tk.LabelFrame(parent, text="đŸ›Ąī¸ Drawdown Protection", font=("Arial", 10, "bold")) frame.pack(fill=tk.X, pady=5) self.drawdown_status = tk.Label( frame, text="â¸ī¸ Not Active", font=("Arial", 9), justify=tk.LEFT ) self.drawdown_status.pack(pady=10, padx=10) def create_manual_controls(self, parent): """Manual Trading Controls""" frame = tk.LabelFrame(parent, text="🎮 Manual Controls", font=("Arial", 10, "bold")) frame.pack(fill=tk.X, pady=5) btn_frame = tk.Frame(frame) btn_frame.pack(pady=10) tk.Button( btn_frame, text="📊 Check Status", command=self.check_status, width=15, cursor="hand2" ).pack(pady=2) tk.Button( btn_frame, text="🔄 Check Positions", command=self.check_positions, width=15, cursor="hand2" ).pack(pady=2) tk.Button( btn_frame, text="❌ Close All Positions", command=self.close_all_positions, width=15, cursor="hand2", bg="#e74c3c", fg="white" ).pack(pady=2) def create_trading_status(self, parent): """Trading Status Display""" frame = tk.LabelFrame(parent, text="📈 Trading Status", font=("Arial", 10, "bold")) frame.pack(fill=tk.X, pady=5) # Status Grid status_frame = tk.Frame(frame) status_frame.pack(fill=tk.X, padx=10, pady=10) # Current Session tk.Label(status_frame, text="Session:", font=("Arial", 9, "bold")).grid(row=0, column=0, sticky=tk.W) self.current_session = tk.Label(status_frame, text="-", font=("Arial", 9)) self.current_session.grid(row=0, column=1, sticky=tk.W, padx=10) # Interval tk.Label(status_frame, text="Interval:", font=("Arial", 9, "bold")).grid(row=1, column=0, sticky=tk.W) self.current_interval = tk.Label(status_frame, text="-", font=("Arial", 9)) self.current_interval.grid(row=1, column=1, sticky=tk.W, padx=10) # Positions tk.Label(status_frame, text="Positions:", font=("Arial", 9, "bold")).grid(row=2, column=0, sticky=tk.W) self.positions_count = tk.Label(status_frame, text="-", font=("Arial", 9)) self.positions_count.grid(row=2, column=1, sticky=tk.W, padx=10) # Daily P/L tk.Label(status_frame, text="Daily P/L:", font=("Arial", 9, "bold")).grid(row=3, column=0, sticky=tk.W) self.daily_pl = tk.Label(status_frame, text="-", font=("Arial", 9)) self.daily_pl.grid(row=3, column=1, sticky=tk.W, padx=10) def create_log_panel(self, parent): """Log Output Panel""" frame = tk.LabelFrame(parent, text="📋 Activity Log", font=("Arial", 10, "bold")) frame.pack(fill=tk.BOTH, expand=True, pady=5) # Scrolled Text self.log_text = scrolledtext.ScrolledText( frame, wrap=tk.WORD, font=("Courier", 9), bg="#1e1e1e", fg="#00ff00", insertbackground="white" ) self.log_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) # Clear Button tk.Button( frame, text="đŸ—‘ī¸ Clear Log", command=lambda: self.log_text.delete(1.0, tk.END), cursor="hand2" ).pack(pady=5) # ========================================== # FUNCTIONALITY # ========================================== def log(self, message, level="INFO"): """Thread-safe logging to GUI""" timestamp = datetime.now().strftime("%H:%M:%S") log_message = f"[{timestamp}] {level}: {message}\n" self.gui_queue.put(("log", log_message)) logger.info(message) def process_queue(self): """Process GUI updates from queue""" try: while True: item = self.gui_queue.get_nowait() action, data = item if action == "log": self.log_text.insert(tk.END, data) self.log_text.see(tk.END) elif action == "status": self.update_status_display(data) except queue.Empty: pass # Schedule next check self.root.after(100, self.process_queue) def connect_mt5(self): """Connect to MT5""" def connect_thread(): try: self.log("Connecting to MT5...") if not mt.initialize(): self.log("MT5 initialization failed!", "ERROR") return # Login (you'll need to add your credentials) import keyring as kr login = 10800246 server = 'VantageInternational-Demo' password = kr.get_password(server, str(login)) if not mt.login(login, password, server): self.log(f"MT5 login failed: {mt.last_error()}", "ERROR") return account_info = mt.account_info() if account_info: self.gui_queue.put(("log", f"[{datetime.now().strftime('%H:%M:%S')}] INFO: ✅ Connected to MT5\n")) self.gui_queue.put(("log", f"[{datetime.now().strftime('%H:%M:%S')}] INFO: Account: {account_info.login}\n")) self.gui_queue.put(("log", f"[{datetime.now().strftime('%H:%M:%S')}] INFO: Balance: ${account_info.balance:.2f}\n")) # Update GUI self.connection_status.config(text="✅ Connected", fg="green") self.account_info.config(text=f"Account: {account_info.login} | Balance: ${account_info.balance:.2f}") self.connect_btn.config(state=tk.DISABLED) except Exception as e: self.log(f"Connection error: {e}", "ERROR") threading.Thread(target=connect_thread, daemon=True).start() def start_bot(self): """Start Trading Bot""" def start_thread(): try: self.log("🚀 Starting Trading Bot...") # Initialize Infrastructure self.log("Initializing infrastructure...") self.infra = TradingInfrastructure( db_path="trading_bot.db", enable_telegram=True, enable_database=True ) # Initialize Position Monitor self.log("Initializing Position Monitor...") self.position_monitor = PositionMonitor(self.infra.db, self.infra.telegram) # Initialize Rhythm Manager self.log("Initializing Adaptive Rhythm Manager...") self.rhythm_manager = AdaptiveRhythmManager(self.symbol) # Create Session-Filtered Trading Check self.log("Setting up Session Filter...") base_trading_check = create_session_filtered_check( rhythm_manager=self.rhythm_manager, execute_func=execute_trade_v2_adaptive, symbol=self.symbol, strategy_name=self.strategy_name, max_positions=self.max_positions, logger=logger, datetime=datetime ) # Add Drawdown Protection self.log("Setting up Drawdown Protection...") self.trading_check = create_protected_trading_check(self.infra, base_trading_check) self.drawdown_protection = self.trading_check.protection # Start Scheduler self.log("Starting Scheduler...") self.scheduler = BackgroundScheduler() # Add Jobs self.scheduler.add_job( func=self.trading_check, trigger='cron', minute='*', id='adaptive_trading_check' ) self.scheduler.add_job( func=self.position_monitor.check_open_positions, trigger='interval', minutes=1, id='position_monitor' ) create_scheduled_reports(self.infra, self.scheduler) self.scheduler.start() self.bot_running = True self.log("✅ Trading Bot Started Successfully!") # Update GUI self.bot_status_label.config(text="✅ Bot Running", fg="green") self.start_btn.config(state=tk.DISABLED) self.stop_btn.config(state=tk.NORMAL) # Send Telegram notification if self.infra.telegram: bot_config = { 'version': 'V1.9', 'enabled_sessions': SESSION_WHITELIST_CONFIG['enabled_sessions'], 'base_confidence': SESSION_WHITELIST_CONFIG['base_confidence'] } self.infra.send_bot_started(bot_config) except Exception as e: self.log(f"Error starting bot: {e}", "ERROR") threading.Thread(target=start_thread, daemon=True).start() def stop_bot(self): """Stop Trading Bot""" if self.scheduler: self.scheduler.shutdown() self.bot_running = False self.log("🛑 Trading Bot Stopped") # Update GUI self.bot_status_label.config(text="â¸ī¸ Bot Stopped", fg="orange") self.start_btn.config(state=tk.NORMAL) self.stop_btn.config(state=tk.DISABLED) def update_session_config(self, session): """Update session filter config""" enabled = self.session_vars[session].get() SESSION_WHITELIST_CONFIG['enabled_sessions'][session] = enabled self.log(f"Session {session.upper()}: {'ENABLED' if enabled else 'DISABLED'}") def update_confidence(self, value): """Update confidence threshold""" confidence = int(float(value)) SESSION_WHITELIST_CONFIG['base_confidence'] = confidence self.confidence_label.config(text=f"{confidence}%") self.log(f"Confidence threshold updated: {confidence}%") def check_status(self): """Check current bot status""" def status_thread(): try: if not self.bot_running: self.log("Bot is not running", "WARNING") return self.log("📊 Checking Status...") # Session session = self.rhythm_manager.get_current_session() self.current_session.config(text=session.upper()) # Interval interval = self.rhythm_manager.calculate_optimal_interval() self.current_interval.config(text=f"{interval} min") # Positions positions = mt.positions_get(symbol=self.symbol) count = len(positions) if positions else 0 self.positions_count.config(text=f"{count}/{self.max_positions}") # Drawdown Status if self.drawdown_protection: status = self.drawdown_protection.get_status() status_text = f"Trading: {'✅ Allowed' if status['trading_allowed'] else '🛑 Paused'}\n" status_text += f"Daily: ${status['daily_loss']:.2f}/${status['daily_limit']:.2f}\n" status_text += f"Consecutive: {status['consecutive_losses']}/{status['consecutive_limit']}" self.drawdown_status.config(text=status_text) self.log("✅ Status updated") except Exception as e: self.log(f"Error checking status: {e}", "ERROR") threading.Thread(target=status_thread, daemon=True).start() def check_positions(self): """Check open positions""" def positions_thread(): try: positions = mt.positions_get(symbol=self.symbol) if not positions: self.log("No open positions") return self.log(f"📊 {len(positions)} Open Position(s):") for pos in positions: self.log(f" Ticket: {pos.ticket} | {pos.type} | P/L: ${pos.profit:.2f}") except Exception as e: self.log(f"Error checking positions: {e}", "ERROR") threading.Thread(target=positions_thread, daemon=True).start() def close_all_positions(self): """Close all open positions""" confirm = messagebox.askyesno( "Confirm Close", "Are you sure you want to close ALL positions?" ) if not confirm: return def close_thread(): try: positions = mt.positions_get(symbol=self.symbol) if not positions: self.log("No positions to close") return self.log(f"Closing {len(positions)} position(s)...") for pos in positions: close_request = { "action": mt.TRADE_ACTION_DEAL, "symbol": self.symbol, "volume": pos.volume, "type": mt.ORDER_TYPE_SELL if pos.type == 0 else mt.ORDER_TYPE_BUY, "position": pos.ticket, "price": mt.symbol_info_tick(self.symbol).bid if pos.type == 0 else mt.symbol_info_tick(self.symbol).ask, "deviation": 20, "magic": 234000, "comment": "Manual close from GUI", "type_time": mt.ORDER_TIME_GTC, "type_filling": mt.ORDER_FILLING_IOC, } result = mt.order_send(close_request) if result.retcode == mt.TRADE_RETCODE_DONE: self.log(f"✅ Closed position {pos.ticket}") else: self.log(f"❌ Failed to close {pos.ticket}: {result.comment}", "ERROR") except Exception as e: self.log(f"Error closing positions: {e}", "ERROR") threading.Thread(target=close_thread, daemon=True).start() def update_status_display(self, data): """Update status display from data""" pass def main(): """Main Entry Point""" root = tk.Tk() app = TradingBotGUI(root) root.mainloop() if __name__ == "__main__": main()