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%
1# =============================================================================2# Project : anomaly-atlas3# File : src/anomaly_atlas/stats/bootstrap.py4# Purpose : Moving-block bootstrap and percentile confidence intervals5# 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"""Moving-block bootstrap for serially dependent data (Künsch 1989).1415Blocks preserve short-range dependence, so statistics like AC1 or variance16ratios get honest sampling distributions. Every call takes an explicit seed.17"""1819from __future__ import annotations2021from collections.abc import Callable2223import numpy as np242526def moving_block_bootstrap(27 x: np.ndarray,28 stat: Callable[[np.ndarray], float],29 block: int,30 n_boot: int = 500,31 seed: int = 0,32) -> np.ndarray:33 """Bootstrap distribution of `stat` using moving blocks of length `block`."""34 x = np.asarray(x, dtype=float)35 n = len(x)36 if n < 2 * block:37 return np.array([])38 rng = np.random.default_rng(seed)39 n_blocks = int(np.ceil(n / block))40 starts_max = n - block + 141 out = np.empty(n_boot)42 for i in range(n_boot):43 starts = rng.integers(0, starts_max, n_blocks)44 sample = np.concatenate([x[s : s + block] for s in starts])[:n]45 out[i] = stat(sample)46 return out474849def percentile_ci(samples: np.ndarray, alpha: float = 0.05) -> tuple[float, float]:50 """Two-sided percentile confidence interval."""51 s = np.asarray(samples, dtype=float)52 s = s[np.isfinite(s)]53 if len(s) == 0:54 return (float("nan"), float("nan"))55 return (56 float(np.percentile(s, 100 * alpha / 2)),57 float(np.percentile(s, 100 * (1 - alpha / 2))),58 )59