feat: cockpit
This commit is contained in:
@@ -302,5 +302,5 @@ def remove_simulation(sim_id: str):
|
|||||||
|
|
||||||
@router.get("/watchlist-signals")
|
@router.get("/watchlist-signals")
|
||||||
def watchlist_signals():
|
def watchlist_signals():
|
||||||
from services.database import get_latest_wavelet_signals
|
from services.database import get_latest_wavelet_state_by_instrument
|
||||||
return {"signals": get_latest_wavelet_signals()}
|
return {"signals": get_latest_wavelet_state_by_instrument()}
|
||||||
|
|||||||
@@ -3622,6 +3622,36 @@ def get_latest_wavelet_signals() -> List[Dict]:
|
|||||||
return [dict(r) for r in rows]
|
return [dict(r) for r in rows]
|
||||||
|
|
||||||
|
|
||||||
|
def get_latest_wavelet_state_by_instrument() -> List[Dict]:
|
||||||
|
"""One row per Watchlist instrument from the most recent scan — a fired signal if this
|
||||||
|
run had one for it (any band), otherwise the fastest band's current slope/direction, so
|
||||||
|
every scanned instrument shows *something* here instead of only the ones with an active
|
||||||
|
trigger this run. get_latest_wavelet_signals() (fired-only) stays as-is for any other
|
||||||
|
caller; this is specifically for the Dashboard's Wavelets Signal card, which should
|
||||||
|
reflect the latest state per instrument, not just a list of firings."""
|
||||||
|
run_id = _latest_wavelet_run_id()
|
||||||
|
if not run_id:
|
||||||
|
return []
|
||||||
|
conn = get_conn()
|
||||||
|
rows = conn.execute(
|
||||||
|
"SELECT * FROM wavelet_watchlist_signals WHERE run_id=? AND band_label != 'ridge' "
|
||||||
|
"ORDER BY ticker, (signal_kind IS NULL), period_low_days ASC",
|
||||||
|
(run_id,),
|
||||||
|
).fetchall()
|
||||||
|
conn.close()
|
||||||
|
by_ticker: Dict[str, Dict] = {}
|
||||||
|
for r in rows:
|
||||||
|
d = dict(r)
|
||||||
|
by_ticker.setdefault(d["ticker"], d)
|
||||||
|
out = list(by_ticker.values())
|
||||||
|
# No fired signal this run for this band -> direction is null; fall back to the band's
|
||||||
|
# own slope sign so the card can still show an up/down arrow instead of a blank one.
|
||||||
|
for d in out:
|
||||||
|
if d.get("direction") is None and d.get("slope") is not None:
|
||||||
|
d["direction"] = "up" if d["slope"] > 0 else "down"
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
def get_latest_wavelet_state() -> List[Dict]:
|
def get_latest_wavelet_state() -> List[Dict]:
|
||||||
"""Every (ticker, band) row from the most recent cycle scan — signal or
|
"""Every (ticker, band) row from the most recent cycle scan — signal or
|
||||||
not. Used for the rich AI chat context block (slope/energy/ridge state)."""
|
not. Used for the rich AI chat context block (slope/energy/ridge state)."""
|
||||||
|
|||||||
@@ -1056,8 +1056,8 @@ export default function Dashboard() {
|
|||||||
<ArrowUpRight className="w-3 h-3 text-slate-600" />
|
<ArrowUpRight className="w-3 h-3 text-slate-600" />
|
||||||
</Link>
|
</Link>
|
||||||
{signals.length > 0 ? (
|
{signals.length > 0 ? (
|
||||||
<div className="space-y-1.5 mt-1">
|
<div className="space-y-1.5 mt-1 max-h-[135px] overflow-y-auto pr-0.5">
|
||||||
{signals.slice(0, 5).map((s: any, i: number) => (
|
{signals.map((s: any, i: number) => (
|
||||||
<div
|
<div
|
||||||
key={i}
|
key={i}
|
||||||
onDoubleClick={() => quickAddInstrument.mutate(s.ticker, {
|
onDoubleClick={() => quickAddInstrument.mutate(s.ticker, {
|
||||||
@@ -1073,7 +1073,7 @@ export default function Dashboard() {
|
|||||||
<span className="text-slate-200 font-bold shrink-0 truncate max-w-[90px]" title={s.ticker}>
|
<span className="text-slate-200 font-bold shrink-0 truncate max-w-[90px]" title={s.ticker}>
|
||||||
{nameByTicker[s.ticker] || s.ticker}
|
{nameByTicker[s.ticker] || s.ticker}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-slate-500 truncate flex-1 text-[9px]">{s.band_label} · {s.signal_kind}</span>
|
<span className="text-slate-500 truncate flex-1 text-[9px]">{s.band_label} · {s.signal_kind ?? 'veille'}</span>
|
||||||
{s.computed_at && <span className="text-[8px] text-slate-700 shrink-0 font-mono">{s.computed_at.slice(0, 10)}</span>}
|
{s.computed_at && <span className="text-[8px] text-slate-700 shrink-0 font-mono">{s.computed_at.slice(0, 10)}</span>}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|||||||
Reference in New Issue
Block a user