feat: curve regime

This commit is contained in:
OpenSquared
2026-07-25 09:23:26 +02:00
parent 3addea6850
commit ad7ab35d1d
8 changed files with 177 additions and 31 deletions

View File

@@ -310,6 +310,20 @@ def init_db():
"ALTER TABLE instrument_overrides ADD COLUMN name TEXT",
"ALTER TABLE instrument_overrides ADD COLUMN yf_ticker TEXT",
"ALTER TABLE instrument_overrides ADD COLUMN category TEXT",
# Curve Regime — one row per Watchlist ticker, overwritten every refresh pass (see
# services/wavelet_scheduler.py, same cadence as the wavelet cache). Instrument-level
# regime (services.curve_regime), NOT the global macro regime shown elsewhere. Feeds
# the Dashboard's "Curve Regime" card so it never needs a live classify call per view.
"""CREATE TABLE IF NOT EXISTS curve_regime_cache (
ticker TEXT PRIMARY KEY,
computed_at TEXT DEFAULT (datetime('now')),
regime_key TEXT,
regime_label TEXT,
score REAL,
direction TEXT,
profile_json TEXT,
has_options_data INTEGER DEFAULT 0
)""",
]:
try:
c.execute(_sql)
@@ -3721,6 +3735,48 @@ def get_wavelet_decomposition_cache(ticker: str) -> Optional[Dict]:
return d
def save_curve_regime_cache(ticker: str, classification: Dict) -> None:
top = classification.get("top_match") or {}
conn = get_conn()
conn.execute(
"INSERT INTO curve_regime_cache (ticker, computed_at, regime_key, regime_label, score, direction, profile_json, has_options_data) "
"VALUES (?, datetime('now'), ?, ?, ?, ?, ?, ?) "
"ON CONFLICT(ticker) DO UPDATE SET computed_at=excluded.computed_at, regime_key=excluded.regime_key, "
"regime_label=excluded.regime_label, score=excluded.score, direction=excluded.direction, "
"profile_json=excluded.profile_json, has_options_data=excluded.has_options_data",
(
ticker.upper(), top.get("key"), top.get("label"), top.get("score"),
(classification.get("profile") or {}).get("direction"),
json.dumps(classification), 1 if classification.get("has_options_data") else 0,
),
)
conn.commit()
conn.close()
def get_all_curve_regime_cache() -> List[Dict]:
"""One row per Watchlist ticker, in Watchlist sort order — a data=None placeholder for
any ticker never successfully classified yet, same "always show every Watchlist
instrument" convention as get_latest_wavelet_state_by_instrument()."""
conn = get_conn()
rows = conn.execute("SELECT * FROM curve_regime_cache").fetchall()
conn.close()
by_ticker = {r["ticker"]: dict(r) for r in rows}
out: List[Dict] = []
for item in get_instruments_watchlist():
ticker = item["ticker"].upper()
row = by_ticker.get(ticker)
if row:
row["profile"] = json.loads(row.pop("profile_json")).get("profile") if row.get("profile_json") else None
out.append(row)
else:
out.append({
"ticker": ticker, "computed_at": None, "regime_key": None, "regime_label": None,
"score": None, "direction": None, "profile": None, "has_options_data": 0,
})
return out
# ── AI Chat widget — persisted conversation ────────────────────────────────────
def save_chat_message(session_id: str, role: str, content: str) -> None: