Aggiunge la persistence della option chain Deribit con cron settimanale
``55 13 * * MON`` (5 minuti prima del trigger entry alle 14:00 UTC),
sbloccando il backtest non-stilizzato e la calibrazione empirica
dello skew premium.
**Schema (migrazione 0004)**
Nuova tabella ``option_chain_snapshots`` con primary key composta
``(timestamp, instrument_name)`` — tutti i quote prelevati nello
stesso tick condividono il timestamp, così le query "lo snapshot del
2026-05-04 alle 13:55" diventano una singola WHERE timestamp = X.
Indici su (asset, timestamp DESC) e (asset, expiry) per supportare
sia listing recenti sia query per scadenza specifica.
Campi: instrument_name, strike, expiry, option_type (C/P), bid, ask,
mid, iv, delta, gamma, theta, vega, open_interest, volume_24h,
book_depth_top3. Tutti i numerici sono nullable: il collector è
best-effort, un ticker mancante produce comunque una riga (utile
per sapere che lo strumento esisteva ma non era quotato).
**Modello + repository**
- ``OptionChainQuoteRecord`` (Pydantic, in ``state/models.py``).
- ``Repository.record_option_chain_snapshot`` (bulk insert
idempotente).
- ``Repository.list_option_chain_snapshots`` (filtri su asset,
timestamp window, expiry window, limit default 50000).
- ``Repository.latest_option_chain_timestamp`` (freshness check
per dashboard GUI).
**Collector**
Nuovo ``runtime/option_chain_snapshot_cycle.py`` che:
1. Calcola la finestra scadenze ``[now+dte_min, now+dte_max]`` da
``cfg.structure``: niente richieste su scadenze che il rule
engine non userebbe mai.
2. Chiama ``deribit.options_chain()`` con
``min_open_interest=cfg.liquidity.open_interest_min``.
3. Batch ``deribit.get_tickers()`` (max 20 per call, limite Deribit)
con error-isolation per batch — un batch fallito non blocca
gli altri.
4. NON chiama l'order book per ogni strike (rate-limit guard);
``book_depth_top3`` resta NULL e il liquidity gate live lo
chiede on-the-fly per gli strike candidati al picker.
Best-effort end-to-end: chain assente, get_tickers giù, persist
fallito → ritorna 0 senza alzare eccezioni, logga sempre.
**Schedulazione**
Wired in ``Orchestrator.install_scheduler`` come job parallelo a
``market_snapshot``, attivo solo quando
``ENABLE_DATA_ANALYSIS=true``. Cron parametrizzabile via il nuovo
kwarg ``option_chain_cron`` (default ``55 13 * * MON``).
**Test**
- 4 unit test del collector (happy path, ticker mancante, chain
vuota, fetch fail best-effort) con mock di RuntimeContext.
- Aggiornato ``test_install_scheduler_registers_canonical_jobs``
per includere il nuovo job nel set canonico.
**Cosa sblocca**
- Backtest non-stilizzato: il PR ``feat/backtest-engine`` può
dropparsi il modello BS+skew_premium e leggere prezzi reali
``mid`` dalla chain registrata.
- Calibrazione empirica dello skew premium (hardcoded a 1.5 nel
backtest stilizzato): plot del rapporto fra quote reali Deribit
e BS per delta/expiry, regressione → valore data-driven.
- Validazione ex-post: "il delta-0.12 era davvero a 25% OTM in
quella settimana?" diventa una query SELECT.
- Dimensione attesa: ~50 strike × 3 scadenze × 1 snapshot/settimana
× 17 colonne ≈ 12 KB/settimana, ~600 KB/anno. Trascurabile.
Suite: 409 passed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>