from __future__ import annotations import pyarrow as pa import pyarrow.parquet as pq from libs.intraday.cache import DailyBarCache, IntradayCache def test_legacy_cache_file_is_treated_as_miss_and_removed(tmp_path) -> None: cache = IntradayCache(str(tmp_path)) cache_path = tmp_path / "AAPL" / "2026-01-05.parquet" cache_path.parent.mkdir(parents=True, exist_ok=True) legacy = pa.table({ "timestamp": ["2026-01-05T13:30:00Z"], "open": [100.0], "high": [101.0], "low": [99.5], "close": [100.5], "volume": [1000.0], "vwap": [100.4], }) pq.write_table(legacy, str(cache_path)) assert cache.has("AAPL", "2026-01-05") is False assert cache.get("AAPL", "2026-01-05") is None assert cache_path.exists() is False def test_cache_put_writes_metadata_valid_file(tmp_path) -> None: cache = IntradayCache(str(tmp_path)) bars = [] for i in range(10): bars.append({ "timestamp": f"2026-01-05T13:{30 + i:02d}:00Z", "open": 100.0, "high": 101.0, "low": 99.5, "close": 100.5, "volume": 1000.0, "vwap": 100.4, }) cache.put("AAPL", "2026-01-05", bars) assert cache.has("AAPL", "2026-01-05") is True assert cache.get("AAPL", "2026-01-05") == bars def test_intraday_cache_rejects_too_few_rows(tmp_path) -> None: cache = IntradayCache(str(tmp_path)) cache_path = tmp_path / "AAPL" / "2026-01-05.parquet" cache_path.parent.mkdir(parents=True, exist_ok=True) tiny = pa.table({ "timestamp": [f"2026-01-05T13:{30+i:02d}:00Z" for i in range(5)], "open": [100.0] * 5, "high": [101.0] * 5, "low": [99.5] * 5, "close": [100.5] * 5, "volume": [1000.0] * 5, "vwap": [100.4] * 5, }, schema=pa.schema([ pa.field("timestamp", pa.string()), pa.field("open", pa.float64()), pa.field("high", pa.float64()), pa.field("low", pa.float64()), pa.field("close", pa.float64()), pa.field("volume", pa.float64()), pa.field("vwap", pa.float64()), ]).with_metadata({ b"intraday_cache_version": b"3", b"intraday_cache_source": b"api_v1_alpaca_intraday", b"intraday_cache_interval": b"5min", })) pq.write_table(tiny, str(cache_path)) assert cache.has("AAPL", "2026-01-05") is False assert cache_path.exists() is False def test_intraday_cache_accepts_existing_positive_file_without_kind_metadata(tmp_path) -> None: cache = IntradayCache(str(tmp_path)) cache_path = tmp_path / "AAPL" / "2026-01-05.parquet" cache_path.parent.mkdir(parents=True, exist_ok=True) rows = 12 table = pa.table({ "timestamp": [f"2026-01-05T13:{30+i:02d}:00Z" for i in range(rows)], "open": [100.0] * rows, "high": [101.0] * rows, "low": [99.5] * rows, "close": [100.5] * rows, "volume": [1000.0] * rows, "vwap": [100.4] * rows, }, schema=pa.schema([ pa.field("timestamp", pa.string()), pa.field("open", pa.float64()), pa.field("high", pa.float64()), pa.field("low", pa.float64()), pa.field("close", pa.float64()), pa.field("volume", pa.float64()), pa.field("vwap", pa.float64()), ]).with_metadata({ b"intraday_cache_version": b"3", b"intraday_cache_source": b"api_v1_alpaca_intraday", b"intraday_cache_interval": b"5min", })) pq.write_table(table, str(cache_path)) assert cache.has("AAPL", "2026-01-05") is True assert len(cache.get("AAPL", "2026-01-05") or []) == rows def test_intraday_cache_negative_entry_suppresses_rereads(tmp_path) -> None: cache = IntradayCache(str(tmp_path)) cache.put_negative("AAPL", "2026-01-05", reason="sparse") assert cache.has("AAPL", "2026-01-05") is True assert cache.get("AAPL", "2026-01-05") == [] def test_daily_cache_put_and_get_uses_requested_coverage_range(tmp_path) -> None: cache = DailyBarCache(str(tmp_path)) bars = [ { "date": "2026-01-02", "open": 100.0, "high": 101.0, "low": 99.0, "close": 100.5, "volume": 1000.0, }, { "date": "2026-01-05", "open": 101.0, "high": 102.0, "low": 100.0, "close": 101.5, "volume": 1200.0, }, ] cache.put("AAPL", "2026-01-01", "2026-01-10", bars) assert cache.get("AAPL", "2026-01-01", "2026-01-10") == bars assert cache.get("AAPL", "2026-01-02", "2026-01-05") == bars assert cache.get("AAPL", "2025-12-31", "2026-01-10") is None def test_daily_cache_rejects_egregiously_partial_warmup_file(tmp_path) -> None: cache = DailyBarCache(str(tmp_path)) cache_path = tmp_path / "BLD.parquet" rows = [] for i in range(11): rows.append( { "date": f"2026-04-{6 + i:02d}", "open": 100.0 + i, "high": 101.0 + i, "low": 99.0 + i, "close": 100.5 + i, "volume": 1_000.0 + i, } ) schema = pa.schema([ pa.field("date", pa.string()), pa.field("open", pa.float64()), pa.field("high", pa.float64()), pa.field("low", pa.float64()), pa.field("close", pa.float64()), pa.field("volume", pa.float64()), ]).with_metadata({ b"daily_cache_version": b"1", b"daily_cache_source": b"api_v1_price_data", b"daily_cache_interval": b"1d", b"daily_cache_coverage_start": b"2026-01-20", b"daily_cache_coverage_end": b"2026-04-20", }) pq.write_table(pa.Table.from_pylist(rows, schema=schema), str(cache_path)) assert cache.get("BLD", "2026-01-20", "2026-04-20") is None assert cache_path.exists() is False