""" APEX Professional Trading System — Main Application Window Assembles all 6 tabs: - Tab 1: Dashboard (Layer 1 fundamental signals) - Tab 2: Monthly Entry (CPI + PMI data input) - Tab 3: Layer 2 Monitor (real-time technical analysis) - Tab 4: Confluence Signals (merged Layer 1 + Layer 2) - Tab 5: History (past signals) - Tab 6: Settings (configuration) Responsibilities: - Create QMainWindow with QTabWidget - Instantiate all UI tabs - Manage database connection - Run FRED API fetch in background thread - Run Alpha Vantage real-time data fetching - Connect inter-tab signals - Handle window events and cleanup """ from PyQt5.QtWidgets import QMainWindow, QTabWidget, QMessageBox from PyQt5.QtCore import QThread, pyqtSignal, QTimer from typing import Dict, Optional import config from database import Database from layer2_technical import TechnicalAnalyzer from currency_strength_matrix import CurrencyStrengthMatrix from ui.dashboard_tab import DashboardTab from ui.entry_tab import MonthlyEntryTab from ui.layer2_monitor_tab import Layer2MonitorTab from ui.confluence_tab import ConfluenceSignalsTab from ui.history_tab import HistoryTab from ui.settings_tab import SettingsTab from fred_client import FredClient class FredFetchWorker(QThread): """Background worker thread for fetching rates from FRED API.""" # Signals rates_fetched = pyqtSignal(dict) # Emitted with {currency: rate} dict error_occurred = pyqtSignal(str) # Emitted on error def __init__(self, db: Database): """ Initialize FRED fetch worker. Args: db: Database instance to save rates """ super().__init__() self.db = db self.client = FredClient() def run(self): """ Fetch rates from FRED API and save to database. """ try: if config.DEBUG: print("[Worker] Starting FRED rate fetch...") rates = self.client.fetch_all_rates() # Save to database for currency, rate in rates.items(): if rate is not None: self.db.upsert_rate(currency, rate, source="FRED") if config.DEBUG: print("[Worker] FRED fetch complete") self.rates_fetched.emit(rates) except Exception as e: error_msg = f"FRED fetch error: {str(e)}" if config.DEBUG: print(f"[Worker] {error_msg}") self.error_occurred.emit(error_msg) class MainWindow(QMainWindow): """Main application window — Professional hybrid trading system.""" def __init__(self): """Initialize main window.""" super().__init__() # Initialize database try: self.db = Database() except Exception as e: QMessageBox.critical( self, "Database Error", f"Failed to initialize database: {e}\n\nPlease check your configuration." ) raise # Initialize Layer 2 components bars = config.TIMEFRAMES[config.DEFAULT_TIMEFRAME]["bars"] self.tech_analyzer = TechnicalAnalyzer(lookback=bars) # UI components self.dashboard_tab = None self.entry_tab = None self.layer2_tab = None self.confluence_tab = None self.history_tab = None self.settings_tab = None # Worker threads self.fred_worker = None self._init_ui() self._connect_signals() self._setup_auto_fetch() self._setup_live_signal_timer() def _init_ui(self): """Build the main window UI.""" self.setWindowTitle(config.APP_TITLE) self.setGeometry(100, 100, config.WINDOW_WIDTH, config.WINDOW_HEIGHT) # Tab widget tabs = QTabWidget() # Tab 1: Dashboard (Live + Macro Backdrop) self.dashboard_tab = DashboardTab(self.db, tech_analyzer=self.tech_analyzer) tabs.addTab(self.dashboard_tab, config.TAB_NAMES["dashboard"]) # Tab 2: Monthly Entry (Data input) self.entry_tab = MonthlyEntryTab(self.db) tabs.addTab(self.entry_tab, config.TAB_NAMES["entry"]) # Tab 3: Layer 2 Monitor (Real-time technical) self.layer2_tab = Layer2MonitorTab(self.tech_analyzer) tabs.addTab(self.layer2_tab, config.TAB_NAMES["layer2"]) # Tab 4: Confluence Signals (Layer 1 + Layer 2) self.confluence_tab = ConfluenceSignalsTab(self.db, self.tech_analyzer) tabs.addTab(self.confluence_tab, config.TAB_NAMES["confluence"]) # Tab 5: History self.history_tab = HistoryTab(self.db) tabs.addTab(self.history_tab, config.TAB_NAMES["history"]) # Tab 6: Settings self.settings_tab = SettingsTab() tabs.addTab(self.settings_tab, config.TAB_NAMES["settings"]) # Set main widget self.setCentralWidget(tabs) def _connect_signals(self): """Connect inter-tab signals.""" # Entry tab saves data → Dashboard tab refreshes self.entry_tab.data_saved.connect(self.dashboard_tab.on_data_saved) # Entry tab saves data → History tab refreshes self.entry_tab.data_saved.connect(self.history_tab.refresh_history) # Dashboard generates signal → Confluence tab receives signal self.dashboard_tab.signal_generated.connect(self._on_dashboard_signal) # Dashboard requests FRED fetch → Start worker thread self.dashboard_tab.fetch_rates_requested.connect(self._fetch_rates) def _setup_auto_fetch(self): """Auto-fetch rates on startup if enabled.""" if config.AUTO_FETCH_RATES_ON_STARTUP: if config.DEBUG: print("[Main] Auto-fetch enabled, fetching rates on startup...") self._fetch_rates() def _setup_live_signal_timer(self): """Periodically refresh the live intraday signal on the dashboard.""" self._live_signal_timer = QTimer() self._live_signal_timer.timeout.connect(self._tick_live_signal) self._live_signal_timer.start(3000) def _tick_live_signal(self): """Refresh live signal on dashboard.""" if self.dashboard_tab: self.dashboard_tab.update_live_signal() def _fetch_rates(self): """ Trigger background FRED rate fetch. Emits results to dashboard when complete. """ if self.fred_worker is not None and self.fred_worker.isRunning(): # Already fetching return self.fred_worker = FredFetchWorker(self.db) self.fred_worker.rates_fetched.connect(self._on_rates_fetched) self.fred_worker.error_occurred.connect(self._on_fetch_error) self.fred_worker.start() def _on_rates_fetched(self, rates: Dict[str, Optional[float]]): """ Handle successful FRED fetch. Args: rates: Dict mapping currency to rate """ if config.DEBUG: print("[Main] Rates fetched successfully, updating dashboard...") # Update dashboard display self.dashboard_tab.on_rates_updated(rates) def _on_fetch_error(self, error_msg: str): """ Handle FRED fetch error. Args: error_msg: Error message string """ print(f"[ERROR] {error_msg}") # Don't show error message to user; display gracefully in dashboard def _on_dashboard_signal(self, strongest: str, weakest: str, gap: float, bias_matrix: dict = None): """ Handle dashboard signal generation. Pass to confluence tab for Layer 2 analysis. Args: strongest: Strongest currency weakest: Weakest currency gap: Gap score bias_matrix: Monthly directional bias matrix from Layer 1 """ if config.DEBUG: print(f"[Main] Signal generated: {strongest}/{weakest} gap={gap:.1f}") # Update confluence tab with new Layer 1 signal + bias matrix self.confluence_tab.set_layer1_signal(strongest, weakest, gap, bias_matrix) def closeEvent(self, event): """Handle window close event.""" try: # Stop any running threads if self.fred_worker is not None and self.fred_worker.isRunning(): self.fred_worker.quit() self.fred_worker.wait() # Stop Layer 2 monitoring if self.layer2_tab: self.layer2_tab.closeEvent(event) # Close database self.db.close() event.accept() except Exception as e: print(f"[ERROR] Error during shutdown: {e}") event.accept()