feat(analysis): skeleton modulo data_audit (dataclass + soglie)
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
"""Data quality audit over market_snapshots + option_chain_snapshots.
|
||||
|
||||
Pure functions: each takes a ``sqlite3.Connection`` and a UTC time
|
||||
window, returns a frozen dataclass. No side effects, no MCP, no
|
||||
writes. The CLI layer (``cli.audit``) is responsible for I/O and
|
||||
formatting.
|
||||
|
||||
Thresholds are module-level constants by design: the audit and the
|
||||
runtime live in different contexts and must not share operational
|
||||
parameters. To tune a threshold, edit this file.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sqlite3
|
||||
import statistics
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import UTC, datetime, timedelta
|
||||
from decimal import Decimal
|
||||
|
||||
__all__ = [
|
||||
"ChainAuditReport",
|
||||
"GapRecord",
|
||||
"MarketAuditReport",
|
||||
"audit_market_snapshots",
|
||||
"audit_option_chain",
|
||||
]
|
||||
|
||||
# Tick cadence + gap tolerance. Cron is */15; +5 min tolerance covers
|
||||
# late-arriving MCP responses.
|
||||
_TICK_INTERVAL_MIN: int = 15
|
||||
_GAP_THRESHOLD_MIN: int = 20
|
||||
|
||||
# fetch_ok=0 streak threshold: 1-2 are transient MCP failures, 3+ is a
|
||||
# pattern worth flagging.
|
||||
_FETCH_OK_STREAK_THRESHOLD: int = 3
|
||||
|
||||
# A numeric column with >10% NULL in the window is too unreliable for
|
||||
# backtesting that metric.
|
||||
_NULL_RATE_FLAG: Decimal = Decimal("0.10")
|
||||
|
||||
# Columns to NULL-audit on market_snapshots. fetch_ok / fetch_errors_json
|
||||
# are excluded (they are status fields, not metrics).
|
||||
_MARKET_NUMERIC_COLUMNS: tuple[str, ...] = (
|
||||
"spot",
|
||||
"dvol",
|
||||
"realized_vol_30d",
|
||||
"iv_minus_rv",
|
||||
"funding_perp_annualized",
|
||||
"funding_cross_annualized",
|
||||
"dealer_net_gamma",
|
||||
"gamma_flip_level",
|
||||
"oi_delta_pct_4h",
|
||||
"macro_days_to_event",
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class GapRecord:
|
||||
"""One gap between consecutive market_snapshots ticks."""
|
||||
|
||||
prev_timestamp: datetime
|
||||
next_timestamp: datetime
|
||||
gap_minutes: int
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class MarketAuditReport:
|
||||
asset: str
|
||||
since: datetime
|
||||
until: datetime
|
||||
expected_ticks: int
|
||||
actual_ticks: int
|
||||
coverage_pct: Decimal
|
||||
gaps: tuple[GapRecord, ...] = field(default_factory=tuple)
|
||||
fetch_ok_zero_count: int = 0
|
||||
max_fetch_ok_zero_streak: int = 0
|
||||
null_rate_by_column: dict[str, Decimal] = field(default_factory=dict)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ChainAuditReport:
|
||||
asset: str
|
||||
since: datetime
|
||||
until: datetime
|
||||
expected_snapshots: int
|
||||
actual_snapshots: int
|
||||
coverage_pct: Decimal
|
||||
quotes_per_snap_median: int = 0
|
||||
quotes_per_snap_p10: int = 0
|
||||
quotes_per_snap_p90: int = 0
|
||||
bid_gt_ask_count: int = 0
|
||||
iv_null_count: int = 0
|
||||
iv_null_pct: Decimal = Decimal("0")
|
||||
depth_zero_pct: Decimal = Decimal("0")
|
||||
Reference in New Issue
Block a user