flash-crash-watchdog / ml /tests /test_cascade.py
Dev2506's picture
Add files using upload-large-folder tool
8035461 verified
Raw
History Blame Contribute Delete
2.05 kB
"""Tests for the 5-stage detection cascade."""
import pytest
from flash_crash_watchdog.cascade import DetectionCascade
from flash_crash_watchdog.lob import OrderBookSnapshot, PriceLevel
from flash_crash_watchdog.tick import Tick
def make_normal_tick(ts: int = 1000) -> Tick:
"""A tick that should NOT trigger an alert."""
return Tick(
book=OrderBookSnapshot(
timestamp_ms=ts,
bids=[PriceLevel(99.5, 1.0)],
asks=[PriceLevel(100.5, 1.0)],
),
symbol="BTCUSDT",
)
def make_anomalous_tick(ts: int = 1000) -> Tick:
"""A tick with extreme OBI that SHOULD trigger Stage 1."""
return Tick(
book=OrderBookSnapshot(
timestamp_ms=ts,
bids=[PriceLevel(99.5, 50.0)], # huge bid-side imbalance
asks=[PriceLevel(100.5, 0.1)],
),
symbol="BTCUSDT",
)
def test_stage1_rejects_normal_ticks():
"""Stage 1 should reject normal ticks quickly."""
from flash_crash_watchdog.models.stage1_statistical import Stage1Statistical
s1 = Stage1Statistical()
# Feed a lot of normal ticks to build baseline
for i in range(200):
score, passed = s1.score(make_normal_tick(ts=1000 + i))
# The latest tick should have a low score
assert score < 0.5
assert not passed
def test_cascade_processes_tick():
"""The full cascade should process a tick without error."""
cascade = DetectionCascade.from_config("configs/pipeline.yml")
# Process many normal ticks
for i in range(100):
result = cascade.process_tick(make_normal_tick(ts=1000 + i))
assert result is None # no alert for normal tick
assert cascade.stats.ticks_total == 100
def test_cascade_stats():
"""Cascade stats should be tracked correctly."""
cascade = DetectionCascade.from_config("configs/pipeline.yml")
for i in range(50):
cascade.process_tick(make_normal_tick(ts=1000 + i))
stats = cascade.stats
assert stats.ticks_total == 50
assert stats.total_latency_ms > 0