feat(analysis): audit_market_snapshots — coverage, gap, fetch_ok, NULL rate
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -153,3 +153,71 @@ def _detect_gaps(timestamps: list[datetime]) -> tuple[GapRecord, ...]:
|
||||
)
|
||||
)
|
||||
return tuple(out)
|
||||
|
||||
|
||||
def _fetch_market_rows(
|
||||
conn: sqlite3.Connection,
|
||||
*,
|
||||
asset: str,
|
||||
since: datetime,
|
||||
until: datetime,
|
||||
) -> list[sqlite3.Row]:
|
||||
cols = ", ".join(("timestamp", "fetch_ok", *_MARKET_NUMERIC_COLUMNS))
|
||||
rows = conn.execute(
|
||||
f"SELECT {cols} FROM market_snapshots "
|
||||
"WHERE asset = ? AND timestamp >= ? AND timestamp < ? "
|
||||
"ORDER BY timestamp ASC",
|
||||
(asset, since.isoformat(), until.isoformat()),
|
||||
).fetchall()
|
||||
return list(rows)
|
||||
|
||||
|
||||
def _compute_null_rate(
|
||||
rows: list[sqlite3.Row], columns: tuple[str, ...]
|
||||
) -> dict[str, Decimal]:
|
||||
if not rows:
|
||||
return {c: Decimal("0") for c in columns}
|
||||
total = Decimal(len(rows))
|
||||
out: dict[str, Decimal] = {}
|
||||
for c in columns:
|
||||
nulls = sum(1 for r in rows if r[c] is None)
|
||||
out[c] = (Decimal(nulls) / total).quantize(Decimal("0.0001"))
|
||||
return out
|
||||
|
||||
|
||||
def audit_market_snapshots(
|
||||
conn: sqlite3.Connection,
|
||||
*,
|
||||
asset: str,
|
||||
since: datetime,
|
||||
until: datetime,
|
||||
) -> MarketAuditReport:
|
||||
"""Compute the market_snapshots audit report for an asset in [since, until)."""
|
||||
rows = _fetch_market_rows(conn, asset=asset, since=since, until=until)
|
||||
timestamps = [datetime.fromisoformat(r["timestamp"]) for r in rows]
|
||||
expected = _expected_ticks(since, until)
|
||||
actual = len(rows)
|
||||
coverage = (
|
||||
(Decimal(actual) / Decimal(expected) * Decimal("100")).quantize(
|
||||
Decimal("0.01")
|
||||
)
|
||||
if expected > 0
|
||||
else Decimal("0")
|
||||
)
|
||||
gaps = _detect_gaps(timestamps)
|
||||
fetch_ok_flags = [int(r["fetch_ok"]) for r in rows]
|
||||
fetch_ok_zero_count = sum(1 for v in fetch_ok_flags if v == 0)
|
||||
max_streak = _max_zero_streak(fetch_ok_flags)
|
||||
null_rates = _compute_null_rate(rows, _MARKET_NUMERIC_COLUMNS)
|
||||
return MarketAuditReport(
|
||||
asset=asset,
|
||||
since=since,
|
||||
until=until,
|
||||
expected_ticks=expected,
|
||||
actual_ticks=actual,
|
||||
coverage_pct=coverage,
|
||||
gaps=gaps,
|
||||
fetch_ok_zero_count=fetch_ok_zero_count,
|
||||
max_fetch_ok_zero_streak=max_streak,
|
||||
null_rate_by_column=null_rates,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user