fix(analysis): distingui ask<=0 (missing ask) da bid>ask (cross reale)

Nuovo campo ChainAuditReport.ask_zero_count. La logica del loop
parsifica prima l'ask: se ask<=0 è "missing ask side" (su Deribit
significa nessun ordine in vendita), conta in ask_zero_count e non
applica il check bid>ask. Solo con ask>0 si confronta con bid.

Sul DB prod gli "bid>ask" del primo audit (27 ETH + 10 BTC negli
ultimi 7d) erano tutti falsi positivi con ask=0 — concentrati sulla
weekly 2026-05-29, OI alti ma volume basso. Dopo il fix: bid>ask=0,
ask<=0=83 ETH + 13 BTC.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Adriano Dal Pastro
2026-05-16 19:59:16 +00:00
parent 374822a931
commit 16d73c309a
4 changed files with 76 additions and 3 deletions
+55
View File
@@ -303,6 +303,7 @@ def test_audit_chain_full_coverage(tmp_path: Path) -> None:
assert report.quotes_per_snap_p10 == 10
assert report.quotes_per_snap_p90 == 10
assert report.bid_gt_ask_count == 0
assert report.ask_zero_count == 0
assert report.iv_null_count == 0
assert report.iv_null_pct == Decimal("0")
@@ -346,11 +347,65 @@ def test_audit_chain_detects_bid_gt_ask_and_iv_null(tmp_path: Path) -> None:
finally:
conn.close()
assert report.bid_gt_ask_count == 1
assert report.ask_zero_count == 0
assert report.iv_null_count == 1
# 1 of 3 quotes → 33.33%
assert report.iv_null_pct == Decimal("33.33")
def test_audit_chain_ask_zero_counted_separately(tmp_path: Path) -> None:
conn = _make_conn(tmp_path)
since = datetime(2026, 5, 12, 12, 0, tzinfo=UTC)
until = datetime(2026, 5, 12, 12, 30, tzinfo=UTC)
try:
with transaction(conn):
ts = since.replace(minute=0)
# ask=0 → conta in ask_zero, NON in bid_gt_ask
_seed_chain_row(
conn,
asset="ETH",
ts=ts,
instrument="ETH-A",
bid="0.05",
ask="0",
)
# ask negativo (impossibile economicamente) → ask_zero
_seed_chain_row(
conn,
asset="ETH",
ts=ts,
instrument="ETH-B",
strike="3050",
bid="0.05",
ask="-0.01",
)
# Crossed BBO reale (ask>0) → bid_gt_ask
_seed_chain_row(
conn,
asset="ETH",
ts=ts,
instrument="ETH-C",
strike="3100",
bid="0.10",
ask="0.05",
)
# Normal control
_seed_chain_row(
conn,
asset="ETH",
ts=ts,
instrument="ETH-D",
strike="3150",
)
report = audit_option_chain(
conn, asset="ETH", since=since, until=until
)
finally:
conn.close()
assert report.ask_zero_count == 2
assert report.bid_gt_ask_count == 1
def test_audit_chain_missing_snapshots(tmp_path: Path) -> None:
conn = _make_conn(tmp_path)
since = datetime(2026, 5, 12, 12, 0, tzinfo=UTC)