Files
2026-06-18 10:16:51 +01:00

262 lines
8.9 KiB
Python

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