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 <noreply@anthropic.com>
This commit is contained in:
parent
df032593bd
commit
d756bc99ce
|
|
@ -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())
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue