Riflette la struttura effettiva del CLI: il group 'audit' contiene già 'verify' per la hash chain; il data-quality audit è entrato come sibling 'data'. Documentata la deviazione in linea. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6.7 KiB
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 data, 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 data`
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:
@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 data [--db PATH] [--since DAYS] [--json] [--asset ETH|BTC]
Deviazione dal design originale: il group
auditera già usato peraudit verify(hash chain dell'audit log). Il data-quality audit è stato inserito come siblingaudit dataper evitare collisione di namespace, anziché come comando top-levelcerbero-bite audit.
--db PATH(defaultdata/state.sqlite): path al DB SQLite.--since DAYS(default7): finestra di analisi, retro dalnow()corrente.--json(default off): stampa solo dump JSON serializzabile, niente tabelle umane.--asset(defaulttutti): filtra ad un singolo asset.
Exit code:
0: audit completato (a prescindere dai problemi trovati).2: errori di connessione/DB (DB inesistente, schema mancante).
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):
{
"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 viarun_migrations. - Seed deterministico: insert manuali per riprodurre lo scenario.
- Test cases:
- market: copertura piena → 100%; un gap iniettato → conteggio gap=1;
streak
fetch_ok=0lunga 3 → flagged. - chain: snap mancante → expected − actual = 1; quote dimezzate in
un tick → quotes/snap p10 cala;
bid=10 ask=5→ bid>ask=1.
- market: copertura piena → 100%; un gap iniettato → conteggio gap=1;
streak
- 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.