feat(analysis): skeleton modulo data_audit (dataclass + soglie)
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
"""Analysis utilities — pure functions over the state DB.
|
||||||
|
|
||||||
|
Modules here read SQLite, never write. They are ergonomic to call
|
||||||
|
from CLI commands, notebooks, or one-off scripts.
|
||||||
|
"""
|
||||||
@@ -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