Files
XauBot/dashboard_modern.py
T
GifariKemal 7af9183af3 feat: Smart AI Trading Bot for XAUUSD with ML and SMC
- XGBoost ML model with 37 features for market direction prediction
- Smart Money Concepts (SMC): Order Blocks, FVG, BOS, CHoCH
- HMM market regime detection (trending/ranging/volatile)
- ATR-based stop loss with 1.5 ATR minimum distance
- Broker-level SL protection with fallback
- Time-based exit (max 6 hours per trade)
- Session-aware trading optimized for London/NY overlap
- Auto-retraining based on market conditions
- Telegram notifications and web dashboard
- Backtest results: 63.9% win rate, 2.64 profit factor, 4.83 Sharpe

Backtest period: Jan 2025 - Feb 2026, 654 trades, $4,189 net P/L

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-06 09:01:35 +07:00

908 lines
34 KiB
Python

"""
Trading Bot Dashboard - Modern UI with CustomTkinter
=====================================================
Beautiful responsive dashboard with dark/light mode toggle.
"""
import customtkinter as ctk
import threading
import time
from datetime import datetime
from zoneinfo import ZoneInfo
from pathlib import Path
import sys
# Add project to path
sys.path.insert(0, str(Path(__file__).parent))
# Load environment variables
from dotenv import load_dotenv
load_dotenv()
# Import bot components
try:
from src.mt5_connector import MT5Connector
from src.smc_polars import SMCAnalyzer
from src.ml_model import TradingModel
from src.regime_detector import MarketRegimeDetector
from src.session_filter import SessionFilter
from src.feature_eng import FeatureEngineer
from src.config import TradingConfig
except ImportError as e:
print(f"Import error: {e}")
sys.exit(1)
# Set default appearance
ctk.set_default_color_theme("blue")
# Theme colors
THEMES = {
"dark": {
"bg": "#0d0d1a",
"card": "#1a1a2e",
"card_inner": "#0d0d1a",
"text": "#ffffff",
"text_secondary": "#888888",
"text_muted": "#666666",
"accent": "#00d4ff",
"green": "#00ff88",
"red": "#ff4757",
"orange": "#ffa500",
"highlight": "#2d4a2d",
"highlight_off": "#2d2d44",
},
"light": {
"bg": "#f0f2f5",
"card": "#ffffff",
"card_inner": "#f8f9fa",
"text": "#1a1a2e",
"text_secondary": "#555555",
"text_muted": "#888888",
"accent": "#0066cc",
"green": "#00aa55",
"red": "#dd3344",
"orange": "#ee8800",
"highlight": "#d4edda",
"highlight_off": "#e9ecef",
}
}
class ModernCard(ctk.CTkFrame):
"""Reusable card component with title - theme aware"""
def __init__(self, master, title, theme="dark", **kwargs):
self.theme = theme
colors = THEMES[theme]
super().__init__(master, corner_radius=12, fg_color=colors["card"], **kwargs)
# Title
self.title_label = ctk.CTkLabel(
self,
text=title,
font=ctk.CTkFont(size=12, weight="bold"),
text_color=colors["accent"]
)
self.title_label.pack(anchor="w", padx=12, pady=(10, 6))
# Content frame
self.content = ctk.CTkFrame(self, fg_color="transparent")
self.content.pack(fill="both", expand=True, padx=12, pady=(0, 10))
def update_theme(self, theme):
"""Update card colors for theme"""
self.theme = theme
colors = THEMES[theme]
self.configure(fg_color=colors["card"])
self.title_label.configure(text_color=colors["accent"])
class TradingDashboard(ctk.CTk):
"""Modern Trading Dashboard with CustomTkinter - Responsive & Theme Toggle"""
def __init__(self):
super().__init__()
# Theme state
self.current_theme = "dark"
ctk.set_appearance_mode("dark")
# Window setup - responsive minimum size for split screen
self.title("AI Trading Bot")
self.geometry("700x800")
self.minsize(600, 700)
self.configure(fg_color=THEMES[self.current_theme]["bg"])
# Store all themed widgets for updates
self.themed_widgets = []
self.cards = []
# Initialize components
self.mt5 = None
self.smc = None
self.ml = None
self.hmm = None
self.session = None
self.feature_eng = None
self.config = TradingConfig()
# State
self.running = False
self.last_update = None
# Create UI
self.create_header()
self.create_main_layout()
self.create_status_bar()
# Start connection
self.after(100, self.connect_systems)
def toggle_theme(self):
"""Toggle between dark and light mode"""
self.current_theme = "light" if self.current_theme == "dark" else "dark"
ctk.set_appearance_mode(self.current_theme)
self.apply_theme()
def apply_theme(self):
"""Apply current theme to all widgets"""
colors = THEMES[self.current_theme]
# Update main window
self.configure(fg_color=colors["bg"])
# Update theme button
if self.current_theme == "dark":
self.theme_btn.configure(text="☀️ Light")
else:
self.theme_btn.configure(text="🌙 Dark")
# Update all cards
for card in self.cards:
card.update_theme(self.current_theme)
# Update header
self.title_label.configure(text_color=colors["text"])
self.subtitle_label.configure(text_color=colors["text_muted"])
self.time_label.configure(text_color=colors["text_secondary"])
# Update status bar
self.status_frame.configure(fg_color=colors["card"])
self.status_label.configure(text_color=colors["text_secondary"])
self.update_label.configure(text_color=colors["text_muted"])
# Update text boxes
self.log_text.configure(fg_color=colors["card_inner"], text_color=colors["green"])
self.positions_text.configure(fg_color=colors["card_inner"], text_color=colors["text"])
# Update settings labels
if hasattr(self, 'settings_labels'):
for lbl, val_lbl in self.settings_labels:
lbl.configure(text_color=colors["text_secondary"])
val_lbl.configure(text_color=colors["text"])
# Update golden time frame
is_golden = hasattr(self, 'golden_frame')
if is_golden:
# Reapply golden time based on current state
current_text = self.golden_label.cget("text")
if "YES" in current_text:
self.golden_frame.configure(fg_color=colors["highlight"])
self.golden_label.configure(text_color=colors["green"])
else:
self.golden_frame.configure(fg_color=colors["highlight_off"])
self.golden_label.configure(text_color=colors["text_secondary"])
def create_header(self):
"""Create header section"""
colors = THEMES[self.current_theme]
header_frame = ctk.CTkFrame(self, fg_color="transparent", height=50)
header_frame.pack(fill="x", padx=15, pady=(10, 5))
header_frame.pack_propagate(False)
# Logo/Title
self.title_label = ctk.CTkLabel(
header_frame,
text="AI TRADING BOT",
font=ctk.CTkFont(size=20, weight="bold"),
text_color=colors["text"]
)
self.title_label.pack(side="left")
self.subtitle_label = ctk.CTkLabel(
header_frame,
text=" Live",
font=ctk.CTkFont(size=12),
text_color=colors["text_muted"]
)
self.subtitle_label.pack(side="left", pady=(4, 0))
# Theme toggle button
self.theme_btn = ctk.CTkButton(
header_frame,
text="☀️ Light",
width=80,
height=28,
corner_radius=14,
font=ctk.CTkFont(size=11),
command=self.toggle_theme
)
self.theme_btn.pack(side="right", padx=5)
# Connection status
self.connection_label = ctk.CTkLabel(
header_frame,
text="● Connecting...",
font=ctk.CTkFont(size=12),
text_color=colors["orange"]
)
self.connection_label.pack(side="right", padx=10)
# Time display
self.time_label = ctk.CTkLabel(
header_frame,
text="--:--:-- WIB",
font=ctk.CTkFont(size=12, weight="bold"),
text_color=colors["text_secondary"]
)
self.time_label.pack(side="right", padx=10)
def create_main_layout(self):
"""Create main dashboard layout - responsive 2-column for split screen"""
# Scrollable main container for small windows
self.main_scroll = ctk.CTkScrollableFrame(self, fg_color="transparent")
self.main_scroll.pack(fill="both", expand=True, padx=10, pady=5)
# Configure 2-column grid (responsive for split screen)
self.main_scroll.grid_columnconfigure(0, weight=1, minsize=280)
self.main_scroll.grid_columnconfigure(1, weight=1, minsize=280)
# Row 0: Price & Account
self.create_price_card(self.main_scroll, 0, 0)
self.create_account_card(self.main_scroll, 0, 1)
# Row 1: Session & Risk
self.create_session_card(self.main_scroll, 1, 0)
self.create_risk_card(self.main_scroll, 1, 1)
# Row 2: SMC & ML
self.create_smc_card(self.main_scroll, 2, 0)
self.create_ml_card(self.main_scroll, 2, 1)
# Row 3: Regime & Positions
self.create_regime_card(self.main_scroll, 3, 0)
self.create_positions_card(self.main_scroll, 3, 1)
# Row 4: Settings (full width)
self.create_settings_card(self.main_scroll, 4, 0)
# Row 5: Log (full width)
self.create_log_card(self.main_scroll, 5, 0)
def create_price_card(self, parent, row, col):
"""Price information card"""
colors = THEMES[self.current_theme]
card = ModernCard(parent, "PRICE", theme=self.current_theme)
card.grid(row=row, column=col, padx=4, pady=4, sticky="nsew")
self.cards.append(card)
# Price value
self.price_label = ctk.CTkLabel(
card.content,
text="-----.--",
font=ctk.CTkFont(size=28, weight="bold"),
text_color=colors["green"]
)
self.price_label.pack(pady=(5, 0))
# Symbol
self.symbol_label = ctk.CTkLabel(
card.content,
text="XAUUSD",
font=ctk.CTkFont(size=11),
text_color=colors["text_muted"]
)
self.symbol_label.pack()
# Spread
self.spread_label = ctk.CTkLabel(
card.content,
text="Spread: -- pips",
font=ctk.CTkFont(size=10),
text_color=colors["text_secondary"]
)
self.spread_label.pack(pady=(3, 0))
def create_account_card(self, parent, row, col):
"""Account information card"""
colors = THEMES[self.current_theme]
card = ModernCard(parent, "ACCOUNT", theme=self.current_theme)
card.grid(row=row, column=col, padx=4, pady=4, sticky="nsew")
self.cards.append(card)
# Balance
balance_frame = ctk.CTkFrame(card.content, fg_color="transparent")
balance_frame.pack(fill="x", pady=2)
ctk.CTkLabel(balance_frame, text="Balance:", font=ctk.CTkFont(size=11), text_color=colors["text_secondary"]).pack(side="left")
self.balance_label = ctk.CTkLabel(balance_frame, text="$-----.--", font=ctk.CTkFont(size=11, weight="bold"), text_color=colors["text"])
self.balance_label.pack(side="right")
# Equity
equity_frame = ctk.CTkFrame(card.content, fg_color="transparent")
equity_frame.pack(fill="x", pady=2)
ctk.CTkLabel(equity_frame, text="Equity:", font=ctk.CTkFont(size=11), text_color=colors["text_secondary"]).pack(side="left")
self.equity_label = ctk.CTkLabel(equity_frame, text="$-----.--", font=ctk.CTkFont(size=11, weight="bold"), text_color=colors["text"])
self.equity_label.pack(side="right")
# P/L
pl_frame = ctk.CTkFrame(card.content, fg_color="transparent")
pl_frame.pack(fill="x", pady=2)
ctk.CTkLabel(pl_frame, text="P/L:", font=ctk.CTkFont(size=11), text_color=colors["text_secondary"]).pack(side="left")
self.pl_label = ctk.CTkLabel(pl_frame, text="$0.00", font=ctk.CTkFont(size=12, weight="bold"), text_color=colors["green"])
self.pl_label.pack(side="right")
def create_session_card(self, parent, row, col):
"""Session information card"""
colors = THEMES[self.current_theme]
card = ModernCard(parent, "SESSION", theme=self.current_theme)
card.grid(row=row, column=col, padx=4, pady=4, sticky="nsew")
self.cards.append(card)
# Current session
self.session_label = ctk.CTkLabel(
card.content,
text="Loading...",
font=ctk.CTkFont(size=12, weight="bold"),
text_color=colors["orange"]
)
self.session_label.pack(pady=(3, 0))
# Golden time indicator
self.golden_frame = ctk.CTkFrame(card.content, fg_color=colors["highlight_off"], corner_radius=6)
self.golden_frame.pack(pady=6, padx=3, fill="x")
self.golden_label = ctk.CTkLabel(
self.golden_frame,
text="GOLDEN: --",
font=ctk.CTkFont(size=10, weight="bold"),
text_color=colors["text_secondary"]
)
self.golden_label.pack(pady=4)
# Can trade
self.can_trade_label = ctk.CTkLabel(
card.content,
text="Can Trade: --",
font=ctk.CTkFont(size=10),
text_color=colors["text_secondary"]
)
self.can_trade_label.pack()
def create_risk_card(self, parent, row, col):
"""Risk status card"""
colors = THEMES[self.current_theme]
card = ModernCard(parent, "RISK STATUS", theme=self.current_theme)
card.grid(row=row, column=col, padx=4, pady=4, sticky="nsew")
self.cards.append(card)
# Daily loss
dl_frame = ctk.CTkFrame(card.content, fg_color="transparent")
dl_frame.pack(fill="x", pady=2)
ctk.CTkLabel(dl_frame, text="Daily Loss:", font=ctk.CTkFont(size=10), text_color=colors["text_secondary"]).pack(side="left")
self.daily_loss_label = ctk.CTkLabel(dl_frame, text="$0.00", font=ctk.CTkFont(size=10, weight="bold"), text_color=colors["red"])
self.daily_loss_label.pack(side="right")
# Daily profit
dp_frame = ctk.CTkFrame(card.content, fg_color="transparent")
dp_frame.pack(fill="x", pady=2)
ctk.CTkLabel(dp_frame, text="Daily Profit:", font=ctk.CTkFont(size=10), text_color=colors["text_secondary"]).pack(side="left")
self.daily_profit_label = ctk.CTkLabel(dp_frame, text="$0.00", font=ctk.CTkFont(size=10, weight="bold"), text_color=colors["green"])
self.daily_profit_label.pack(side="right")
# Consecutive losses
cl_frame = ctk.CTkFrame(card.content, fg_color="transparent")
cl_frame.pack(fill="x", pady=2)
ctk.CTkLabel(cl_frame, text="Consec. Losses:", font=ctk.CTkFont(size=10), text_color=colors["text_secondary"]).pack(side="left")
self.consec_loss_label = ctk.CTkLabel(cl_frame, text="0", font=ctk.CTkFont(size=10, weight="bold"), text_color=colors["text"])
self.consec_loss_label.pack(side="right")
def create_smc_card(self, parent, row, col):
"""SMC Signal card"""
colors = THEMES[self.current_theme]
card = ModernCard(parent, "SMC SIGNAL", theme=self.current_theme)
card.grid(row=row, column=col, padx=4, pady=4, sticky="nsew")
self.cards.append(card)
# Signal
self.smc_signal_label = ctk.CTkLabel(
card.content,
text="---",
font=ctk.CTkFont(size=24, weight="bold"),
text_color=colors["text_muted"]
)
self.smc_signal_label.pack(pady=(5, 3))
# Confidence bar
self.smc_confidence_bar = ctk.CTkProgressBar(card.content, height=6, corner_radius=3)
self.smc_confidence_bar.pack(pady=4, fill="x", padx=10)
self.smc_confidence_bar.set(0)
# Confidence text
self.smc_conf_label = ctk.CTkLabel(
card.content,
text="Confidence: --%",
font=ctk.CTkFont(size=10),
text_color=colors["text_secondary"]
)
self.smc_conf_label.pack()
# Reason
self.smc_reason_label = ctk.CTkLabel(
card.content,
text="Waiting...",
font=ctk.CTkFont(size=9),
text_color=colors["text_muted"],
wraplength=250
)
self.smc_reason_label.pack(pady=(3, 0))
def create_ml_card(self, parent, row, col):
"""ML Prediction card"""
colors = THEMES[self.current_theme]
card = ModernCard(parent, "ML PREDICTION", theme=self.current_theme)
card.grid(row=row, column=col, padx=4, pady=4, sticky="nsew")
self.cards.append(card)
# Signal
self.ml_signal_label = ctk.CTkLabel(
card.content,
text="---",
font=ctk.CTkFont(size=24, weight="bold"),
text_color=colors["text_muted"]
)
self.ml_signal_label.pack(pady=(5, 3))
# Confidence bar
self.ml_confidence_bar = ctk.CTkProgressBar(card.content, height=6, corner_radius=3)
self.ml_confidence_bar.pack(pady=4, fill="x", padx=10)
self.ml_confidence_bar.set(0)
# Confidence text
self.ml_conf_label = ctk.CTkLabel(
card.content,
text="Confidence: --%",
font=ctk.CTkFont(size=10),
text_color=colors["text_secondary"]
)
self.ml_conf_label.pack()
# Probabilities
self.ml_prob_label = ctk.CTkLabel(
card.content,
text="Buy: --% | Sell: --%",
font=ctk.CTkFont(size=9),
text_color=colors["text_muted"]
)
self.ml_prob_label.pack(pady=(3, 0))
def create_regime_card(self, parent, row, col):
"""Market Regime card"""
colors = THEMES[self.current_theme]
card = ModernCard(parent, "MARKET REGIME", theme=self.current_theme)
card.grid(row=row, column=col, padx=4, pady=4, sticky="nsew")
self.cards.append(card)
# Regime
self.regime_label = ctk.CTkLabel(
card.content,
text="---",
font=ctk.CTkFont(size=16, weight="bold"),
text_color=colors["text_muted"]
)
self.regime_label.pack(pady=(8, 5))
# Volatility
vol_frame = ctk.CTkFrame(card.content, fg_color="transparent")
vol_frame.pack(fill="x", pady=2)
ctk.CTkLabel(vol_frame, text="Volatility:", font=ctk.CTkFont(size=10), text_color=colors["text_secondary"]).pack(side="left")
self.volatility_label = ctk.CTkLabel(vol_frame, text="--", font=ctk.CTkFont(size=10, weight="bold"), text_color=colors["text"])
self.volatility_label.pack(side="right")
# Confidence
conf_frame = ctk.CTkFrame(card.content, fg_color="transparent")
conf_frame.pack(fill="x", pady=2)
ctk.CTkLabel(conf_frame, text="Confidence:", font=ctk.CTkFont(size=10), text_color=colors["text_secondary"]).pack(side="left")
self.regime_conf_label = ctk.CTkLabel(conf_frame, text="--%", font=ctk.CTkFont(size=10, weight="bold"), text_color=colors["text"])
self.regime_conf_label.pack(side="right")
def create_positions_card(self, parent, row, col):
"""Open Positions card"""
colors = THEMES[self.current_theme]
card = ModernCard(parent, "OPEN POSITIONS", theme=self.current_theme)
card.grid(row=row, column=col, padx=4, pady=4, sticky="nsew")
self.cards.append(card)
# Positions text
self.positions_text = ctk.CTkTextbox(
card.content,
font=ctk.CTkFont(family="Consolas", size=10),
fg_color=colors["card_inner"],
text_color=colors["text"],
height=90,
corner_radius=6
)
self.positions_text.pack(fill="both", expand=True)
self.positions_text.insert("1.0", "No open positions")
self.positions_text.configure(state="disabled")
def create_settings_card(self, parent, row, col):
"""Settings display card"""
colors = THEMES[self.current_theme]
card = ModernCard(parent, "SETTINGS", theme=self.current_theme)
card.grid(row=row, column=col, columnspan=2, padx=4, pady=4, sticky="nsew")
self.cards.append(card)
# Settings grid - use 2 columns for compact layout
settings_frame = ctk.CTkFrame(card.content, fg_color="transparent")
settings_frame.pack(fill="both", expand=True)
settings_frame.grid_columnconfigure(0, weight=1)
settings_frame.grid_columnconfigure(1, weight=1)
settings = [
("Symbol", self.config.symbol),
("Capital", f"${self.config.capital:,.2f}"),
("Max Daily Loss", f"{self.config.risk.max_daily_loss}%"),
("Risk Per Trade", f"{self.config.risk.risk_per_trade}%"),
("Min Lot", f"{self.config.risk.min_lot_size}"),
("Max Lot", f"{self.config.risk.max_lot_size}"),
("Timeframe", self.config.execution_timeframe),
("Golden Time", "19:00-23:00 WIB"),
]
self.settings_labels = [] # Store for theme updates
for i, (label, value) in enumerate(settings):
row_idx = i // 2
col_idx = i % 2
row_frame = ctk.CTkFrame(settings_frame, fg_color="transparent")
row_frame.grid(row=row_idx, column=col_idx, sticky="w", padx=5, pady=1)
lbl = ctk.CTkLabel(
row_frame,
text=f"{label}:",
font=ctk.CTkFont(size=10),
text_color=colors["text_secondary"],
anchor="w"
)
lbl.pack(side="left")
val_lbl = ctk.CTkLabel(
row_frame,
text=f" {value}",
font=ctk.CTkFont(size=10, weight="bold"),
text_color=colors["text"],
anchor="w"
)
val_lbl.pack(side="left")
self.settings_labels.append((lbl, val_lbl))
def create_log_card(self, parent, row, col):
"""Activity log card"""
colors = THEMES[self.current_theme]
card = ModernCard(parent, "ACTIVITY LOG", theme=self.current_theme)
card.grid(row=row, column=col, columnspan=2, padx=4, pady=4, sticky="nsew")
self.cards.append(card)
self.log_text = ctk.CTkTextbox(
card.content,
font=ctk.CTkFont(family="Consolas", size=10),
fg_color=colors["card_inner"],
text_color=colors["green"],
height=100,
corner_radius=6
)
self.log_text.pack(fill="both", expand=True)
def create_status_bar(self):
"""Create status bar"""
colors = THEMES[self.current_theme]
self.status_frame = ctk.CTkFrame(self, fg_color=colors["card"], height=28, corner_radius=0)
self.status_frame.pack(fill="x", side="bottom")
self.status_frame.pack_propagate(False)
self.status_label = ctk.CTkLabel(
self.status_frame,
text="Initializing...",
font=ctk.CTkFont(size=10),
text_color=colors["text_secondary"]
)
self.status_label.pack(side="left", padx=12, pady=4)
self.update_label = ctk.CTkLabel(
self.status_frame,
text="Last update: --:--:--",
font=ctk.CTkFont(size=10),
text_color=colors["text_muted"]
)
self.update_label.pack(side="right", padx=12, pady=4)
def log(self, message):
"""Add message to activity log"""
timestamp = datetime.now().strftime("%H:%M:%S")
self.log_text.insert("end", f"[{timestamp}] {message}\n")
self.log_text.see("end")
def connect_systems(self):
"""Connect to MT5 and initialize components"""
colors = THEMES[self.current_theme]
self.log("Connecting to MT5...")
try:
self.mt5 = MT5Connector(
login=self.config.mt5_login,
password=self.config.mt5_password,
server=self.config.mt5_server,
path=self.config.mt5_path,
)
if not self.mt5.connect():
self.log("ERROR: Failed to connect to MT5!")
self.connection_label.configure(text="● Disconnected", text_color=colors["red"])
return
self.log("MT5 connected successfully!")
self.connection_label.configure(text="● Connected", text_color=colors["green"])
# Initialize components
self.smc = SMCAnalyzer()
self.ml = TradingModel(model_path="models/xgboost_model")
self.ml.load()
self.hmm = MarketRegimeDetector(model_path="models/hmm_regime")
self.hmm.load()
self.session = SessionFilter()
self.feature_eng = FeatureEngineer()
self.log("All components initialized")
if self.ml.fitted:
self.log(f"ML Model loaded ({len(self.ml.feature_names)} features)")
self.running = True
# Start update thread
update_thread = threading.Thread(target=self.update_loop, daemon=True)
update_thread.start()
except Exception as e:
self.log(f"ERROR: {e}")
self.connection_label.configure(text="● Error", text_color=colors["red"])
def update_loop(self):
"""Main update loop"""
while self.running:
try:
self.after(0, self.update_data)
time.sleep(1)
except Exception as e:
self.after(0, lambda: self.log(f"Update error: {e}"))
time.sleep(5)
def update_data(self):
"""Fetch and update all data"""
if not self.mt5:
return
# Update time
wib = ZoneInfo("Asia/Jakarta")
now = datetime.now(wib)
self.time_label.configure(text=now.strftime("%H:%M:%S WIB"))
# Check golden time (use theme colors)
colors = THEMES[self.current_theme]
is_golden = 19 <= now.hour < 23
if is_golden:
self.golden_frame.configure(fg_color=colors["highlight"])
self.golden_label.configure(text="GOLDEN TIME: YES", text_color=colors["green"])
else:
self.golden_frame.configure(fg_color=colors["highlight_off"])
self.golden_label.configure(text="GOLDEN TIME: NO", text_color=colors["text_secondary"])
# Update price
try:
tick = self.mt5.get_tick(self.config.symbol)
if tick:
price = (tick.bid + tick.ask) / 2
spread = (tick.ask - tick.bid) * 100
self.price_label.configure(text=f"{price:.2f}")
self.spread_label.configure(text=f"Spread: {spread:.1f} pips")
except:
pass
# Update account
try:
balance = self.mt5.account_balance
equity = self.mt5.account_equity
profit = equity - balance
self.balance_label.configure(text=f"${balance:,.2f}")
self.equity_label.configure(text=f"${equity:,.2f}")
if profit >= 0:
self.pl_label.configure(text=f"+${profit:.2f}", text_color=colors["green"])
else:
self.pl_label.configure(text=f"-${abs(profit):.2f}", text_color=colors["red"])
except:
pass
# Update session
try:
session_info = self.session.get_status_report()
if session_info:
self.session_label.configure(text=session_info.get('current_session', 'Unknown'))
can_trade, reason, _ = self.session.can_trade()
if can_trade:
self.can_trade_label.configure(text="Can Trade: YES", text_color=colors["green"])
else:
self.can_trade_label.configure(text="Can Trade: NO", text_color=colors["red"])
except:
pass
# Get market data and update signals
try:
df = self.mt5.get_market_data(self.config.symbol, self.config.execution_timeframe, 500)
if df is not None and len(df) > 100:
self.update_signals(df)
except:
pass
# Update positions
try:
positions = self.mt5.get_open_positions(self.config.symbol)
self.update_positions(positions)
except:
pass
# Update risk state
self.update_risk_state()
# Update status
self.last_update = datetime.now()
self.update_label.configure(text=f"Last update: {self.last_update.strftime('%H:%M:%S')}")
self.status_label.configure(text="Running...")
def update_signals(self, df):
"""Update SMC, ML, and Regime signals"""
colors = THEMES[self.current_theme]
# Build complete features
df = self.feature_eng.calculate_all(df, include_ml_features=True)
df = self.smc.calculate_all(df)
# Regime
try:
df = self.hmm.predict(df)
regime = self.hmm.get_current_state(df)
if regime:
regime_name = regime.regime.value.replace('_', ' ').title()
self.regime_label.configure(text=regime_name)
self.volatility_label.configure(text=f"{regime.volatility:.2f}")
self.regime_conf_label.configure(text=f"{regime.confidence:.0%}")
if "HIGH" in regime.regime.value:
self.regime_label.configure(text_color=colors["red"])
elif "LOW" in regime.regime.value:
self.regime_label.configure(text_color=colors["green"])
else:
self.regime_label.configure(text_color=colors["orange"])
except:
pass
# SMC Signal
try:
smc_signal = self.smc.generate_signal(df)
if smc_signal:
self.smc_signal_label.configure(text=smc_signal.signal_type)
self.smc_confidence_bar.set(smc_signal.confidence)
self.smc_conf_label.configure(text=f"Confidence: {smc_signal.confidence:.0%}")
self.smc_reason_label.configure(text=smc_signal.reason[:60] + "..." if len(smc_signal.reason) > 60 else smc_signal.reason)
if smc_signal.signal_type == "BUY":
self.smc_signal_label.configure(text_color=colors["green"])
self.smc_confidence_bar.configure(progress_color=colors["green"])
else:
self.smc_signal_label.configure(text_color=colors["red"])
self.smc_confidence_bar.configure(progress_color=colors["red"])
else:
self.smc_signal_label.configure(text="NO SIGNAL", text_color=colors["text_muted"])
self.smc_confidence_bar.set(0)
self.smc_conf_label.configure(text="Confidence: --%")
self.smc_reason_label.configure(text="Waiting for setup...")
except:
pass
# ML Prediction
try:
if self.ml.fitted:
available_features = [f for f in self.ml.feature_names if f in df.columns]
ml_pred = self.ml.predict(df, available_features)
if ml_pred:
self.ml_signal_label.configure(text=ml_pred.signal)
self.ml_confidence_bar.set(ml_pred.confidence)
self.ml_conf_label.configure(text=f"Confidence: {ml_pred.confidence:.0%}")
buy_prob = ml_pred.probability
sell_prob = 1.0 - buy_prob
self.ml_prob_label.configure(text=f"Buy: {buy_prob:.0%} | Sell: {sell_prob:.0%}")
if ml_pred.signal == "BUY":
self.ml_signal_label.configure(text_color=colors["green"])
self.ml_confidence_bar.configure(progress_color=colors["green"])
elif ml_pred.signal == "SELL":
self.ml_signal_label.configure(text_color=colors["red"])
self.ml_confidence_bar.configure(progress_color=colors["red"])
else:
self.ml_signal_label.configure(text_color=colors["orange"])
self.ml_confidence_bar.configure(progress_color=colors["orange"])
except:
pass
def update_positions(self, positions):
"""Update positions display"""
self.positions_text.configure(state="normal")
self.positions_text.delete("1.0", "end")
if positions is None or positions.is_empty():
self.positions_text.insert("1.0", "No open positions")
else:
for row in positions.iter_rows(named=True):
ticket = row.get('ticket', 'N/A')
pos_type = "BUY" if row.get('type', 0) == 0 else "SELL"
volume = row.get('volume', 0)
profit = row.get('profit', 0)
price_open = row.get('price_open', 0)
line = f"#{ticket} | {pos_type} {volume} @ {price_open:.2f} | P/L: ${profit:+.2f}\n"
self.positions_text.insert("end", line)
self.positions_text.configure(state="disabled")
def update_risk_state(self):
"""Update risk state from file"""
try:
risk_file = Path("data/risk_state.txt")
if risk_file.exists():
content = risk_file.read_text()
lines = content.strip().split('\n')
for line in lines:
if ':' in line:
key, value = line.split(':', 1)
key = key.strip()
value = value.strip()
if key == 'daily_loss':
self.daily_loss_label.configure(text=f"${float(value):,.2f}")
elif key == 'daily_profit':
self.daily_profit_label.configure(text=f"${float(value):,.2f}")
elif key == 'consecutive_losses':
self.consec_loss_label.configure(text=value)
except:
pass
def on_closing(self):
"""Handle window close"""
self.running = False
if self.mt5:
self.mt5.disconnect()
self.destroy()
def main():
app = TradingDashboard()
app.protocol("WM_DELETE_WINDOW", app.on_closing)
app.mainloop()
if __name__ == "__main__":
main()