mirror of
https://github.com/Sabermrddz/QuantCore-FX.git
synced 2026-08-17 12:48:07 +00:00
layer one v4
This commit is contained in:
+99
-77
@@ -19,10 +19,10 @@ Display:
|
||||
|
||||
from PyQt5.QtWidgets import (
|
||||
QWidget, QVBoxLayout, QHBoxLayout, QLabel, QTableWidget, QTableWidgetItem,
|
||||
QFrame, QPushButton, QSpinBox
|
||||
QFrame, QPushButton, QProgressBar, QHeaderView
|
||||
)
|
||||
from PyQt5.QtCore import Qt, pyqtSignal, QSize
|
||||
from PyQt5.QtGui import QColor, QFont, QBrush, QPixmap
|
||||
from PyQt5.QtCore import Qt, pyqtSignal
|
||||
from PyQt5.QtGui import QColor, QFont
|
||||
from typing import Dict, Optional
|
||||
from datetime import datetime
|
||||
import config
|
||||
@@ -57,14 +57,16 @@ class DashboardTab(QWidget):
|
||||
def _init_ui(self):
|
||||
"""Build the UI layout."""
|
||||
layout = QVBoxLayout()
|
||||
layout.setSpacing(12)
|
||||
|
||||
# ====== Signal Card ======
|
||||
signal_card = self._build_signal_card()
|
||||
layout.addWidget(signal_card)
|
||||
layout.addSpacing(15)
|
||||
|
||||
# ====== Ranked Score Table ======
|
||||
layout.addWidget(QLabel("Currency Rankings"))
|
||||
heading = QLabel("Currency Rankings")
|
||||
heading.setProperty("heading", True)
|
||||
layout.addWidget(heading)
|
||||
|
||||
self.score_table = QTableWidget()
|
||||
self.score_table.setColumnCount(8)
|
||||
@@ -76,22 +78,35 @@ class DashboardTab(QWidget):
|
||||
self.score_table.setSelectionBehavior(QTableWidget.SelectRows)
|
||||
self.score_table.setSelectionMode(QTableWidget.SingleSelection)
|
||||
|
||||
# Store progress bars for strength column (column 7)
|
||||
self.strength_bars = {}
|
||||
|
||||
# Pre-fill with placeholder rows
|
||||
for row in range(len(config.CURRENCIES)):
|
||||
for col in range(8):
|
||||
item = QTableWidgetItem("—")
|
||||
item.setFlags(item.flags() & ~Qt.ItemIsEditable)
|
||||
self.score_table.setItem(row, col, item)
|
||||
# Add progress bar for strength column
|
||||
bar = QProgressBar()
|
||||
bar.setRange(0, 100)
|
||||
bar.setValue(0)
|
||||
bar.setTextVisible(True)
|
||||
bar.setFormat("")
|
||||
self.score_table.setCellWidget(row, 7, bar)
|
||||
self.strength_bars[row] = bar
|
||||
|
||||
self.score_table.resizeColumnsToContents()
|
||||
for col in range(7):
|
||||
self.score_table.horizontalHeader().setSectionResizeMode(col, QHeaderView.Stretch)
|
||||
self.score_table.setColumnWidth(7, 140)
|
||||
layout.addWidget(self.score_table)
|
||||
layout.addSpacing(15)
|
||||
|
||||
# ====== Refresh Button ======
|
||||
button_layout = QHBoxLayout()
|
||||
button_layout.addStretch()
|
||||
|
||||
self.refresh_btn = QPushButton("Refresh")
|
||||
self.refresh_btn.setObjectName("secondary")
|
||||
self.refresh_btn.clicked.connect(self._refresh_display)
|
||||
button_layout.addWidget(self.refresh_btn)
|
||||
|
||||
@@ -107,42 +122,41 @@ class DashboardTab(QWidget):
|
||||
def _build_signal_card(self) -> QFrame:
|
||||
"""Build the signal card frame."""
|
||||
card = QFrame()
|
||||
card.setStyleSheet("""
|
||||
QFrame {
|
||||
background-color: #f8f9fa;
|
||||
border: 2px solid #dee2e6;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
}
|
||||
""")
|
||||
card.setObjectName("statusCard")
|
||||
|
||||
layout = QVBoxLayout()
|
||||
layout.setSpacing(6)
|
||||
|
||||
# Title
|
||||
title = QLabel("PRIMARY SIGNAL")
|
||||
title.setFont(QFont("Arial", 10, QFont.Bold))
|
||||
title.setStyleSheet("color: #495057;")
|
||||
title.setProperty("subheading", True)
|
||||
layout.addWidget(title)
|
||||
layout.addSpacing(5)
|
||||
|
||||
# Signal text (large, bold)
|
||||
self.signal_label = QLabel("NO TRADE — Initializing...")
|
||||
self.signal_label.setFont(QFont("Arial", 24, QFont.Bold))
|
||||
self.signal_label.setProperty("value", True)
|
||||
self.signal_label.setStyleSheet("color: #2c3e50;")
|
||||
layout.addWidget(self.signal_label)
|
||||
layout.addSpacing(10)
|
||||
|
||||
# Gap and status
|
||||
self.gap_label = QLabel("Gap: — points")
|
||||
self.gap_label.setFont(QFont("Arial", 12))
|
||||
self.gap_label.setStyleSheet("font-size: 15px; color: #5d6d7e;")
|
||||
layout.addWidget(self.gap_label)
|
||||
|
||||
# Updated timestamp
|
||||
self.updated_label = QLabel("Updated: —")
|
||||
self.updated_label.setFont(QFont("Arial", 10))
|
||||
self.updated_label.setStyleSheet("color: #7f8c8d;")
|
||||
self.updated_label.setStyleSheet("color: #95a5a6; font-size: 12px;")
|
||||
layout.addWidget(self.updated_label)
|
||||
|
||||
# Staleness warning (hidden by default)
|
||||
self.stale_warning = QLabel("")
|
||||
self.stale_warning.setStyleSheet(
|
||||
"color: #e74c3c; font-weight: 700; font-size: 13px; padding: 6px 0;"
|
||||
)
|
||||
self.stale_warning.hide()
|
||||
layout.addWidget(self.stale_warning)
|
||||
|
||||
layout.addStretch()
|
||||
card.setLayout(layout)
|
||||
return card
|
||||
|
||||
@@ -168,14 +182,11 @@ class DashboardTab(QWidget):
|
||||
bias_matrix = {}
|
||||
self.signal_generated.emit(strongest, weakest, gap, bias_matrix)
|
||||
|
||||
# Update signal label
|
||||
self.signal_label.setText(signal_text)
|
||||
|
||||
# Color code based on status
|
||||
if status == "ACTIVE":
|
||||
self.signal_label.setStyleSheet("color: #27ae60;") # Green
|
||||
self.signal_label.setStyleSheet("color: #27ae60;")
|
||||
else:
|
||||
self.signal_label.setStyleSheet("color: #e74c3c;") # Red
|
||||
self.signal_label.setStyleSheet("color: #e74c3c;")
|
||||
|
||||
# Update gap label
|
||||
gap_tier = scorer.get_gap_tier(gap)
|
||||
@@ -195,6 +206,9 @@ class DashboardTab(QWidget):
|
||||
# Update timestamp
|
||||
self.updated_label.setText(f"Updated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||||
|
||||
# Check for stale CPI/PMI data
|
||||
self._check_data_staleness()
|
||||
|
||||
# Refresh score table
|
||||
self._refresh_score_table()
|
||||
|
||||
@@ -203,28 +217,54 @@ class DashboardTab(QWidget):
|
||||
self.signal_label.setText("ERROR")
|
||||
self.signal_label.setStyleSheet("color: #e74c3c;")
|
||||
|
||||
def _check_data_staleness(self):
|
||||
"""Show a warning if CPI/PMI data is older than 35 days."""
|
||||
try:
|
||||
monthly = self.db.get_monthly_data(self.current_month)
|
||||
if not monthly:
|
||||
return
|
||||
timestamps = []
|
||||
for ccy, data in monthly.items():
|
||||
ts = data.get("entered_at")
|
||||
if ts:
|
||||
timestamps.append(ts)
|
||||
if not timestamps:
|
||||
return
|
||||
latest = max(timestamps)
|
||||
try:
|
||||
latest_dt = datetime.strptime(latest.split(".")[0], "%Y-%m-%dT%H:%M:%S")
|
||||
except ValueError:
|
||||
latest_dt = datetime.strptime(latest[:10], "%Y-%m-%d")
|
||||
days_old = (datetime.now() - latest_dt).days
|
||||
if days_old > 35:
|
||||
self.stale_warning.setText(
|
||||
f"⚠ CPI/PMI data is {days_old} days old — refresh fundamental data"
|
||||
)
|
||||
self.stale_warning.show()
|
||||
else:
|
||||
self.stale_warning.hide()
|
||||
except Exception:
|
||||
self.stale_warning.hide()
|
||||
|
||||
def _refresh_score_table(self):
|
||||
"""Refresh the ranked currency table."""
|
||||
try:
|
||||
scores = self.db.get_month_scores(self.current_month)
|
||||
|
||||
if not scores:
|
||||
# No scores yet
|
||||
for row in range(len(config.CURRENCIES)):
|
||||
for col in range(8):
|
||||
self.score_table.item(row, col).setText("—")
|
||||
self.strength_bars[row].setValue(0)
|
||||
self.strength_bars[row].setFormat("")
|
||||
return
|
||||
|
||||
# Get sorted list
|
||||
ranked = [(c, s) for c, s in sorted(
|
||||
scores.items(),
|
||||
key=lambda x: x[1]['rank']
|
||||
)]
|
||||
|
||||
# Get rates for display
|
||||
rates = self.db.get_all_rates()
|
||||
|
||||
# Get monthly data for CPI display
|
||||
monthly_data = self.db.get_monthly_data(self.current_month)
|
||||
|
||||
for row, (currency, score_data) in enumerate(ranked):
|
||||
@@ -235,29 +275,15 @@ class DashboardTab(QWidget):
|
||||
cpi = cpi_data.get('cpi_actual')
|
||||
pmi = cpi_data.get('pmi_actual')
|
||||
|
||||
# Rank
|
||||
self.score_table.item(row, 0).setText(str(rank))
|
||||
|
||||
# Currency
|
||||
currency_text = f"{config.CURRENCY_EMOJIS.get(currency, '')} {currency}"
|
||||
self.score_table.item(row, 1).setText(currency_text)
|
||||
|
||||
# Rate
|
||||
rate_text = f"{rate:.2f}" if rate is not None else "—"
|
||||
self.score_table.item(row, 2).setText(rate_text)
|
||||
|
||||
# CPI
|
||||
cpi_text = f"{cpi:.2f}" if cpi is not None else "—"
|
||||
self.score_table.item(row, 3).setText(cpi_text)
|
||||
|
||||
# PMI
|
||||
pmi_text = f"{pmi:.1f}" if pmi is not None else "—"
|
||||
self.score_table.item(row, 4).setText(pmi_text)
|
||||
|
||||
# Score (two decimals)
|
||||
self.score_table.item(row, 2).setText(f"{rate:.2f}" if rate is not None else "—")
|
||||
self.score_table.item(row, 3).setText(f"{cpi:.2f}" if cpi is not None else "—")
|
||||
self.score_table.item(row, 4).setText(f"{pmi:.1f}" if pmi is not None else "—")
|
||||
self.score_table.item(row, 5).setText(f"{total_score:.1f}")
|
||||
|
||||
# Signal tag (BUY for strongest, SELL for weakest)
|
||||
# Signal tag
|
||||
if rank == 1:
|
||||
self.score_table.item(row, 6).setText("BUY")
|
||||
elif rank == len(config.CURRENCIES):
|
||||
@@ -265,34 +291,30 @@ class DashboardTab(QWidget):
|
||||
else:
|
||||
self.score_table.item(row, 6).setText("")
|
||||
|
||||
# Strength bar (visual progress 0-100)
|
||||
strength_item = self.score_table.item(row, 7)
|
||||
strength_item.setText(f"{int(total_score)}%")
|
||||
# Strength progress bar
|
||||
bar = self.strength_bars[row]
|
||||
score_int = min(int(total_score), 100)
|
||||
bar.setValue(score_int)
|
||||
bar.setFormat(f"{score_int}%")
|
||||
bar.setStyleSheet(
|
||||
"QProgressBar::chunk { background: qlineargradient(x1:0, y1:0, x2:1, y2:0, "
|
||||
"stop:0 #2ecc71, stop:1 #27ae60); border-radius: 4px; }"
|
||||
if rank == 1 else
|
||||
"QProgressBar::chunk { background: qlineargradient(x1:0, y1:0, x2:1, y2:0, "
|
||||
"stop:0 #e74c3c, stop:1 #c0392b); border-radius: 4px; }"
|
||||
if rank == len(config.CURRENCIES) else
|
||||
""
|
||||
)
|
||||
|
||||
# Color code rows
|
||||
if rank == 1:
|
||||
# Strongest = GREEN
|
||||
for col in range(8):
|
||||
self.score_table.item(row, col).setBackground(QColor("#d5f4e6"))
|
||||
self.score_table.item(row, col).setForeground(QColor("#27ae60"))
|
||||
self.score_table.item(row, col).setFont(QFont("Arial", 10, QFont.Bold))
|
||||
# Row color coding
|
||||
bg = QColor("#d5f4e6") if rank == 1 else QColor("#fadbd8") if rank == len(config.CURRENCIES) else QColor("#ffffff")
|
||||
fg = QColor("#1e8449") if rank == 1 else QColor("#c0392b") if rank == len(config.CURRENCIES) else QColor("#2c3e50")
|
||||
font = QFont("Segoe UI", 11, QFont.Bold) if rank in (1, len(config.CURRENCIES)) else QFont("Segoe UI", 11)
|
||||
|
||||
elif rank == len(config.CURRENCIES):
|
||||
# Weakest = RED
|
||||
for col in range(8):
|
||||
self.score_table.item(row, col).setBackground(QColor("#fadbd8"))
|
||||
self.score_table.item(row, col).setForeground(QColor("#e74c3c"))
|
||||
self.score_table.item(row, col).setFont(QFont("Arial", 10, QFont.Bold))
|
||||
|
||||
else:
|
||||
# Middle = neutral
|
||||
for col in range(8):
|
||||
self.score_table.item(row, col).setBackground(QColor("#ffffff"))
|
||||
self.score_table.item(row, col).setForeground(QColor("#2c3e50"))
|
||||
self.score_table.item(row, col).setFont(QFont("Arial", 10))
|
||||
|
||||
# Auto-resize columns to content
|
||||
self.score_table.resizeColumnsToContents()
|
||||
for col in range(7):
|
||||
self.score_table.item(row, col).setBackground(bg)
|
||||
self.score_table.item(row, col).setForeground(fg)
|
||||
self.score_table.item(row, col).setFont(font)
|
||||
|
||||
except Exception as e:
|
||||
print(f"[ERROR] Failed to refresh score table: {e}")
|
||||
|
||||
Reference in New Issue
Block a user