from __future__ import annotations from libs.intraday.features import ( compute_average_range, compute_average_true_range, compute_entropy_approx, compute_gap_zscore, enrich_daily_bars, ) def _daily_bar(day: str, open_: float, high: float, low: float, close: float, volume: float = 1_000_000.0) -> dict: return { "date": day, "open": open_, "high": high, "low": low, "close": close, "volume": volume, } def test_entropy_approx_is_bounded() -> None: bars = [] close = 100.0 for idx in range(30): close *= 1.0 + (0.01 if idx % 2 == 0 else -0.008) bars.append(_daily_bar(f"2024-01-{idx+1:02d}", close * 0.99, close * 1.01, close * 0.98, close)) entropy = compute_entropy_approx(bars, lookback=20) assert entropy is not None assert 0.0 <= entropy <= 1.0 def test_enrich_daily_bars_populates_new_research_features() -> None: ticker = "AAA" bars = [] close = 100.0 for idx in range(70): date_str = f"2024-03-{idx+1:02d}" if idx < 31 else f"2024-04-{idx-30:02d}" gap = 0.002 if idx % 3 == 0 else -0.001 open_ = close * (1.0 + gap) high = open_ * 1.02 low = open_ * 0.99 close = open_ * (1.0 + (0.004 if idx % 2 == 0 else -0.003)) bars.append(_daily_bar(date_str, open_, high, low, close)) trading_day = bars[-1]["date"] enriched = enrich_daily_bars({ticker: bars}, [trading_day]) features = enriched[ticker][trading_day] assert features["entropy_20d"] is not None assert 0.0 <= features["entropy_20d"] <= 1.0 assert features["atr_ratio_10_60"] is not None assert features["range_compression_10_60"] is not None assert features["gap_zscore_20d"] is not None def test_gap_zscore_and_range_helpers_return_values() -> None: bars = [] close = 50.0 for idx in range(65): open_ = close * (1.0 + 0.002) high = open_ * 1.03 low = open_ * 0.98 close = open_ * 1.001 bars.append(_daily_bar(f"2024-05-{idx+1:02d}", open_, high, low, close)) assert compute_average_true_range(bars, 10) is not None assert compute_average_range(bars, 10) is not None assert compute_gap_zscore(bars[:-1], today_open=bars[-1]["open"], lookback=20) is not None