docs(spec): data quality audit design (chain + market_snapshots)
Spec del comando CLI `cerbero-bite audit`: copertura temporale, gap, fetch_ok streaks, NULL rate per market_snapshots; snap mancanti, quote/snap, bid>ask, IV null, depth zero per option_chain_snapshots. Output stdout + opzionale --json. Pre-requisito al backtest non-stilizzato. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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.
|
||||
Reference in New Issue
Block a user