Code / benchmarks/synthetic/test_gate_expd.py
benchmarks/synthetic/test_gate_expd.py
99 lines
# =============================================================================
# Project : anomaly-atlas
# File : benchmarks/synthetic/test_gate_expd.py
# Purpose : §8.1 gate for expD: grids, LOCF masks, both-fresh de-artifacting
# 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)
# =============================================================================
"""Gate the expD alignment primitives before real data:
* grid building from bar dicts is exact (slots, NaN gaps, RTH filter);
* LOCF returns carry correct fresh-masks; missing days never leak;
* the both-fresh treatment REMOVES the planted non-synchronicity artifact
that the raw LOCF join manufactures (the core expD claim).
"""
from __future__ import annotations
import sys
from pathlib import Path
import numpy as np
sys.path.insert(0, str(Path(__file__).resolve().parent))
from generators import correlated_pair # noqa: E402
from anomaly_atlas.data.cleaning import (
both_fresh,
locf_day_returns,
nan_xcorr,
rth_day_grids,
)
SEEDS = [1, 2, 3]
def bars_for_day(day: str, prices: dict[str, float]) -> list[dict]:
return [{"datetime": f"{day} {hhmm}:00", "close": p} for hhmm, p in prices.items()]
def test_grid_building_slots_and_rth_filter():
bars = bars_for_day("2024-01-02", {"09:30": 100.0, "09:32": 101.0, "15:59": 102.0})
bars += bars_for_day("2024-01-02", {"04:00": 99.0, "16:00": 103.0}) # outside RTH
days = rth_day_grids(bars)
g = days["2024-01-02"]
assert np.isfinite(g[[0, 2, 389]]).all()
assert np.isnan(g[1]) and np.isnan(g[3])
assert np.isfinite(g).sum() == 3 # extended-hours bars excluded
def test_locf_masks_and_missing_days():
bars = bars_for_day("2024-01-02", {"09:30": 100.0, "09:32": 101.0, "09:33": 101.5})
days = rth_day_grids(bars)
r, fresh = locf_day_returns(days, ["2024-01-02", "2024-01-03"])
assert len(r) == 389 * 2
assert r[0] == 0.0 and not fresh[0] # 09:31 carried forward
# 09:31->09:32 return ends fresh but STARTS on a carried value -> not fresh
assert abs(r[1]) > 0 and not fresh[1]
# 09:32->09:33: both endpoints fresh -> a genuine 1-minute return
assert abs(r[2]) > 0 and fresh[2]
assert np.isnan(r[389:]).all() # absent day never leaks
assert not fresh[389:].any()
def synthetic_pair_day_grids(n_days: int, rho: float, p_obs: float, seed: int):
"""Correlated 1min walks; y observed sparsely. Returns (days_x, days_y, day_list)."""
rng = np.random.default_rng(seed)
px, py = correlated_pair(n_days * 390, rho=rho, sigma=0.001, seed=seed)
days_x, days_y, day_list = {}, {}, []
for d in range(n_days):
day = f"2024-02-{d + 1:02d}"
day_list.append(day)
gx = px[d * 390 : (d + 1) * 390].copy()
gy = py[d * 390 : (d + 1) * 390].copy()
mask = rng.random(390) < p_obs
mask[0] = True
gy[~mask] = np.nan
days_x[day] = gx
days_y[day] = gy
return days_x, days_y, day_list
def test_both_fresh_removes_planted_nonsync_artifact():
for seed in SEEDS:
dx, dy, dl = synthetic_pair_day_grids(25, rho=0.7, p_obs=0.3, seed=seed)
rx, fx = locf_day_returns(dx, dl)
ry, fy = locf_day_returns(dy, dl)
raw = nan_xcorr(rx, ry, 2)
assert raw[1] > 0.10 # artifact present in the raw LOCF join
bx, by = both_fresh(rx, fx, ry, fy)
sync = nan_xcorr(bx, by, 2)
assert abs(sync[1]) < 0.05 # and gone on the synchronized subsample
assert sync[0] > 0.6 # true contemporaneous corr (rho=0.7) recovered