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/leadlag.py

src/anomaly_atlas/stats/leadlag.py 59 lines
# =============================================================================
#  Project   : anomaly-atlas
#  File      : src/anomaly_atlas/stats/leadlag.py
#  Purpose   : Lead-lag tests: lagged cross-correlation and asymmetry
#  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)
# =============================================================================
"""Lagged cross-correlation between two return series.

Sign convention: lag k > 0 means x LEADS y by k bars — corr(x_{t-k}, y_t).
Validated on synthetic ground truth (charter §8.1) before real data.
"""

from __future__ import annotations

import numpy as np


def lagged_xcorr(x: np.ndarray, y: np.ndarray, max_lag: int) -> dict[int, float]:
    """corr(x_{t-k}, y_t) for k in [-max_lag, +max_lag]; k>0 = x leads y."""
    x = np.asarray(x, dtype=float)
    y = np.asarray(y, dtype=float)
    n = min(len(x), len(y))
    x, y = x[:n], y[:n]
    out: dict[int, float] = {}
    for k in range(-max_lag, max_lag + 1):
        if k >= 0:
            a, b = x[: n - k] if k else x, y[k:] if k else y
        else:
            a, b = x[-k:], y[: n + k]
        if len(a) < 3 or a.std() == 0.0 or b.std() == 0.0:
            out[k] = float("nan")
            continue
        out[k] = float(np.corrcoef(a, b)[0, 1])
    return out


def peak_lag(xc: dict[int, float]) -> int:
    """Lag with the largest |corr| (ties: smallest |lag|)."""
    finite = {k: v for k, v in xc.items() if np.isfinite(v)}
    if not finite:
        return 0
    return min(finite, key=lambda k: (-abs(finite[k]), abs(k)))


def leadlag_asymmetry(xc: dict[int, float]) -> float:
    """Sum of corr at positive lags minus sum at negative lags.

    Zero (in expectation) for synchronous series; positive when x leads y.
    """
    pos = sum(v for k, v in xc.items() if k > 0 and np.isfinite(v))
    neg = sum(v for k, v in xc.items() if k < 0 and np.isfinite(v))
    return float(pos - neg)