diff --git a/docs/superpowers/specs/2026-05-12-data-quality-audit-design.md b/docs/superpowers/specs/2026-05-12-data-quality-audit-design.md new file mode 100644 index 0000000..78be3ea --- /dev/null +++ b/docs/superpowers/specs/2026-05-12-data-quality-audit-design.md @@ -0,0 +1,188 @@ +# Data Quality Audit — Design Spec + +**Date:** 2026-05-12 +**Status:** Approved (design phase) +**Author:** session-driven (operator + agent) + +## Motivation + +Prima di costruire il backtest non-stilizzato su `option_chain_snapshots` +(prossimo macro-step del progetto), serve confermare che i dati +raccolti negli ultimi 11 giorni (ETH) e 8 giorni (BTC) siano usabili: +copertura temporale piena, niente buchi sistemici, niente quote +malformate (bid > ask, IV mancante, depth book a zero). Lo stesso +audit dev'essere ri-eseguibile periodicamente come check operativo. + +`market_snapshots` rientra nello scope per simmetria (entrambe le +tabelle alimentano la decisione di entrata e il monitoring), mentre +`dvol_history` è escluso: appena migrato a multi-asset (commit +`19695e4`), serie troppo corta per BTC (29 righe al momento del +design) e copertura ETH già implicita in `market_snapshots`. + +## Scope + +**In scope:** +- `market_snapshots`: continuità temporale, fetch_ok streaks, NULL rate + per colonna numerica, parità ETH/BTC. +- `option_chain_snapshots`: snapshot mancanti, distribuzione quote per + snap, bid/ask sanity, IV null rate, book depth. +- CLI subcommand `cerbero-bite audit`, output stdout + opzionale `--json`. + +**Out of scope:** +- `dvol_history`, `decisions`, `positions`, `instructions`, + `manual_actions` (non rilevanti per il backtest non-stilizzato). +- Audit di consistenza cross-tabella (es: per ogni snapshot chain esiste + uno snapshot market) — interessante ma rinviato. +- Persistenza dei risultati audit nello stesso DB. + +## Architecture + +``` +src/cerbero_bite/analysis/ + __init__.py + data_audit.py # logica pura, no I/O lato MCP + +src/cerbero_bite/cli.py # nuovo subcommand `audit` + +tests/unit/ + test_data_audit.py # DB temporaneo + seed deterministico +``` + +`data_audit.py` espone funzioni pure che prendono una `sqlite3.Connection` +e una finestra temporale, ritornano `dataclass` di risultati. Il CLI +apre la connection in read-only, chiama le funzioni, formatta l'output. + +Funzioni principali: + +```python +@dataclass(frozen=True) +class MarketAuditReport: + asset: str + expected_ticks: int + actual_ticks: int + coverage_pct: Decimal + gaps_over_threshold: list[GapRecord] + fetch_ok_zero_count: int + max_fetch_ok_zero_streak: int + null_rate_by_column: dict[str, Decimal] + +@dataclass(frozen=True) +class ChainAuditReport: + asset: str + expected_snapshots: int + actual_snapshots: int + coverage_pct: Decimal + quotes_per_snap_median: int + quotes_per_snap_p10: int + quotes_per_snap_p90: int + bid_gt_ask_count: int + iv_null_count: int + iv_null_pct: Decimal + depth_zero_pct: Decimal + +def audit_market_snapshots(conn, *, asset, since, now) -> MarketAuditReport: ... +def audit_option_chain(conn, *, asset, since, now) -> ChainAuditReport: ... +``` + +## Checks & Thresholds + +| Tabella | Check | Soglia "bad" | Rationale | +|---|---|---|---| +| market_snapshots | gap tra tick consecutivi | > 20 min | cron è `*/15`; +5 min tolleranza | +| market_snapshots | streak `fetch_ok=0` | ≥ 3 consecutivi | 1-2 = transient MCP, 3+ = pattern | +| market_snapshots | NULL rate per colonna | > 10% nella finestra | una metrica con >10% NULL non è affidabile per backtest | +| option_chain_snapshots | snap mancanti | qualsiasi (count visibile) | cron `*/15`, ogni miss è significativo | +| option_chain_snapshots | quote/snap < 50% mediana 24h | qualsiasi | rilevatore di chain truncate (mismatch with width filter) | +| option_chain_snapshots | bid > ask | qualsiasi | dato corrotto, da indagare | +| option_chain_snapshots | IV null/non-parseable | conteggio + % | IV è chiave per BS skew calibration | +| option_chain_snapshots | `book_depth_top3 = 0` | % per snapshot | proxy di illiquidità | + +Le soglie sono costanti modulo (non config YAML) per ridurre il blast +radius dei cambi: il backtest e l'audit girano in contesti diversi, +non condividono parametri operativi. + +## CLI + +``` +cerbero-bite audit [--since DAYS] [--json] [--asset ETH|BTC] +``` + +- `--since DAYS` (default `7`): finestra di analisi, retro dal `now()` corrente. +- `--json` (default off): stampa solo dump JSON serializzabile, niente tabelle umane. +- `--asset` (default `tutti`): filtra ad un singolo asset. + +Exit code: +- `0`: audit completato (a prescindere dai problemi trovati). +- `2`: errori di connessione/DB (separare da problemi nei dati). + +Niente exit code per "found issues": l'audit è informativo, decide +l'umano. Far diventare l'audit un gate CI è out of scope. + +## Output + +**Stdout (default):** + +``` +=== ETH — market_snapshots (last 7d, 2026-05-05 → 2026-05-12) === + ticks: 672 expected: 672 coverage: 100.0% + gaps > 20min: 0 + fetch_ok=0: 4 rows (max streak: 1) + null rate: dealer_net_gamma 2.1% oi_delta_pct_4h 0.3% + +=== ETH — option_chain_snapshots (last 7d) === + snapshots: 672 expected: 672 coverage: 100.0% + quotes/snap: median 55 p10 50 p90 60 + bid > ask: 0 + IV null: 12 quotes (0.03%) + depth_top3 = 0: 1.2% of quotes + +=== BTC — ... +``` + +**JSON (`--json`):** + +```json +{ + "since": "2026-05-05T20:46:00+00:00", + "until": "2026-05-12T20:46:00+00:00", + "assets": { + "ETH": { + "market": {"expected_ticks": 672, "actual_ticks": 672, ...}, + "chain": {"expected_snapshots": 672, ...} + }, + "BTC": {...} + } +} +``` + +## Testing + +`tests/unit/test_data_audit.py`. Per ogni funzione: + +- DB temporaneo (`tmp_path`), schema migrato via `run_migrations`. +- Seed deterministico: insert manuali per riprodurre lo scenario. +- Test cases: + - market: copertura piena → 100%; un gap iniettato → conteggio gap=1; + streak `fetch_ok=0` lunga 3 → flagged. + - chain: snap mancante → expected − actual = 1; quote dimezzate in + un tick → quotes/snap p10 cala; `bid=10 ask=5` → bid>ask=1. +- 0 dipendenze nuove (sqlite + pytest standard). + +## Performance + +Tabelle attuali: ~57k quote chain. Le query usano gli index +`idx_option_chain_asset_ts` e `(asset, timestamp)` di +`market_snapshots`. L'audit deve girare in < 2s su 7gg. + +## Anti-goals (esplicito) + +- Nessun salvataggio dei risultati nello stato del DB. +- Nessun trigger automatico (no cron job, no APScheduler). +- Nessun alert/notifica: stdout + JSON sono lo strumento, l'operatore + decide cosa farne. +- Nessun ML / detection di anomalie sofisticate. Soglie costanti. + +## Open Questions + +Nessuna al momento della scrittura. Eventuali punti emergeranno durante +l'implementazione e andranno annotati qui.