SPB Git

spb/anomaly-atlas Public License

Systematic discovery & rigorous validation of statistical anomalies in open HF market data (hfmarketdata.io) — pre-registered, artifact-null-driven, fully reproducible. Live atlas: www.anomaly-atlas.io

Python 61.4% JavaScript 28.7% CSS 8.6% Shell 0.7% Makefile 0.5%
2.6 KB · 69 lines python
Raw Blame History
1# =============================================================================2#  Project   : anomaly-atlas3#  File      : src/anomaly_atlas/stats/multiple_testing.py4#  Purpose   : FDR/Bonferroni corrections (White RC / SPA / DSR arrive in expF)5#  Author    : Simon-Pierre Boucher6#  Contact   : contact@spboucher.ai7#  Data src  : hfmarketdata.io (sole data source)8#  Created   : 2026-08-129#  Modified  : 2026-08-1210#  Platform  : macOS / Apple Silicon (arm64)11#  License   : All rights reserved (research code)12# =============================================================================13"""Multiple-testing corrections for the scan stage (Level-0 triage).1415Scans use Benjamini–Hochberg FDR (the right error rate when a controlled16fraction of false leads into the next stage is acceptable). Level-117promotion uses SPA/StepM against artifact nulls — implemented with expF.18Validated on synthetic ground truth (§8.1 gate).19"""2021from __future__ import annotations2223import numpy as np242526def benjamini_hochberg(pvals: np.ndarray, alpha: float = 0.05) -> np.ndarray:27    """BH step-up FDR procedure. Returns a boolean rejection mask.2829    NaN p-values are never rejected but still COUNT toward m (conservative:30    an unevaluable test is a spent test, not a free one).31    """32    p = np.asarray(pvals, dtype=float)33    m = len(p)34    if m == 0:35        return np.zeros(0, dtype=bool)36    finite = np.where(np.isfinite(p))[0]37    reject = np.zeros(m, dtype=bool)38    if len(finite) == 0:39        return reject40    order = finite[np.argsort(p[finite])]41    thresholds = alpha * (np.arange(1, len(order) + 1) / m)42    passed = np.where(p[order] <= thresholds)[0]43    if len(passed):44        reject[order[: passed.max() + 1]] = True45    return reject464748def bonferroni(pvals: np.ndarray, alpha: float = 0.05) -> np.ndarray:49    """Bonferroni FWE mask (reported alongside FDR for reference)."""50    p = np.asarray(pvals, dtype=float)51    return np.isfinite(p) & (p <= alpha / max(len(p), 1))525354def bootstrap_pvalue(samples: np.ndarray, null_value: float = 0.0) -> float:55    """Two-sided percentile-bootstrap p-value of a statistic vs a null value.5657    p = 2 * min(P(boot <= null), P(boot >= null)), with the +1/(B+1)58    correction so p is never exactly 0. Triage-grade inference for scans —59    Level-1 promotion re-tests with SPA machinery.60    """61    s = np.asarray(samples, dtype=float)62    s = s[np.isfinite(s)]63    b = len(s)64    if b < 50:65        return float("nan")66    lo = (np.sum(s <= null_value) + 1) / (b + 1)67    hi = (np.sum(s >= null_value) + 1) / (b + 1)68    return float(min(1.0, 2.0 * min(lo, hi)))69