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