Honesty doctrine. Every candidate anomaly is an artifact until proven otherwise; in-sample results are never findings; past statistical regularity does not imply future returns. This is research on statistical properties of market data — not investment advice, not a trading system.

Code / src/anomaly_atlas/stats/bootstrap.py

src/anomaly_atlas/stats/bootstrap.py 59 lines
# =============================================================================
#  Project   : anomaly-atlas
#  File      : src/anomaly_atlas/stats/bootstrap.py
#  Purpose   : Moving-block bootstrap and percentile confidence intervals
#  Author    : Simon-Pierre Boucher
#  Contact   : contact@spboucher.ai
#  Data src  : hfmarketdata.io (sole data source)
#  Created   : 2026-08-12
#  Modified  : 2026-08-12
#  Platform  : macOS / Apple Silicon (arm64)
#  License   : All rights reserved (research code)
# =============================================================================
"""Moving-block bootstrap for serially dependent data (Künsch 1989).

Blocks preserve short-range dependence, so statistics like AC1 or variance
ratios get honest sampling distributions. Every call takes an explicit seed.
"""

from __future__ import annotations

from collections.abc import Callable

import numpy as np


def moving_block_bootstrap(
    x: np.ndarray,
    stat: Callable[[np.ndarray], float],
    block: int,
    n_boot: int = 500,
    seed: int = 0,
) -> np.ndarray:
    """Bootstrap distribution of `stat` using moving blocks of length `block`."""
    x = np.asarray(x, dtype=float)
    n = len(x)
    if n < 2 * block:
        return np.array([])
    rng = np.random.default_rng(seed)
    n_blocks = int(np.ceil(n / block))
    starts_max = n - block + 1
    out = np.empty(n_boot)
    for i in range(n_boot):
        starts = rng.integers(0, starts_max, n_blocks)
        sample = np.concatenate([x[s : s + block] for s in starts])[:n]
        out[i] = stat(sample)
    return out


def percentile_ci(samples: np.ndarray, alpha: float = 0.05) -> tuple[float, float]:
    """Two-sided percentile confidence interval."""
    s = np.asarray(samples, dtype=float)
    s = s[np.isfinite(s)]
    if len(s) == 0:
        return (float("nan"), float("nan"))
    return (
        float(np.percentile(s, 100 * alpha / 2)),
        float(np.percentile(s, 100 * (1 - alpha / 2))),
    )