Source code for gui.panels.settings_panel

"""
Cerebrum Forex - Settings Panel
Configuration with tabs: General and Data Paths
"""

import logging
from pathlib import Path
from config.settings import get_resource_path

from PyQt6.QtWidgets import (
    QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit,
    QSpinBox, QPushButton, QGroupBox, QFormLayout, QFileDialog,
    QTabWidget, QCheckBox, QDoubleSpinBox, QComboBox, QFrame, QScrollArea,
    QTextEdit, QProgressBar, QSlider, QSizePolicy, QMessageBox, QGridLayout
)
from PyQt6.QtGui import QFont, QColor, QPainter, QPen
from PyQt6.QtCore import Qt, pyqtSignal, QThread, QObject

# Charting
try:
    import matplotlib
    matplotlib.use('Qt5Agg') # Compatible with Qt6 usually via wrapper or just works
    from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg as FigureCanvas
    from matplotlib.figure import Figure
    HAS_MATPLOTLIB = True
except ImportError:
    HAS_MATPLOTLIB = False

logger = logging.getLogger(__name__)





[docs] class SettingsPanel(QWidget): """Panel for application settings with tabs""" profile_changed = pyqtSignal(str) def __init__(self, app_controller=None): super().__init__() self.app_controller = app_controller self._init_ui() def _init_ui(self): # Apply Global Style for Checkboxes self.setStyleSheet(""" QWidget { font-family: 'Segoe UI', sans-serif; } QCheckBox { color: #e5e7eb; spacing: 8px; font-size: 13px; } QCheckBox::indicator { width: 18px; height: 18px; background-color: #1f2937; border: 1px solid #4b5563; border-radius: 4px; } QCheckBox::indicator:checked { background-color: #2563eb; /* BLUE Background */ border: 1px solid #2563eb; } QCheckBox::indicator:hover { border: 1px solid #60a5fa; } """) layout = QVBoxLayout(self) layout.setSpacing(10) layout.setContentsMargins(10, 10, 10, 10) # Header header = QLabel("⚙️ Settings") header.setFont(QFont("Segoe UI", 16, QFont.Weight.Bold)) header.setStyleSheet("color: #0e639c;") layout.addWidget(header) # Tabs self.tabs = QTabWidget() self.tabs.setStyleSheet(""" QTabWidget::pane { border: 1px solid #3c3c3c; background: #252526; } QTabBar::tab { background: #2d2d2d; color: #ccc; padding: 8px 20px; border: 1px solid #3c3c3c; border-bottom: none; } QTabBar::tab:selected { background: #252526; border-top: 2px solid #0e639c; } """) # Tab 1: General self.general_tab = self._create_general_tab() self.tabs.addTab(self.general_tab, "🔧 General") # Tab 2: Paths self.paths_tab = self._create_paths_tab() self.tabs.addTab(self.paths_tab, "📁 Data Paths") # Tab 3: AI & Signals self.ai_tab = self._create_ai_tab() self.tabs.addTab(self.ai_tab, "🧠 AI Signals") # Tab 4: Logic Tuning self.logic_tab = self._create_logic_tab() self.tabs.addTab(self.logic_tab, "⚖️ Logic Tuning") layout.addWidget(self.tabs) # Actions actions = QHBoxLayout() from gui.widgets.styled_button import Premium3DButton self.save_btn = Premium3DButton("💾 Save Settings", color="#059669", hover_color="#10b981", pressed_color="#047857") self.save_btn.clicked.connect(self._save_settings) self.save_btn.setToolTip("Save all settings to configuration database (requires application restart for some changes)") actions.addWidget(self.save_btn) self.reset_btn = Premium3DButton("🔄 Reset Defaults", color="#4b5563", hover_color="#6b7280", pressed_color="#374151") self.reset_btn.clicked.connect(self._reset_defaults) self.reset_btn.setToolTip("Reset all settings to their default factory values") actions.addWidget(self.reset_btn) actions.addStretch() layout.addLayout(actions) self._load_settings() def _create_general_tab(self): """Create general settings tab""" tab = QWidget() layout = QVBoxLayout(tab) layout.setSpacing(15) layout.setContentsMargins(10, 15, 10, 10) # MT5 Settings mt5_group = QGroupBox("MetaTrader 5") mt5_layout = QFormLayout(mt5_group) self.mt5_path = QLineEdit() self.mt5_path.setPlaceholderText("Path to MT5 terminal...") from gui.widgets.styled_button import Premium3DButton mt5_browse = Premium3DButton("📂 Browse", color="#0e639c", hover_color="#2563eb", pressed_color="#1e3a8a") mt5_browse.setFixedSize(100, 36) mt5_browse.setToolTip("Select the MetaTrader 5 terminal executable (terminal64.exe)") mt5_browse.clicked.connect(self._browse_mt5) mt5_row = QHBoxLayout() mt5_row.addWidget(self.mt5_path) mt5_row.addWidget(mt5_browse) mt5_layout.addRow("MT5 Path:", mt5_row) self.symbol_input = QLineEdit("EURUSD") mt5_layout.addRow("Symbol:", self.symbol_input) layout.addWidget(mt5_group) # Expert Advisor Deployment ea_group = QGroupBox("Expert Advisor Setup") ea_layout = QVBoxLayout(ea_group) ea_desc = QLabel("Automatically deploy the Cerebrum EA to your MT5 Data Folder.\nEnsure MT5 is running and connected first.") ea_desc.setStyleSheet("color: #888; font-style: italic;") ea_layout.addWidget(ea_desc) self.deploy_btn = Premium3DButton("🚀 Install / Deploy EA", color="#d97706", hover_color="#f59e0b", pressed_color="#b45309") self.deploy_btn.clicked.connect(self._deploy_ea) self.deploy_btn.setToolTip("Copies the EA and Signal folders to the active MT5 terminal's Data Directory") ea_layout.addWidget(self.deploy_btn) layout.addWidget(ea_group) # System Preferences sys_group = QGroupBox("System Preferences") sys_layout = QVBoxLayout(sys_group) self.audio_alerts = QCheckBox("🔊 Enable Audio Alerts") self.audio_alerts.setChecked(True) # self.audio_alerts.setStyleSheet(checkbox_style) sys_layout.addWidget(self.audio_alerts) self.auto_connect = QCheckBox("🔌 Auto-Connect MT5 on Startup") self.auto_connect.setChecked(True) # self.auto_connect.setStyleSheet(checkbox_style) sys_layout.addWidget(self.auto_connect) layout.addWidget(sys_group) layout.addStretch() return tab def _deploy_ea(self): """Deploy EA files to MT5 Data Directory""" import shutil import os if not self.app_controller or not self.app_controller.mt5: QMessageBox.warning(self, "Error", "App Controller not available") return # 1. Get MT5 Data Path data_path_str = self.app_controller.mt5.get_terminal_data_path() if not data_path_str: QMessageBox.critical(self, "Deploy Failed", "Could not detect MT5 Data Path.\nPlease ensure MetaTrader 5 is RUNNING and connected.") return try: mt5_data = Path(data_path_str) # Source Paths (use get_resource_path for bundled app compatibility) src_experts = get_resource_path("MQL5/Experts/Cerebrum") src_files = get_resource_path("MQL5/Files/Cerebrum") if not src_experts.exists(): QMessageBox.critical(self, "Error", f"Source EA not found at: {src_experts}") return # Target Paths dst_experts = mt5_data / "MQL5" / "Experts" / "Cerebrum" dst_files = mt5_data / "MQL5" / "Files" / "Cerebrum" # 2. Copy EA Executable (.ex5) ONLY - No source code dst_experts.mkdir(parents=True, exist_ok=True) ea_file = src_experts / "Cerebrum.ex5" if ea_file.exists(): shutil.copy2(ea_file, dst_experts / "Cerebrum.ex5") else: logger.warning(f"EA Executable not found at {ea_file}") # 3. Create Files Directory (Signals) dst_files.mkdir(parents=True, exist_ok=True) signals_dir = dst_files / "Signals" signals_dir.mkdir(exist_ok=True) # Copy README if exists readme = src_files / "README.txt" if readme.exists(): shutil.copy2(readme, dst_files / "README.txt") # 4. Auto-Update Settings to point to this new location new_signals_path = str(signals_dir) self.signals_path.setText(new_signals_path) # AUTO-SAVE SETTINGS to ensure persistence self._save_settings() QMessageBox.information(self, "Success", f"✅ EA Deployed Successfully!\n\n" f"Location: {dst_experts}\n\n" f"1. Go to MT5 -> Navigator -> Experts\n" f"2. Right-Click -> Refresh\n" f"3. You should see 'Cerebrum'\n\n" f"Signal Path updated to: {new_signals_path}\n" f"(Settings saved automatically)" ) except Exception as e: logger.error(f"Deployment failed: {e}") QMessageBox.critical(self, "Deployment Failed", f"An error occurred:\n{str(e)}") def _create_paths_tab(self): """Create data paths tab""" tab = QWidget() layout = QVBoxLayout(tab) layout.setSpacing(15) layout.setContentsMargins(10, 15, 10, 10) paths_group = QGroupBox("Data Directories") paths_layout = QFormLayout(paths_group) # OHLC Dir self.ohlc_path = QLineEdit("data/ohlc") from gui.widgets.styled_button import Premium3DButton ohlc_browse = Premium3DButton("📂 Browse", color="#0e639c", hover_color="#2563eb", pressed_color="#1e3a8a") ohlc_browse.setFixedSize(100, 36) ohlc_browse.setToolTip("Select directory for historical OHLC data (CSV files)") ohlc_browse.clicked.connect(lambda: self._browse_dir(self.ohlc_path)) ohlc_row = QHBoxLayout() ohlc_row.addWidget(self.ohlc_path) ohlc_row.addWidget(ohlc_browse) paths_layout.addRow("OHLC:", ohlc_row) # Indicators Dir self.indicators_path = QLineEdit("data/indicators") ind_browse = Premium3DButton("📂 Browse", color="#0e639c", hover_color="#2563eb", pressed_color="#1e3a8a") ind_browse.setFixedSize(100, 36) ind_browse.setToolTip("Select directory for calculated indicators") ind_browse.clicked.connect(lambda: self._browse_dir(self.indicators_path)) ind_row = QHBoxLayout() ind_row.addWidget(self.indicators_path) ind_row.addWidget(ind_browse) paths_layout.addRow("Indicators:", ind_row) # Features Dir self.features_path = QLineEdit("data/features") feat_browse = Premium3DButton("📂 Browse", color="#0e639c", hover_color="#2563eb", pressed_color="#1e3a8a") feat_browse.setFixedSize(100, 36) feat_browse.setToolTip("Select directory for engineered features") feat_browse.clicked.connect(lambda: self._browse_dir(self.features_path)) feat_row = QHBoxLayout() feat_row.addWidget(self.features_path) feat_row.addWidget(feat_browse) paths_layout.addRow("Features:", feat_row) # Models Dir self.models_path = QLineEdit("data/models") mod_browse = Premium3DButton("📂 Browse", color="#0e639c", hover_color="#2563eb", pressed_color="#1e3a8a") mod_browse.setFixedSize(100, 36) mod_browse.setToolTip("Select directory for saving trained AI models") mod_browse.clicked.connect(lambda: self._browse_dir(self.models_path)) mod_row = QHBoxLayout() mod_row.addWidget(self.models_path) mod_row.addWidget(mod_browse) paths_layout.addRow("Models:", mod_row) # Signals Dir self.signals_path = QLineEdit("data/signals") sig_browse = Premium3DButton("📂 Browse", color="#0e639c", hover_color="#2563eb", pressed_color="#1e3a8a") sig_browse.setFixedSize(100, 36) sig_browse.setToolTip("Select directory to save generated trading signals (Use MT5 Common Folder!)") sig_browse.clicked.connect(lambda: self._browse_dir(self.signals_path)) sig_row = QHBoxLayout() sig_row.addWidget(self.signals_path) sig_row.addWidget(sig_browse) paths_layout.addRow("Signals:", sig_row) layout.addWidget(paths_group) layout.addStretch() return tab def _create_ai_tab(self): """Create AI/Signal settings tab with scroll""" # Scrollable container scroll = QScrollArea() scroll.setWidgetResizable(True) scroll.setFrameShape(QFrame.Shape.NoFrame) # Content widget content = QWidget() layout = QVBoxLayout(content) layout.setSpacing(15) layout.setContentsMargins(10, 15, 10, 10) # Signal Parameters signal_group = QGroupBox("Signal Parameters") signal_layout = QFormLayout(signal_group) self.confidence_threshold = QDoubleSpinBox() self.confidence_threshold.setRange(0.0, 1.0) self.confidence_threshold.setSingleStep(0.01) self.confidence_threshold.setValue(0.65) signal_layout.addRow("Confidence Threshold:", self.confidence_threshold) self.min_consensus = QSpinBox() self.min_consensus.setRange(1, 10) self.min_consensus.setValue(2) signal_layout.addRow("Minimum Consensus:", self.min_consensus) layout.addWidget(signal_group) # AI Configuration ai_group = QGroupBox("AI Configuration") ai_layout = QFormLayout(ai_group) ai_layout.setVerticalSpacing(15) ai_layout.setRowWrapPolicy(QFormLayout.RowWrapPolicy.DontWrapRows) ai_layout.setFieldGrowthPolicy(QFormLayout.FieldGrowthPolicy.ExpandingFieldsGrow) ai_layout.setContentsMargins(10, 15, 10, 10) self.validation_ratio = QDoubleSpinBox() self.validation_ratio.setRange(0.05, 0.5) self.validation_ratio.setSingleStep(0.05) self.validation_ratio.setValue(0.2) self.validation_ratio.setMinimumHeight(28) ai_layout.addRow("Validation Ratio:", self.validation_ratio) self.incremental_training = QCheckBox("Incremental Training") self.incremental_training.setToolTip("Enable to train only on new data (faster but may forget old patterns)") self.incremental_training.setMinimumHeight(28) ai_layout.addRow("Training Mode:", self.incremental_training) self.hyperopt = QCheckBox("Enable Hyper-optimization") self.hyperopt.setMinimumHeight(28) ai_layout.addRow("Optimization:", self.hyperopt) layout.addWidget(ai_group) # Resource Management res_group = QGroupBox("Resource Management") res_layout = QFormLayout(res_group) self.cpu_threads = QSpinBox() self.cpu_threads.setRange(1, 32) self.cpu_threads.setValue(4) res_layout.addRow("CPU Threads:", self.cpu_threads) self.performance_profile = QComboBox() self.performance_profile.addItems(["ECO", "BALANCED", "PRO"]) self.performance_profile.setToolTip("ECO: Low RAM/CPU (Slow updates).\nBALANCED: Standard performance.\nPRO: High performance (Fastest updates).") self.performance_profile.currentTextChanged.connect(self.profile_changed.emit) res_layout.addRow("Execution Profile:", self.performance_profile) layout.addWidget(res_group) # Risk Management (Financial Guard) risk_group = QGroupBox("Risk Management") risk_layout = QFormLayout(risk_group) self.guard_policy = QComboBox() self.guard_policy.addItems(["Advisory (Notify Only)", "Strict (Block Trade)"]) self.guard_policy.setToolTip("Advisory: Warm if profit < $1 but allow trade.\nStrict: Block trade if profit < $1.") risk_layout.addRow("Financial Guard Policy:", self.guard_policy) layout.addWidget(risk_group) layout.addStretch() scroll.setWidget(content) return scroll def _create_logic_tab(self): """Create Logic Tuning tab""" scroll = QScrollArea() scroll.setWidgetResizable(True) scroll.setFrameShape(QFrame.Shape.NoFrame) content = QWidget() layout = QVBoxLayout(content) layout.setSpacing(15) layout.setContentsMargins(10, 15, 10, 10) # Note: Prop Firm and Trailing settings are in EA Manager widget # 1. Preset Strategies preset_group = QGroupBox("Optimization Strategy") preset_layout = QFormLayout(preset_group) self.logic_preset = QComboBox() self.logic_preset.addItems(["Balanced (Standard)", "Opportunist (Aggressive)", "Guardian (Conservative)"]) self.logic_preset.setToolTip("Select a preset to auto-configure weights and thresholds.") self.logic_preset.currentIndexChanged.connect(self._apply_logic_preset) preset_layout.addRow("Preset:", self.logic_preset) layout.addWidget(preset_group) # 2. Congress Voting Weights (Range) weights_group = QGroupBox("Voting Weights (Range Market)") weights_layout = QGridLayout(weights_group) weights_layout.addWidget(QLabel("Model"), 0, 0) weights_layout.addWidget(QLabel("Weight"), 0, 1) weights_layout.addWidget(QLabel("Percent"), 0, 2) self.sliders = {} models = [ ("Quant (XGBoost)", "quant"), ("Archivist (LightGBM)", "archivist"), ("Futurist (RandomForest)", "futurist"), ("Leader (Stacking)", "leader") ] for i, (label, key) in enumerate(models): lbl = QLabel(label) slider = QSlider(Qt.Orientation.Horizontal) slider.setRange(0, 100) slider.setValue(25) val_lbl = QLabel("25%") # Connect slider to label update slider.valueChanged.connect(lambda v, l=val_lbl: l.setText(f"{v}%")) weights_layout.addWidget(lbl, i+1, 0) weights_layout.addWidget(slider, i+1, 1) weights_layout.addWidget(val_lbl, i+1, 2) self.sliders[key] = slider layout.addWidget(weights_group) # 3. Decision Rules rules_group = QGroupBox("Advanced Rules") rules_layout = QVBoxLayout(rules_group) self.democratic_override = QCheckBox("Enable 'Democratic Override' (Global Majority ignores Leader Veto)") self.democratic_override.setToolTip("If enabled, 2+ experts agreeing can override the Stacking model in Range markets.") rules_layout.addWidget(self.democratic_override) layout.addWidget(rules_group) # 4. Thresholds thresh_group = QGroupBox("Decision Thresholds") thresh_layout = QFormLayout(thresh_group) self.range_threshold = QDoubleSpinBox() self.range_threshold.setRange(0.01, 0.50) self.range_threshold.setSingleStep(0.01) self.range_threshold.setValue(0.07) thresh_layout.addRow("Range Threshold (±):", self.range_threshold) layout.addWidget(thresh_group) layout.addStretch() scroll.setWidget(content) return scroll def _apply_logic_preset(self, index): """Auto-configure sliders based on preset""" # 0: Balanced, 1: Opportunist, 2: Conservative if index == 0: # Standard self.sliders['quant'].setValue(20) self.sliders['archivist'].setValue(20) self.sliders['futurist'].setValue(25) self.sliders['leader'].setValue(35) self.range_threshold.setValue(0.07) self.democratic_override.setChecked(False) elif index == 1: # Opportunist (The User Request) self.sliders['quant'].setValue(25) self.sliders['archivist'].setValue(25) self.sliders['futurist'].setValue(20) self.sliders['leader'].setValue(30) self.range_threshold.setValue(0.05) self.democratic_override.setChecked(True) elif index == 2: # Conservative self.sliders['quant'].setValue(15) self.sliders['archivist'].setValue(15) self.sliders['futurist'].setValue(20) self.sliders['leader'].setValue(50) # Strong Veto self.range_threshold.setValue(0.10) self.democratic_override.setChecked(False) def _browse_mt5(self): path, _ = QFileDialog.getOpenFileName(self, "Select MT5 Terminal", "", "Executable (*.exe)") if path: self.mt5_path.setText(path) def _browse_dir(self, line_edit): path = QFileDialog.getExistingDirectory(self, "Select Directory") if path: line_edit.setText(path) def _load_settings(self): if self.app_controller: settings = self.app_controller.get_settings() self.mt5_path.setText(settings.get("mt5_path", "")) self.symbol_input.setText(settings.get("symbol", "EURUSD")) self.audio_alerts.setChecked(settings.get("audio_alerts", True)) self.auto_connect.setChecked(settings.get("auto_connect", True)) # AI Settings self.confidence_threshold.setValue(settings.get("confidence_threshold", 0.65)) self.min_consensus.setValue(settings.get("min_consensus", 2)) self.validation_ratio.setValue(settings.get("validation_ratio", 0.2)) self.incremental_training.setChecked(settings.get("incremental_training", True)) self.hyperopt.setChecked(settings.get("hyper_optimization", False)) self.cpu_threads.setValue(settings.get("cpu_threads", 4)) profile = settings.get("performance_profile", "BALANCED") idx = self.performance_profile.findText(profile) if idx >= 0: self.performance_profile.setCurrentIndex(idx) # Risk Settings policy = settings.get("financial_guard_policy", "Advisory (Notify Only)") index = self.guard_policy.findText(policy) if index >= 0: self.guard_policy.setCurrentIndex(index) else: self.guard_policy.setCurrentIndex(0) # Default Advisory # Paths self.ohlc_path.setText(settings.get("ohlc_dir", str(Path("data/ohlc")))) self.indicators_path.setText(settings.get("indicators_dir", str(Path("data/indicators")))) self.features_path.setText(settings.get("features_dir", str(Path("data/features")))) self.models_path.setText(settings.get("models_dir", str(Path("data/models")))) # SIGNALS: Default to MT5 Instance Folder if not set saved_signals_dir = settings.get("signals_dir") if saved_signals_dir: self.signals_path.setText(saved_signals_dir) else: # Auto-detect MT5 Instance Path default_signals_dir = str(Path("data/signals")) try: import MetaTrader5 as mt5 if mt5.initialize(): term_info = mt5.terminal_info() if term_info and term_info.data_path: instance_path = Path(term_info.data_path) / "MQL5" / "Files" / "Cerebrum" / "Signals" default_signals_dir = str(instance_path) except Exception: pass self.signals_path.setText(default_signals_dir) # Logic Tuning (Congress) congress_config = self.app_controller.get_congress_config() weights = congress_config.get("weights", {}).get("range", {}) self.sliders['quant'].setValue(int(weights.get('quant', 0.25) * 100)) self.sliders['archivist'].setValue(int(weights.get('archivist', 0.25) * 100)) self.sliders['futurist'].setValue(int(weights.get('futurist', 0.20) * 100)) self.sliders['leader'].setValue(int(weights.get('leader', 0.30) * 100)) rules = congress_config.get("rules", {}) self.democratic_override.setChecked(rules.get("democratic_override", False)) thresholds = congress_config.get("thresholds", {}) thresholds = congress_config.get("thresholds", {}) self.range_threshold.setValue(thresholds.get("range_base", 0.07)) # Note: Prop Firm and Trailing settings are now in EA Manager widget def _save_settings(self): """Save settings to database via controller""" if not self.app_controller: return settings = { "mt5_path": self.mt5_path.text(), "symbol": self.symbol_input.text(), "audio_alerts": self.audio_alerts.isChecked(), "auto_connect": self.auto_connect.isChecked(), "confidence_threshold": self.confidence_threshold.value(), "min_consensus": self.min_consensus.value(), "validation_ratio": self.validation_ratio.value(), "incremental_training": self.incremental_training.isChecked(), "hyper_optimization": self.hyperopt.isChecked(), "cpu_threads": self.cpu_threads.value(), "performance_profile": self.performance_profile.currentText(), "financial_guard_policy": self.guard_policy.currentText(), # Note: Prop Firm and Trailing settings are now in EA Manager widget "ohlc_dir": self.ohlc_path.text(), "indicators_dir": self.indicators_path.text(), "features_dir": self.features_path.text(), "models_dir": self.models_path.text(), "signals_dir": self.signals_path.text(), } # Save Basic Settings self.app_controller.save_settings(settings) # Save Congress Logic Config range_weights = { 'quant': self.sliders['quant'].value() / 100.0, 'archivist': self.sliders['archivist'].value() / 100.0, 'futurist': self.sliders['futurist'].value() / 100.0, 'leader': self.sliders['leader'].value() / 100.0, } congress_config = { "weights": { "range": range_weights, # Trend weights remain default/unchanged for now (or could be exposed later) "trend": {'quant': 0.20, 'archivist': 0.20, 'futurist': 0.15, 'leader': 0.45} }, "rules": { "democratic_override": self.democratic_override.isChecked() }, "thresholds": { "range_base": self.range_threshold.value() } } self.app_controller.save_congress_config(congress_config) from PyQt6.QtWidgets import QMessageBox QMessageBox.information(self, "Settings Saved", "Configuration updated successfully.\nLogic changes apply to the NEXT prediction cycle.") logger.info("Settings saved") def _reset_defaults(self): self.mt5_path.clear() self.symbol_input.setText("EURUSD") self.audio_alerts.setChecked(True) self.auto_connect.setChecked(True) self.confidence_threshold.setValue(0.65) self.min_consensus.setValue(2) self.validation_ratio.setValue(0.2) self.hyperopt.setChecked(False) self.incremental_training.setChecked(True) self.cpu_threads.setValue(4) self.guard_policy.setCurrentIndex(0) # Default Advisory # Reset Logic Tuning (Congress) self.logic_preset.setCurrentIndex(0) # Balanced self._apply_logic_preset(0) # Triggers slider/checkbox reset from config.settings import OHLC_DIR, INDICATORS_DIR, FEATURES_DIR, MODELS_DIR, SIGNALS_DIR self.ohlc_path.setText(str(OHLC_DIR)) self.indicators_path.setText(str(INDICATORS_DIR)) self.features_path.setText(str(FEATURES_DIR)) self.models_path.setText(str(MODELS_DIR)) # SMART DEFAULT: Aggressively try to detect Instance Path instance_signals = SIGNALS_DIR try: # Method 1: Use MT5Connector's method if self.app_controller and self.app_controller.mt5: data_path = self.app_controller.mt5.get_terminal_data_path() if data_path: instance_signals = Path(data_path) / "MQL5" / "Files" / "Cerebrum" / "Signals" else: # Method 2: Direct Import (Fallback) import MetaTrader5 as mt5 if mt5.initialize(): term_info = mt5.terminal_info() if term_info and term_info.data_path: instance_signals = Path(term_info.data_path) / "MQL5" / "Files" / "Cerebrum" / "Signals" except Exception: pass self.signals_path.setText(str(instance_signals)) def _open_logs_folder(self): """Open logs directory in explorer""" from config.settings import LOGS_DIR import os import subprocess if not LOGS_DIR.exists(): LOGS_DIR.mkdir(parents=True, exist_ok=True) try: # For Windows os.startfile(str(LOGS_DIR)) except Exception as e: logger.error(f"Failed to open logs folder: {e}") from PyQt6.QtWidgets import QMessageBox QMessageBox.warning(self, "Logs", f"Could not open logs folder:\n{LOGS_DIR}") # --- UI Helpers for Settings Panel --- def _create_separator(self): from PyQt6.QtWidgets import QFrame line = QFrame() line.setFrameShape(QFrame.Shape.HLine) line.setFrameShadow(QFrame.Shadow.Sunken) line.setStyleSheet("background: #374151; max-height: 1px;") return line def _groupbox_style(self): return """ QGroupBox { font-weight: bold; border: 1px solid #4b5563; border-radius: 6px; margin-top: 10px; padding-top: 15px; background: transparent; } QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 5px; color: #e5e7eb; font-size: 11px; } """ def _combo_style(self): return """ QComboBox { background: #252526; color: white; border: 1px solid #4b5563; padding: 5px; border-radius: 4px; font-size: 13px; } QComboBox::drop-down { border: none; width: 20px; } QComboBox::down-arrow { image: none; border-left: 5px solid transparent; border-right: 5px solid transparent; border-top: 5px solid #9ca3af; margin-right: 8px; } """ def _spinbox_style(self): return "QSpinBox { background: #252526; color: white; border: 1px solid #4b5563; padding: 5px; border-radius: 4px; font-size: 13px; }" def _create_info_badge(self, label, value, val_color): from PyQt6.QtWidgets import QWidget, QVBoxLayout, QLabel from PyQt6.QtGui import QFont badge = QWidget() l = QVBoxLayout(badge) l.setContentsMargins(0, 0, 0, 0) l.setSpacing(2) lbl = QLabel(label) lbl.setStyleSheet("color: #9ca3af; font-size: 10px; font-weight: bold;") val = QLabel(str(value)) val.setStyleSheet(f"color: {val_color}; font-size: 13px; font-weight: bold;") l.addWidget(lbl) l.addWidget(val) return badge