layer one v4

This commit is contained in:
saber
2026-06-18 09:35:03 +01:00
parent e28f2db97c
commit 5d4ffb1da5
29 changed files with 1056 additions and 2067 deletions
+99 -77
View File
@@ -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}")