"""Day-9 tests — bootstrap estimators.""" from __future__ import annotations import numpy as np import pandas as pd import pytest from src.bootstrap import ( BootstrapCI, cluster_bootstrap_did, cluster_bootstrap_mean_car, stationary_bootstrap, ) def _make_did_panel( delta: float = 0.01, n_treated: int = 8, n_controls: int = 8, n_per: int = 60 ) -> pd.DataFrame: rng = np.random.default_rng(0) rows: list[dict[str, float | int | str]] = [] treated_ids = [f"T{i}" for i in range(n_treated)] control_ids = [f"C{i}" for i in range(n_controls)] for tk in treated_ids + control_ids: for t in range(-n_per // 2, n_per // 2): ret = rng.normal(0, 0.005) if tk in treated_ids and t >= 0: ret += delta rows.append({"ticker": tk, "t": t, "ret": ret}) return pd.DataFrame(rows) def test_bootstrap_ci_has_required_fields() -> None: ci = BootstrapCI( metric="x", scope="y", point=0.5, lower=0.4, upper=0.6, n_replications=100 ) assert ci.metric == "x" and ci.scope == "y" assert 0.4 <= ci.point <= 0.6 assert ci.lower_pct == 5.0 and ci.upper_pct == 95.0 def test_cluster_bootstrap_did_recovers_delta() -> None: panel = _make_did_panel(delta=0.01, n_treated=8, n_controls=8, n_per=80) res = cluster_bootstrap_did( panel, treated={f"T{i}" for i in range(8)}, controls={f"C{i}" for i in range(8)}, metric_name="did_delta", scope="toy", n_reps=200, # small for unit test speed seed=42, ) assert res.point == pytest.approx(0.01, abs=3e-3) # CI should contain the true δ at 5–95 assert res.lower < 0.01 < res.upper def test_cluster_bootstrap_did_ci_excludes_zero_when_signal_strong() -> None: panel = _make_did_panel(delta=0.02, n_treated=10, n_controls=10, n_per=100) res = cluster_bootstrap_did( panel, treated={f"T{i}" for i in range(10)}, controls={f"C{i}" for i in range(10)}, metric_name="did_delta", scope="toy_strong", n_reps=300, seed=0, ) # δ=0.02 with σ=0.005 → strong signal, 5–95 CI should not cover 0 assert res.lower > 0 def test_stationary_bootstrap_recovers_mean() -> None: rng = np.random.default_rng(123) x = rng.normal(loc=0.05, scale=0.01, size=300) res = stationary_bootstrap( pd.Series(x), fn=np.mean, metric_name="mean", scope="toy", n_reps=500, mean_block_len=5, seed=0, ) assert res.point == pytest.approx(0.05, abs=2e-3) assert res.lower < 0.05 < res.upper def test_stationary_bootstrap_rejects_too_short_series() -> None: with pytest.raises(ValueError, match="too short"): stationary_bootstrap( pd.Series([0.0]), fn=np.mean, metric_name="m", scope="s", n_reps=10, seed=0, ) def test_cluster_bootstrap_mean_car_point_equals_sample_mean() -> None: """Bootstrap point estimate is the sample mean by definition.""" rng = np.random.default_rng(7) cars = pd.Series(rng.normal(loc=0.02, scale=0.05, size=300)) res = cluster_bootstrap_mean_car( cars, metric_name="car", scope="t1", n_reps=500, seed=0 ) assert res.point == pytest.approx(float(cars.mean()), abs=1e-12) # CI brackets the point estimate assert res.lower < res.point < res.upper # CI width is on order of 2·SE(mean) ≈ 2·0.05/√300 ≈ 0.006 → 90% CI ~0.01 assert 0.001 < (res.upper - res.lower) < 0.05 def test_cluster_bootstrap_mean_car_rejects_empty() -> None: with pytest.raises(ValueError, match="empty"): cluster_bootstrap_mean_car( pd.Series([np.nan, np.nan]), metric_name="m", scope="s", n_reps=10, ) def test_cluster_bootstrap_did_rejects_empty_sets() -> None: panel = _make_did_panel(delta=0.0, n_treated=4, n_controls=4, n_per=50) with pytest.raises(ValueError, match="empty"): cluster_bootstrap_did( panel, treated=set(), controls={f"C{i}" for i in range(4)}, metric_name="m", scope="s", n_reps=10, )