"""
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