1"""pytest for the Python layer (requires the compiled core on sys.path: build with -DRAID_BUILD_PYTHON=ON)."""2import os, sys3import numpy as np4import pytest56sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))7raid = pytest.importorskip("raid")8if not raid.HAS_CORE:9 pytest.skip("raid._core not built", allow_module_level=True)101112def test_devices_has_cpu():13 kinds = [d["kind"] for d in raid.devices()]14 assert "cpu" in kinds151617def test_ols_matches_statsmodels_hc3():18 sm = pytest.importorskip("statsmodels.api")19 rng = np.random.default_rng(3); X = np.column_stack([np.ones(400), rng.normal(size=400), rng.normal(size=400)]); y = X @ [1, 2, -1] + rng.normal(size=400) * (1 + X[:, 1] ** 2)20 e = raid.ols(X, y, cov="hc3"); f = sm.OLS(y, X).fit(cov_type="HC3")21 np.testing.assert_allclose(e["coefficients"], f.params, rtol=1e-10)22 np.testing.assert_allclose(e["inference"]["std_errors"], f.bse, rtol=1e-9)23 assert e["ok"] and e["backend"] == "cpu" and e["precision"] == "fp64"242526def test_ols_rejects_nan():27 with pytest.raises(ValueError):28 raid.ols(np.array([[1.0, np.nan]]), np.array([1.0]))293031def test_realized_measures_cpu_matches_numpy():32 rng = np.random.default_rng(5); r = rng.normal(0, 1e-3, size=390 * 10); off = np.arange(0, 3901, 390)33 m = raid.realized_measures(r, off, backend="cpu")34 for k in range(10):35 seg = r[off[k]:off[k + 1]]36 assert abs(m["rv"][k] - np.sum(seg ** 2)) <= 1e-15 * np.sum(seg ** 2) * 1037 assert abs(m["rsv_neg"][k] + m["rsv_pos"][k] - m["rv"][k]) <= 1e-1838 assert m["backend"] == "cpu"394041def test_garch_fit_converges_and_forecasts():42 rng = np.random.default_rng(9); n = 2500; r = np.empty(n); s2 = 1e-4; e = 0.043 for t in range(n):44 s2 = 1e-6 + 0.08 * e * e + 0.9 * s2; e = np.sqrt(s2) * rng.standard_normal(); r[t] = e45 g = raid.garch_fit(r, model="garch", dist="gaussian", horizon=5)46 assert g["ok"] and g["convergence"]["converged"]47 assert abs(g["coefficients"][3] - 0.9) < 0.08 and len(g["extra"]["forecast_variance"]) == 5484950def test_calendar_sessions_thanksgiving_week():51 assert raid.calendar_sessions("2024-11-25", "2024-11-29") == ["2024-11-25", "2024-11-26", "2024-11-27", "2024-11-29"]525354def test_validate_bars_counts_missing():55 # one session, 391 regular minutes 2024-11-27 (09:30–16:00 ET = 14:30–21:00 UTC), drop 3 bars56 base = np.datetime64("2024-11-27T14:30:00").astype("datetime64[us]").astype(np.int64)57 t = base + np.arange(391) * 60_000_000; keep = np.ones(391, bool); keep[[10, 20, 30]] = False; t = t[keep]58 p = 100 + np.cumsum(np.random.default_rng(1).normal(0, 0.01, size=t.size))59 q = raid.validate_bars(t, p, p + 0.01, p - 0.01, p, np.full(t.size, 100.0))60 assert q["n_sessions"] == 1 and q["missing_regular_bars"] == 3 and not q["has_errors"]61