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 : experiments/micro/expA_data_reality/benchmark.py4# Purpose : Benchmark runner: empirical data reality check of hfmarketdata.io5# 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"""Experiment A — establish empirically what hfmarketdata.io actually returns.1415Probes (all through the single hf_client, so every response is cached and16manifest-indexed):17 A1 dataset inventory (/v1/status): assets x timeframes x adjustments18 A2 history bounds per asset class (earliest/latest 1min and 1day)19 A3 intraday session structure and missing-minute patterns (liquid vs sparse)20 A4 daily-bar vs 1min-aggregate semantics (official close vs last bar)21 A5 corporate-action adjustment semantics around the AAPL 2020 4:1 split22 A6 response row cap + pagination correctness on a full year of 1min bars23 A7 request latency profile (small cached-miss requests)24 A8 options coverage (quarters, expirations, chain columns)2526Writes results/expA_data_reality/<UTC timestamp>/results.json embedding the27hardware manifest and the client's instrumentation stats.28"""2930from __future__ import annotations3132import json33import sys34import time35from collections import Counter36from datetime import UTC, datetime37from pathlib import Path3839REPO_ROOT = Path(__file__).resolve().parents[3]40sys.path.insert(0, str(REPO_ROOT / "benchmarks"))41sys.path.insert(0, str(REPO_ROOT / "src"))4243from hardware_manifest import collect_manifest # noqa: E4024445from anomaly_atlas.data.hf_client import HFMarketDataClient # noqa: E4024647REPRESENTATIVE = {48 "stock": ("AAPL", "adj_splitdiv"),49 "etf": ("SPY", "adj_splitdiv"),50 "futures": ("ES", "contin_adj_ratio"),51 "index": ("SPX", None),52 "fx": ("EURUSD", None),53 "crypto": ("BTC", None),54}55PROBE_DAY = "2026-08-06" # a regular Thursday inside every dataset's coverage565758def bounds(59 client: HFMarketDataClient, asset: str, ticker: str, adjustment: str | None, timeframe: str60) -> dict:61 def one(order: str) -> str | None:62 rows = client.get(63 f"/v1/bars/{asset}/{ticker}",64 {65 "timeframe": timeframe,66 "adjustment": adjustment,67 "order": order,68 "limit": 1,69 },70 ).get("data", [])71 return rows[0]["datetime"] if rows else None7273 return {"earliest": one("asc"), "latest": one("desc")}747576def session_structure(77 client: HFMarketDataClient, asset: str, ticker: str, adjustment: str | None78) -> dict:79 rows = client.get(80 f"/v1/bars/{asset}/{ticker}",81 {82 "timeframe": "1min",83 "adjustment": adjustment,84 "start": PROBE_DAY,85 "end": "2026-08-07",86 "order": "asc",87 "limit": 50_000,88 },89 ).get("data", [])90 rows = [r for r in rows if r["datetime"][:10] == PROBE_DAY]91 if not rows:92 return {"bars": 0}93 per_hour = Counter(r["datetime"][11:13] for r in rows)94 rth = [r for r in rows if "09:30" <= r["datetime"][11:16] < "16:00"]95 return {96 "bars": len(rows),97 "first": rows[0]["datetime"],98 "last": rows[-1]["datetime"],99 "bars_per_hour": dict(sorted(per_hour.items())),100 "zero_volume_bars": sum(1 for r in rows if r.get("volume") == 0),101 "has_volume_field": "volume" in rows[0],102 "rth_bars_of_390": len(rth),103 }104105106def main() -> None:107 run_utc = datetime.now(UTC)108 client = HFMarketDataClient()109 results: dict = {110 "experiment": "expA_data_reality",111 "run_utc": run_utc.isoformat(),112 "author": "Simon-Pierre Boucher",113 "contact": "contact@spboucher.ai",114 "data_source": "hfmarketdata.io",115 }116117 # A1 — inventory118 status = client.status()119 results["A1_inventory"] = status.get("datasets", {})120 results["A1_options_quarters"] = (121 status.get("datasets", {}).get("options", {}).get("quarters", [])122 )123124 # A2 — history bounds per class, 1min and 1day125 results["A2_bounds"] = {126 asset: {tf: bounds(client, asset, tk, adj, tf) for tf in ("1min", "1day")}127 for asset, (tk, adj) in REPRESENTATIVE.items()128 }129130 # A3 — session structure, liquid + sparse131 results["A3_session"] = {132 f"{asset}:{tk}": session_structure(client, asset, tk, adj)133 for asset, (tk, adj) in REPRESENTATIVE.items()134 }135 results["A3_session"]["stock:AIZN(sparse)"] = session_structure(136 client, "stock", "AIZN", "adj_splitdiv"137 )138139 # A4 — daily bar vs 1min RTH aggregate (close semantics)140 day = client.get(141 "/v1/bars/stock/AAPL",142 {143 "timeframe": "1day",144 "adjustment": "adj_splitdiv",145 "start": PROBE_DAY,146 "end": "2026-08-07",147 },148 )["data"][0]149 intraday = client.get(150 "/v1/bars/stock/AAPL",151 {152 "timeframe": "1min",153 "adjustment": "adj_splitdiv",154 "start": PROBE_DAY,155 "end": "2026-08-07",156 "order": "asc",157 "limit": 50_000,158 },159 )["data"]160 rth = [r for r in intraday if "09:30" <= r["datetime"][11:16] < "16:00"]161 results["A4_daily_vs_intraday"] = {162 "daily_bar": day,163 "rth_1min_aggregate": {164 "open": rth[0]["open"],165 "high": max(r["high"] for r in rth),166 "low": min(r["low"] for r in rth),167 "close": rth[-1]["close"],168 "volume": sum(r["volume"] for r in rth),169 "bars": len(rth),170 },171 "extended_1min_volume": sum(r["volume"] for r in intraday),172 }173174 # A5 — adjustment semantics around AAPL 2020-08-31 4:1 split175 results["A5_adjustments"] = {}176 for adj in ("UNADJUSTED", "adj_split", "adj_splitdiv"):177 rows = client.get(178 "/v1/bars/stock/AAPL",179 {180 "timeframe": "1day",181 "adjustment": adj,182 "start": "2020-08-28",183 "end": "2020-09-01",184 },185 )["data"]186 results["A5_adjustments"][adj] = [187 {"date": r["datetime"][:10], "close": r["close"]} for r in rows188 ]189190 # A6 — row cap + pagination on a full year of SPY 1min191 capped = client.get(192 "/v1/bars/etf/SPY",193 {194 "timeframe": "1min",195 "adjustment": "adj_splitdiv",196 "start": "2020-01-01",197 "end": "2021-01-01",198 "order": "asc",199 "limit": 1_000_000,200 },201 )202 year = client.get_bars("etf", "SPY", "1min", "adj_splitdiv", "2020-01-01", "2021-01-01")203 dts = [r["datetime"] for r in year]204 results["A6_row_cap_and_pagination"] = {205 "requested_rows": 1_000_000,206 "returned_rows_single_request": capped.get("count"),207 "paginated_total_rows": len(year),208 "paginated_first": dts[0],209 "paginated_last": dts[-1],210 "duplicates_after_pagination": len(dts) - len(set(dts)),211 "monotonic_ascending": all(a < b for a, b in zip(dts, dts[1:], strict=False)),212 }213214 # A7 — latency profile (cache misses: distinct small requests)215 latencies = []216 for month in range(1, 11):217 t0 = time.perf_counter()218 client.get(219 "/v1/bars/stock/MSFT",220 {221 "timeframe": "1day",222 "adjustment": "adj_splitdiv",223 "start": f"2025-{month:02d}-01",224 "end": f"2025-{month:02d}-05",225 },226 )227 latencies.append(round(time.perf_counter() - t0, 4))228 results["A7_latency_s"] = {229 "samples": latencies,230 "note": "sequential small requests, cold cache; no rate-limit headers observed",231 }232233 # A8 — options coverage234 expiries = client.options_expirations("SPY", trade_date="2026-06-15")235 chain = client.options_chain("SPY", "2026-06-15", limit=2)236 results["A8_options"] = {237 "n_quarters": len(results["A1_options_quarters"]),238 "first_quarter": results["A1_options_quarters"][:1],239 "last_quarter": results["A1_options_quarters"][-1:],240 "spy_expirations_on_2026-06-15": len(expiries),241 "chain_columns": sorted(chain[0].keys()) if chain else None,242 "granularity": "daily (one row per contract per trade_date)",243 }244245 # instrumentation + hardware246 results["client_stats"] = {247 "network_requests": client.stats.network_requests,248 "cache_hits": client.stats.cache_hits,249 "rows_fetched": client.stats.rows_fetched,250 "seconds_waiting": round(client.stats.seconds_waiting, 2),251 "errors_retried": client.stats.errors_retried,252 }253 results["manifest"] = collect_manifest()254255 out_dir = REPO_ROOT / "results" / "expA_data_reality" / run_utc.strftime("%Y%m%dT%H%M%SZ")256 out_dir.mkdir(parents=True)257 out = out_dir / "results.json"258 out.write_text(json.dumps(results, indent=2) + "\n")259 print(f"wrote {out.relative_to(REPO_ROOT)}")260 print(json.dumps(results["client_stats"], indent=2))261262263if __name__ == "__main__":264 main()265