Files
Place-Order-Trading-Bot/trading_bot_gui.py
T

642 lines
22 KiB
Python
Raw Normal View History

2025-12-16 22:02:15 +01:00
#!/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()