Files
Cerbero-Bite/docs/superpowers/specs/2026-05-12-data-quality-audit-design.md
T
Adriano Dal Pastro 923acd79f6 docs(spec): rename CLI subcommand audit → audit data
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>
2026-05-16 19:46:54 +00:00

6.7 KiB
Raw Blame History

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 audit era già usato per audit verify (hash chain dell'audit log). Il data-quality audit è stato inserito come sibling audit data per evitare collisione di namespace, anziché come comando top-level cerbero-bite audit.

  • --db PATH (default data/state.sqlite): path al DB SQLite.
  • --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 (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 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.