From d756bc99ce677fa70e0434a5ced991ffd7e3afab Mon Sep 17 00:00:00 2001 From: Nolan Kovacs Date: Wed, 8 Apr 2026 19:40:32 -0400 Subject: [PATCH] Fix detail panel: 5 bugs causing all fields to show dashes - _derive_row_extras -> _prepare_row_dict (undefined function call) - Define _GREEN_KEYS (NameError crashed _color_for_key loop) - Fix Q_ARG(object,...) in chart, search, insider trades, hedge funds - Fix total equity 100x overstatement (de_ratio already a true ratio) - Launch maximized on primary monitor instead of centered/split Co-Authored-By: Claude Sonnet 4.6 --- dist/screener_gui.py | 63 ++++++++++++++++++++++++++++---------------- 1 file changed, 41 insertions(+), 22 deletions(-) diff --git a/dist/screener_gui.py b/dist/screener_gui.py index 96eadb2..d9aee96 100644 --- a/dist/screener_gui.py +++ b/dist/screener_gui.py @@ -99,6 +99,14 @@ QToolTip {{ background: #1c1c1c; color: {fg}; border: 1px solid #333333; padding # --------------------------------------------------------------------------- def _nan(v): return v is None or (isinstance(v, float) and math.isnan(v)) + +_GREEN_KEYS = { + "ret_1m", "ret_3m", "ret_6m", "ret_12m", + "revenue_growth", "earnings_growth", + "roe", "roa", "fcf_yield", "dividend_yield", + "analyst_upside", "profit_margin", "operating_margin", + "eps_forward", "eps_trailing", +} def _pct(v): return "—" if _nan(v) else f"{v * 100:.1f}%" def _flt(v, d=2): return "—" if _nan(v) else f"{v:.{d}f}" def _price(v): return "—" if _nan(v) else f"${v:,.2f}" @@ -1197,14 +1205,19 @@ class ChartWidget(QWidget): QMetaObject.invokeMethod(self._placeholder, "setText", Qt.QueuedConnection, Q_ARG(str, f"No price history found for {ticker}.")) return - QMetaObject.invokeMethod(self, "_render_chart", Qt.QueuedConnection, - Q_ARG(object, hist), Q_ARG(str, ticker)) + self._pending_hist = hist + self._pending_ticker = ticker + QMetaObject.invokeMethod(self, "_render_chart", Qt.QueuedConnection) except Exception as e: QMetaObject.invokeMethod(self._placeholder, "setText", Qt.QueuedConnection, Q_ARG(str, f"Chart error: {e}")) - @Slot(object, str) - def _render_chart(self, hist, ticker): + @Slot() + def _render_chart(self): + hist = getattr(self, "_pending_hist", None) + ticker = getattr(self, "_pending_ticker", "") + if hist is None: + return if not _MATPLOTLIB_OK: return import pandas as pd # Clear old canvas @@ -1362,7 +1375,7 @@ def _prepare_row_dict(row): ns = d.get("news_sentiment") if not _nan(total_debt) and not _nan(de_ratio) and de_ratio != 0: - d["_equity_fmt"] = _mcap(total_debt / (de_ratio / 100.0)) + d["_equity_fmt"] = _mcap(total_debt / de_ratio) else: d["_equity_fmt"] = "—" @@ -1624,7 +1637,7 @@ class DetailPanel(QWidget): else: self._no_data_bar.hide() - d = _derive_row_extras(row) + d = _prepare_row_dict(row) # Update metric rows for key, row_w in self._all_rows.items(): @@ -2401,8 +2414,8 @@ class SearchPage(QWidget): if row is not None: import pandas as pd if isinstance(row, pd.Series): row = row.to_dict() - QMetaObject.invokeMethod(self, "_show_result", Qt.QueuedConnection, - Q_ARG(object, row), Q_ARG(str, f"Found in screener data: {row['ticker']}")) + self._pending_result = (row, f"Found in screener data: {row['ticker']}") + QMetaObject.invokeMethod(self, "_show_result", Qt.QueuedConnection) return QMetaObject.invokeMethod(self._search_status, "setText", Qt.QueuedConnection, Q_ARG(str, "Fetching data…")) @@ -2435,8 +2448,8 @@ class SearchPage(QWidget): if col in s: live_row[col] = s[col] except Exception: pass - QMetaObject.invokeMethod(self, "_show_result", Qt.QueuedConnection, - Q_ARG(object, live_row), Q_ARG(str, f"Live data: {live_row['ticker']}")) + self._pending_result = (live_row, f"Live data: {live_row['ticker']}") + QMetaObject.invokeMethod(self, "_show_result", Qt.QueuedConnection) def _find_in_df(self, query): if self._df is None or self._df.empty: return None @@ -2523,8 +2536,11 @@ class SearchPage(QWidget): } except: return None - @Slot(object, str) - def _show_result(self, row, status_msg): + @Slot() + def _show_result(self): + row, status_msg = getattr(self, "_pending_result", (None, "")) + if row is None: + return self._last_row = row self._search_status.setText(status_msg) self._status2.setText(status_msg) @@ -2644,12 +2660,14 @@ class InsiderTable(QWidget): def _worker(): try: trades = fetch_insider_trades(days_back=days, progress_cb=_progress) except: trades = [] - QMetaObject.invokeMethod(self, "_on_fetched", Qt.QueuedConnection, Q_ARG(object, trades)) + self._pending_trades = trades + QMetaObject.invokeMethod(self, "_on_fetched", Qt.QueuedConnection) threading.Thread(target=_worker, daemon=True).start() - @Slot(object) - def _on_fetched(self, trades): + @Slot() + def _on_fetched(self): + trades = getattr(self, "_pending_trades", []) self._loading = False; self._has_loaded = True self._all_trades = trades self._refresh_btn.setEnabled(True) @@ -2801,12 +2819,14 @@ class HedgeFundTable(QWidget): def _worker(): try: holdings = fetch_hedge_fund_filings(days_back=days, progress_cb=_progress) except: holdings = [] - QMetaObject.invokeMethod(self, "_on_fetched", Qt.QueuedConnection, Q_ARG(object, holdings)) + self._pending_holdings = holdings + QMetaObject.invokeMethod(self, "_on_fetched", Qt.QueuedConnection) threading.Thread(target=_worker, daemon=True).start() - @Slot(object) - def _on_fetched(self, holdings): + @Slot() + def _on_fetched(self): + holdings = getattr(self, "_pending_holdings", []) self._loading = False; self._has_loaded = True self._all_holdings = holdings; self._refresh_btn.setEnabled(True) self._apply_filter() @@ -2958,12 +2978,11 @@ def main(): app = QApplication(sys.argv) app.setStyleSheet(_QSS) win = ScreenerApp() - win.show() - # Force window onto primary screen + # Move to primary screen before maximizing so it doesn't span to secondary primary = app.primaryScreen() geo = primary.availableGeometry() - win.move(geo.x() + (geo.width() - win.width()) // 2, - geo.y() + (geo.height() - win.height()) // 2) + win.move(geo.x(), geo.y()) + win.showMaximized() sys.exit(app.exec())