feat(state+runtime+gui): market_snapshots — calibrazione soglie da dati

Sistema dedicato di raccolta dati per scegliere le soglie dei filtri
sui percentili reali invece di valori a istinto.

Nuovi componenti:

* state/migrations/0003_market_snapshots.sql — tabella + index, PK
  composta (timestamp, asset). Ogni colonna numerica è NULL-able per
  preservare la continuità della serie quando un singolo MCP fallisce.
* state/models.py — MarketSnapshotRecord Pydantic.
* state/repository.py — record_market_snapshot, list_market_snapshots,
  _row_to_market_snapshot.
* runtime/market_snapshot_cycle.py — collettore best-effort che chiama
  spot/dvol/realized_vol/dealer_gamma/funding_perp/funding_cross/
  liquidation_heatmap/macro per ogni asset; raccoglie gli errori in
  fetch_errors_json e segna fetch_ok=false ma persiste comunque la
  riga.
* clients/deribit.py — generalizzati dealer_gamma_profile(currency),
  realized_vol(currency), spot_perp_price(asset). dealer_gamma_profile_eth
  resta come alias per la chiamata dell'entry cycle.
* runtime/orchestrator.py — nuovo job APScheduler `market_snapshot`
  cron */15 con assets configurabili (default ETH+BTC); il consumer
  manual_actions ora dispatcha anche kind=run_cycle cycle=market_snapshot
  per la GUI.
* gui/data_layer.py — load_market_snapshots, enqueue_run_cycle accetta
  market_snapshot; tipo MarketSnapshotRecord esposto.
* gui/pages/6_📐_Calibrazione.py — selezione asset+finestra, conteggio
  fetch_ok, per ogni metrica: istogramma, soglia da strategy.yaml come
  vline rossa, percentili P5/P10/P25/P50/P75/P90/P95, % di tick che la
  soglia avrebbe filtrato.
* gui/pages/1_📊_Status.py — bottone "📐 Forza snapshot" (4° del pannello
  Forza ciclo) per popolare la tabella senza aspettare il cron.

5 nuovi test sul collector (happy, fault tolerance, asset switch,
macro fail, empty assets); test_orchestrator job set aggiornato.
368/368 tests pass; ruff clean; mypy strict src clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-30 14:39:09 +02:00
parent 63d1aa4262
commit d9454fc996
11 changed files with 956 additions and 7 deletions
+30
View File
@@ -21,6 +21,7 @@ __all__ = [
"DvolSnapshot",
"InstructionRecord",
"ManualAction",
"MarketSnapshotRecord",
"PositionRecord",
"PositionStatus",
"SystemStateRecord",
@@ -118,6 +119,35 @@ class DvolSnapshot(BaseModel):
eth_spot: Decimal
class MarketSnapshotRecord(BaseModel):
"""Row of the ``market_snapshots`` table.
Single point in time, single asset. Every numeric field is
optional because the ``market_snapshot`` collector is best-effort:
a single MCP failure NULLs the affected metric without dropping
the row.
"""
model_config = ConfigDict(extra="forbid")
timestamp: datetime
asset: str # "ETH", "BTC"
spot: Decimal | None = None
dvol: Decimal | None = None
realized_vol_30d: Decimal | None = None
iv_minus_rv: Decimal | None = None
funding_perp_annualized: Decimal | None = None
funding_cross_annualized: Decimal | None = None
dealer_net_gamma: Decimal | None = None
gamma_flip_level: Decimal | None = None
oi_delta_pct_4h: Decimal | None = None
liquidation_long_risk: str | None = None
liquidation_short_risk: str | None = None
macro_days_to_event: int | None = None
fetch_ok: bool
fetch_errors_json: str | None = None
class ManualAction(BaseModel):
"""Row of the ``manual_actions`` table."""