Files
XauBot/archive/dashboard_pro.py
GifariKemal 0d25548ed5 refactor: restructure repository and add README, CLAUDE.md, LICENSE
- Move utility scripts to scripts/ (check_market, check_positions, etc.)
- Move test files to tests/ (test_modules, test_mt5_connection, etc.)
- Move deprecated dashboards to archive/
- Move research files to docs/research/
- Add sys.path fix to all moved Python files
- Rewrite README.md with architecture diagram and badges
- Add CLAUDE.md project guide
- Add MIT LICENSE
- Update .gitignore with archive/ pattern

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 13:22:46 +07:00

1148 lines
43 KiB
Python

"""
Trading Bot Dashboard PRO - MONITORING ONLY
============================================
Pure monitoring dashboard (no control) with:
- Real-time Price & Equity charts
- Visual alarms for critical conditions
- Data freshness/heartbeat indicator
- Detailed AI reasoning in logs
- Stale data visual warning
"""
import customtkinter as ctk
import threading
import time
from datetime import datetime
from zoneinfo import ZoneInfo
from pathlib import Path
from collections import deque
import sys
# Add project to path
sys.path.insert(0, str(Path(__file__).parent))
# Load environment variables
from dotenv import load_dotenv
load_dotenv()
# Matplotlib for charts
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import matplotlib
matplotlib.use('TkAgg')
# 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
from loguru import logger
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",
"yellow": "#ffd93d",
"highlight": "#2d4a2d",
"highlight_off": "#2d2d44",
"chart_bg": "#0d0d1a",
"chart_line": "#00d4ff",
"chart_equity": "#00ff88",
"danger": "#ff2222",
"stale": "#444444", # Gray for stale data
},
"light": {
"bg": "#f0f2f5",
"card": "#ffffff",
"card_inner": "#f8f9fa",
"text": "#1a1a2e",
"text_secondary": "#555555",
"text_muted": "#888888",
"accent": "#0066cc",
"green": "#00aa55",
"red": "#dd3344",
"orange": "#ee8800",
"yellow": "#cc9900",
"highlight": "#d4edda",
"highlight_off": "#e9ecef",
"chart_bg": "#f8f9fa",
"chart_line": "#0066cc",
"chart_equity": "#00aa55",
"danger": "#cc0000",
"stale": "#cccccc",
}
}
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"])
def set_alarm(self, is_alarm=False):
"""Set alarm state (red background)"""
colors = THEMES[self.theme]
if is_alarm:
self.configure(fg_color=colors["danger"])
else:
self.configure(fg_color=colors["card"])
class TradingDashboardPro(ctk.CTk):
"""Production Monitoring Dashboard - NO CONTROL, PURE MONITORING"""
def __init__(self):
super().__init__()
# Theme state
self.current_theme = "dark"
ctk.set_appearance_mode("dark")
# Window setup
self.title("AI Trading Bot - Monitor")
self.geometry("750x900")
self.minsize(650, 750)
self.configure(fg_color=THEMES[self.current_theme]["bg"])
# Store all 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
self.last_data_time = None # For stale data detection
self.data_stale = False
self.consecutive_errors = 0
# Data history for charts
self.price_history = deque(maxlen=120) # 2 hours of data
self.equity_history = deque(maxlen=120)
self.balance_history = deque(maxlen=120)
self.time_history = deque(maxlen=120)
# Risk alarm state
self.risk_alarm_active = False
# Create UI
self.create_header()
self.create_main_layout()
self.create_status_bar()
# Start connection
self.after(100, self.connect_systems)
# Heartbeat checker
self.after(5000, self.check_data_freshness)
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["accent"])
self.time_label.configure(text_color=colors["text_secondary"])
# Update charts
self.update_price_chart()
self.update_equity_chart()
# Update text boxes
if hasattr(self, 'log_text'):
self.log_text.configure(fg_color=colors["card_inner"], text_color=colors["green"])
def check_data_freshness(self):
"""Check if data is stale (>5 seconds old) - HEARTBEAT"""
colors = THEMES[self.current_theme]
if self.last_data_time:
age = (datetime.now() - self.last_data_time).total_seconds()
if age > 5:
# DATA IS STALE - Visual warning
self.data_stale = True
self.heartbeat_label.configure(
text=f"DATA STALE ({age:.0f}s)",
text_color=colors["red"]
)
# Gray out the main window slightly
self.status_frame.configure(fg_color=colors["stale"])
self.status_label.configure(text="WARNING: Data tidak terupdate!", text_color=colors["red"])
else:
self.data_stale = False
self.heartbeat_label.configure(
text=f"LIVE ({age:.1f}s)",
text_color=colors["green"]
)
if self.consecutive_errors == 0:
self.status_frame.configure(fg_color=colors["card"])
# Schedule next check
self.after(1000, self.check_data_freshness)
def create_header(self):
"""Create header section with heartbeat indicator"""
colors = THEMES[self.current_theme]
header_frame = ctk.CTkFrame(self, fg_color="transparent", height=45)
header_frame.pack(fill="x", padx=15, pady=(8, 3))
header_frame.pack_propagate(False)
# Logo/Title
self.title_label = ctk.CTkLabel(
header_frame,
text="AI TRADING BOT",
font=ctk.CTkFont(size=18, weight="bold"),
text_color=colors["text"]
)
self.title_label.pack(side="left")
self.subtitle_label = ctk.CTkLabel(
header_frame,
text=" MONITOR",
font=ctk.CTkFont(size=11),
text_color=colors["accent"]
)
self.subtitle_label.pack(side="left", pady=(3, 0))
# Theme toggle
self.theme_btn = ctk.CTkButton(
header_frame,
text="Light",
width=55,
height=24,
corner_radius=12,
font=ctk.CTkFont(size=10),
command=self.toggle_theme
)
self.theme_btn.pack(side="right", padx=3)
# Heartbeat indicator (DATA FRESHNESS)
self.heartbeat_label = ctk.CTkLabel(
header_frame,
text="CONNECTING...",
font=ctk.CTkFont(size=10, weight="bold"),
text_color=colors["orange"]
)
self.heartbeat_label.pack(side="right", padx=10)
# Connection status
self.connection_label = ctk.CTkLabel(
header_frame,
text="Connecting...",
font=ctk.CTkFont(size=10),
text_color=colors["orange"]
)
self.connection_label.pack(side="right", padx=8)
# Time display
self.time_label = ctk.CTkLabel(
header_frame,
text="--:--:-- WIB",
font=ctk.CTkFont(size=11, weight="bold"),
text_color=colors["text_secondary"]
)
self.time_label.pack(side="right", padx=8)
def create_main_layout(self):
"""Create main dashboard layout"""
# Scrollable main container
self.main_scroll = ctk.CTkScrollableFrame(self, fg_color="transparent")
self.main_scroll.pack(fill="both", expand=True, padx=10, pady=3)
# Configure 2-column grid
self.main_scroll.grid_columnconfigure(0, weight=1, minsize=300)
self.main_scroll.grid_columnconfigure(1, weight=1, minsize=300)
# Row 0: Price Chart (full width)
self.create_price_chart_card(self.main_scroll, 0, 0)
# Row 1: Price & Account
self.create_price_card(self.main_scroll, 1, 0)
self.create_account_card(self.main_scroll, 1, 1)
# Row 2: Session & Risk (with ALARM capability)
self.create_session_card(self.main_scroll, 2, 0)
self.create_risk_card(self.main_scroll, 2, 1)
# Row 3: SMC & ML
self.create_smc_card(self.main_scroll, 3, 0)
self.create_ml_card(self.main_scroll, 3, 1)
# Row 4: Regime & Positions
self.create_regime_card(self.main_scroll, 4, 0)
self.create_positions_card(self.main_scroll, 4, 1)
# Row 5: Equity Chart (replaced Settings) - full width
self.create_equity_chart_card(self.main_scroll, 5, 0)
# Row 6: Log (full width, larger)
self.create_log_card(self.main_scroll, 6, 0)
def create_price_chart_card(self, parent, row, col):
"""Mini price chart - sparkline style"""
colors = THEMES[self.current_theme]
card = ModernCard(parent, "PRICE CHART (2H)", theme=self.current_theme)
card.grid(row=row, column=col, columnspan=2, padx=4, pady=4, sticky="nsew")
self.cards.append(card)
# Create matplotlib figure
self.price_fig = Figure(figsize=(6, 1.3), dpi=100, facecolor=colors["chart_bg"])
self.price_ax = self.price_fig.add_subplot(111)
self.price_ax.set_facecolor(colors["chart_bg"])
# Style
self.price_ax.tick_params(colors=colors["text_muted"], labelsize=7)
for spine in ['top', 'right']:
self.price_ax.spines[spine].set_visible(False)
for spine in ['bottom', 'left']:
self.price_ax.spines[spine].set_color(colors["text_muted"])
self.price_fig.tight_layout(pad=0.3)
# Embed in tkinter
self.price_canvas = FigureCanvasTkAgg(self.price_fig, master=card.content)
self.price_canvas.get_tk_widget().pack(fill="both", expand=True)
def create_equity_chart_card(self, parent, row, col):
"""Equity/Balance chart - REPLACED SETTINGS"""
colors = THEMES[self.current_theme]
card = ModernCard(parent, "EQUITY vs BALANCE (2H)", theme=self.current_theme)
card.grid(row=row, column=col, columnspan=2, padx=4, pady=4, sticky="nsew")
self.cards.append(card)
# Create matplotlib figure
self.equity_fig = Figure(figsize=(6, 1.3), dpi=100, facecolor=colors["chart_bg"])
self.equity_ax = self.equity_fig.add_subplot(111)
self.equity_ax.set_facecolor(colors["chart_bg"])
# Style
self.equity_ax.tick_params(colors=colors["text_muted"], labelsize=7)
for spine in ['top', 'right']:
self.equity_ax.spines[spine].set_visible(False)
for spine in ['bottom', 'left']:
self.equity_ax.spines[spine].set_color(colors["text_muted"])
self.equity_fig.tight_layout(pad=0.3)
# Embed
self.equity_canvas = FigureCanvasTkAgg(self.equity_fig, master=card.content)
self.equity_canvas.get_tk_widget().pack(fill="both", expand=True)
def update_price_chart(self):
"""Redraw the price chart"""
if len(self.price_history) < 2:
return
colors = THEMES[self.current_theme]
self.price_ax.clear()
prices = list(self.price_history)
self.price_ax.plot(prices, color=colors["chart_line"], linewidth=1.5)
self.price_ax.fill_between(range(len(prices)), prices, alpha=0.15, color=colors["chart_line"])
# Style
self.price_ax.set_facecolor(colors["chart_bg"])
self.price_ax.tick_params(colors=colors["text_muted"], labelsize=7)
for spine in ['top', 'right']:
self.price_ax.spines[spine].set_visible(False)
for spine in ['bottom', 'left']:
self.price_ax.spines[spine].set_color(colors["text_muted"])
self.price_ax.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, p: f'{x:.0f}'))
self.price_fig.tight_layout(pad=0.3)
self.price_canvas.draw()
def update_equity_chart(self):
"""Redraw equity/balance chart"""
if len(self.equity_history) < 2:
return
colors = THEMES[self.current_theme]
self.equity_ax.clear()
equity = list(self.equity_history)
balance = list(self.balance_history)
# Plot both lines
self.equity_ax.plot(equity, color=colors["chart_equity"], linewidth=1.5, label='Equity')
self.equity_ax.plot(balance, color=colors["text_muted"], linewidth=1, linestyle='--', label='Balance')
# Fill between (profit/loss visual)
self.equity_ax.fill_between(
range(len(equity)), balance, equity,
where=[e >= b for e, b in zip(equity, balance)],
alpha=0.2, color=colors["green"]
)
self.equity_ax.fill_between(
range(len(equity)), balance, equity,
where=[e < b for e, b in zip(equity, balance)],
alpha=0.2, color=colors["red"]
)
# Style
self.equity_ax.set_facecolor(colors["chart_bg"])
self.equity_ax.tick_params(colors=colors["text_muted"], labelsize=7)
for spine in ['top', 'right']:
self.equity_ax.spines[spine].set_visible(False)
for spine in ['bottom', 'left']:
self.equity_ax.spines[spine].set_color(colors["text_muted"])
self.equity_ax.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, p: f'${x:.0f}'))
self.equity_fig.tight_layout(pad=0.3)
self.equity_canvas.draw()
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=26, weight="bold"),
text_color=colors["green"]
)
self.price_label.pack(pady=(3, 0))
# Change indicator
self.price_change_label = ctk.CTkLabel(
card.content,
text="-- (--)",
font=ctk.CTkFont(size=10),
text_color=colors["text_muted"]
)
self.price_change_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=(2, 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=10), text_color=colors["text_secondary"]).pack(side="left")
self.balance_label = ctk.CTkLabel(balance_frame, text="$-----.--", font=ctk.CTkFont(size=10, 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=10), text_color=colors["text_secondary"]).pack(side="left")
self.equity_label = ctk.CTkLabel(equity_frame, text="$-----.--", font=ctk.CTkFont(size=10, 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=10), text_color=colors["text_secondary"]).pack(side="left")
self.pl_label = ctk.CTkLabel(pl_frame, text="$0.00", font=ctk.CTkFont(size=11, 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=11, weight="bold"),
text_color=colors["orange"]
)
self.session_label.pack(pady=(2, 0))
# Golden time
self.golden_frame = ctk.CTkFrame(card.content, fg_color=colors["highlight_off"], corner_radius=6)
self.golden_frame.pack(pady=5, 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=3)
# 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 - WITH ALARM CAPABILITY"""
colors = THEMES[self.current_theme]
self.risk_card = ModernCard(parent, "RISK STATUS", theme=self.current_theme)
self.risk_card.grid(row=row, column=col, padx=4, pady=4, sticky="nsew")
self.cards.append(self.risk_card)
# Daily loss
dl_frame = ctk.CTkFrame(self.risk_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(self.risk_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(self.risk_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")
# Risk % indicator
risk_pct_frame = ctk.CTkFrame(self.risk_card.content, fg_color="transparent")
risk_pct_frame.pack(fill="x", pady=2)
ctk.CTkLabel(risk_pct_frame, text="Risk Used:", font=ctk.CTkFont(size=10), text_color=colors["text_secondary"]).pack(side="left")
self.risk_pct_label = ctk.CTkLabel(risk_pct_frame, text="0%", font=ctk.CTkFont(size=10, weight="bold"), text_color=colors["green"])
self.risk_pct_label.pack(side="right")
def create_smc_card(self, parent, row, col):
"""SMC Signal card with reasoning"""
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=22, weight="bold"),
text_color=colors["text_muted"]
)
self.smc_signal_label.pack(pady=(3, 2))
# Confidence bar
self.smc_confidence_bar = ctk.CTkProgressBar(card.content, height=6, corner_radius=3)
self.smc_confidence_bar.pack(pady=3, fill="x", padx=10)
self.smc_confidence_bar.set(0)
# Reason (AI REASONING)
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=(2, 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=22, weight="bold"),
text_color=colors["text_muted"]
)
self.ml_signal_label.pack(pady=(3, 2))
# Confidence bar
self.ml_confidence_bar = ctk.CTkProgressBar(card.content, height=6, corner_radius=3)
self.ml_confidence_bar.pack(pady=3, fill="x", padx=10)
self.ml_confidence_bar.set(0)
# Probabilities
self.ml_prob_label = ctk.CTkLabel(
card.content,
text="Buy: --% | Sell: --%",
font=ctk.CTkFont(size=10),
text_color=colors["text_secondary"]
)
self.ml_prob_label.pack()
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=14, weight="bold"),
text_color=colors["text_muted"]
)
self.regime_label.pack(pady=(5, 3))
# 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 frame
self.positions_frame = ctk.CTkScrollableFrame(
card.content,
fg_color=colors["card_inner"],
height=70,
corner_radius=6
)
self.positions_frame.pack(fill="both", expand=True)
# Placeholder
self.no_positions_label = ctk.CTkLabel(
self.positions_frame,
text="No open positions",
font=ctk.CTkFont(size=10),
text_color=colors["text_muted"]
)
self.no_positions_label.pack(pady=10)
def create_log_card(self, parent, row, col):
"""Activity log card - LARGER for better monitoring"""
colors = THEMES[self.current_theme]
card = ModernCard(parent, "AI 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=120, # Larger log area
corner_radius=6
)
self.log_text.pack(fill="both", expand=True)
def create_status_bar(self):
"""Create status bar with data freshness indicator"""
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, level="info"):
"""Add message to activity log with better formatting"""
try:
timestamp = datetime.now().strftime("%H:%M:%S")
colors = THEMES[self.current_theme]
# Color based on level
if level == "error":
prefix = "[ERROR]"
elif level == "warn":
prefix = "[WARN]"
elif level == "trade":
prefix = "[TRADE]"
else:
prefix = "[INFO]"
self.log_text.insert("end", f"[{timestamp}] {prefix} {message}\n")
self.log_text.see("end")
# Also log to file
logger.info(f"Dashboard: {message}")
except Exception as e:
print(f"Log error: {e}")
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("Failed to connect to MT5!", "error")
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"Connection error: {e}", "error")
logger.error(f"Dashboard connect error: {e}")
self.connection_label.configure(text="Error", text_color=colors["red"])
def update_loop(self):
"""Main update loop with proper error tracking"""
while self.running:
try:
self.after(0, self.update_data)
self.consecutive_errors = 0
time.sleep(1)
except Exception as e:
self.consecutive_errors += 1
logger.error(f"Dashboard update error: {e}")
self.after(0, lambda: self.log(f"Update error: {e}", "error"))
time.sleep(3)
def update_data(self):
"""Fetch and update all data"""
if not self.mt5:
return
colors = THEMES[self.current_theme]
# Mark data as fresh
self.last_data_time = datetime.now()
# Update time
wib = ZoneInfo("Asia/Jakarta")
now = datetime.now(wib)
self.time_label.configure(text=now.strftime("%H:%M:%S WIB"))
# Check golden time
is_golden = 19 <= now.hour < 23
if is_golden:
self.golden_frame.configure(fg_color=colors["highlight"])
self.golden_label.configure(text="GOLDEN: YES", text_color=colors["green"])
else:
self.golden_frame.configure(fg_color=colors["highlight_off"])
self.golden_label.configure(text="GOLDEN: 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")
# Update price history
self.price_history.append(price)
# Calculate change
if len(self.price_history) > 1:
prev_price = list(self.price_history)[-2]
change = price - prev_price
if change >= 0:
self.price_change_label.configure(text=f"+{change:.2f}", text_color=colors["green"])
else:
self.price_change_label.configure(text=f"{change:.2f}", text_color=colors["red"])
# Update chart every 10 ticks
if len(self.price_history) % 10 == 0:
self.update_price_chart()
except Exception as e:
logger.debug(f"Price update error: {e}")
# Update account & equity chart
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"])
# Update equity/balance history
self.equity_history.append(equity)
self.balance_history.append(balance)
# Update equity chart every 10 updates
if len(self.equity_history) % 10 == 0:
self.update_equity_chart()
except Exception as e:
logger.debug(f"Account update error: {e}")
# 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 Exception as e:
logger.debug(f"Session update error: {e}")
# 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 Exception as e:
logger.debug(f"Signal update error: {e}")
# Update positions
try:
positions = self.mt5.get_open_positions(self.config.symbol)
self.update_positions(positions)
except Exception as e:
logger.debug(f"Position update error: {e}")
# Update risk state with ALARM check
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')}")
if not self.data_stale:
self.status_label.configure(text="Monitoring...")
def update_signals(self, df):
"""Update SMC, ML, and Regime signals with REASONING"""
colors = THEMES[self.current_theme]
try:
df = self.feature_eng.calculate_all(df, include_ml_features=True)
df = self.smc.calculate_all(df)
except Exception as e:
logger.debug(f"Feature calculation error: {e}")
return
# 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 Exception as e:
logger.debug(f"Regime update error: {e}")
# SMC Signal with REASONING
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)
# Show AI REASONING
reason = smc_signal.reason if smc_signal.reason else "Signal generated"
self.smc_reason_label.configure(text=reason[:80] + "..." if len(reason) > 80 else 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_reason_label.configure(text="Waiting for SMC setup...")
except Exception as e:
logger.debug(f"SMC update error: {e}")
# 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)
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 Exception as e:
logger.debug(f"ML update error: {e}")
def update_positions(self, positions):
"""Update positions display"""
colors = THEMES[self.current_theme]
# Clear existing
for widget in self.positions_frame.winfo_children():
widget.destroy()
if positions is None or positions.is_empty():
label = ctk.CTkLabel(
self.positions_frame,
text="No open positions",
font=ctk.CTkFont(size=10),
text_color=colors["text_muted"]
)
label.pack(pady=10)
return
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)
# Position row
pos_frame = ctk.CTkFrame(self.positions_frame, fg_color="transparent")
pos_frame.pack(fill="x", pady=1, padx=3)
type_color = colors["green"] if pos_type == "BUY" else colors["red"]
profit_color = colors["green"] if profit >= 0 else colors["red"]
ctk.CTkLabel(
pos_frame,
text=f"{pos_type} {volume} @ {price_open:.2f}",
font=ctk.CTkFont(size=9, weight="bold"),
text_color=type_color
).pack(side="left")
ctk.CTkLabel(
pos_frame,
text=f"${profit:+.2f}",
font=ctk.CTkFont(size=9, weight="bold"),
text_color=profit_color
).pack(side="right")
def update_risk_state(self):
"""Update risk state with ALARM for high risk"""
colors = THEMES[self.current_theme]
try:
risk_file = Path("data/risk_state.txt")
if risk_file.exists():
content = risk_file.read_text()
lines = content.strip().split('\n')
daily_loss = 0.0
daily_profit = 0.0
consec_losses = 0
for line in lines:
if ':' in line:
key, value = line.split(':', 1)
key = key.strip()
value = value.strip()
if key == 'daily_loss':
daily_loss = float(value)
self.daily_loss_label.configure(text=f"${daily_loss:,.2f}")
elif key == 'daily_profit':
daily_profit = float(value)
self.daily_profit_label.configure(text=f"${daily_profit:,.2f}")
elif key == 'consecutive_losses':
consec_losses = int(value)
self.consec_loss_label.configure(text=str(consec_losses))
# Calculate risk percentage (based on $250 max daily loss = 5% of $5000)
max_daily_loss = self.config.capital * (self.config.risk.max_daily_loss / 100)
risk_pct = (daily_loss / max_daily_loss * 100) if max_daily_loss > 0 else 0
self.risk_pct_label.configure(text=f"{risk_pct:.0f}%")
# ALARM: If risk > 80%
if risk_pct >= 80:
self.risk_card.set_alarm(True)
self.risk_pct_label.configure(text_color=colors["danger"])
if not self.risk_alarm_active:
self.log(f"RISK ALARM: Daily loss at {risk_pct:.0f}%!", "warn")
self.risk_alarm_active = True
elif risk_pct >= 50:
self.risk_card.set_alarm(False)
self.risk_pct_label.configure(text_color=colors["orange"])
self.risk_alarm_active = False
else:
self.risk_card.set_alarm(False)
self.risk_pct_label.configure(text_color=colors["green"])
self.risk_alarm_active = False
except Exception as e:
logger.debug(f"Risk state update error: {e}")
def on_closing(self):
"""Handle window close"""
self.running = False
if self.mt5:
self.mt5.disconnect()
self.destroy()
def main():
app = TradingDashboardPro()
app.protocol("WM_DELETE_WINDOW", app.on_closing)
app.mainloop()
if __name__ == "__main__":
main()