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))
|
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 _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 _flt(v, d=2): return "—" if _nan(v) else f"{v:.{d}f}"
|
||||||
def _price(v): return "—" if _nan(v) else f"${v:,.2f}"
|
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,
|
QMetaObject.invokeMethod(self._placeholder, "setText", Qt.QueuedConnection,
|
||||||
Q_ARG(str, f"No price history found for {ticker}."))
|
Q_ARG(str, f"No price history found for {ticker}."))
|
||||||
return
|
return
|
||||||
QMetaObject.invokeMethod(self, "_render_chart", Qt.QueuedConnection,
|
self._pending_hist = hist
|
||||||
Q_ARG(object, hist), Q_ARG(str, ticker))
|
self._pending_ticker = ticker
|
||||||
|
QMetaObject.invokeMethod(self, "_render_chart", Qt.QueuedConnection)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
QMetaObject.invokeMethod(self._placeholder, "setText", Qt.QueuedConnection,
|
QMetaObject.invokeMethod(self._placeholder, "setText", Qt.QueuedConnection,
|
||||||
Q_ARG(str, f"Chart error: {e}"))
|
Q_ARG(str, f"Chart error: {e}"))
|
||||||
|
|
||||||
@Slot(object, str)
|
@Slot()
|
||||||
def _render_chart(self, hist, ticker):
|
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
|
if not _MATPLOTLIB_OK: return
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
# Clear old canvas
|
# Clear old canvas
|
||||||
|
|
@ -1362,7 +1375,7 @@ def _prepare_row_dict(row):
|
||||||
ns = d.get("news_sentiment")
|
ns = d.get("news_sentiment")
|
||||||
|
|
||||||
if not _nan(total_debt) and not _nan(de_ratio) and de_ratio != 0:
|
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:
|
else:
|
||||||
d["_equity_fmt"] = "—"
|
d["_equity_fmt"] = "—"
|
||||||
|
|
||||||
|
|
@ -1624,7 +1637,7 @@ class DetailPanel(QWidget):
|
||||||
else:
|
else:
|
||||||
self._no_data_bar.hide()
|
self._no_data_bar.hide()
|
||||||
|
|
||||||
d = _derive_row_extras(row)
|
d = _prepare_row_dict(row)
|
||||||
|
|
||||||
# Update metric rows
|
# Update metric rows
|
||||||
for key, row_w in self._all_rows.items():
|
for key, row_w in self._all_rows.items():
|
||||||
|
|
@ -2401,8 +2414,8 @@ class SearchPage(QWidget):
|
||||||
if row is not None:
|
if row is not None:
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
if isinstance(row, pd.Series): row = row.to_dict()
|
if isinstance(row, pd.Series): row = row.to_dict()
|
||||||
QMetaObject.invokeMethod(self, "_show_result", Qt.QueuedConnection,
|
self._pending_result = (row, f"Found in screener data: {row['ticker']}")
|
||||||
Q_ARG(object, row), Q_ARG(str, f"Found in screener data: {row['ticker']}"))
|
QMetaObject.invokeMethod(self, "_show_result", Qt.QueuedConnection)
|
||||||
return
|
return
|
||||||
|
|
||||||
QMetaObject.invokeMethod(self._search_status, "setText", Qt.QueuedConnection, Q_ARG(str, "Fetching data…"))
|
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]
|
if col in s: live_row[col] = s[col]
|
||||||
except Exception: pass
|
except Exception: pass
|
||||||
|
|
||||||
QMetaObject.invokeMethod(self, "_show_result", Qt.QueuedConnection,
|
self._pending_result = (live_row, f"Live data: {live_row['ticker']}")
|
||||||
Q_ARG(object, live_row), Q_ARG(str, f"Live data: {live_row['ticker']}"))
|
QMetaObject.invokeMethod(self, "_show_result", Qt.QueuedConnection)
|
||||||
|
|
||||||
def _find_in_df(self, query):
|
def _find_in_df(self, query):
|
||||||
if self._df is None or self._df.empty: return None
|
if self._df is None or self._df.empty: return None
|
||||||
|
|
@ -2523,8 +2536,11 @@ class SearchPage(QWidget):
|
||||||
}
|
}
|
||||||
except: return None
|
except: return None
|
||||||
|
|
||||||
@Slot(object, str)
|
@Slot()
|
||||||
def _show_result(self, row, status_msg):
|
def _show_result(self):
|
||||||
|
row, status_msg = getattr(self, "_pending_result", (None, ""))
|
||||||
|
if row is None:
|
||||||
|
return
|
||||||
self._last_row = row
|
self._last_row = row
|
||||||
self._search_status.setText(status_msg)
|
self._search_status.setText(status_msg)
|
||||||
self._status2.setText(status_msg)
|
self._status2.setText(status_msg)
|
||||||
|
|
@ -2644,12 +2660,14 @@ class InsiderTable(QWidget):
|
||||||
def _worker():
|
def _worker():
|
||||||
try: trades = fetch_insider_trades(days_back=days, progress_cb=_progress)
|
try: trades = fetch_insider_trades(days_back=days, progress_cb=_progress)
|
||||||
except: trades = []
|
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()
|
threading.Thread(target=_worker, daemon=True).start()
|
||||||
|
|
||||||
@Slot(object)
|
@Slot()
|
||||||
def _on_fetched(self, trades):
|
def _on_fetched(self):
|
||||||
|
trades = getattr(self, "_pending_trades", [])
|
||||||
self._loading = False; self._has_loaded = True
|
self._loading = False; self._has_loaded = True
|
||||||
self._all_trades = trades
|
self._all_trades = trades
|
||||||
self._refresh_btn.setEnabled(True)
|
self._refresh_btn.setEnabled(True)
|
||||||
|
|
@ -2801,12 +2819,14 @@ class HedgeFundTable(QWidget):
|
||||||
def _worker():
|
def _worker():
|
||||||
try: holdings = fetch_hedge_fund_filings(days_back=days, progress_cb=_progress)
|
try: holdings = fetch_hedge_fund_filings(days_back=days, progress_cb=_progress)
|
||||||
except: holdings = []
|
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()
|
threading.Thread(target=_worker, daemon=True).start()
|
||||||
|
|
||||||
@Slot(object)
|
@Slot()
|
||||||
def _on_fetched(self, holdings):
|
def _on_fetched(self):
|
||||||
|
holdings = getattr(self, "_pending_holdings", [])
|
||||||
self._loading = False; self._has_loaded = True
|
self._loading = False; self._has_loaded = True
|
||||||
self._all_holdings = holdings; self._refresh_btn.setEnabled(True)
|
self._all_holdings = holdings; self._refresh_btn.setEnabled(True)
|
||||||
self._apply_filter()
|
self._apply_filter()
|
||||||
|
|
@ -2958,12 +2978,11 @@ def main():
|
||||||
app = QApplication(sys.argv)
|
app = QApplication(sys.argv)
|
||||||
app.setStyleSheet(_QSS)
|
app.setStyleSheet(_QSS)
|
||||||
win = ScreenerApp()
|
win = ScreenerApp()
|
||||||
win.show()
|
# Move to primary screen before maximizing so it doesn't span to secondary
|
||||||
# Force window onto primary screen
|
|
||||||
primary = app.primaryScreen()
|
primary = app.primaryScreen()
|
||||||
geo = primary.availableGeometry()
|
geo = primary.availableGeometry()
|
||||||
win.move(geo.x() + (geo.width() - win.width()) // 2,
|
win.move(geo.x(), geo.y())
|
||||||
geo.y() + (geo.height() - win.height()) // 2)
|
win.showMaximized()
|
||||||
sys.exit(app.exec())
|
sys.exit(app.exec())
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue