feat: cockpit

This commit is contained in:
OpenSquared
2026-07-24 15:52:58 +02:00
parent 3ec0c024ad
commit 7cb81cbd65
6 changed files with 211 additions and 19 deletions

View File

@@ -223,6 +223,22 @@ def init_db():
price_at_signal REAL
)""",
"ALTER TABLE cycle_runs ADD COLUMN wavelet_signals_count INTEGER DEFAULT 0",
# Wavelets — full band-decomposition series from the same per-cycle watchlist scan
# (see wavelet_watchlist_signals above, which only keeps the latest scalar
# slope/energy/signal per band). One row per ticker, overwritten every cycle — this
# is what Instrument Analysis's Wavelet tab reads by default so opening it doesn't
# trigger a live recompute (services.wavelet_signals.scan_watchlist_wavelet_signals
# writes both tables together from the exact same decomposition run).
"""CREATE TABLE IF NOT EXISTS wavelet_decomposition_cache (
ticker TEXT PRIMARY KEY,
run_id TEXT,
computed_at TEXT DEFAULT (datetime('now')),
method TEXT,
wavelet TEXT,
num_levels INTEGER,
lookback_days REAL,
decomposition_json TEXT
)""",
# AI Chat widget — persisted conversation turns, one growing thread per session_id
"""CREATE TABLE IF NOT EXISTS ai_chat_messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -3631,6 +3647,40 @@ def get_wavelet_signals_history(ticker: str, days: int = 30) -> List[Dict]:
return [dict(r) for r in rows]
def save_wavelet_decomposition_cache(
ticker: str, run_id: Optional[str], method: str, wavelet: str,
num_levels: int, lookback_days: float, decomposition: Dict,
) -> None:
"""Overwrites this ticker's cached full band-decomposition — one row per ticker, not a
history (services.wavelet_signals.scan_watchlist_wavelet_signals calls this once per
cycle, right after computing the same decomposition object it also flattens into
wavelet_watchlist_signals)."""
conn = get_conn()
conn.execute(
"INSERT INTO wavelet_decomposition_cache (ticker, run_id, computed_at, method, wavelet, num_levels, lookback_days, decomposition_json) "
"VALUES (?, ?, datetime('now'), ?, ?, ?, ?, ?) "
"ON CONFLICT(ticker) DO UPDATE SET run_id=excluded.run_id, computed_at=excluded.computed_at, method=excluded.method, "
"wavelet=excluded.wavelet, num_levels=excluded.num_levels, lookback_days=excluded.lookback_days, "
"decomposition_json=excluded.decomposition_json",
(ticker.upper(), run_id, method, wavelet, num_levels, lookback_days, json.dumps(decomposition)),
)
conn.commit()
conn.close()
def get_wavelet_decomposition_cache(ticker: str) -> Optional[Dict]:
conn = get_conn()
row = conn.execute(
"SELECT * FROM wavelet_decomposition_cache WHERE ticker = ? COLLATE NOCASE", (ticker,)
).fetchone()
conn.close()
if not row:
return None
d = dict(row)
d["decomposition"] = json.loads(d.pop("decomposition_json")) if d.get("decomposition_json") else None
return d
# ── AI Chat widget — persisted conversation ────────────────────────────────────
def save_chat_message(session_id: str, role: str, content: str) -> None: