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:
@@ -29,6 +29,10 @@ from cerbero_bite.runtime.entry_cycle import EntryCycleResult, run_entry_cycle
|
||||
from cerbero_bite.runtime.health_check import HealthCheck, HealthCheckResult
|
||||
from cerbero_bite.runtime.lockfile import EngineLock
|
||||
from cerbero_bite.runtime.manual_actions_consumer import consume_manual_actions
|
||||
from cerbero_bite.runtime.market_snapshot_cycle import (
|
||||
DEFAULT_ASSETS,
|
||||
collect_market_snapshot,
|
||||
)
|
||||
from cerbero_bite.runtime.monitor_cycle import MonitorCycleResult, run_monitor_cycle
|
||||
from cerbero_bite.runtime.recovery import recover_state
|
||||
from cerbero_bite.runtime.scheduler import JobSpec, build_scheduler
|
||||
@@ -47,6 +51,7 @@ _CRON_MONITOR = "0 2,14 * * *"
|
||||
_CRON_HEALTH = "*/5 * * * *"
|
||||
_CRON_BACKUP = "0 * * * *"
|
||||
_CRON_MANUAL_ACTIONS = "*/1 * * * *"
|
||||
_CRON_MARKET_SNAPSHOT = "*/15 * * * *"
|
||||
_BACKUP_RETENTION_DAYS = 30
|
||||
|
||||
|
||||
@@ -194,6 +199,8 @@ class Orchestrator:
|
||||
health_cron: str = _CRON_HEALTH,
|
||||
backup_cron: str = _CRON_BACKUP,
|
||||
manual_actions_cron: str = _CRON_MANUAL_ACTIONS,
|
||||
market_snapshot_cron: str = _CRON_MARKET_SNAPSHOT,
|
||||
market_snapshot_assets: tuple[str, ...] = DEFAULT_ASSETS,
|
||||
backup_dir: Path | None = None,
|
||||
backup_retention_days: int = _BACKUP_RETENTION_DAYS,
|
||||
) -> AsyncIOScheduler:
|
||||
@@ -232,6 +239,11 @@ class Orchestrator:
|
||||
|
||||
await _safe("backup", _do)
|
||||
|
||||
async def _run_market_snapshot_via_action() -> None:
|
||||
await collect_market_snapshot(
|
||||
self._ctx, assets=market_snapshot_assets
|
||||
)
|
||||
|
||||
async def _manual_actions() -> None:
|
||||
async def _do() -> None:
|
||||
await consume_manual_actions(
|
||||
@@ -240,11 +252,20 @@ class Orchestrator:
|
||||
"entry": self.run_entry,
|
||||
"monitor": self.run_monitor,
|
||||
"health": self.run_health,
|
||||
"market_snapshot": _run_market_snapshot_via_action,
|
||||
},
|
||||
)
|
||||
|
||||
await _safe("manual_actions", _do)
|
||||
|
||||
async def _market_snapshot() -> None:
|
||||
async def _do() -> None:
|
||||
await collect_market_snapshot(
|
||||
self._ctx, assets=market_snapshot_assets
|
||||
)
|
||||
|
||||
await _safe("market_snapshot", _do)
|
||||
|
||||
self._scheduler = build_scheduler(
|
||||
[
|
||||
JobSpec(name="entry", cron=entry_cron, coro_factory=_entry),
|
||||
@@ -256,6 +277,11 @@ class Orchestrator:
|
||||
cron=manual_actions_cron,
|
||||
coro_factory=_manual_actions,
|
||||
),
|
||||
JobSpec(
|
||||
name="market_snapshot",
|
||||
cron=market_snapshot_cron,
|
||||
coro_factory=_market_snapshot,
|
||||
),
|
||||
]
|
||||
)
|
||||
return self._scheduler
|
||||
|
||||
Reference in New Issue
Block a user