SPB Git

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%

Bootstrap: full research skeleton (CLAUDE.md §3, localvm-research layout) + web platform

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Simon-Pierre Boucher committed 5 h ago (Aug 12, 2026)

Showing 90 changed files with +4,088 and −0

added .gitignore +42 −0
@@ -0,0 +1,42 @@
1 +# =============================================================================
2 +# Project : anomaly-atlas
3 +# File : .gitignore
4 +# Purpose : Git ignore rules (venvs, caches, raw data lake, OS noise)
5 +# Author : Simon-Pierre Boucher
6 +# Contact : contact@spboucher.ai
7 +# Data src : hfmarketdata.io (sole data source)
8 +# Created : 2026-08-12
9 +# Modified : 2026-08-12
10 +# Platform : macOS / Apple Silicon (arm64)
11 +# License : All rights reserved (research code)
12 +# =============================================================================
13 +
14 +# Python
15 +__pycache__/
16 +*.py[cod]
17 +.venv/
18 +venv/
19 +*.egg-info/
20 +.pytest_cache/
21 +.ruff_cache/
22 +.mypy_cache/
23 +
24 +# macOS
25 +.DS_Store
26 +
27 +# Raw market data lives OUTSIDE git — only the data-manifest index is committed
28 +data/
29 +*.parquet
30 +*.duckdb
31 +*.duckdb.wal
32 +
33 +# Local editor/session
34 +.claude/settings.local.json
35 +
36 +# Large raw traces
37 +results/**/*.trace
38 +results/**/*.fsusage
39 +
40 +# web platform: generated content snapshot + deps
41 +web/content/
42 +web/node_modules/
added CITATION.cff +21 −0
@@ -0,0 +1,21 @@
1 +# =============================================================================
2 +# Project : anomaly-atlas
3 +# File : CITATION.cff
4 +# Purpose : Citation metadata for the research repository
5 +# Author : Simon-Pierre Boucher
6 +# Contact : contact@spboucher.ai
7 +# Data src : hfmarketdata.io (sole data source)
8 +# Created : 2026-08-12
9 +# Modified : 2026-08-12
10 +# Platform : macOS / Apple Silicon (arm64)
11 +# License : All rights reserved (research code)
12 +# =============================================================================
13 +cff-version: 1.2.0
14 +message: "If you use this research, please cite it as below."
15 +title: "anomaly-atlas: systematic discovery & rigorous validation of statistical anomalies in open HF market data"
16 +authors:
17 + - family-names: "Boucher"
18 + given-names: "Simon-Pierre"
19 + email: "contact@spboucher.ai"
20 +date-released: "2026-08-12"
21 +version: "0.0.1"
added CLAUDE.md +542 −0
@@ -0,0 +1,542 @@
1 +# CLAUDE.md
2 +
3 +**Project codename:** `anomaly-atlas` — Systematic Discovery & Rigorous Validation of Statistical Anomalies in Open High-Frequency Market Data
4 +**Data source (sole permitted source):** HF Market Data — https://www.hfmarketdata.io (API)
5 +**Public site (living atlas of findings):** provisional — decided later (e.g. anomalyatlas.io)
6 +**Principal investigator / Author:** Simon-Pierre Boucher — <contact@spboucher.ai>
7 +**Primary platform:** Apple Silicon Mac (macOS 14+)
8 +**Document status:** Living research charter. Claude must treat this as the authoritative project specification.
9 +
10 +---
11 +
12 +## 0. Administrative conventions (MANDATORY — read before anything else)
13 +
14 +### 0.1 Author header requirement
15 +
16 +**Every single source file created in this project — without exception — must begin with a standardized author header.**
17 +
18 +Applies to: Python, C++, shell, SQL, Makefiles, CMake, TypeScript/JS (site), and any config that supports comments.
19 +
20 +#### Python / shell / SQL / YAML / TOML header
21 +
22 +```python
23 +# =============================================================================
24 +# Project : anomaly-atlas
25 +# File : <relative/path/to/file.py>
26 +# Purpose : <one-line description of what this file does>
27 +# Author : Simon-Pierre Boucher
28 +# Contact : contact@spboucher.ai
29 +# Data src : hfmarketdata.io (sole data source)
30 +# Created : <YYYY-MM-DD>
31 +# Modified : <YYYY-MM-DD>
32 +# Platform : macOS / Apple Silicon (arm64)
33 +# License : All rights reserved (research code)
34 +# =============================================================================
35 +```
36 +
37 +#### C++ / TS / JS header
38 +
39 +```cpp
40 +// ============================================================================
41 +// Project : anomaly-atlas
42 +// File : <relative/path/to/file>
43 +// Purpose : <one-line description>
44 +// Author : Simon-Pierre Boucher
45 +// Contact : contact@spboucher.ai
46 +// Data src : hfmarketdata.io (sole data source)
47 +// Created : <YYYY-MM-DD>
48 +// Modified : <YYYY-MM-DD>
49 +// Platform : macOS / Apple Silicon (arm64)
50 +// License : All rights reserved (research code)
51 +// ============================================================================
52 +```
53 +
54 +#### Markdown research documents (front matter)
55 +
56 +```markdown
57 +---
58 +project: anomaly-atlas
59 +document: <name>
60 +author: Simon-Pierre Boucher
61 +contact: contact@spboucher.ai
62 +data_source: hfmarketdata.io
63 +created: <YYYY-MM-DD>
64 +status: draft | reviewed | final
65 +---
66 +```
67 +
68 +Rules:
69 +
70 +1. Header is the **first content** of the file (after a shebang if present).
71 +2. `Modified` updated on every substantial change.
72 +3. Write `tools/check_headers.py` early; it fails if any tracked source file lacks a conforming header, and runs before every commit.
73 +4. Every result file (JSON/CSV/parquet) embeds `"author": "Simon-Pierre Boucher", "contact": "contact@spboucher.ai", "data_source": "hfmarketdata.io"` in its metadata.
74 +5. Every published finding on the site carries a visible attribution footer.
75 +
76 +### 0.2 macOS-first design constraint
77 +
78 +**Everything must run on a Mac.** Specifically:
79 +
80 +* Target machine: Apple Silicon (M1–M4), 16–64 GB unified memory, internal NVMe SSD.
81 +* Stack: Python (pandas/polars, numpy, statsmodels, arch, scipy) + DuckDB for out-of-core columnar queries over the local data cache; Accelerate/NEON via numpy. No Linux-only assumptions.
82 +* Data volumes (years of 1-minute bars across many symbols + full options chains) will exceed RAM: use **DuckDB + parquet** on the internal NVMe, columnar and memory-mapped, benchmarked on macOS/APFS. Never assume the dataset fits in memory.
83 +* Instrumentation uses macOS-native sources where relevant (mach `task_info`, `vm_stat`, `fs_usage`).
84 +* Site is a static site that builds and previews locally on the Mac.
85 +* A hardware/software manifest (`benchmarks/hardware_manifest.py`) is recorded into every result.
86 +
87 +### 0.3 Data-source discipline (CRITICAL)
88 +
89 +* **hfmarketdata.io is the SOLE data source.** No other market data may enter any experiment or finding. Every result must be reproducible from hfmarketdata.io alone.
90 +* Build a single, well-tested API client (`src/anomaly_atlas/data/hf_client.py`) that ALL code uses. No ad-hoc requests scattered around.
91 +* The client must: respect rate limits (read them from response headers, back off on 429, never hammer), cache every response to a local parquet/DuckDB store keyed by (endpoint, params, as-of date), and **never silently refetch** — the local cache is the reproducibility anchor.
92 +* Record, for every symbol/field used: the exact endpoint, parameters, granularity actually returned, and the fetch timestamp, into a committed data-manifest index (the raw data lives outside git; the index is committed).
93 +* **Confirm the real capabilities empirically before designing anything** (Phase 0.5): what symbols, what history depth, what the finest true granularity is (advertised 1-minute to daily; verify), whether timestamps are exchange time or vendor time, how corporate actions/splits/dividends are handled, and what the rate/credit limits are. Write findings to `research/data_source_profile.md`.
94 +
95 +### 0.4 Repository discipline
96 +
97 +* Git from day one; no result reported from an uncommitted tree.
98 +* Every finding reproducible from: commit hash + config + data-manifest index + seed + hardware manifest.
99 +* Python: type hints, `ruff`, `pytest` (statistical routines get correctness tests on synthetic data with known answers).
100 +
101 +---
102 +
103 +## 1. Mission
104 +
105 +Investigate, design, implement, and rigorously validate a systematic methodology — and the tooling behind it — to **discover statistical anomalies in open high-frequency market data (mean-reversion, lead-lag relationships, calendar/seasonal effects, and related regularities), and to distinguish genuine, out-of-sample, cost-aware regularities from data artifacts and statistical illusions**, using only hfmarketdata.io data, on a consumer Mac, and publish the results as a reproducible atlas.
106 +
107 +The target is **not**:
108 +
109 +* a live trading system, a trading bot, or trading advice;
110 +* a claim of "arbitrage" or "free money" (see §2.1 — that is almost never real in historical 1-minute data);
111 +* a backtest that reports in-sample returns as if they were discoveries;
112 +* p-hacking a large symbol universe until something looks significant;
113 +* reproducing a known factor/anomaly without adding rigor or a new test.
114 +
115 +The target is:
116 +
117 +> **Given only open HF market data, determine which statistical regularities are real (survive out-of-sample testing, multiple-comparison correction, transaction costs, and robustness checks) versus which are artifacts (bid-ask bounce, stale/non-synchronized timestamps, survivorship, look-ahead, microstructure noise) — and publish each with its honest confidence level and the exact script that regenerates it.**
118 +
119 +Do not assume any anomaly is real. Assume it is an artifact until the evidence forces otherwise.
120 +
121 +---
122 +
123 +## 2. Core research questions & the honesty doctrine
124 +
125 +### 2.1 The honesty doctrine (non-negotiable, stated up front and on the site)
126 +
127 +**In financial data, the equivalent of a mathematical proof is a pre-specified, out-of-sample, transaction-cost-adjusted, multiple-testing-corrected test that a naive researcher cannot fake after the fact.** There is no riskless "proof of profit" in historical bar data. Therefore:
128 +
129 +* Every candidate anomaly is guilty (an artifact) until proven innocent.
130 +* Any regularity that *looks* like arbitrage in 1-minute historical bars is treated as a **data-artifact hypothesis first** (stale quotes, timestamp misalignment, bid-ask bounce, missing corporate actions), and only after those are excluded may it be considered a real risk-bearing regularity — never riskless arbitrage.
131 +* In-sample results are never findings. Only out-of-sample, robustness-checked results are.
132 +* Past statistical regularity does not imply future returns; the site says so on every page.
133 +
134 +### 2.2 Research questions
135 +
136 +**Q1 — Mean-reversion.** At which horizons, on which instruments, does price/return mean-revert beyond what bid-ask bounce alone explains? Does any reversion survive realistic costs?
137 +
138 +**Q2 — Lead-lag.** Do movements in one instrument systematically precede correlated instruments (e.g., large-cap → related ETF, index → constituents, one FX cross → another) at the available granularity? Is the lead-lag stable out-of-sample, or an artifact of non-synchronous last-trade timestamps?
139 +
140 +**Q3 — Calendar / seasonal effects.** Are there robust intraday (open/close), day-of-week, turn-of-month, or holiday-adjacent regularities in returns or volatility that survive multiple-testing correction across the many calendar hypotheses one can pose?
141 +
142 +**Q4 — Artifact taxonomy.** What is the complete catalogue of artifacts in *this specific dataset* that masquerade as anomalies, and how is each detected and neutralized? (This is a primary deliverable, not a side note.)
143 +
144 +**Q5 — Cost frontier.** At what transaction-cost / holding-period assumptions does each surviving regularity stop being economically meaningful?
145 +
146 +The distinction to keep sharp everywhere:
147 +
148 +```text
149 +statistically detectable in-sample
150 +
151 +reproducible out-of-sample
152 +
153 +robust to artifacts and specification choices
154 +
155 +economically meaningful after costs
156 +```
157 +
158 +A finding only advances one confidence level at a time (§10).
159 +
160 +---
161 +
162 +## 3. Project structure (real research-project layout)
163 +
164 +Create this skeleton (with header-compliant placeholders) before Phase 1 concludes:
165 +
166 +```text
167 +anomaly-atlas/
168 +├── CLAUDE.md
169 +├── README.md
170 +├── CITATION.cff
171 +├── LICENSE
172 +├── pyproject.toml
173 +├── Makefile # setup, lint, test, fetch, scan, validate, site, headers
174 +
175 +├── research/
176 +│ ├── LOG.md # dated, append-only research log
177 +│ ├── data_source_profile.md # Phase 0.5: what hfmarketdata.io actually provides
178 +│ ├── state_of_the_art.md # Phase 2
179 +│ ├── artifact_taxonomy.md # the catalogue of dataset artifacts (living doc)
180 +│ ├── research_gaps.md # Phase 3
181 +│ ├── candidate_ranking.md # Phase 4
182 +│ ├── methodology.md # the formal validation protocol (Phase 9)
183 +│ ├── novelty_check.md # Phase 11
184 +│ └── bibliography.md
185 +
186 +├── src/
187 +│ └── anomaly_atlas/
188 +│ ├── data/
189 +│ │ ├── hf_client.py # THE single API client (rate-limit, cache, manifest)
190 +│ │ ├── cache.py # DuckDB/parquet local store
191 +│ │ ├── universe.py # symbol universe definitions (versioned)
192 +│ │ ├── calendars.py # trading calendars, sessions, holidays
193 +│ │ └── cleaning.py # corporate actions, dedup, alignment, quality flags
194 +│ ├── stats/
195 +│ │ ├── reversion.py # variance ratios, Hurst, AR tests, half-life
196 +│ │ ├── leadlag.py # cross-correlation, Granger, lagged regressions
197 +│ │ ├── calendar.py # seasonal/intraday effect tests
198 +│ │ ├── multiple_testing.py # FDR/Bonferroni, White reality check, SPA, DSR
199 +│ │ └── bootstrap.py # block/stationary bootstrap, CIs
200 +│ ├── validation/
201 +│ │ ├── oos.py # train/validate/holdout splits, walk-forward
202 +│ │ ├── costs.py # spread/impact/commission models
203 +│ │ ├── artifacts.py # artifact detectors (bounce, staleness, look-ahead)
204 +│ │ └── robustness.py # specification/parameter perturbation
205 +│ ├── atlas/ # finding schema, provenance, confidence serialization
206 +│ ├── viz/
207 +│ └── instrumentation/
208 +
209 +├── experiments/
210 +│ ├── micro/
211 +│ │ ├── expA_data_reality/ # verify what the API really gives
212 +│ │ ├── expB_artifact_baselines/ # measure bounce/staleness null distributions
213 +│ │ ├── expC_reversion_scan/
214 +│ │ ├── expD_leadlag_scan/
215 +│ │ ├── expE_calendar_scan/
216 +│ │ ├── expF_multiple_testing/ # how much survives correction
217 +│ │ ├── expG_cost_frontier/
218 +│ │ └── expH_oos_stability/
219 +│ ├── candidate_01/
220 +│ ├── candidate_02/
221 +│ └── candidate_03/
222 +│ └── (each: README.md, hypothesis.md, implementation/, benchmark.py,
223 +│ results/, analysis.md)
224 +
225 +├── atlas/ # the findings, as versioned data products
226 +│ └── <anomaly_id>/<version>/
227 +│ ├── finding.json
228 +│ ├── provenance.json # commit, config, data-manifest hash, hardware, dates
229 +│ └── confidence.md # Level 0–3 with evidence
230 +
231 +├── site/ # static site (builds locally on Mac)
232 +│ ├── content/ # one page per finding + the artifact taxonomy + methodology
233 +│ └── data/
234 +
235 +├── benchmarks/
236 +│ ├── hardware_manifest.py
237 +│ └── synthetic/ # synthetic series with KNOWN properties (test the tests)
238 +
239 +├── results/
240 +├── tools/
241 +│ ├── check_headers.py
242 +│ ├── new_experiment.py
243 +│ └── new_finding.py # scaffolds a compliant atlas entry (provenance+confidence)
244 +└── docs/
245 +```
246 +
247 +`tools/new_finding.py` refuses to create an atlas entry without a completed `provenance.json` and `confidence.md`.
248 +
249 +---
250 +
251 +## 4. Phase 1 — Ultra-deep literature research
252 +
253 +Before proposing methodology, deeply search the literature and implementations. Prefer primary sources; log everything in `research/bibliography.md` with URLs and access dates; follow citations both directions.
254 +
255 +### Areas that must be investigated
256 +
257 +#### 4.1 The anomalies themselves
258 +Short-horizon mean-reversion (Lo–MacKinlay, Lehmann); variance-ratio tests; lead-lag effects and the Epps effect; non-synchronous trading bias (Scholes–Williams); calendar effects (Monday/weekend, turn-of-month, turn-of-year, holiday, intraday U-shape); the historical rise-and-fade of these effects after publication.
259 +
260 +#### 4.2 The statistics of not fooling yourself (most important)
261 +Multiple-testing in finance (Harvey–Liu–Zhu "…and the Cross-Section of Expected Returns"); White's Reality Check; Hansen's SPA test; the Deflated Sharpe Ratio and backtest overfitting (Bailey–López de Prado); data-snooping (Sullivan–Timmermann–White); false-discovery-rate control; the replication crisis in cross-sectional anomalies (McLean–Pontiff decay; Hou–Xue–Zhang replication).
262 +
263 +#### 4.3 Market microstructure & artifacts
264 +Bid-ask bounce (Roll model); microstructure noise in high-frequency data; realized-volatility bias; stale prices; the effect of using bar/last-trade timestamps vs synchronized clocks; survivorship and look-ahead bias; corporate-action handling.
265 +
266 +#### 4.4 Time-series methodology
267 +Variance ratios; Hurst/long-memory estimation pitfalls; Granger causality caveats; block/stationary bootstrap (Politis–Romano); walk-forward and combinatorial purged cross-validation; regime changes and structural breaks.
268 +
269 +#### 4.5 Transaction-cost realism
270 +Effective spread estimation from bar data; the gap between paper returns and net returns; how costs kill most short-horizon anomalies; capacity constraints.
271 +
272 +#### 4.6 This dataset specifically
273 +Whatever hfmarketdata.io documents about coverage, granularity, timestamp semantics, and corporate-action adjustment — treat gaps in documentation as things to establish empirically (Phase 0.5 / Experiment A).
274 +
275 +---
276 +
277 +## 5. Phase 2 — State-of-the-art map
278 +
279 +Produce `research/state_of_the_art.md`. Organize each anomaly/technique by:
280 +
281 +```text
282 +Anomaly / method
283 +What it claims
284 +Data granularity it requires (does 1-minute-to-daily suffice?)
285 +Known artifact confounds
286 +Post-publication decay evidence
287 +Correct statistical test (and its assumptions)
288 +Multiple-testing exposure
289 +Transaction-cost sensitivity
290 +Open-source implementation (builds on macOS arm64?)
291 +Main limitation
292 +Opportunity for a genuinely new, honest test on THIS data
293 +```
294 +
295 +Do not summarize papers uncritically. Flag each anomaly's epistemic status: robust / decayed-after-publication / disputed / likely-artifact.
296 +
297 +---
298 +
299 +## 6. Phase 3 — Identify genuine gaps
300 +
301 +Produce `research/research_gaps.md`. Aim for **≥20 substantially different, testable hypotheses** on this dataset. For each:
302 +
303 +1. what existing work shows;
304 +2. what is untested on open HF data at this granularity;
305 +3. why it might matter;
306 +4. the plausible mechanism;
307 +5. the biggest artifact/statistical reason it would be fake;
308 +6. the smallest Mac-runnable experiment that would falsify it.
309 +
310 +Prefer hypotheses where **the null (artifact) model is explicitly constructable**, so "real vs artifact" is a clean test — not a vibe.
311 +
312 +---
313 +
314 +## 7. Phase 4 — Rank candidate approaches
315 +
316 +Create `research/candidate_ranking.md`. Score every hypothesis 1–10 with reasoning on:
317 +
318 +```text
319 +Testability on hfmarketdata.io (data actually available)
320 +Cleanliness of the artifact null (can we build the fake-signal baseline?)
321 +Out-of-sample feasibility (enough history to hold out)
322 +Multiple-testing discipline (small, pre-specified hypothesis set?)
323 +Survival odds after costs
324 +Novelty of the honest test
325 +Reproducibility
326 +Public-atlas value
327 +Implementation complexity
328 +Risk of self-deception
329 +```
330 +
331 +Select **3–5 strongest** for prototyping. Prefer small, pre-registered hypothesis sets over giant blind scans (which invite p-hacking).
332 +
333 +---
334 +
335 +## 8. Phase 5 — Experimental framework
336 +
337 +Build the framework **before** any scan. Correctness first.
338 +
339 +### 8.1 The tests must be tested (mandatory)
340 +Before running any anomaly detector on real data, run it on **synthetic series with known properties** (`benchmarks/synthetic/`): pure random walks (must yield NO anomaly), series with a *planted* mean-reversion / lead-lag / seasonal effect (must be recovered), and series with *only* bid-ask bounce (must be flagged as artifact, not anomaly). A detector that finds anomalies in a random walk is broken and must be fixed before touching real data.
341 +
342 +### 8.2 Universe & splits
343 +Pre-specify the symbol universe and the **train / validation / holdout** time split *before* looking at results. The holdout is touched **once**, at the end, for each finding. Walk-forward / purged CV where appropriate.
344 +
345 +### 8.3 Instrumentation
346 +Measure API credits consumed, cache hit rate, wall-clock, RAM/peak, DuckDB query cost. And for every statistical result: number of hypotheses tested, correction applied, out-of-sample status, bootstrap CIs, effect size, cost-adjusted effect size.
347 +
348 +### 8.4 Most important metrics
349 +```text
350 +OUT-OF-SAMPLE SURVIVAL (does the effect hold on untouched data?)
351 +ARTIFACT-ADJUSTED EFFECT (effect minus what the artifact null explains)
352 +COST-ADJUSTED EFFECT (net of realistic transaction costs)
353 +CORRECTED SIGNIFICANCE (after multiple-testing correction)
354 +```
355 +A large in-sample effect with zero out-of-sample survival is a **negative result** and is published as one.
356 +
357 +### 8.5 Controls
358 +Every detector runs against: a random-walk null, a bid-ask-bounce null, a phase-randomized/bootstrap null, and (for calendar effects) a permuted-calendar null. No claim without its null shown.
359 +
360 +---
361 +
362 +## 9. Phase 6 — Micro-experiments
363 +
364 +Each in `experiments/micro/` with the standard scaffold.
365 +
366 +**Experiment A — Data reality check.** Empirically establish what hfmarketdata.io returns: true granularity, history depth per asset class, timestamp semantics (exchange vs vendor), corporate-action adjustment, missing-data patterns, gaps, and rate/credit limits. Output → `research/data_source_profile.md`. Everything downstream depends on this.
367 +
368 +**Experiment B — Artifact baselines.** Build and measure the null distributions of the key artifacts on this data: Roll-model bid-ask bounce, stale-price autocorrelation, non-synchronous lead-lag. These nulls become the baseline every real detector must beat.
369 +
370 +**Experiment C — Mean-reversion scan.** Variance ratios / half-life / AR tests across horizons and instruments, always reported *net of* the bounce null, with OOS split and bootstrap CIs.
371 +
372 +**Experiment D — Lead-lag scan.** Lagged cross-correlations / Granger across correlated instrument pairs, explicitly separating true lead-lag from non-synchronous-timestamp artifacts (test on synchronized vs raw timestamps).
373 +
374 +**Experiment E — Calendar scan.** Intraday, day-of-week, turn-of-month, holiday effects — with a *pre-counted* hypothesis budget and mandatory multiple-testing correction (the calendar space is where p-hacking is easiest).
375 +
376 +**Experiment F — Multiple-testing survival.** Take everything C–E surfaced and apply White Reality Check / SPA / FDR / Deflated Sharpe. Report the **survival rate**: what fraction of "significant" effects survive correction. This number calibrates the whole atlas.
377 +
378 +**Experiment G — Cost frontier.** For each surviving effect, sweep transaction-cost assumptions and report where net effect crosses zero.
379 +
380 +**Experiment H — Out-of-sample stability.** For survivors of F and G, evaluate on the untouched holdout and across sub-periods. Only Level-3 findings survive this.
381 +
382 +---
383 +
384 +## 10. Scientific discipline & confidence taxonomy
385 +
386 +For every experiment write, in `hypothesis.md` / `analysis.md`:
387 +
388 +```text
389 +Hypothesis
390 +Falsification criterion
391 +Artifact null(s)
392 +Method
393 +Result (effect size, CIs, corrected p-values, credits used)
394 +Interpretation (with confidence level)
395 +Next experiment
396 +```
397 +
398 +**Never** report an in-sample number as a finding. **Never** claim "arbitrage." **Never** imply future profit. Report: number of hypotheses, correction method, OOS status, costs, hardware, data-manifest hash, software versions.
399 +
400 +### Confidence taxonomy (used everywhere, including on the site)
401 +
402 +```text
403 +Level 0 — in-sample only (never published as a finding; scan output only)
404 +Level 1 — corrected & OOS (survives multiple-testing correction AND a clean
405 + out-of-sample split, with the artifact null subtracted)
406 +Level 2 — robust (Level 1 + robust to specification/parameter choices
407 + and across sub-periods / instruments)
408 +Level 3 — cost-real & held-out (Level 2 + economically nonzero after realistic costs
409 + AND confirmed on the once-touched holdout)
410 +```
411 +
412 +Every atlas entry carries its level in `confidence.md` and visibly on the site. **Negative results (effects that vanish out-of-sample or after costs) are first-class published findings** — they are the most trustworthy output of an honest anomaly project.
413 +
414 +---
415 +
416 +## 11. Phase 7 — Prototype candidate detectors
417 +
418 +Each strongest hypothesis → `experiments/candidate_0N/` with full scaffold. Each `analysis.md` ends with an explicit verdict: promote to atlas, iterate, or reject (with reasons and the confidence level reached).
419 +
420 +---
421 +
422 +## 12. Phase 8 — Automatic research loop
423 +
424 +Operate as a research agent: analyze; pick the most informative next experiment; re-search literature when results surprise (especially if something looks *too good* — that is a red flag for an artifact, not a discovery); update hypotheses; rerun; compare; continue until evidence settles each hypothesis. Never expand the hypothesis space silently to chase significance — that is p-hacking; any expansion is logged and re-budgeted for multiple testing.
425 +
426 +Maintain `research/LOG.md` (append-only): date/time+timezone, question, experiment, result, interpretation, decision.
427 +
428 +---
429 +
430 +## 13. Phase 9 — Formalize the validation methodology
431 +
432 +Write `research/methodology.md`: the reusable, pre-registered protocol that turns *(a pre-specified hypothesis + universe + split)* into *(a confidence-labeled atlas entry)*. It must specify: the artifact nulls, the correction procedure, the OOS/holdout protocol, the cost model, the robustness perturbations, the finding schema, and the publication checklist. The methodology is itself a primary deliverable and the main defense against self-deception.
433 +
434 +---
435 +
436 +## 14. Phase 10 — Build the atlas pipeline and site
437 +
438 +Build the pipeline supported by evidence. Possible CLI shape (finalize after research):
439 +
440 +```bash
441 +anomaly-atlas fetch --universe universes/core.yaml # cache from hfmarketdata.io
442 +anomaly-atlas scan --family reversion,leadlag,calendar # in-sample candidates (Level 0)
443 +anomaly-atlas validate <anomaly_id> # corrections, OOS, costs, robustness
444 +anomaly-atlas publish <anomaly_id> --site site/ # only Level ≥1 gets published
445 +```
446 +
447 +### The site
448 +* Static, builds locally on the Mac; every page generated from atlas data (never hand-typed numbers).
449 +* Front-and-center honesty banner (the §2.1 doctrine) and a "not trading advice" statement on every page.
450 +* One page per finding, showing: confidence level, the artifact null it beat, OOS and cost-adjusted numbers, provenance (commit, data-manifest hash, dates, hardware), and the exact command to reproduce.
451 +* A prominent **artifact taxonomy** page (often the most useful output).
452 +* A prominent **negative-results** section.
453 +* Attribution footer: *Simon-Pierre Boucher — contact@spboucher.ai*.
454 +
455 +### Hard constraints
456 +* hfmarketdata.io is the only data source.
457 +* No claim above its evidence level; no "arbitrage"/profit claims; not a trading system.
458 +* Nothing published that cannot be regenerated from committed code + the data-manifest index.
459 +
460 +---
461 +
462 +## 15. Target hardware
463 +
464 +```text
465 +Apple Silicon Mac (laptop or desktop), 16–64 GB unified memory, internal NVMe SSD.
466 +```
467 +DuckDB + parquet over the internal SSD handles multi-year, multi-symbol data out-of-core; numpy/Accelerate handle the math. Document (without necessarily implementing) how the pipeline maps to other hardware.
468 +
469 +### Stretch target
470 +```text
471 +A small set (single digits) of Level-3 findings — statistical regularities that survive
472 +correction, out-of-sample testing, robustness checks, AND realistic costs on the
473 +once-touched holdout — OR a rigorously documented conclusion that, on this open data,
474 +the honest answer is "the detectable effects do not survive," which is itself a
475 +publishable, valuable result.
476 +```
477 +Do not assume Level-3 findings exist. The research must establish the truth either way.
478 +
479 +---
480 +
481 +## 16. What counts as a real result
482 +
483 +**A.** A statistical regularity that reaches Level 3 (corrected + OOS + robust + cost-real + held-out) on hfmarketdata.io data.
484 +**B.** A rigorous, reusable artifact taxonomy for this dataset that lets others avoid mistaking artifacts for signals.
485 +**C.** A clean demonstration that a widely-cited anomaly does NOT survive honest testing on this open data (negative result).
486 +**D.** A methodological contribution: a better artifact null or a better honest test for short-horizon effects on bar data.
487 +**E.** A measured "survival curve": what fraction of naively-significant effects remain after each layer of rigor.
488 +
489 +## 17. Failure criteria (and why they are fine)
490 +
491 +Be willing to conclude:
492 +
493 +```text
494 +nothing survives multiple-testing correction
495 +everything vanishes out-of-sample
496 +all apparent effects are explained by bid-ask bounce / stale prices / timestamp misalignment
497 +surviving effects die after realistic costs
498 +the data granularity is too coarse to test the interesting hypotheses
499 +```
500 +
501 +If so, document it in `research/LOG.md` and the relevant `analysis.md`, publish the negative result, and pivot. **A well-documented "it's all artifacts" is a success, not a failure** — it is exactly what an honest anomaly project should be willing to conclude.
502 +
503 +---
504 +
505 +## 18. Deliverables
506 +
507 +```text
508 +README.md, CITATION.cff
509 +research/ (data_source_profile, state_of_the_art, artifact_taxonomy, research_gaps,
510 + candidate_ranking, methodology, novelty_check, LOG, bibliography)
511 +src/ (the anomaly_atlas library, incl. the single hf_client)
512 +experiments/, benchmarks/ (incl. synthetic test series), results/
513 +atlas/ (confidence-labeled findings with provenance)
514 +site/ (the public atlas)
515 +tools/, docs/
516 +```
517 +
518 +The final README explains: the problem; the honesty doctrine; what the data actually provides; the artifact taxonomy; the validation methodology; the findings with their confidence levels (including negatives); limitations; exact Mac reproduction instructions; future research.
519 +
520 +---
521 +
522 +## 19. Phase 11 — Novelty verification
523 +
524 +Before claiming any finding or method is new, search using terminology derived from what was actually found, plus every synonym and the specific anomaly's literature (it may be a known, already-decayed effect). Assume **not novel** until evidence says otherwise. Record in `research/novelty_check.md`. Note explicitly whether each "finding" is a genuinely new regularity or a re-confirmation/decay-measurement of a known one — both are legitimate, but must not be conflated.
525 +
526 +---
527 +
528 +## 20. Most important instruction
529 +
530 +Do not become attached to finding a profitable anomaly. The purpose of this project is **not to discover free money** — that almost certainly is not there in open historical bar data, and pretending otherwise is how researchers fool themselves.
531 +
532 +The actual assignment is:
533 +
534 +> **Search deeply, test honestly, build the artifact nulls, correct for multiple testing, hold out data you touch only once, subtract realistic costs — and report, at its true confidence level, what actually survives, including (and especially) when the honest answer is "nothing survives."**
535 +
536 +Start with literature. Verify what the data really is. Build detectors that a random walk cannot fool. Then let the out-of-sample, cost-adjusted, correction-survived evidence — and nothing else — decide what enters the atlas.
537 +
538 +The ideal outcome is not a backtest that looks good. The ideal outcome is a **trustworthy, reproducible atlas of what is real, what is artifact, and what is merely wishful** in open high-frequency market data — where every claim carries its evidence level and the exact command that regenerates it from hfmarketdata.io.
539 +
540 +---
541 +
542 +*Author: Simon-Pierre Boucher — contact@spboucher.ai — Data source: hfmarketdata.io — All research artifacts in this repository carry this attribution. This project is research on statistical properties of market data; it is not investment advice and is not a trading system.*
added LICENSE +10 −0
@@ -0,0 +1,10 @@
1 +Copyright (c) 2026 Simon-Pierre Boucher <contact@spboucher.ai>
2 +
3 +All rights reserved (research code).
4 +
5 +This repository contains research code and documents. No permission is granted
6 +to use, copy, modify, or distribute this work, in whole or in part, without
7 +prior written authorization from the author.
8 +
9 +This project is research on statistical properties of market data; it is not
10 +investment advice and is not a trading system.
added Makefile +45 −0
@@ -0,0 +1,45 @@
1 +# =============================================================================
2 +# Project : anomaly-atlas
3 +# File : Makefile
4 +# Purpose : Top-level tasks: setup, lint, test, headers, manifest, web
5 +# Author : Simon-Pierre Boucher
6 +# Contact : contact@spboucher.ai
7 +# Data src : hfmarketdata.io (sole data source)
8 +# Created : 2026-08-12
9 +# Modified : 2026-08-12
10 +# Platform : macOS / Apple Silicon (arm64)
11 +# License : All rights reserved (research code)
12 +# =============================================================================
13 +
14 +PYTHON ?= python3
15 +
16 +.PHONY: setup lint test headers manifest fetch scan validate web
17 +
18 +setup:
19 + $(PYTHON) -m venv .venv
20 + .venv/bin/pip install -e ".[dev]"
21 +
22 +lint:
23 + .venv/bin/ruff check src tools benchmarks experiments
24 + .venv/bin/ruff format --check src tools benchmarks experiments
25 +
26 +test:
27 + .venv/bin/pytest -q
28 +
29 +headers:
30 + $(PYTHON) tools/check_headers.py
31 +
32 +manifest:
33 + $(PYTHON) benchmarks/hardware_manifest.py
34 +
35 +fetch:
36 + @echo "anomaly-atlas fetch not yet implemented (Phase 0.5 — Experiment A first)"; exit 1
37 +
38 +scan:
39 + @echo "anomaly-atlas scan not yet implemented (Phase 5+ — detectors must pass synthetic tests first)"; exit 1
40 +
41 +validate:
42 + @echo "anomaly-atlas validate not yet implemented (Phase 9 — methodology.md first)"; exit 1
43 +
44 +web:
45 + cd web && ./sync-content.sh && npm start
added README.md +65 −0
@@ -0,0 +1,65 @@
1 +---
2 +project: anomaly-atlas
3 +document: README
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +data_source: hfmarketdata.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# anomaly-atlas
12 +
13 +**Which statistical regularities in open high-frequency market data are real —
14 +surviving out-of-sample testing, multiple-comparison correction, transaction
15 +costs and robustness checks — and which are artifacts (bid-ask bounce, stale
16 +timestamps, survivorship, look-ahead, microstructure noise)?**
17 +
18 +This repository is a living research project (charter: [CLAUDE.md](CLAUDE.md)).
19 +Data source: **[hfmarketdata.io](https://www.hfmarketdata.io) — the sole permitted
20 +source**; every result is reproducible from it alone.
21 +
22 +The distinction kept sharp everywhere:
23 +
24 +```text
25 +statistically detectable in-sample
26 +≠ reproducible out-of-sample
27 +≠ robust to artifacts and specification choices
28 +≠ economically meaningful after costs
29 +```
30 +
31 +- **Author:** Simon-Pierre Boucher — <contact@spboucher.ai>
32 +- **Primary platform:** Apple Silicon Mac (DuckDB + parquet out-of-core), macOS 14+
33 +- **Status:** bootstrap. Next: Phase 0.5 (Experiment A — data reality check). See `research/LOG.md`.
34 +
35 +## Honesty doctrine (charter §2.1)
36 +
37 +Every candidate anomaly is an **artifact until proven otherwise**. In-sample
38 +results are never findings. Nothing here is trading advice, a trading system,
39 +or a claim of arbitrage; past statistical regularity does not imply future
40 +returns. Negative results are first-class published findings.
41 +
42 +## Layout
43 +
44 +| Path | Contents |
45 +|---|---|
46 +| `research/` | Paper trail: log, data-source profile, state of the art, artifact taxonomy, gaps, ranking, methodology, bibliography |
47 +| `src/anomaly_atlas/` | Library: `data/` (single `hf_client`, cache, universe, calendars, cleaning), `stats/`, `validation/`, `atlas/`, `viz/`, `instrumentation/` |
48 +| `experiments/` | Micro-experiments A–H and candidate prototypes (hypothesis → falsification criterion → analysis) |
49 +| `benchmarks/` | Hardware manifest + synthetic series with known properties ("test the tests") |
50 +| `atlas/` | Confidence-labeled findings (Level 0–3) with provenance, via `tools/new_finding.py` |
51 +| `results/` | Raw runs, reproducible from commit + config + data-manifest + seed + manifest |
52 +| `web/` | The public atlas platform (the charter's `site/`, implemented as in localvm-research) — deployed at www.anomaly-atlas.io |
53 +| `tools/` | `check_headers.py`, `new_experiment.py`, `new_finding.py` |
54 +
55 +## Confidence taxonomy
56 +
57 +`Level 0` in-sample only (never published) → `Level 1` corrected & OOS →
58 +`Level 2` robust → `Level 3` cost-real & confirmed on the once-touched holdout.
59 +
60 +## Reproducibility
61 +
62 +Every finding: commit hash + config + data-manifest index + seed + hardware
63 +manifest (`benchmarks/hardware_manifest.py`). No result from an uncommitted tree.
64 +
65 +*Research on statistical properties of market data — not investment advice.*
added atlas/README.md +17 −0
@@ -0,0 +1,17 @@
1 +---
2 +project: anomaly-atlas
3 +document: Atlas data products index
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +data_source: hfmarketdata.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# Atlas — confidence-labeled findings
12 +
13 +One directory per finding: `<anomaly_id>/<version>/` containing `finding.json`,
14 +`provenance.json` (commit, config, data-manifest hash, hardware, dates) and
15 +`confidence.md` (Level 0-3 with evidence). Entries are created ONLY via
16 +`tools/new_finding.py`, which refuses incomplete provenance/confidence.
17 +Negative results are first-class entries.
added benchmarks/hardware_manifest.py +144 −0
@@ -0,0 +1,144 @@
1 +#!/usr/bin/env python3
2 +# =============================================================================
3 +# Project : anomaly-atlas
4 +# File : benchmarks/hardware_manifest.py
5 +# Purpose : macOS hardware/software fingerprint embedded in every result file
6 +# Author : Simon-Pierre Boucher
7 +# Contact : contact@spboucher.ai
8 +# Data src : hfmarketdata.io (sole data source)
9 +# Created : 2026-08-12
10 +# Modified : 2026-08-12
11 +# Platform : macOS / Apple Silicon (arm64)
12 +# License : All rights reserved (research code)
13 +# =============================================================================
14 +"""Collect a reproducibility manifest for the current Mac.
15 +
16 +Records chip model, P/E core counts, GPU core count, unified memory size,
17 +SSD model, macOS version, and versions of the key software stack (Python,
18 +NumPy, pandas, polars, DuckDB, statsmodels, arch), plus the current git
19 +commit. Every result JSON must embed this manifest plus the author/contact/
20 +data-source attribution (CLAUDE.md §0.1, §0.2, §10).
21 +
22 +Usage:
23 + python3 benchmarks/hardware_manifest.py # pretty-print JSON
24 + from hardware_manifest import collect_manifest # programmatic use
25 +"""
26 +
27 +from __future__ import annotations
28 +
29 +import json
30 +import platform
31 +import subprocess
32 +import sys
33 +from datetime import datetime, timezone
34 +
35 +
36 +def _run(cmd: list[str]) -> str:
37 + try:
38 + return subprocess.run(cmd, capture_output=True, text=True, timeout=30).stdout.strip()
39 + except (OSError, subprocess.TimeoutExpired):
40 + return ""
41 +
42 +
43 +def _sysctl(key: str) -> str:
44 + return _run(["sysctl", "-n", key])
45 +
46 +
47 +def _sysctl_int(key: str) -> int | None:
48 + val = _sysctl(key)
49 + try:
50 + return int(val)
51 + except ValueError:
52 + return None
53 +
54 +
55 +def _gpu_cores() -> int | None:
56 + """GPU core count via system_profiler (no sysctl key exposes it)."""
57 + out = _run(["system_profiler", "SPDisplaysDataType", "-json"])
58 + try:
59 + displays = json.loads(out)["SPDisplaysDataType"]
60 + for gpu in displays:
61 + cores = gpu.get("sppci_cores")
62 + if cores is not None:
63 + return int(cores)
64 + except (json.JSONDecodeError, KeyError, ValueError, TypeError):
65 + pass
66 + return None
67 +
68 +
69 +def _ssd_info() -> dict:
70 + out = _run(["system_profiler", "SPNVMeDataType", "-json"])
71 + try:
72 + items = json.loads(out)["SPNVMeDataType"]
73 + for controller in items:
74 + for dev in controller.get("_items", []):
75 + return {
76 + "model": dev.get("device_model", "").strip(),
77 + "size": dev.get("size", ""),
78 + "smart_status": dev.get("smart_status", ""),
79 + }
80 + except (json.JSONDecodeError, KeyError, TypeError):
81 + pass
82 + return {"model": None, "size": None, "smart_status": None}
83 +
84 +
85 +def _pkg_version(module: str) -> str | None:
86 + try:
87 + from importlib.metadata import version
88 +
89 + return version(module)
90 + except Exception:
91 + return None
92 +
93 +
94 +def _git_commit() -> dict:
95 + commit = _run(["git", "rev-parse", "HEAD"])
96 + dirty = bool(_run(["git", "status", "--porcelain"]))
97 + return {"commit": commit or None, "dirty_tree": dirty}
98 +
99 +
100 +def collect_manifest() -> dict:
101 + """Return the full hardware/software manifest as a dict."""
102 + mem_bytes = _sysctl_int("hw.memsize") or 0
103 + manifest = {
104 + "author": "Simon-Pierre Boucher",
105 + "contact": "contact@spboucher.ai",
106 + "project": "anomaly-atlas",
107 + "data_source": "hfmarketdata.io",
108 + "collected_utc": datetime.now(timezone.utc).isoformat(),
109 + "chip": {
110 + "brand": _sysctl("machdep.cpu.brand_string"),
111 + "arch": platform.machine(),
112 + "cores_total": _sysctl_int("hw.ncpu"),
113 + "cores_performance": _sysctl_int("hw.perflevel0.physicalcpu"),
114 + "cores_efficiency": _sysctl_int("hw.perflevel1.physicalcpu"),
115 + "gpu_cores": _gpu_cores(),
116 + },
117 + "memory": {
118 + "unified_bytes": mem_bytes,
119 + "unified_gb": round(mem_bytes / 2**30, 1),
120 + "pagesize": _sysctl_int("hw.pagesize"),
121 + },
122 + "ssd": _ssd_info(),
123 + "os": {
124 + "product": _run(["sw_vers", "-productName"]),
125 + "version": _run(["sw_vers", "-productVersion"]),
126 + "build": _run(["sw_vers", "-buildVersion"]),
127 + "kernel": platform.release(),
128 + },
129 + "software": {
130 + "python": sys.version.split()[0],
131 + "numpy": _pkg_version("numpy"),
132 + "pandas": _pkg_version("pandas"),
133 + "polars": _pkg_version("polars"),
134 + "duckdb": _pkg_version("duckdb"),
135 + "statsmodels": _pkg_version("statsmodels"),
136 + "arch": _pkg_version("arch"),
137 + },
138 + "git": _git_commit(),
139 + }
140 + return manifest
141 +
142 +
143 +if __name__ == "__main__":
144 + print(json.dumps(collect_manifest(), indent=2))
added benchmarks/synthetic/README.md +17 −0
@@ -0,0 +1,17 @@
1 +---
2 +project: anomaly-atlas
3 +document: Synthetic series with known properties
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +data_source: hfmarketdata.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# Synthetic test series — "test the tests" (CLAUDE.md §8.1)
12 +
13 +Before any detector touches real data it must pass, on series generated here:
14 + * pure random walks -> NO anomaly detected;
15 + * planted mean-reversion / lead-lag / seasonal effects -> recovered;
16 + * pure bid-ask bounce -> flagged as ARTIFACT, not anomaly.
17 +A detector that finds anomalies in a random walk is broken.
added docs/README.md +13 −0
@@ -0,0 +1,13 @@
1 +---
2 +project: anomaly-atlas
3 +document: Documentation index
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +data_source: hfmarketdata.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# docs
12 +
13 +Supplementary documentation. See CLAUDE.md (charter) and research/.
added experiments/candidate_01/README.md +15 −0
@@ -0,0 +1,15 @@
1 +---
2 +project: anomaly-atlas
3 +document: candidate_01/README
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +data_source: hfmarketdata.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# candidate_01
12 +
13 +Candidate 01 (selected in Phase 4 from research/candidate_ranking.md)
14 +
15 +Status: scaffolded 2026-08-12, not yet run.
added experiments/candidate_01/analysis.md +13 −0
@@ -0,0 +1,13 @@
1 +---
2 +project: anomaly-atlas
3 +document: candidate_01/analysis
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +data_source: hfmarketdata.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# Analysis — candidate_01
12 +
13 +*To be written after results exist. Must include the seven-field block and the evidence standard of CLAUDE.md §10 (never report an in-sample number as a finding).*
added experiments/candidate_01/benchmark.py +35 −0
@@ -0,0 +1,35 @@
1 +# =============================================================================
2 +# Project : anomaly-atlas
3 +# File : experiments/candidate_01/benchmark.py
4 +# Purpose : Benchmark runner: Candidate 01 (selected in Phase 4 from research/candidate_ranking.md)
5 +# Author : Simon-Pierre Boucher
6 +# Contact : contact@spboucher.ai
7 +# Data src : hfmarketdata.io (sole data source)
8 +# Created : 2026-08-12
9 +# Modified : 2026-08-12
10 +# Platform : macOS / Apple Silicon (arm64)
11 +# License : All rights reserved (research code)
12 +# =============================================================================
13 +
14 +"""Benchmark entry point for candidate_01.
15 +
16 +Must embed the hardware manifest in all result output
17 +(see benchmarks/hardware_manifest.py) and write results to
18 +results/candidate_01/<timestamp>/. Uses hfmarketdata.io data ONLY, exclusively
19 +through src/anomaly_atlas/data/hf_client.py.
20 +"""
21 +
22 +import sys
23 +from pathlib import Path
24 +
25 +sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "benchmarks"))
26 +from hardware_manifest import collect_manifest # noqa: E402
27 +
28 +
29 +def main() -> None:
30 + manifest = collect_manifest()
31 + raise NotImplementedError("experiment not yet implemented")
32 +
33 +
34 +if __name__ == "__main__":
35 + main()
added experiments/candidate_01/hypothesis.md +38 −0
@@ -0,0 +1,38 @@
1 +---
2 +project: anomaly-atlas
3 +document: candidate_01/hypothesis
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +data_source: hfmarketdata.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# Hypothesis — candidate_01
12 +
13 +```text
14 +Hypothesis
15 + <what we believe and why — pre-specified BEFORE looking at results>
16 +
17 +Falsification criterion
18 + <the concrete measurable outcome that would prove this wrong>
19 +
20 +Artifact null(s)
21 + <the fake-signal baseline(s) this must beat: bounce / staleness /
22 + non-synchronous timestamps / permuted calendar / random walk>
23 +
24 +Method
25 + <exact procedure, universe, split (train/validation/holdout), seeds,
26 + number of hypotheses tested, correction applied>
27 +
28 +Result
29 + <filled after the run: effect size, bootstrap CIs, corrected p-values,
30 + OOS status, cost-adjusted effect, credits used>
31 +
32 +Interpretation
33 + <what the numbers mean, WITH confidence level (0-3); alternative
34 + explanations considered — artifact first>
35 +
36 +Next experiment
37 + <the most informative follow-up given this result>
38 +```
added experiments/candidate_02/README.md +15 −0
@@ -0,0 +1,15 @@
1 +---
2 +project: anomaly-atlas
3 +document: candidate_02/README
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +data_source: hfmarketdata.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# candidate_02
12 +
13 +Candidate 02 (selected in Phase 4 from research/candidate_ranking.md)
14 +
15 +Status: scaffolded 2026-08-12, not yet run.
added experiments/candidate_02/analysis.md +13 −0
@@ -0,0 +1,13 @@
1 +---
2 +project: anomaly-atlas
3 +document: candidate_02/analysis
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +data_source: hfmarketdata.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# Analysis — candidate_02
12 +
13 +*To be written after results exist. Must include the seven-field block and the evidence standard of CLAUDE.md §10 (never report an in-sample number as a finding).*
added experiments/candidate_02/benchmark.py +35 −0
@@ -0,0 +1,35 @@
1 +# =============================================================================
2 +# Project : anomaly-atlas
3 +# File : experiments/candidate_02/benchmark.py
4 +# Purpose : Benchmark runner: Candidate 02 (selected in Phase 4 from research/candidate_ranking.md)
5 +# Author : Simon-Pierre Boucher
6 +# Contact : contact@spboucher.ai
7 +# Data src : hfmarketdata.io (sole data source)
8 +# Created : 2026-08-12
9 +# Modified : 2026-08-12
10 +# Platform : macOS / Apple Silicon (arm64)
11 +# License : All rights reserved (research code)
12 +# =============================================================================
13 +
14 +"""Benchmark entry point for candidate_02.
15 +
16 +Must embed the hardware manifest in all result output
17 +(see benchmarks/hardware_manifest.py) and write results to
18 +results/candidate_02/<timestamp>/. Uses hfmarketdata.io data ONLY, exclusively
19 +through src/anomaly_atlas/data/hf_client.py.
20 +"""
21 +
22 +import sys
23 +from pathlib import Path
24 +
25 +sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "benchmarks"))
26 +from hardware_manifest import collect_manifest # noqa: E402
27 +
28 +
29 +def main() -> None:
30 + manifest = collect_manifest()
31 + raise NotImplementedError("experiment not yet implemented")
32 +
33 +
34 +if __name__ == "__main__":
35 + main()
added experiments/candidate_02/hypothesis.md +38 −0
@@ -0,0 +1,38 @@
1 +---
2 +project: anomaly-atlas
3 +document: candidate_02/hypothesis
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +data_source: hfmarketdata.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# Hypothesis — candidate_02
12 +
13 +```text
14 +Hypothesis
15 + <what we believe and why — pre-specified BEFORE looking at results>
16 +
17 +Falsification criterion
18 + <the concrete measurable outcome that would prove this wrong>
19 +
20 +Artifact null(s)
21 + <the fake-signal baseline(s) this must beat: bounce / staleness /
22 + non-synchronous timestamps / permuted calendar / random walk>
23 +
24 +Method
25 + <exact procedure, universe, split (train/validation/holdout), seeds,
26 + number of hypotheses tested, correction applied>
27 +
28 +Result
29 + <filled after the run: effect size, bootstrap CIs, corrected p-values,
30 + OOS status, cost-adjusted effect, credits used>
31 +
32 +Interpretation
33 + <what the numbers mean, WITH confidence level (0-3); alternative
34 + explanations considered — artifact first>
35 +
36 +Next experiment
37 + <the most informative follow-up given this result>
38 +```
added experiments/candidate_03/README.md +15 −0
@@ -0,0 +1,15 @@
1 +---
2 +project: anomaly-atlas
3 +document: candidate_03/README
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +data_source: hfmarketdata.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# candidate_03
12 +
13 +Candidate 03 (selected in Phase 4 from research/candidate_ranking.md)
14 +
15 +Status: scaffolded 2026-08-12, not yet run.
added experiments/candidate_03/analysis.md +13 −0
@@ -0,0 +1,13 @@
1 +---
2 +project: anomaly-atlas
3 +document: candidate_03/analysis
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +data_source: hfmarketdata.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# Analysis — candidate_03
12 +
13 +*To be written after results exist. Must include the seven-field block and the evidence standard of CLAUDE.md §10 (never report an in-sample number as a finding).*
added experiments/candidate_03/benchmark.py +35 −0
@@ -0,0 +1,35 @@
1 +# =============================================================================
2 +# Project : anomaly-atlas
3 +# File : experiments/candidate_03/benchmark.py
4 +# Purpose : Benchmark runner: Candidate 03 (selected in Phase 4 from research/candidate_ranking.md)
5 +# Author : Simon-Pierre Boucher
6 +# Contact : contact@spboucher.ai
7 +# Data src : hfmarketdata.io (sole data source)
8 +# Created : 2026-08-12
9 +# Modified : 2026-08-12
10 +# Platform : macOS / Apple Silicon (arm64)
11 +# License : All rights reserved (research code)
12 +# =============================================================================
13 +
14 +"""Benchmark entry point for candidate_03.
15 +
16 +Must embed the hardware manifest in all result output
17 +(see benchmarks/hardware_manifest.py) and write results to
18 +results/candidate_03/<timestamp>/. Uses hfmarketdata.io data ONLY, exclusively
19 +through src/anomaly_atlas/data/hf_client.py.
20 +"""
21 +
22 +import sys
23 +from pathlib import Path
24 +
25 +sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "benchmarks"))
26 +from hardware_manifest import collect_manifest # noqa: E402
27 +
28 +
29 +def main() -> None:
30 + manifest = collect_manifest()
31 + raise NotImplementedError("experiment not yet implemented")
32 +
33 +
34 +if __name__ == "__main__":
35 + main()
added experiments/candidate_03/hypothesis.md +38 −0
@@ -0,0 +1,38 @@
1 +---
2 +project: anomaly-atlas
3 +document: candidate_03/hypothesis
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +data_source: hfmarketdata.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# Hypothesis — candidate_03
12 +
13 +```text
14 +Hypothesis
15 + <what we believe and why — pre-specified BEFORE looking at results>
16 +
17 +Falsification criterion
18 + <the concrete measurable outcome that would prove this wrong>
19 +
20 +Artifact null(s)
21 + <the fake-signal baseline(s) this must beat: bounce / staleness /
22 + non-synchronous timestamps / permuted calendar / random walk>
23 +
24 +Method
25 + <exact procedure, universe, split (train/validation/holdout), seeds,
26 + number of hypotheses tested, correction applied>
27 +
28 +Result
29 + <filled after the run: effect size, bootstrap CIs, corrected p-values,
30 + OOS status, cost-adjusted effect, credits used>
31 +
32 +Interpretation
33 + <what the numbers mean, WITH confidence level (0-3); alternative
34 + explanations considered — artifact first>
35 +
36 +Next experiment
37 + <the most informative follow-up given this result>
38 +```
added experiments/micro/expA_data_reality/README.md +15 −0
@@ -0,0 +1,15 @@
1 +---
2 +project: anomaly-atlas
3 +document: expA_data_reality/README
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +data_source: hfmarketdata.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# expA_data_reality
12 +
13 +Data reality check: what hfmarketdata.io actually returns (granularity, depth, timestamps, corporate actions, limits)
14 +
15 +Status: scaffolded 2026-08-12, not yet run.
added experiments/micro/expA_data_reality/analysis.md +13 −0
@@ -0,0 +1,13 @@
1 +---
2 +project: anomaly-atlas
3 +document: expA_data_reality/analysis
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +data_source: hfmarketdata.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# Analysis — expA_data_reality
12 +
13 +*To be written after results exist. Must include the seven-field block and the evidence standard of CLAUDE.md §10 (never report an in-sample number as a finding).*
added experiments/micro/expA_data_reality/benchmark.py +35 −0
@@ -0,0 +1,35 @@
1 +# =============================================================================
2 +# Project : anomaly-atlas
3 +# File : experiments/micro/expA_data_reality/benchmark.py
4 +# Purpose : Benchmark runner: Data reality check: what hfmarketdata.io actually returns (granularity, depth, timestamps, corporate actions, limits)
5 +# Author : Simon-Pierre Boucher
6 +# Contact : contact@spboucher.ai
7 +# Data src : hfmarketdata.io (sole data source)
8 +# Created : 2026-08-12
9 +# Modified : 2026-08-12
10 +# Platform : macOS / Apple Silicon (arm64)
11 +# License : All rights reserved (research code)
12 +# =============================================================================
13 +
14 +"""Benchmark entry point for expA_data_reality.
15 +
16 +Must embed the hardware manifest in all result output
17 +(see benchmarks/hardware_manifest.py) and write results to
18 +results/expA_data_reality/<timestamp>/. Uses hfmarketdata.io data ONLY, exclusively
19 +through src/anomaly_atlas/data/hf_client.py.
20 +"""
21 +
22 +import sys
23 +from pathlib import Path
24 +
25 +sys.path.insert(0, str(Path(__file__).resolve().parents[3] / "benchmarks"))
26 +from hardware_manifest import collect_manifest # noqa: E402
27 +
28 +
29 +def main() -> None:
30 + manifest = collect_manifest()
31 + raise NotImplementedError("experiment not yet implemented")
32 +
33 +
34 +if __name__ == "__main__":
35 + main()
added experiments/micro/expA_data_reality/hypothesis.md +38 −0
@@ -0,0 +1,38 @@
1 +---
2 +project: anomaly-atlas
3 +document: expA_data_reality/hypothesis
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +data_source: hfmarketdata.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# Hypothesis — expA_data_reality
12 +
13 +```text
14 +Hypothesis
15 + <what we believe and why — pre-specified BEFORE looking at results>
16 +
17 +Falsification criterion
18 + <the concrete measurable outcome that would prove this wrong>
19 +
20 +Artifact null(s)
21 + <the fake-signal baseline(s) this must beat: bounce / staleness /
22 + non-synchronous timestamps / permuted calendar / random walk>
23 +
24 +Method
25 + <exact procedure, universe, split (train/validation/holdout), seeds,
26 + number of hypotheses tested, correction applied>
27 +
28 +Result
29 + <filled after the run: effect size, bootstrap CIs, corrected p-values,
30 + OOS status, cost-adjusted effect, credits used>
31 +
32 +Interpretation
33 + <what the numbers mean, WITH confidence level (0-3); alternative
34 + explanations considered — artifact first>
35 +
36 +Next experiment
37 + <the most informative follow-up given this result>
38 +```
added experiments/micro/expB_artifact_baselines/README.md +15 −0
@@ -0,0 +1,15 @@
1 +---
2 +project: anomaly-atlas
3 +document: expB_artifact_baselines/README
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +data_source: hfmarketdata.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# expB_artifact_baselines
12 +
13 +Artifact baselines: null distributions of bounce, staleness, non-synchronous lead-lag
14 +
15 +Status: scaffolded 2026-08-12, not yet run.
added experiments/micro/expB_artifact_baselines/analysis.md +13 −0
@@ -0,0 +1,13 @@
1 +---
2 +project: anomaly-atlas
3 +document: expB_artifact_baselines/analysis
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +data_source: hfmarketdata.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# Analysis — expB_artifact_baselines
12 +
13 +*To be written after results exist. Must include the seven-field block and the evidence standard of CLAUDE.md §10 (never report an in-sample number as a finding).*
added experiments/micro/expB_artifact_baselines/benchmark.py +35 −0
@@ -0,0 +1,35 @@
1 +# =============================================================================
2 +# Project : anomaly-atlas
3 +# File : experiments/micro/expB_artifact_baselines/benchmark.py
4 +# Purpose : Benchmark runner: Artifact baselines: null distributions of bounce, staleness, non-synchronous lead-lag
5 +# Author : Simon-Pierre Boucher
6 +# Contact : contact@spboucher.ai
7 +# Data src : hfmarketdata.io (sole data source)
8 +# Created : 2026-08-12
9 +# Modified : 2026-08-12
10 +# Platform : macOS / Apple Silicon (arm64)
11 +# License : All rights reserved (research code)
12 +# =============================================================================
13 +
14 +"""Benchmark entry point for expB_artifact_baselines.
15 +
16 +Must embed the hardware manifest in all result output
17 +(see benchmarks/hardware_manifest.py) and write results to
18 +results/expB_artifact_baselines/<timestamp>/. Uses hfmarketdata.io data ONLY, exclusively
19 +through src/anomaly_atlas/data/hf_client.py.
20 +"""
21 +
22 +import sys
23 +from pathlib import Path
24 +
25 +sys.path.insert(0, str(Path(__file__).resolve().parents[3] / "benchmarks"))
26 +from hardware_manifest import collect_manifest # noqa: E402
27 +
28 +
29 +def main() -> None:
30 + manifest = collect_manifest()
31 + raise NotImplementedError("experiment not yet implemented")
32 +
33 +
34 +if __name__ == "__main__":
35 + main()
added experiments/micro/expB_artifact_baselines/hypothesis.md +38 −0
@@ -0,0 +1,38 @@
1 +---
2 +project: anomaly-atlas
3 +document: expB_artifact_baselines/hypothesis
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +data_source: hfmarketdata.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# Hypothesis — expB_artifact_baselines
12 +
13 +```text
14 +Hypothesis
15 + <what we believe and why — pre-specified BEFORE looking at results>
16 +
17 +Falsification criterion
18 + <the concrete measurable outcome that would prove this wrong>
19 +
20 +Artifact null(s)
21 + <the fake-signal baseline(s) this must beat: bounce / staleness /
22 + non-synchronous timestamps / permuted calendar / random walk>
23 +
24 +Method
25 + <exact procedure, universe, split (train/validation/holdout), seeds,
26 + number of hypotheses tested, correction applied>
27 +
28 +Result
29 + <filled after the run: effect size, bootstrap CIs, corrected p-values,
30 + OOS status, cost-adjusted effect, credits used>
31 +
32 +Interpretation
33 + <what the numbers mean, WITH confidence level (0-3); alternative
34 + explanations considered — artifact first>
35 +
36 +Next experiment
37 + <the most informative follow-up given this result>
38 +```
added experiments/micro/expC_reversion_scan/README.md +15 −0
@@ -0,0 +1,15 @@
1 +---
2 +project: anomaly-atlas
3 +document: expC_reversion_scan/README
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +data_source: hfmarketdata.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# expC_reversion_scan
12 +
13 +Mean-reversion scan: variance ratios, half-life, AR tests net of the bounce null
14 +
15 +Status: scaffolded 2026-08-12, not yet run.
added experiments/micro/expC_reversion_scan/analysis.md +13 −0
@@ -0,0 +1,13 @@
1 +---
2 +project: anomaly-atlas
3 +document: expC_reversion_scan/analysis
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +data_source: hfmarketdata.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# Analysis — expC_reversion_scan
12 +
13 +*To be written after results exist. Must include the seven-field block and the evidence standard of CLAUDE.md §10 (never report an in-sample number as a finding).*
added experiments/micro/expC_reversion_scan/benchmark.py +35 −0
@@ -0,0 +1,35 @@
1 +# =============================================================================
2 +# Project : anomaly-atlas
3 +# File : experiments/micro/expC_reversion_scan/benchmark.py
4 +# Purpose : Benchmark runner: Mean-reversion scan: variance ratios, half-life, AR tests net of the bounce null
5 +# Author : Simon-Pierre Boucher
6 +# Contact : contact@spboucher.ai
7 +# Data src : hfmarketdata.io (sole data source)
8 +# Created : 2026-08-12
9 +# Modified : 2026-08-12
10 +# Platform : macOS / Apple Silicon (arm64)
11 +# License : All rights reserved (research code)
12 +# =============================================================================
13 +
14 +"""Benchmark entry point for expC_reversion_scan.
15 +
16 +Must embed the hardware manifest in all result output
17 +(see benchmarks/hardware_manifest.py) and write results to
18 +results/expC_reversion_scan/<timestamp>/. Uses hfmarketdata.io data ONLY, exclusively
19 +through src/anomaly_atlas/data/hf_client.py.
20 +"""
21 +
22 +import sys
23 +from pathlib import Path
24 +
25 +sys.path.insert(0, str(Path(__file__).resolve().parents[3] / "benchmarks"))
26 +from hardware_manifest import collect_manifest # noqa: E402
27 +
28 +
29 +def main() -> None:
30 + manifest = collect_manifest()
31 + raise NotImplementedError("experiment not yet implemented")
32 +
33 +
34 +if __name__ == "__main__":
35 + main()
added experiments/micro/expC_reversion_scan/hypothesis.md +38 −0
@@ -0,0 +1,38 @@
1 +---
2 +project: anomaly-atlas
3 +document: expC_reversion_scan/hypothesis
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +data_source: hfmarketdata.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# Hypothesis — expC_reversion_scan
12 +
13 +```text
14 +Hypothesis
15 + <what we believe and why — pre-specified BEFORE looking at results>
16 +
17 +Falsification criterion
18 + <the concrete measurable outcome that would prove this wrong>
19 +
20 +Artifact null(s)
21 + <the fake-signal baseline(s) this must beat: bounce / staleness /
22 + non-synchronous timestamps / permuted calendar / random walk>
23 +
24 +Method
25 + <exact procedure, universe, split (train/validation/holdout), seeds,
26 + number of hypotheses tested, correction applied>
27 +
28 +Result
29 + <filled after the run: effect size, bootstrap CIs, corrected p-values,
30 + OOS status, cost-adjusted effect, credits used>
31 +
32 +Interpretation
33 + <what the numbers mean, WITH confidence level (0-3); alternative
34 + explanations considered — artifact first>
35 +
36 +Next experiment
37 + <the most informative follow-up given this result>
38 +```
added experiments/micro/expD_leadlag_scan/README.md +15 −0
@@ -0,0 +1,15 @@
1 +---
2 +project: anomaly-atlas
3 +document: expD_leadlag_scan/README
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +data_source: hfmarketdata.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# expD_leadlag_scan
12 +
13 +Lead-lag scan: lagged cross-correlations and Granger, separating timestamp artifacts
14 +
15 +Status: scaffolded 2026-08-12, not yet run.
added experiments/micro/expD_leadlag_scan/analysis.md +13 −0
@@ -0,0 +1,13 @@
1 +---
2 +project: anomaly-atlas
3 +document: expD_leadlag_scan/analysis
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +data_source: hfmarketdata.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# Analysis — expD_leadlag_scan
12 +
13 +*To be written after results exist. Must include the seven-field block and the evidence standard of CLAUDE.md §10 (never report an in-sample number as a finding).*
added experiments/micro/expD_leadlag_scan/benchmark.py +35 −0
@@ -0,0 +1,35 @@
1 +# =============================================================================
2 +# Project : anomaly-atlas
3 +# File : experiments/micro/expD_leadlag_scan/benchmark.py
4 +# Purpose : Benchmark runner: Lead-lag scan: lagged cross-correlations and Granger, separating timestamp artifacts
5 +# Author : Simon-Pierre Boucher
6 +# Contact : contact@spboucher.ai
7 +# Data src : hfmarketdata.io (sole data source)
8 +# Created : 2026-08-12
9 +# Modified : 2026-08-12
10 +# Platform : macOS / Apple Silicon (arm64)
11 +# License : All rights reserved (research code)
12 +# =============================================================================
13 +
14 +"""Benchmark entry point for expD_leadlag_scan.
15 +
16 +Must embed the hardware manifest in all result output
17 +(see benchmarks/hardware_manifest.py) and write results to
18 +results/expD_leadlag_scan/<timestamp>/. Uses hfmarketdata.io data ONLY, exclusively
19 +through src/anomaly_atlas/data/hf_client.py.
20 +"""
21 +
22 +import sys
23 +from pathlib import Path
24 +
25 +sys.path.insert(0, str(Path(__file__).resolve().parents[3] / "benchmarks"))
26 +from hardware_manifest import collect_manifest # noqa: E402
27 +
28 +
29 +def main() -> None:
30 + manifest = collect_manifest()
31 + raise NotImplementedError("experiment not yet implemented")
32 +
33 +
34 +if __name__ == "__main__":
35 + main()
added experiments/micro/expD_leadlag_scan/hypothesis.md +38 −0
@@ -0,0 +1,38 @@
1 +---
2 +project: anomaly-atlas
3 +document: expD_leadlag_scan/hypothesis
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +data_source: hfmarketdata.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# Hypothesis — expD_leadlag_scan
12 +
13 +```text
14 +Hypothesis
15 + <what we believe and why — pre-specified BEFORE looking at results>
16 +
17 +Falsification criterion
18 + <the concrete measurable outcome that would prove this wrong>
19 +
20 +Artifact null(s)
21 + <the fake-signal baseline(s) this must beat: bounce / staleness /
22 + non-synchronous timestamps / permuted calendar / random walk>
23 +
24 +Method
25 + <exact procedure, universe, split (train/validation/holdout), seeds,
26 + number of hypotheses tested, correction applied>
27 +
28 +Result
29 + <filled after the run: effect size, bootstrap CIs, corrected p-values,
30 + OOS status, cost-adjusted effect, credits used>
31 +
32 +Interpretation
33 + <what the numbers mean, WITH confidence level (0-3); alternative
34 + explanations considered — artifact first>
35 +
36 +Next experiment
37 + <the most informative follow-up given this result>
38 +```
added experiments/micro/expE_calendar_scan/README.md +15 −0
@@ -0,0 +1,15 @@
1 +---
2 +project: anomaly-atlas
3 +document: expE_calendar_scan/README
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +data_source: hfmarketdata.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# expE_calendar_scan
12 +
13 +Calendar scan: intraday, day-of-week, turn-of-month with pre-counted hypothesis budget
14 +
15 +Status: scaffolded 2026-08-12, not yet run.
added experiments/micro/expE_calendar_scan/analysis.md +13 −0
@@ -0,0 +1,13 @@
1 +---
2 +project: anomaly-atlas
3 +document: expE_calendar_scan/analysis
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +data_source: hfmarketdata.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# Analysis — expE_calendar_scan
12 +
13 +*To be written after results exist. Must include the seven-field block and the evidence standard of CLAUDE.md §10 (never report an in-sample number as a finding).*
added experiments/micro/expE_calendar_scan/benchmark.py +35 −0
@@ -0,0 +1,35 @@
1 +# =============================================================================
2 +# Project : anomaly-atlas
3 +# File : experiments/micro/expE_calendar_scan/benchmark.py
4 +# Purpose : Benchmark runner: Calendar scan: intraday, day-of-week, turn-of-month with pre-counted hypothesis budget
5 +# Author : Simon-Pierre Boucher
6 +# Contact : contact@spboucher.ai
7 +# Data src : hfmarketdata.io (sole data source)
8 +# Created : 2026-08-12
9 +# Modified : 2026-08-12
10 +# Platform : macOS / Apple Silicon (arm64)
11 +# License : All rights reserved (research code)
12 +# =============================================================================
13 +
14 +"""Benchmark entry point for expE_calendar_scan.
15 +
16 +Must embed the hardware manifest in all result output
17 +(see benchmarks/hardware_manifest.py) and write results to
18 +results/expE_calendar_scan/<timestamp>/. Uses hfmarketdata.io data ONLY, exclusively
19 +through src/anomaly_atlas/data/hf_client.py.
20 +"""
21 +
22 +import sys
23 +from pathlib import Path
24 +
25 +sys.path.insert(0, str(Path(__file__).resolve().parents[3] / "benchmarks"))
26 +from hardware_manifest import collect_manifest # noqa: E402
27 +
28 +
29 +def main() -> None:
30 + manifest = collect_manifest()
31 + raise NotImplementedError("experiment not yet implemented")
32 +
33 +
34 +if __name__ == "__main__":
35 + main()
added experiments/micro/expE_calendar_scan/hypothesis.md +38 −0
@@ -0,0 +1,38 @@
1 +---
2 +project: anomaly-atlas
3 +document: expE_calendar_scan/hypothesis
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +data_source: hfmarketdata.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# Hypothesis — expE_calendar_scan
12 +
13 +```text
14 +Hypothesis
15 + <what we believe and why — pre-specified BEFORE looking at results>
16 +
17 +Falsification criterion
18 + <the concrete measurable outcome that would prove this wrong>
19 +
20 +Artifact null(s)
21 + <the fake-signal baseline(s) this must beat: bounce / staleness /
22 + non-synchronous timestamps / permuted calendar / random walk>
23 +
24 +Method
25 + <exact procedure, universe, split (train/validation/holdout), seeds,
26 + number of hypotheses tested, correction applied>
27 +
28 +Result
29 + <filled after the run: effect size, bootstrap CIs, corrected p-values,
30 + OOS status, cost-adjusted effect, credits used>
31 +
32 +Interpretation
33 + <what the numbers mean, WITH confidence level (0-3); alternative
34 + explanations considered — artifact first>
35 +
36 +Next experiment
37 + <the most informative follow-up given this result>
38 +```
added experiments/micro/expF_multiple_testing/README.md +15 −0
@@ -0,0 +1,15 @@
1 +---
2 +project: anomaly-atlas
3 +document: expF_multiple_testing/README
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +data_source: hfmarketdata.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# expF_multiple_testing
12 +
13 +Multiple-testing survival: White RC, SPA, FDR, deflated Sharpe on C-E output
14 +
15 +Status: scaffolded 2026-08-12, not yet run.
added experiments/micro/expF_multiple_testing/analysis.md +13 −0
@@ -0,0 +1,13 @@
1 +---
2 +project: anomaly-atlas
3 +document: expF_multiple_testing/analysis
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +data_source: hfmarketdata.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# Analysis — expF_multiple_testing
12 +
13 +*To be written after results exist. Must include the seven-field block and the evidence standard of CLAUDE.md §10 (never report an in-sample number as a finding).*
added experiments/micro/expF_multiple_testing/benchmark.py +35 −0
@@ -0,0 +1,35 @@
1 +# =============================================================================
2 +# Project : anomaly-atlas
3 +# File : experiments/micro/expF_multiple_testing/benchmark.py
4 +# Purpose : Benchmark runner: Multiple-testing survival: White RC, SPA, FDR, deflated Sharpe on C-E output
5 +# Author : Simon-Pierre Boucher
6 +# Contact : contact@spboucher.ai
7 +# Data src : hfmarketdata.io (sole data source)
8 +# Created : 2026-08-12
9 +# Modified : 2026-08-12
10 +# Platform : macOS / Apple Silicon (arm64)
11 +# License : All rights reserved (research code)
12 +# =============================================================================
13 +
14 +"""Benchmark entry point for expF_multiple_testing.
15 +
16 +Must embed the hardware manifest in all result output
17 +(see benchmarks/hardware_manifest.py) and write results to
18 +results/expF_multiple_testing/<timestamp>/. Uses hfmarketdata.io data ONLY, exclusively
19 +through src/anomaly_atlas/data/hf_client.py.
20 +"""
21 +
22 +import sys
23 +from pathlib import Path
24 +
25 +sys.path.insert(0, str(Path(__file__).resolve().parents[3] / "benchmarks"))
26 +from hardware_manifest import collect_manifest # noqa: E402
27 +
28 +
29 +def main() -> None:
30 + manifest = collect_manifest()
31 + raise NotImplementedError("experiment not yet implemented")
32 +
33 +
34 +if __name__ == "__main__":
35 + main()
added experiments/micro/expF_multiple_testing/hypothesis.md +38 −0
@@ -0,0 +1,38 @@
1 +---
2 +project: anomaly-atlas
3 +document: expF_multiple_testing/hypothesis
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +data_source: hfmarketdata.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# Hypothesis — expF_multiple_testing
12 +
13 +```text
14 +Hypothesis
15 + <what we believe and why — pre-specified BEFORE looking at results>
16 +
17 +Falsification criterion
18 + <the concrete measurable outcome that would prove this wrong>
19 +
20 +Artifact null(s)
21 + <the fake-signal baseline(s) this must beat: bounce / staleness /
22 + non-synchronous timestamps / permuted calendar / random walk>
23 +
24 +Method
25 + <exact procedure, universe, split (train/validation/holdout), seeds,
26 + number of hypotheses tested, correction applied>
27 +
28 +Result
29 + <filled after the run: effect size, bootstrap CIs, corrected p-values,
30 + OOS status, cost-adjusted effect, credits used>
31 +
32 +Interpretation
33 + <what the numbers mean, WITH confidence level (0-3); alternative
34 + explanations considered — artifact first>
35 +
36 +Next experiment
37 + <the most informative follow-up given this result>
38 +```
added experiments/micro/expG_cost_frontier/README.md +15 −0
@@ -0,0 +1,15 @@
1 +---
2 +project: anomaly-atlas
3 +document: expG_cost_frontier/README
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +data_source: hfmarketdata.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# expG_cost_frontier
12 +
13 +Cost frontier: transaction-cost sweep, where each surviving net effect crosses zero
14 +
15 +Status: scaffolded 2026-08-12, not yet run.
added experiments/micro/expG_cost_frontier/analysis.md +13 −0
@@ -0,0 +1,13 @@
1 +---
2 +project: anomaly-atlas
3 +document: expG_cost_frontier/analysis
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +data_source: hfmarketdata.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# Analysis — expG_cost_frontier
12 +
13 +*To be written after results exist. Must include the seven-field block and the evidence standard of CLAUDE.md §10 (never report an in-sample number as a finding).*
added experiments/micro/expG_cost_frontier/benchmark.py +35 −0
@@ -0,0 +1,35 @@
1 +# =============================================================================
2 +# Project : anomaly-atlas
3 +# File : experiments/micro/expG_cost_frontier/benchmark.py
4 +# Purpose : Benchmark runner: Cost frontier: transaction-cost sweep, where each surviving net effect crosses zero
5 +# Author : Simon-Pierre Boucher
6 +# Contact : contact@spboucher.ai
7 +# Data src : hfmarketdata.io (sole data source)
8 +# Created : 2026-08-12
9 +# Modified : 2026-08-12
10 +# Platform : macOS / Apple Silicon (arm64)
11 +# License : All rights reserved (research code)
12 +# =============================================================================
13 +
14 +"""Benchmark entry point for expG_cost_frontier.
15 +
16 +Must embed the hardware manifest in all result output
17 +(see benchmarks/hardware_manifest.py) and write results to
18 +results/expG_cost_frontier/<timestamp>/. Uses hfmarketdata.io data ONLY, exclusively
19 +through src/anomaly_atlas/data/hf_client.py.
20 +"""
21 +
22 +import sys
23 +from pathlib import Path
24 +
25 +sys.path.insert(0, str(Path(__file__).resolve().parents[3] / "benchmarks"))
26 +from hardware_manifest import collect_manifest # noqa: E402
27 +
28 +
29 +def main() -> None:
30 + manifest = collect_manifest()
31 + raise NotImplementedError("experiment not yet implemented")
32 +
33 +
34 +if __name__ == "__main__":
35 + main()
added experiments/micro/expG_cost_frontier/hypothesis.md +38 −0
@@ -0,0 +1,38 @@
1 +---
2 +project: anomaly-atlas
3 +document: expG_cost_frontier/hypothesis
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +data_source: hfmarketdata.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# Hypothesis — expG_cost_frontier
12 +
13 +```text
14 +Hypothesis
15 + <what we believe and why — pre-specified BEFORE looking at results>
16 +
17 +Falsification criterion
18 + <the concrete measurable outcome that would prove this wrong>
19 +
20 +Artifact null(s)
21 + <the fake-signal baseline(s) this must beat: bounce / staleness /
22 + non-synchronous timestamps / permuted calendar / random walk>
23 +
24 +Method
25 + <exact procedure, universe, split (train/validation/holdout), seeds,
26 + number of hypotheses tested, correction applied>
27 +
28 +Result
29 + <filled after the run: effect size, bootstrap CIs, corrected p-values,
30 + OOS status, cost-adjusted effect, credits used>
31 +
32 +Interpretation
33 + <what the numbers mean, WITH confidence level (0-3); alternative
34 + explanations considered — artifact first>
35 +
36 +Next experiment
37 + <the most informative follow-up given this result>
38 +```
added experiments/micro/expH_oos_stability/README.md +15 −0
@@ -0,0 +1,15 @@
1 +---
2 +project: anomaly-atlas
3 +document: expH_oos_stability/README
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +data_source: hfmarketdata.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# expH_oos_stability
12 +
13 +Out-of-sample stability: once-touched holdout and sub-period evaluation of survivors
14 +
15 +Status: scaffolded 2026-08-12, not yet run.
added experiments/micro/expH_oos_stability/analysis.md +13 −0
@@ -0,0 +1,13 @@
1 +---
2 +project: anomaly-atlas
3 +document: expH_oos_stability/analysis
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +data_source: hfmarketdata.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# Analysis — expH_oos_stability
12 +
13 +*To be written after results exist. Must include the seven-field block and the evidence standard of CLAUDE.md §10 (never report an in-sample number as a finding).*
added experiments/micro/expH_oos_stability/benchmark.py +35 −0
@@ -0,0 +1,35 @@
1 +# =============================================================================
2 +# Project : anomaly-atlas
3 +# File : experiments/micro/expH_oos_stability/benchmark.py
4 +# Purpose : Benchmark runner: Out-of-sample stability: once-touched holdout and sub-period evaluation of survivors
5 +# Author : Simon-Pierre Boucher
6 +# Contact : contact@spboucher.ai
7 +# Data src : hfmarketdata.io (sole data source)
8 +# Created : 2026-08-12
9 +# Modified : 2026-08-12
10 +# Platform : macOS / Apple Silicon (arm64)
11 +# License : All rights reserved (research code)
12 +# =============================================================================
13 +
14 +"""Benchmark entry point for expH_oos_stability.
15 +
16 +Must embed the hardware manifest in all result output
17 +(see benchmarks/hardware_manifest.py) and write results to
18 +results/expH_oos_stability/<timestamp>/. Uses hfmarketdata.io data ONLY, exclusively
19 +through src/anomaly_atlas/data/hf_client.py.
20 +"""
21 +
22 +import sys
23 +from pathlib import Path
24 +
25 +sys.path.insert(0, str(Path(__file__).resolve().parents[3] / "benchmarks"))
26 +from hardware_manifest import collect_manifest # noqa: E402
27 +
28 +
29 +def main() -> None:
30 + manifest = collect_manifest()
31 + raise NotImplementedError("experiment not yet implemented")
32 +
33 +
34 +if __name__ == "__main__":
35 + main()
added experiments/micro/expH_oos_stability/hypothesis.md +38 −0
@@ -0,0 +1,38 @@
1 +---
2 +project: anomaly-atlas
3 +document: expH_oos_stability/hypothesis
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +data_source: hfmarketdata.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# Hypothesis — expH_oos_stability
12 +
13 +```text
14 +Hypothesis
15 + <what we believe and why — pre-specified BEFORE looking at results>
16 +
17 +Falsification criterion
18 + <the concrete measurable outcome that would prove this wrong>
19 +
20 +Artifact null(s)
21 + <the fake-signal baseline(s) this must beat: bounce / staleness /
22 + non-synchronous timestamps / permuted calendar / random walk>
23 +
24 +Method
25 + <exact procedure, universe, split (train/validation/holdout), seeds,
26 + number of hypotheses tested, correction applied>
27 +
28 +Result
29 + <filled after the run: effect size, bootstrap CIs, corrected p-values,
30 + OOS status, cost-adjusted effect, credits used>
31 +
32 +Interpretation
33 + <what the numbers mean, WITH confidence level (0-3); alternative
34 + explanations considered — artifact first>
35 +
36 +Next experiment
37 + <the most informative follow-up given this result>
38 +```
added pyproject.toml +48 −0
@@ -0,0 +1,48 @@
1 +# =============================================================================
2 +# Project : anomaly-atlas
3 +# File : pyproject.toml
4 +# Purpose : Python project configuration (deps, ruff, pytest)
5 +# Author : Simon-Pierre Boucher
6 +# Contact : contact@spboucher.ai
7 +# Data src : hfmarketdata.io (sole data source)
8 +# Created : 2026-08-12
9 +# Modified : 2026-08-12
10 +# Platform : macOS / Apple Silicon (arm64)
11 +# License : All rights reserved (research code)
12 +# =============================================================================
13 +
14 +[project]
15 +name = "anomaly-atlas"
16 +version = "0.0.1"
17 +description = "Research: systematic discovery & rigorous validation of statistical anomalies in open HF market data (hfmarketdata.io)"
18 +authors = [{ name = "Simon-Pierre Boucher", email = "contact@spboucher.ai" }]
19 +requires-python = ">=3.11"
20 +dependencies = [
21 + "numpy",
22 + "pandas",
23 + "polars",
24 + "pyarrow",
25 + "duckdb",
26 + "requests",
27 + "scipy",
28 + "statsmodels",
29 + "arch",
30 + "pyyaml",
31 +]
32 +
33 +[project.optional-dependencies]
34 +dev = ["ruff", "pytest"]
35 +
36 +[tool.setuptools.packages.find]
37 +where = ["src"]
38 +
39 +[tool.ruff]
40 +line-length = 100
41 +target-version = "py311"
42 +
43 +[tool.ruff.lint]
44 +select = ["E", "F", "W", "I", "UP", "B"]
45 +
46 +[tool.pytest.ini_options]
47 +testpaths = ["src", "experiments", "benchmarks"]
48 +python_files = ["test_*.py"]
added research/LOG.md +30 −0
@@ -0,0 +1,30 @@
1 +---
2 +project: anomaly-atlas
3 +document: Research log
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +data_source: hfmarketdata.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# Research log — anomaly-atlas
12 +
13 +Append-only. Every entry: date/time+timezone, question, experiment, result,
14 +interpretation, decision (CLAUDE.md §12).
15 +
16 +## 2026-08-12 21:00 ET — Project bootstrap
17 +
18 +**Question.** None yet — administrative.
19 +
20 +**Action.** Repository scaffolded per CLAUDE.md §3, mirroring the
21 +localvm-research layout (the `site/` of the charter is implemented as `web/`,
22 +a Node/Express research-showcase platform, like localvm-research). Author
23 +headers with the mandatory `Data src: hfmarketdata.io` field on every source
24 +file; `tools/check_headers.py` enforces them. Web platform deployed on
25 +MacLustr node M3U96b (port 8150) behind ngrok at www.anomaly-atlas.io.
26 +
27 +**Decision.** Next step is Phase 0.5 / Experiment A (data reality check):
28 +establish empirically what hfmarketdata.io actually provides before any
29 +methodology is designed. No statistical work before the detectors pass the
30 +synthetic-series tests (§8.1).
added research/artifact_taxonomy.md +17 −0
@@ -0,0 +1,17 @@
1 +---
2 +project: anomaly-atlas
3 +document: Artifact taxonomy
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +data_source: hfmarketdata.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# Artifact taxonomy (living document)
12 +
13 +*Primary deliverable (Q4). Not yet written.*
14 +
15 +Catalogue of artifacts in THIS dataset that masquerade as anomalies —
16 +bid-ask bounce, stale prices, non-synchronous timestamps, survivorship,
17 +look-ahead, corporate-action gaps — with detection and neutralization for each.
added research/bibliography.md +13 −0
@@ -0,0 +1,13 @@
1 +---
2 +project: anomaly-atlas
3 +document: Bibliography
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +data_source: hfmarketdata.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# Bibliography
12 +
13 +Every consulted source with URL and access date. Phase 1 populates this.
added research/candidate_ranking.md +14 −0
@@ -0,0 +1,14 @@
1 +---
2 +project: anomaly-atlas
3 +document: Candidate ranking
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +data_source: hfmarketdata.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# Candidate ranking
12 +
13 +*Phase 4 output. Not yet written.* 10-axis scoring per CLAUDE.md §7;
14 +3-5 candidates selected for prototyping.
added research/data_source_profile.md +17 −0
@@ -0,0 +1,17 @@
1 +---
2 +project: anomaly-atlas
3 +document: Data source profile
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +data_source: hfmarketdata.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# Data source profile — hfmarketdata.io
12 +
13 +*Phase 0.5 output (Experiment A). Not yet written.*
14 +
15 +To establish empirically: symbols per asset class, history depth, true finest
16 +granularity, timestamp semantics (exchange vs vendor), corporate-action
17 +handling, missing-data patterns, rate/credit limits.
added research/methodology.md +14 −0
@@ -0,0 +1,14 @@
1 +---
2 +project: anomaly-atlas
3 +document: Validation methodology
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +data_source: hfmarketdata.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# Validation methodology
12 +
13 +*Phase 9 output. Not yet written.* The pre-registered protocol that turns
14 +(hypothesis + universe + split) into a confidence-labeled atlas entry.
added research/notes/README.md +13 −0
@@ -0,0 +1,13 @@
1 +---
2 +project: anomaly-atlas
3 +document: Literature notes index
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +data_source: hfmarketdata.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# Phase 1 literature notes
12 +
13 +One note file per theme (CLAUDE.md §4.1-4.6). None written yet.
added research/novelty_check.md +13 −0
@@ -0,0 +1,13 @@
1 +---
2 +project: anomaly-atlas
3 +document: Novelty check
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +data_source: hfmarketdata.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# Novelty check
12 +
13 +*Phase 11 output — written last. Assume NOT novel until evidence says otherwise.*
added research/research_gaps.md +14 −0
@@ -0,0 +1,14 @@
1 +---
2 +project: anomaly-atlas
3 +document: Research gaps
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +data_source: hfmarketdata.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# Research gaps
12 +
13 +*Phase 3 output. Not yet written.* Target: >= 20 substantially different,
14 +testable hypotheses, each with an explicitly constructable artifact null (CLAUDE.md §6).
added research/state_of_the_art.md +13 −0
@@ -0,0 +1,13 @@
1 +---
2 +project: anomaly-atlas
3 +document: State of the art
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +data_source: hfmarketdata.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# State of the art
12 +
13 +*Phase 2 output. Not yet written.* Per-anomaly template in CLAUDE.md §5.
added results/README.md +16 −0
@@ -0,0 +1,16 @@
1 +---
2 +project: anomaly-atlas
3 +document: Results directory conventions
4 +author: Simon-Pierre Boucher
5 +contact: contact@spboucher.ai
6 +data_source: hfmarketdata.io
7 +created: 2026-08-12
8 +status: draft
9 +---
10 +
11 +# results
12 +
13 +Raw experiment outputs: `results/<experiment>/<UTC timestamp>/…`.
14 +Every result JSON embeds the hardware manifest, the data-manifest hash, and
15 +author/contact/data-source attribution. Reproducible from commit + config +
16 +manifest + seed. Generated files here are exempt from header checks.
added src/anomaly_atlas/__init__.py +20 −0
@@ -0,0 +1,20 @@
1 +# =============================================================================
2 +# Project : anomaly-atlas
3 +# File : src/anomaly_atlas/__init__.py
4 +# Purpose : anomaly_atlas package root
5 +# Author : Simon-Pierre Boucher
6 +# Contact : contact@spboucher.ai
7 +# Data src : hfmarketdata.io (sole data source)
8 +# Created : 2026-08-12
9 +# Modified : 2026-08-12
10 +# Platform : macOS / Apple Silicon (arm64)
11 +# License : All rights reserved (research code)
12 +# =============================================================================
13 +"""anomaly-atlas — systematic discovery & rigorous validation of statistical
14 +anomalies in open HF market data (hfmarketdata.io, sole source).
15 +
16 +Honesty doctrine (CLAUDE.md §2.1): every candidate anomaly is an artifact
17 +until proven otherwise; in-sample results are never findings.
18 +"""
19 +
20 +__version__ = "0.0.1"
added src/anomaly_atlas/atlas/__init__.py +16 −0
@@ -0,0 +1,16 @@
1 +# =============================================================================
2 +# Project : anomaly-atlas
3 +# File : src/anomaly_atlas/atlas/__init__.py
4 +# Purpose : Finding schema, provenance, confidence serialization (Levels 0-3)
5 +# Author : Simon-Pierre Boucher
6 +# Contact : contact@spboucher.ai
7 +# Data src : hfmarketdata.io (sole data source)
8 +# Created : 2026-08-12
9 +# Modified : 2026-08-12
10 +# Platform : macOS / Apple Silicon (arm64)
11 +# License : All rights reserved (research code)
12 +# =============================================================================
13 +"""Finding schema, provenance, confidence serialization (Levels 0-3).
14 +
15 +Stub scaffolded 2026-08-12; implemented in later phases (see CLAUDE.md).
16 +"""
added src/anomaly_atlas/instrumentation/__init__.py +16 −0
@@ -0,0 +1,16 @@
1 +# =============================================================================
2 +# Project : anomaly-atlas
3 +# File : src/anomaly_atlas/instrumentation/__init__.py
4 +# Purpose : API credits, cache hit rate, wall-clock, RAM, DuckDB query cost
5 +# Author : Simon-Pierre Boucher
6 +# Contact : contact@spboucher.ai
7 +# Data src : hfmarketdata.io (sole data source)
8 +# Created : 2026-08-12
9 +# Modified : 2026-08-12
10 +# Platform : macOS / Apple Silicon (arm64)
11 +# License : All rights reserved (research code)
12 +# =============================================================================
13 +"""API credits, cache hit rate, wall-clock, RAM, DuckDB query cost.
14 +
15 +Stub scaffolded 2026-08-12; implemented in later phases (see CLAUDE.md).
16 +"""
added src/anomaly_atlas/stats/__init__.py +16 −0
@@ -0,0 +1,16 @@
1 +# =============================================================================
2 +# Project : anomaly-atlas
3 +# File : src/anomaly_atlas/stats/__init__.py
4 +# Purpose : anomaly_atlas.stats subpackage
5 +# Author : Simon-Pierre Boucher
6 +# Contact : contact@spboucher.ai
7 +# Data src : hfmarketdata.io (sole data source)
8 +# Created : 2026-08-12
9 +# Modified : 2026-08-12
10 +# Platform : macOS / Apple Silicon (arm64)
11 +# License : All rights reserved (research code)
12 +# =============================================================================
13 +"""anomaly_atlas.stats subpackage.
14 +
15 +Stub scaffolded 2026-08-12; implemented in later phases (see CLAUDE.md).
16 +"""
added src/anomaly_atlas/stats/bootstrap.py +16 −0
@@ -0,0 +1,16 @@
1 +# =============================================================================
2 +# Project : anomaly-atlas
3 +# File : src/anomaly_atlas/stats/bootstrap.py
4 +# Purpose : Block/stationary bootstrap and confidence intervals
5 +# Author : Simon-Pierre Boucher
6 +# Contact : contact@spboucher.ai
7 +# Data src : hfmarketdata.io (sole data source)
8 +# Created : 2026-08-12
9 +# Modified : 2026-08-12
10 +# Platform : macOS / Apple Silicon (arm64)
11 +# License : All rights reserved (research code)
12 +# =============================================================================
13 +"""Block/stationary bootstrap and confidence intervals.
14 +
15 +Stub scaffolded 2026-08-12; implemented in later phases (see CLAUDE.md).
16 +"""
added src/anomaly_atlas/stats/calendar.py +16 −0
@@ -0,0 +1,16 @@
1 +# =============================================================================
2 +# Project : anomaly-atlas
3 +# File : src/anomaly_atlas/stats/calendar.py
4 +# Purpose : Calendar/seasonal/intraday effect tests
5 +# Author : Simon-Pierre Boucher
6 +# Contact : contact@spboucher.ai
7 +# Data src : hfmarketdata.io (sole data source)
8 +# Created : 2026-08-12
9 +# Modified : 2026-08-12
10 +# Platform : macOS / Apple Silicon (arm64)
11 +# License : All rights reserved (research code)
12 +# =============================================================================
13 +"""Calendar/seasonal/intraday effect tests.
14 +
15 +Stub scaffolded 2026-08-12; implemented in later phases (see CLAUDE.md).
16 +"""
added src/anomaly_atlas/stats/leadlag.py +16 −0
@@ -0,0 +1,16 @@
1 +# =============================================================================
2 +# Project : anomaly-atlas
3 +# File : src/anomaly_atlas/stats/leadlag.py
4 +# Purpose : Lead-lag tests: cross-correlation, Granger, lagged regressions
5 +# Author : Simon-Pierre Boucher
6 +# Contact : contact@spboucher.ai
7 +# Data src : hfmarketdata.io (sole data source)
8 +# Created : 2026-08-12
9 +# Modified : 2026-08-12
10 +# Platform : macOS / Apple Silicon (arm64)
11 +# License : All rights reserved (research code)
12 +# =============================================================================
13 +"""Lead-lag tests: cross-correlation, Granger, lagged regressions.
14 +
15 +Stub scaffolded 2026-08-12; implemented in later phases (see CLAUDE.md).
16 +"""
added src/anomaly_atlas/stats/multiple_testing.py +16 −0
@@ -0,0 +1,16 @@
1 +# =============================================================================
2 +# Project : anomaly-atlas
3 +# File : src/anomaly_atlas/stats/multiple_testing.py
4 +# Purpose : FDR/Bonferroni, White reality check, SPA, deflated Sharpe
5 +# Author : Simon-Pierre Boucher
6 +# Contact : contact@spboucher.ai
7 +# Data src : hfmarketdata.io (sole data source)
8 +# Created : 2026-08-12
9 +# Modified : 2026-08-12
10 +# Platform : macOS / Apple Silicon (arm64)
11 +# License : All rights reserved (research code)
12 +# =============================================================================
13 +"""FDR/Bonferroni, White reality check, SPA, deflated Sharpe.
14 +
15 +Stub scaffolded 2026-08-12; implemented in later phases (see CLAUDE.md).
16 +"""
added src/anomaly_atlas/stats/reversion.py +16 −0
@@ -0,0 +1,16 @@
1 +# =============================================================================
2 +# Project : anomaly-atlas
3 +# File : src/anomaly_atlas/stats/reversion.py
4 +# Purpose : Mean-reversion tests: variance ratios, Hurst, AR, half-life
5 +# Author : Simon-Pierre Boucher
6 +# Contact : contact@spboucher.ai
7 +# Data src : hfmarketdata.io (sole data source)
8 +# Created : 2026-08-12
9 +# Modified : 2026-08-12
10 +# Platform : macOS / Apple Silicon (arm64)
11 +# License : All rights reserved (research code)
12 +# =============================================================================
13 +"""Mean-reversion tests: variance ratios, Hurst, AR, half-life.
14 +
15 +Stub scaffolded 2026-08-12; implemented in later phases (see CLAUDE.md).
16 +"""
added src/anomaly_atlas/validation/__init__.py +16 −0
@@ -0,0 +1,16 @@
1 +# =============================================================================
2 +# Project : anomaly-atlas
3 +# File : src/anomaly_atlas/validation/__init__.py
4 +# Purpose : anomaly_atlas.validation subpackage
5 +# Author : Simon-Pierre Boucher
6 +# Contact : contact@spboucher.ai
7 +# Data src : hfmarketdata.io (sole data source)
8 +# Created : 2026-08-12
9 +# Modified : 2026-08-12
10 +# Platform : macOS / Apple Silicon (arm64)
11 +# License : All rights reserved (research code)
12 +# =============================================================================
13 +"""anomaly_atlas.validation subpackage.
14 +
15 +Stub scaffolded 2026-08-12; implemented in later phases (see CLAUDE.md).
16 +"""
added src/anomaly_atlas/validation/artifacts.py +16 −0
@@ -0,0 +1,16 @@
1 +# =============================================================================
2 +# Project : anomaly-atlas
3 +# File : src/anomaly_atlas/validation/artifacts.py
4 +# Purpose : Artifact detectors: bid-ask bounce, staleness, look-ahead
5 +# Author : Simon-Pierre Boucher
6 +# Contact : contact@spboucher.ai
7 +# Data src : hfmarketdata.io (sole data source)
8 +# Created : 2026-08-12
9 +# Modified : 2026-08-12
10 +# Platform : macOS / Apple Silicon (arm64)
11 +# License : All rights reserved (research code)
12 +# =============================================================================
13 +"""Artifact detectors: bid-ask bounce, staleness, look-ahead.
14 +
15 +Stub scaffolded 2026-08-12; implemented in later phases (see CLAUDE.md).
16 +"""
added src/anomaly_atlas/validation/costs.py +16 −0
@@ -0,0 +1,16 @@
1 +# =============================================================================
2 +# Project : anomaly-atlas
3 +# File : src/anomaly_atlas/validation/costs.py
4 +# Purpose : Transaction-cost models: spread, impact, commissions
5 +# Author : Simon-Pierre Boucher
6 +# Contact : contact@spboucher.ai
7 +# Data src : hfmarketdata.io (sole data source)
8 +# Created : 2026-08-12
9 +# Modified : 2026-08-12
10 +# Platform : macOS / Apple Silicon (arm64)
11 +# License : All rights reserved (research code)
12 +# =============================================================================
13 +"""Transaction-cost models: spread, impact, commissions.
14 +
15 +Stub scaffolded 2026-08-12; implemented in later phases (see CLAUDE.md).
16 +"""
added src/anomaly_atlas/validation/oos.py +16 −0
@@ -0,0 +1,16 @@
1 +# =============================================================================
2 +# Project : anomaly-atlas
3 +# File : src/anomaly_atlas/validation/oos.py
4 +# Purpose : Train/validate/holdout splits, walk-forward protocol
5 +# Author : Simon-Pierre Boucher
6 +# Contact : contact@spboucher.ai
7 +# Data src : hfmarketdata.io (sole data source)
8 +# Created : 2026-08-12
9 +# Modified : 2026-08-12
10 +# Platform : macOS / Apple Silicon (arm64)
11 +# License : All rights reserved (research code)
12 +# =============================================================================
13 +"""Train/validate/holdout splits, walk-forward protocol.
14 +
15 +Stub scaffolded 2026-08-12; implemented in later phases (see CLAUDE.md).
16 +"""
added src/anomaly_atlas/validation/robustness.py +16 −0
@@ -0,0 +1,16 @@
1 +# =============================================================================
2 +# Project : anomaly-atlas
3 +# File : src/anomaly_atlas/validation/robustness.py
4 +# Purpose : Specification/parameter perturbation checks
5 +# Author : Simon-Pierre Boucher
6 +# Contact : contact@spboucher.ai
7 +# Data src : hfmarketdata.io (sole data source)
8 +# Created : 2026-08-12
9 +# Modified : 2026-08-12
10 +# Platform : macOS / Apple Silicon (arm64)
11 +# License : All rights reserved (research code)
12 +# =============================================================================
13 +"""Specification/parameter perturbation checks.
14 +
15 +Stub scaffolded 2026-08-12; implemented in later phases (see CLAUDE.md).
16 +"""
added src/anomaly_atlas/viz/__init__.py +16 −0
@@ -0,0 +1,16 @@
1 +# =============================================================================
2 +# Project : anomaly-atlas
3 +# File : src/anomaly_atlas/viz/__init__.py
4 +# Purpose : Visualization helpers for findings and nulls
5 +# Author : Simon-Pierre Boucher
6 +# Contact : contact@spboucher.ai
7 +# Data src : hfmarketdata.io (sole data source)
8 +# Created : 2026-08-12
9 +# Modified : 2026-08-12
10 +# Platform : macOS / Apple Silicon (arm64)
11 +# License : All rights reserved (research code)
12 +# =============================================================================
13 +"""Visualization helpers for findings and nulls.
14 +
15 +Stub scaffolded 2026-08-12; implemented in later phases (see CLAUDE.md).
16 +"""
added tools/check_headers.py +149 −0
@@ -0,0 +1,149 @@
1 +#!/usr/bin/env python3
2 +# =============================================================================
3 +# Project : anomaly-atlas
4 +# File : tools/check_headers.py
5 +# Purpose : CI-style enforcement of the mandatory author header (CLAUDE.md §0.1)
6 +# Author : Simon-Pierre Boucher
7 +# Contact : contact@spboucher.ai
8 +# Data src : hfmarketdata.io (sole data source)
9 +# Created : 2026-08-12
10 +# Modified : 2026-08-12
11 +# Platform : macOS / Apple Silicon (arm64)
12 +# License : All rights reserved (research code)
13 +# =============================================================================
14 +"""Fail (exit 1) if any tracked source file lacks a conforming author header.
15 +
16 +Usage:
17 + python3 tools/check_headers.py # check all git-tracked files
18 + python3 tools/check_headers.py FILE... # check specific files
19 +
20 +Rules enforced (see CLAUDE.md §0.1):
21 + * Comment-style sources (.py .sh .zsh .yaml .yml .toml .cff .sql Makefile
22 + CMakeLists.txt .gitignore) must contain the '#'-style header block near
23 + the top, including the 'Data src' field.
24 + * C-family / TS / JS / CSS sources must contain the '//'-style header block
25 + ('/*'-style for .css) near the top.
26 + * Markdown documents must begin with YAML front matter declaring
27 + project/author/contact/data_source.
28 + * A shebang line may precede the header.
29 +
30 +Exemptions: generated results under results/, atlas payload JSON, LICENSE,
31 +package-lock.json, CLAUDE.md (the charter is the specification itself).
32 +"""
33 +
34 +from __future__ import annotations
35 +
36 +import subprocess
37 +import sys
38 +from pathlib import Path
39 +
40 +REPO_ROOT = Path(__file__).resolve().parent.parent
41 +
42 +REQUIRED_FIELDS = ("Project", "File", "Purpose", "Author", "Contact",
43 + "Data src", "Created", "Modified", "Platform", "License")
44 +AUTHOR = "Simon-Pierre Boucher"
45 +CONTACT = "contact@spboucher.ai"
46 +DATA_SOURCE = "hfmarketdata.io"
47 +
48 +HASH_EXTS = {".py", ".sh", ".zsh", ".bash", ".yaml", ".yml", ".toml", ".cff", ".sql"}
49 +SLASH_EXTS = {".c", ".cc", ".cpp", ".h", ".hpp", ".metal", ".swift", ".m", ".mm",
50 + ".js", ".mjs", ".ts", ".tsx", ".css"}
51 +HASH_NAMES = {"Makefile", "CMakeLists.txt", ".gitignore"}
52 +
53 +EXEMPT_NAMES = {"LICENSE", "CLAUDE.md", "MEMORY.md", "package-lock.json"}
54 +EXEMPT_DIRS = {"results"}
55 +# How many leading lines to scan for the header block (allows shebang etc.).
56 +SCAN_LINES = 22
57 +
58 +
59 +def tracked_files() -> list[Path]:
60 + out = subprocess.run(
61 + ["git", "ls-files"], cwd=REPO_ROOT, capture_output=True, text=True, check=True
62 + ).stdout
63 + return [REPO_ROOT / line for line in out.splitlines() if line.strip()]
64 +
65 +
66 +def is_exempt(path: Path) -> bool:
67 + rel = path.relative_to(REPO_ROOT)
68 + if rel.name in EXEMPT_NAMES:
69 + return True
70 + return bool(rel.parts and rel.parts[0] in EXEMPT_DIRS)
71 +
72 +
73 +def check_comment_header(lines: list[str], prefixes: tuple[str, ...]) -> list[str]:
74 + """Check for a comment-style header with all required fields near the top."""
75 + head = "\n".join(lines[:SCAN_LINES])
76 + errors = []
77 + for field in REQUIRED_FIELDS:
78 + if not any(f"{p} {field}" in head or f"{p} {field}" in head for p in prefixes):
79 + errors.append(f"missing header field: {field}")
80 + if AUTHOR not in head:
81 + errors.append(f"missing author name '{AUTHOR}'")
82 + if CONTACT not in head:
83 + errors.append(f"missing contact '{CONTACT}'")
84 + if DATA_SOURCE not in head:
85 + errors.append(f"missing data source '{DATA_SOURCE}'")
86 + return errors
87 +
88 +
89 +def check_markdown_front_matter(lines: list[str]) -> list[str]:
90 + if not lines or lines[0].strip() != "---":
91 + return ["markdown file must start with YAML front matter (---)"]
92 + errors = []
93 + try:
94 + end = next(i for i in range(1, min(len(lines), SCAN_LINES)) if lines[i].strip() == "---")
95 + except StopIteration:
96 + return ["unterminated YAML front matter"]
97 + block = "\n".join(lines[1:end])
98 + for key in ("project: anomaly-atlas", f"author: {AUTHOR}",
99 + f"contact: {CONTACT}", f"data_source: {DATA_SOURCE}"):
100 + if key not in block:
101 + errors.append(f"front matter missing '{key}'")
102 + return errors
103 +
104 +
105 +def check_file(path: Path) -> list[str]:
106 + try:
107 + text = path.read_text(encoding="utf-8", errors="replace")
108 + except OSError as exc:
109 + return [f"unreadable: {exc}"]
110 + lines = text.splitlines()
111 + if lines and lines[0].startswith("#!"):
112 + lines = lines[1:]
113 +
114 + name, ext = path.name, path.suffix
115 + if ext in HASH_EXTS or name in HASH_NAMES:
116 + return check_comment_header(lines, ("#",))
117 + if ext in SLASH_EXTS:
118 + return check_comment_header(lines, ("//", "*", "/*"))
119 + if ext == ".md":
120 + return check_markdown_front_matter(lines)
121 + return [] # other file types are not subject to the header rule
122 +
123 +
124 +def main(argv: list[str]) -> int:
125 + paths = [Path(p).resolve() for p in argv] if argv else tracked_files()
126 + failures: dict[str, list[str]] = {}
127 + checked = 0
128 + for path in paths:
129 + if not path.is_file() or is_exempt(path):
130 + continue
131 + errors = check_file(path)
132 + if path.suffix in HASH_EXTS | SLASH_EXTS | {".md"} or path.name in HASH_NAMES:
133 + checked += 1
134 + if errors:
135 + failures[str(path.relative_to(REPO_ROOT))] = errors
136 +
137 + if failures:
138 + print(f"HEADER CHECK FAILED — {len(failures)} non-conforming file(s):\n")
139 + for rel, errors in sorted(failures.items()):
140 + print(f" {rel}")
141 + for err in errors:
142 + print(f" - {err}")
143 + return 1
144 + print(f"Header check passed ({checked} files checked).")
145 + return 0
146 +
147 +
148 +if __name__ == "__main__":
149 + sys.exit(main(sys.argv[1:]))
added tools/new_experiment.py +158 −0
@@ -0,0 +1,158 @@
1 +#!/usr/bin/env python3
2 +# =============================================================================
3 +# Project : anomaly-atlas
4 +# File : tools/new_experiment.py
5 +# Purpose : Scaffold a header-compliant experiment directory (CLAUDE.md §3, §10)
6 +# Author : Simon-Pierre Boucher
7 +# Contact : contact@spboucher.ai
8 +# Data src : hfmarketdata.io (sole data source)
9 +# Created : 2026-08-12
10 +# Modified : 2026-08-12
11 +# Platform : macOS / Apple Silicon (arm64)
12 +# License : All rights reserved (research code)
13 +# =============================================================================
14 +"""Scaffold a new experiment directory with the mandatory structure.
15 +
16 +Creates: README.md, hypothesis.md (seven-field scientific block, incl. the
17 +artifact null), benchmark.py, implementation/, results/, analysis.md — all
18 +with conforming author headers.
19 +
20 +Usage:
21 + python3 tools/new_experiment.py experiments/micro/expX_name "One-line purpose"
22 + python3 tools/new_experiment.py experiments/candidate_04 "Candidate: ..."
23 +"""
24 +
25 +from __future__ import annotations
26 +
27 +import sys
28 +from datetime import date
29 +from pathlib import Path
30 +
31 +REPO_ROOT = Path(__file__).resolve().parent.parent
32 +TODAY = date.today().isoformat()
33 +
34 +PY_HEADER = """\
35 +# =============================================================================
36 +# Project : anomaly-atlas
37 +# File : {rel}
38 +# Purpose : {purpose}
39 +# Author : Simon-Pierre Boucher
40 +# Contact : contact@spboucher.ai
41 +# Data src : hfmarketdata.io (sole data source)
42 +# Created : {today}
43 +# Modified : {today}
44 +# Platform : macOS / Apple Silicon (arm64)
45 +# License : All rights reserved (research code)
46 +# =============================================================================
47 +"""
48 +
49 +MD_HEADER = """\
50 +---
51 +project: anomaly-atlas
52 +document: {doc}
53 +author: Simon-Pierre Boucher
54 +contact: contact@spboucher.ai
55 +data_source: hfmarketdata.io
56 +created: {today}
57 +status: draft
58 +---
59 +"""
60 +
61 +HYPOTHESIS_BODY = """
62 +# Hypothesis — {name}
63 +
64 +```text
65 +Hypothesis
66 + <what we believe and why — pre-specified BEFORE looking at results>
67 +
68 +Falsification criterion
69 + <the concrete measurable outcome that would prove this wrong>
70 +
71 +Artifact null(s)
72 + <the fake-signal baseline(s) this must beat: bounce / staleness /
73 + non-synchronous timestamps / permuted calendar / random walk>
74 +
75 +Method
76 + <exact procedure, universe, split (train/validation/holdout), seeds,
77 + number of hypotheses tested, correction applied>
78 +
79 +Result
80 + <filled after the run: effect size, bootstrap CIs, corrected p-values,
81 + OOS status, cost-adjusted effect, credits used>
82 +
83 +Interpretation
84 + <what the numbers mean, WITH confidence level (0-3); alternative
85 + explanations considered — artifact first>
86 +
87 +Next experiment
88 + <the most informative follow-up given this result>
89 +```
90 +"""
91 +
92 +BENCHMARK_BODY = '''
93 +"""Benchmark entry point for {name}.
94 +
95 +Must embed the hardware manifest in all result output
96 +(see benchmarks/hardware_manifest.py) and write results to
97 +results/{name}/<timestamp>/. Uses hfmarketdata.io data ONLY, exclusively
98 +through src/anomaly_atlas/data/hf_client.py.
99 +"""
100 +
101 +import sys
102 +from pathlib import Path
103 +
104 +sys.path.insert(0, str(Path(__file__).resolve().parents[{depth}] / "benchmarks"))
105 +from hardware_manifest import collect_manifest # noqa: E402
106 +
107 +
108 +def main() -> None:
109 + manifest = collect_manifest()
110 + raise NotImplementedError("experiment not yet implemented")
111 +
112 +
113 +if __name__ == "__main__":
114 + main()
115 +'''
116 +
117 +
118 +def scaffold(exp_dir: Path, purpose: str) -> None:
119 + if exp_dir.exists() and any(exp_dir.iterdir()):
120 + sys.exit(f"error: {exp_dir} already exists and is not empty")
121 + name = exp_dir.name
122 + rel = exp_dir.relative_to(REPO_ROOT)
123 + (exp_dir / "implementation").mkdir(parents=True, exist_ok=True)
124 + (exp_dir / "results").mkdir(exist_ok=True)
125 +
126 + def md(doc: str) -> str:
127 + return MD_HEADER.format(doc=doc, today=TODAY)
128 +
129 + (exp_dir / "README.md").write_text(
130 + md(f"{name}/README") + f"\n# {name}\n\n{purpose}\n\nStatus: scaffolded {TODAY}, not yet run.\n"
131 + )
132 + (exp_dir / "hypothesis.md").write_text(
133 + md(f"{name}/hypothesis") + HYPOTHESIS_BODY.format(name=name)
134 + )
135 + (exp_dir / "analysis.md").write_text(
136 + md(f"{name}/analysis") + f"\n# Analysis — {name}\n\n*To be written after results exist. "
137 + "Must include the seven-field block and the evidence standard of CLAUDE.md §10 "
138 + "(never report an in-sample number as a finding).*\n"
139 + )
140 + depth = len(rel.parts) # parents[] index up to repo root
141 + (exp_dir / "benchmark.py").write_text(
142 + PY_HEADER.format(rel=rel / "benchmark.py", purpose=f"Benchmark runner: {purpose}", today=TODAY)
143 + + BENCHMARK_BODY.format(name=name, depth=depth)
144 + )
145 + print(f"scaffolded {rel} ({purpose})")
146 +
147 +
148 +def main() -> None:
149 + if len(sys.argv) < 3:
150 + sys.exit(__doc__)
151 + exp_dir = (REPO_ROOT / sys.argv[1]).resolve()
152 + if REPO_ROOT not in exp_dir.parents:
153 + sys.exit("error: experiment directory must live inside the repository")
154 + scaffold(exp_dir, sys.argv[2])
155 +
156 +
157 +if __name__ == "__main__":
158 + main()
added tools/new_finding.py +112 −0
@@ -0,0 +1,112 @@
1 +#!/usr/bin/env python3
2 +# =============================================================================
3 +# Project : anomaly-atlas
4 +# File : tools/new_finding.py
5 +# Purpose : Scaffold a compliant atlas entry — refuses incomplete provenance
6 +# Author : Simon-Pierre Boucher
7 +# Contact : contact@spboucher.ai
8 +# Data src : hfmarketdata.io (sole data source)
9 +# Created : 2026-08-12
10 +# Modified : 2026-08-12
11 +# Platform : macOS / Apple Silicon (arm64)
12 +# License : All rights reserved (research code)
13 +# =============================================================================
14 +"""Create atlas/<anomaly_id>/<version>/ from a completed finding payload.
15 +
16 +Refuses to create an entry unless BOTH a complete provenance.json and a
17 +confidence.md are supplied (CLAUDE.md §3, §10). Level 0 findings are refused
18 +outright — in-sample-only output never enters the atlas.
19 +
20 +Usage:
21 + python3 tools/new_finding.py <anomaly_id> <version> \\
22 + --finding finding.json --provenance provenance.json --confidence confidence.md
23 +
24 +Required provenance keys:
25 + commit, config, data_manifest_hash, hardware_manifest, generated_utc,
26 + n_hypotheses_tested, correction_method, oos_status
27 +Required confidence.md content: a 'Level: <1|2|3>' line plus front matter.
28 +"""
29 +
30 +from __future__ import annotations
31 +
32 +import argparse
33 +import json
34 +import re
35 +import shutil
36 +import sys
37 +from pathlib import Path
38 +
39 +REPO_ROOT = Path(__file__).resolve().parent.parent
40 +
41 +PROVENANCE_KEYS = (
42 + "commit", "config", "data_manifest_hash", "hardware_manifest",
43 + "generated_utc", "n_hypotheses_tested", "correction_method", "oos_status",
44 +)
45 +FINDING_ATTRIBUTION = {
46 + "author": "Simon-Pierre Boucher",
47 + "contact": "contact@spboucher.ai",
48 + "data_source": "hfmarketdata.io",
49 +}
50 +
51 +
52 +def fail(msg: str) -> None:
53 + sys.exit(f"REFUSED: {msg}")
54 +
55 +
56 +def validate_provenance(path: Path) -> dict:
57 + try:
58 + prov = json.loads(path.read_text())
59 + except (OSError, json.JSONDecodeError) as exc:
60 + fail(f"provenance.json unreadable/invalid: {exc}")
61 + missing = [k for k in PROVENANCE_KEYS if not prov.get(k)]
62 + if missing:
63 + fail(f"provenance.json incomplete — missing/empty: {', '.join(missing)}")
64 + return prov
65 +
66 +
67 +def validate_confidence(path: Path) -> int:
68 + try:
69 + text = path.read_text()
70 + except OSError as exc:
71 + fail(f"confidence.md unreadable: {exc}")
72 + m = re.search(r"^Level:\s*([0-3])\b", text, re.MULTILINE)
73 + if not m:
74 + fail("confidence.md must contain a 'Level: <0-3>' line with evidence")
75 + level = int(m.group(1))
76 + if level < 1:
77 + fail("Level 0 (in-sample only) never enters the atlas — scan output only")
78 + return level
79 +
80 +
81 +def main() -> None:
82 + ap = argparse.ArgumentParser(description=__doc__)
83 + ap.add_argument("anomaly_id")
84 + ap.add_argument("version")
85 + ap.add_argument("--finding", required=True, type=Path)
86 + ap.add_argument("--provenance", required=True, type=Path)
87 + ap.add_argument("--confidence", required=True, type=Path)
88 + args = ap.parse_args()
89 +
90 + prov = validate_provenance(args.provenance)
91 + level = validate_confidence(args.confidence)
92 + try:
93 + finding = json.loads(args.finding.read_text())
94 + except (OSError, json.JSONDecodeError) as exc:
95 + fail(f"finding.json unreadable/invalid: {exc}")
96 +
97 + finding.update(FINDING_ATTRIBUTION)
98 + finding.setdefault("anomaly_id", args.anomaly_id)
99 + finding["confidence_level"] = level
100 +
101 + dest = REPO_ROOT / "atlas" / args.anomaly_id / args.version
102 + if dest.exists():
103 + fail(f"{dest.relative_to(REPO_ROOT)} already exists — bump the version")
104 + dest.mkdir(parents=True)
105 + (dest / "finding.json").write_text(json.dumps(finding, indent=2) + "\n")
106 + (dest / "provenance.json").write_text(json.dumps(prov, indent=2) + "\n")
107 + shutil.copy(args.confidence, dest / "confidence.md")
108 + print(f"atlas entry created: {dest.relative_to(REPO_ROOT)} (Level {level})")
109 +
110 +
111 +if __name__ == "__main__":
112 + main()
added web/lib/content.js +219 −0
@@ -0,0 +1,219 @@
1 +// ============================================================================
2 +// Project : anomaly-atlas
3 +// File : web/lib/content.js
4 +// Purpose : Content index over the synced research-repo snapshot (content/)
5 +// Author : Simon-Pierre Boucher
6 +// Contact : contact@spboucher.ai
7 +// Data src : hfmarketdata.io (sole data source)
8 +// Created : 2026-08-12
9 +// Modified : 2026-08-12
10 +// Platform : macOS / Apple Silicon (arm64) — Node.js (deployed on MacLustr)
11 +// License : All rights reserved (research code)
12 +// ============================================================================
13 +"use strict";
14 +
15 +const fs = require("fs");
16 +const path = require("path");
17 +const matter = require("gray-matter");
18 +
19 +const CONTENT_ROOT = path.join(__dirname, "..", "content");
20 +
21 +const TEXT_EXT = new Set([
22 + ".md", ".py", ".sh", ".toml", ".yaml", ".yml", ".cff", ".json",
23 + ".js", ".css", ".sql", ".txt", ".log",
24 +]);
25 +
26 +/** Resolve a repo-relative path safely inside content/, or return null. */
27 +function safeResolve(rel) {
28 + const abs = path.resolve(CONTENT_ROOT, rel);
29 + if (!abs.startsWith(CONTENT_ROOT + path.sep) && abs !== CONTENT_ROOT) return null;
30 + return abs;
31 +}
32 +
33 +function exists(rel) {
34 + const abs = safeResolve(rel);
35 + return abs !== null && fs.existsSync(abs);
36 +}
37 +
38 +function readText(rel) {
39 + const abs = safeResolve(rel);
40 + if (!abs || !fs.existsSync(abs) || !fs.statSync(abs).isFile()) return null;
41 + return fs.readFileSync(abs, "utf8");
42 +}
43 +
44 +/** Read a markdown file, returning { data (front matter), content }. */
45 +function readMarkdown(rel) {
46 + const raw = readText(rel);
47 + if (raw === null) return null;
48 + try {
49 + return matter(raw);
50 + } catch {
51 + return { data: {}, content: raw };
52 + }
53 +}
54 +
55 +/** List a directory inside content/: [{name, rel, dir, size}] */
56 +function listDir(rel) {
57 + const abs = safeResolve(rel || ".");
58 + if (!abs || !fs.existsSync(abs) || !fs.statSync(abs).isDirectory()) return null;
59 + return fs
60 + .readdirSync(abs)
61 + .filter((n) => !n.startsWith(".") && n !== "node_modules")
62 + .map((n) => {
63 + const st = fs.statSync(path.join(abs, n));
64 + return {
65 + name: n,
66 + rel: path.posix.join(rel || "", n),
67 + dir: st.isDirectory(),
68 + size: st.size,
69 + };
70 + })
71 + .sort((a, b) => (a.dir !== b.dir ? (a.dir ? -1 : 1) : a.name.localeCompare(b.name)));
72 +}
73 +
74 +/** Recursively collect files under rel matching a predicate. */
75 +function walk(rel, predicate, out = []) {
76 + const entries = listDir(rel);
77 + if (!entries) return out;
78 + for (const e of entries) {
79 + if (e.dir) walk(e.rel, predicate, out);
80 + else if (predicate(e)) out.push(e);
81 + }
82 + return out;
83 +}
84 +
85 +function isTextFile(rel) {
86 + return TEXT_EXT.has(path.extname(rel).toLowerCase()) || path.basename(rel) === "Makefile";
87 +}
88 +
89 +/** Parse research/LOG.md into entries [{title, body}] (## headings). */
90 +function parseLogEntries() {
91 + const md = readMarkdown("research/LOG.md");
92 + if (!md) return [];
93 + const parts = md.content.split(/^## /m).slice(1);
94 + return parts.map((p) => {
95 + const nl = p.indexOf("\n");
96 + return { title: p.slice(0, nl).trim(), body: p.slice(nl + 1).replace(/^---\s*$/m, "").trim() };
97 + });
98 +}
99 +
100 +/** Experiment cards: micro experiments + candidates. */
101 +function listExperiments() {
102 + const out = [];
103 + for (const base of ["experiments/micro", "experiments"]) {
104 + const entries = listDir(base) || [];
105 + for (const e of entries) {
106 + if (!e.dir || e.name === "micro") continue;
107 + const readme = readMarkdown(path.posix.join(e.rel, "README.md"));
108 + const hasAnalysis = (readText(path.posix.join(e.rel, "analysis.md")) || "").length > 400;
109 + const runs = listResultRuns().filter((r) => r.experiment === e.name);
110 + let purpose = "";
111 + if (readme) {
112 + const line = readme.content.split("\n").find((l) => l.trim() && !l.startsWith("#"));
113 + purpose = line ? line.trim() : "";
114 + }
115 + out.push({
116 + id: e.name,
117 + rel: e.rel,
118 + purpose,
119 + status: runs.length > 0 && hasAnalysis ? "completed" : runs.length > 0 ? "has results" : "scaffolded",
120 + runs: runs.length,
121 + });
122 + }
123 + }
124 + return out;
125 +}
126 +
127 +/** Result runs: results/<experiment>/<timestamp>/*.json */
128 +function listResultRuns() {
129 + const out = [];
130 + for (const exp of listDir("results") || []) {
131 + if (!exp.dir) continue;
132 + for (const run of listDir(exp.rel) || []) {
133 + if (!run.dir) continue;
134 + const files = (listDir(run.rel) || []).filter((f) => !f.dir);
135 + out.push({ experiment: exp.name, timestamp: run.name, rel: run.rel, files });
136 + }
137 + }
138 + return out.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
139 +}
140 +
141 +/** Atlas findings: atlas/<anomaly_id>/<version>/finding.json (+ confidence.md). */
142 +function listFindings() {
143 + const out = [];
144 + for (const anomaly of listDir("atlas") || []) {
145 + if (!anomaly.dir) continue;
146 + for (const ver of listDir(anomaly.rel) || []) {
147 + if (!ver.dir) continue;
148 + const finding = readJson(path.posix.join(ver.rel, "finding.json"));
149 + if (!finding) continue;
150 + out.push({
151 + id: anomaly.name,
152 + version: ver.name,
153 + rel: ver.rel,
154 + level: finding.confidence_level ?? 0,
155 + title: finding.title || anomaly.name,
156 + summary: finding.summary || "",
157 + negative: !!finding.negative_result,
158 + finding,
159 + });
160 + }
161 + }
162 + return out.sort((a, b) => b.level - a.level || a.id.localeCompare(b.id));
163 +}
164 +
165 +function readJson(rel) {
166 + const raw = readText(rel);
167 + if (raw === null) return null;
168 + try {
169 + return JSON.parse(raw);
170 + } catch {
171 + return null;
172 + }
173 +}
174 +
175 +/** Build info written by sync-content.sh (commit, counts, date). */
176 +function buildInfo() {
177 + return readJson("build-info.json") || {};
178 +}
179 +
180 +/** Site-wide stats for the home page. */
181 +function stats() {
182 + const bib = readText("research/bibliography.md") || "";
183 + const sources = bib.split("\n").filter((l) => l.trim().startsWith("- ")).length;
184 + const gapsDoc = readText("research/research_gaps.md") || "";
185 + const gaps = (gapsDoc.match(/^#{2,3}\s+H\d{2}/gm) || []).length;
186 + const experiments = listExperiments();
187 + const notes = (listDir("research/notes") || []).filter((f) => f.name.endsWith(".md") && f.name !== "README.md").length;
188 + const findings = listFindings();
189 + return {
190 + sources,
191 + gaps,
192 + notes,
193 + experiments: experiments.length,
194 + experimentsDone: experiments.filter((e) => e.status === "completed").length,
195 + resultRuns: listResultRuns().length,
196 + logEntries: parseLogEntries().length,
197 + findings: findings.length,
198 + negatives: findings.filter((f) => f.negative).length,
199 + build: buildInfo(),
200 + };
201 +}
202 +
203 +module.exports = {
204 + CONTENT_ROOT,
205 + safeResolve,
206 + exists,
207 + readText,
208 + readMarkdown,
209 + readJson,
210 + listDir,
211 + walk,
212 + isTextFile,
213 + parseLogEntries,
214 + listExperiments,
215 + listResultRuns,
216 + listFindings,
217 + buildInfo,
218 + stats,
219 +};
added web/lib/render.js +189 −0
@@ -0,0 +1,189 @@
1 +// ============================================================================
2 +// Project : anomaly-atlas
3 +// File : web/lib/render.js
4 +// Purpose : HTML layout, markdown/code rendering for the atlas platform
5 +// Author : Simon-Pierre Boucher
6 +// Contact : contact@spboucher.ai
7 +// Data src : hfmarketdata.io (sole data source)
8 +// Created : 2026-08-12
9 +// Modified : 2026-08-12
10 +// Platform : macOS / Apple Silicon (arm64) — Node.js (deployed on MacLustr)
11 +// License : All rights reserved (research code)
12 +// ============================================================================
13 +"use strict";
14 +
15 +const { Marked } = require("marked");
16 +const hljs = require("highlight.js");
17 +
18 +function esc(s) {
19 + return String(s).replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[c]));
20 +}
21 +
22 +function slugify(s) {
23 + return s.toLowerCase().replace(/[^\w\s-]/g, "").trim().replace(/\s+/g, "-").slice(0, 80);
24 +}
25 +
26 +/**
27 + * Render markdown → { html, toc }. A fresh Marked instance per call (a shared
28 + * global `marked.use()` would stack walkTokens handlers on every render).
29 + * Headings get stable anchor ids collected into a table of contents.
30 + */
31 +function markdownToHtml(md, baseRel) {
32 + const toc = [];
33 + const seen = new Set();
34 + const m = new Marked({
35 + renderer: {
36 + code({ text, lang }) {
37 + const html = lang && hljs.getLanguage(lang)
38 + ? hljs.highlight(text, { language: lang }).value
39 + : esc(text);
40 + return `<pre class="codeblock"><code class="hljs">${html}</code></pre>`;
41 + },
42 + heading({ tokens, depth }) {
43 + const html = this.parser.parseInline(tokens);
44 + const plain = html.replace(/<[^>]+>/g, "");
45 + let id = slugify(plain) || "section";
46 + while (seen.has(id)) id += "-x";
47 + seen.add(id);
48 + if (depth >= 2 && depth <= 3) toc.push({ depth, id, text: plain });
49 + return `<h${depth} id="${id}">${html}<a class="hanchor" href="#${id}" aria-label="Link to section">#</a></h${depth}>\n`;
50 + },
51 + },
52 + walkTokens(token) {
53 + if (token.type === "link" && token.href && !/^(https?:|mailto:|#|\/)/.test(token.href)) {
54 + const base = baseRel.includes("/") ? baseRel.slice(0, baseRel.lastIndexOf("/")) : "";
55 + const joined = (base ? base + "/" : "") + token.href;
56 + const norm = joined.split("/").reduce((acc, part) => {
57 + if (part === "..") acc.pop();
58 + else if (part !== "." && part !== "") acc.push(part);
59 + return acc;
60 + }, []).join("/");
61 + token.href = norm.endsWith(".md") ? "/doc/" + norm : "/file/" + norm;
62 + }
63 + },
64 + });
65 + return { html: m.parse(md), toc };
66 +}
67 +
68 +/** Sticky table-of-contents sidebar for long documents. */
69 +function tocHtml(toc) {
70 + if (!toc || toc.length < 3) return "";
71 + const items = toc
72 + .map((t) => `<li class="toc-d${t.depth}"><a href="#${t.id}">${esc(t.text)}</a></li>`)
73 + .join("");
74 + return `<nav class="toc" aria-label="Table of contents"><span class="toc-title">On this page</span><ul>${items}</ul></nav>`;
75 +}
76 +
77 +/** Professional document header from front matter. */
78 +function docHeader(data, rel) {
79 + const title = (data.document || rel).split("/").pop().replace(/[_-]/g, " ");
80 + const status = data.status || "draft";
81 + const meta = [
82 + data.created ? `created ${data.created}` : null,
83 + data.modified ? `updated ${data.modified}` : null,
84 + data.author || null,
85 + ].filter(Boolean).join(" · ");
86 + return `<header class="doc-head">
87 + <div class="doc-head-row">
88 + <h1>${esc(title)}</h1>
89 + <span class="badge badge-${status === "reviewed" || status === "final" ? "done" : "in-progress"}">${esc(status)}</span>
90 + </div>
91 + <p class="doc-head-meta">${esc(data.document || rel)}${meta ? " · " + esc(meta) : ""}</p>
92 + </header>`;
93 +}
94 +
95 +/** Research-log timeline: entries already split by content.parseLogEntries. */
96 +function logTimeline(entries, baseRel) {
97 + const items = entries
98 + .slice()
99 + .reverse()
100 + .map((e) => {
101 + const [when, ...rest] = e.title.split(" — ");
102 + const title = rest.join(" — ") || when;
103 + const body = markdownToHtml(e.body, baseRel).html;
104 + return `<article class="tl-entry">
105 + <div class="tl-rail"><span class="tl-dot"></span></div>
106 + <div class="tl-card">
107 + <time class="tl-time">${esc(rest.length ? when : "")}</time>
108 + <h3>${esc(title)}</h3>
109 + <div class="md md-compact">${body}</div>
110 + </div>
111 + </article>`;
112 + })
113 + .join("");
114 + return `<div class="timeline">${items}</div>`;
115 +}
116 +
117 +function highlightFile(text, filename) {
118 + const ext = filename.slice(filename.lastIndexOf(".") + 1).toLowerCase();
119 + const langMap = {
120 + py: "python", sh: "bash", js: "javascript", css: "css", json: "json",
121 + toml: "ini", yaml: "yaml", yml: "yaml", cff: "yaml", md: "markdown", sql: "sql",
122 + };
123 + const lang = filename === "Makefile" ? "makefile" : langMap[ext];
124 + if (lang && hljs.getLanguage(lang)) return hljs.highlight(text, { language: lang }).value;
125 + return esc(text);
126 +}
127 +
128 +/** Confidence-level badge (Levels 0-3, CLAUDE.md §10). */
129 +const LEVEL_LABELS = {
130 + 0: "Level 0 — in-sample only",
131 + 1: "Level 1 — corrected & OOS",
132 + 2: "Level 2 — robust",
133 + 3: "Level 3 — cost-real & held-out",
134 +};
135 +function levelBadge(level) {
136 + return `<span class="badge badge-level-${level}">${LEVEL_LABELS[level] || "Level ?"}</span>`;
137 +}
138 +
139 +const NAV = [
140 + ["/", "Home"],
141 + ["/atlas", "Atlas"],
142 + ["/research", "Research"],
143 + ["/experiments", "Experiments"],
144 + ["/results", "Results"],
145 + ["/code", "Code"],
146 + ["/about", "About"],
147 +];
148 +
149 +function layout({ title, active, body, buildInfo = {} }) {
150 + const nav = NAV.map(
151 + ([href, label]) =>
152 + `<a href="${href}" class="${active === label ? "active" : ""}">${label}</a>`
153 + ).join("");
154 + const commit = buildInfo.commit ? buildInfo.commit.slice(0, 7) : "dev";
155 + const synced = buildInfo.generated ? buildInfo.generated.slice(0, 10) : "";
156 + return `<!DOCTYPE html>
157 +<html lang="en">
158 +<head>
159 +<meta charset="utf-8">
160 +<meta name="viewport" content="width=device-width, initial-scale=1">
161 +<title>${esc(title)} · anomaly-atlas</title>
162 +<meta name="description" content="anomaly-atlas — systematic discovery & rigorous validation of statistical anomalies in open HF market data (hfmarketdata.io). Research by Simon-Pierre Boucher. Not investment advice.">
163 +<link rel="stylesheet" href="/static/style.css">
164 +<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='0.9em' font-size='90'>🗺️</text></svg>">
165 +</head>
166 +<body>
167 +<header class="site-header">
168 + <div class="wrap header-row">
169 + <a class="brand" href="/">anomaly<span class="brand-dim">-atlas</span></a>
170 + <nav class="site-nav">${nav}</nav>
171 + </div>
172 +</header>
173 +<div class="honesty-banner"><div class="wrap">
174 + <strong>Honesty doctrine.</strong> Every candidate anomaly is an artifact until proven otherwise;
175 + in-sample results are never findings; past statistical regularity does not imply future returns.
176 + This is research on statistical properties of market data — <strong>not investment advice, not a trading system</strong>.
177 +</div></div>
178 +<main class="wrap">${body}</main>
179 +<footer class="site-footer">
180 + <div class="wrap">
181 + <span>© 2026 Simon-Pierre Boucher — <a href="mailto:contact@spboucher.ai">contact@spboucher.ai</a> · All rights reserved (research code) · Data source: <a href="https://www.hfmarketdata.io">hfmarketdata.io</a> (sole source)</span>
182 + <span class="footer-meta">snapshot ${esc(commit)}${synced ? " · synced " + esc(synced) : ""} · not trading advice</span>
183 + </div>
184 +</footer>
185 +</body>
186 +</html>`;
187 +}
188 +
189 +module.exports = { esc, markdownToHtml, tocHtml, docHeader, logTimeline, highlightFile, layout, levelBadge, LEVEL_LABELS };
added web/package.json +18 −0
@@ -0,0 +1,18 @@
1 +{
2 + "name": "anomaly-atlas-web",
3 + "version": "1.0.0",
4 + "description": "Public atlas platform for anomaly-atlas — Simon-Pierre Boucher <contact@spboucher.ai> — data source: hfmarketdata.io",
5 + "author": "Simon-Pierre Boucher <contact@spboucher.ai>",
6 + "license": "UNLICENSED",
7 + "private": true,
8 + "main": "server.js",
9 + "scripts": {
10 + "start": "node server.js"
11 + },
12 + "dependencies": {
13 + "express": "^4.21.0",
14 + "gray-matter": "^4.0.3",
15 + "highlight.js": "^11.10.0",
16 + "marked": "^14.1.0"
17 + }
18 +}
added web/public/style.css +311 −0
@@ -0,0 +1,311 @@
1 +/* ============================================================================
2 + * Project : anomaly-atlas
3 + * File : web/public/style.css
4 + * Purpose : Light-theme styling for the research showcase platform
5 + * Author : Simon-Pierre Boucher
6 + * Contact : contact@spboucher.ai
7 + * Data src : hfmarketdata.io (sole data source)
8 + * Created : 2026-08-12
9 + * Modified : 2026-08-12
10 + * Platform : Web (deployed from macOS / Apple Silicon)
11 + * License : All rights reserved (research code)
12 + * ==========================================================================*/
13 +
14 +:root {
15 + color-scheme: light;
16 + --page: #f9f9f7;
17 + --surface: #fcfcfb;
18 + --ink: #0b0b0b;
19 + --ink-2: #52514e;
20 + --muted: #898781;
21 + --grid: #e1e0d9;
22 + --baseline: #c3c2b7;
23 + --border: rgba(11, 11, 11, 0.10);
24 + --accent: #2a78d6;
25 + --accent-dark: #1c5cab;
26 + --good: #0ca30c;
27 + --good-text: #006300;
28 + --warn: #eda100;
29 +}
30 +
31 +* { box-sizing: border-box; }
32 +html { -webkit-text-size-adjust: 100%; }
33 +body {
34 + margin: 0;
35 + font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
36 + background: var(--page);
37 + color: var(--ink);
38 + line-height: 1.6;
39 + font-size: 16px;
40 +}
41 +.wrap { max-width: 1080px; margin: 0 auto; padding: 0 24px; }
42 +a { color: var(--accent-dark); text-decoration: none; }
43 +a:hover { text-decoration: underline; }
44 +code, pre, .mono-small { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
45 +
46 +/* header */
47 +.site-header {
48 + background: var(--surface);
49 + border-bottom: 1px solid var(--grid);
50 + position: sticky; top: 0; z-index: 10;
51 +}
52 +.header-row { display: flex; align-items: center; justify-content: space-between; height: 56px; }
53 +.brand { font-weight: 700; font-size: 18px; color: var(--ink); letter-spacing: -0.02em; }
54 +.brand-dim { color: var(--muted); font-weight: 500; }
55 +.site-nav { display: flex; gap: 4px; flex-wrap: wrap; }
56 +.site-nav a {
57 + padding: 6px 12px; border-radius: 6px; color: var(--ink-2); font-size: 14px; font-weight: 500;
58 +}
59 +.site-nav a:hover { background: var(--page); text-decoration: none; color: var(--ink); }
60 +.site-nav a.active { color: var(--accent-dark); background: #edf3fb; }
61 +
62 +/* hero */
63 +.hero { padding: 56px 0 8px; }
64 +.kicker {
65 + text-transform: uppercase; letter-spacing: 0.08em; font-size: 12px; font-weight: 600;
66 + color: var(--accent-dark); margin: 0 0 12px;
67 +}
68 +.hero h1 { font-size: 40px; line-height: 1.15; letter-spacing: -0.02em; margin: 0 0 16px; }
69 +.lede { font-size: 18px; color: var(--ink-2); max-width: 760px; margin: 0 0 12px; }
70 +.lede-small { color: var(--ink-2); max-width: 760px; }
71 +.lede-eq code {
72 + font-size: 13px; background: var(--surface); border: 1px solid var(--grid);
73 + border-radius: 6px; padding: 6px 10px; color: var(--ink-2); display: inline-block;
74 +}
75 +
76 +/* stat tiles */
77 +.tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 16px; margin: 32px 0; }
78 +.tile {
79 + background: var(--surface); border: 1px solid var(--grid); border-radius: 10px;
80 + padding: 20px 22px; display: flex; flex-direction: column; gap: 2px;
81 +}
82 +.tile-value { font-size: 34px; font-weight: 700; letter-spacing: -0.02em; }
83 +.tile-denom { font-size: 20px; color: var(--muted); font-weight: 500; }
84 +.tile-label { color: var(--ink-2); font-size: 13.5px; }
85 +
86 +/* layout blocks */
87 +.split { display: grid; grid-template-columns: 3fr 2fr; gap: 20px; margin: 8px 0 20px; }
88 +@media (max-width: 880px) { .split { grid-template-columns: 1fr; } .hero h1 { font-size: 30px; } }
89 +.card {
90 + background: var(--surface); border: 1px solid var(--grid); border-radius: 10px;
91 + padding: 24px 26px; margin-bottom: 20px;
92 +}
93 +.card h2 { margin-top: 0; font-size: 20px; letter-spacing: -0.01em; }
94 +.more { font-weight: 600; font-size: 14px; }
95 +
96 +/* phases */
97 +.phases { list-style: none; margin: 0; padding: 0; }
98 +.phase { display: flex; align-items: flex-start; gap: 12px; padding: 10px 0; border-bottom: 1px solid var(--grid); }
99 +.phase:last-child { border-bottom: 0; }
100 +.phase strong { display: block; font-size: 14.5px; }
101 +.phase-detail { font-size: 13px; color: var(--muted); }
102 +.phase-dot { width: 10px; height: 10px; border-radius: 50%; margin-top: 7px; flex: 0 0 auto; background: var(--baseline); }
103 +.phase.done .phase-dot { background: var(--good); }
104 +.phase.in-progress .phase-dot { background: var(--accent); }
105 +.phase .badge { margin-left: auto; }
106 +.badge {
107 + font-size: 11.5px; font-weight: 600; padding: 3px 9px; border-radius: 20px;
108 + white-space: nowrap; align-self: center; border: 1px solid var(--grid); color: var(--ink-2);
109 +}
110 +.badge-done { color: var(--good-text); background: #eef7ee; border-color: #cfe8cf; }
111 +.badge-in-progress, .badge-has-results { color: var(--accent-dark); background: #edf3fb; border-color: #cfe0f5; }
112 +.badge-pending, .badge-scaffolded { color: var(--muted); background: var(--page); }
113 +
114 +/* doc cards */
115 +.page-title { font-size: 30px; letter-spacing: -0.02em; margin: 40px 0 8px; }
116 +.section-title { font-size: 20px; margin: 32px 0 8px; }
117 +.doc-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 16px; margin: 16px 0; }
118 +.doc-card {
119 + background: var(--surface); border: 1px solid var(--grid); border-radius: 10px;
120 + padding: 18px 20px; color: var(--ink); display: block;
121 +}
122 +.doc-card:hover { border-color: var(--accent); text-decoration: none; }
123 +.doc-card.disabled { opacity: 0.55; pointer-events: none; }
124 +.doc-card h3 { margin: 0 0 6px; font-size: 16px; }
125 +.doc-card p { margin: 0 0 8px; font-size: 13.5px; color: var(--ink-2); }
126 +.runs { font-size: 12px; color: var(--muted); margin-left: 8px; }
127 +.note-line { color: var(--ink-2); font-size: 14px; }
128 +
129 +/* markdown body */
130 +.doc { max-width: 860px; margin: 32px auto; }
131 +.crumb { font-size: 13px; color: var(--muted); margin: 24px 0 4px; }
132 +.chips { display: flex; flex-wrap: wrap; gap: 8px; margin: 10px 0 18px; }
133 +.chip {
134 + font-size: 12px; background: var(--surface); border: 1px solid var(--grid);
135 + border-radius: 20px; padding: 3px 12px; color: var(--ink-2);
136 +}
137 +.chip-k { color: var(--muted); margin-right: 6px; text-transform: uppercase; font-size: 10px; letter-spacing: 0.05em; }
138 +.md { overflow-wrap: break-word; }
139 +.md h1 { font-size: 28px; letter-spacing: -0.02em; }
140 +.md h2 { font-size: 21px; margin-top: 36px; border-bottom: 1px solid var(--grid); padding-bottom: 6px; }
141 +.md h3 { font-size: 17px; margin-top: 28px; }
142 +.md code { background: #f1f0ec; border-radius: 4px; padding: 1px 5px; font-size: 0.88em; }
143 +.md pre code, .codeblock code { background: none; padding: 0; font-size: 13px; }
144 +.codeblock, .md pre {
145 + background: #f6f5f2; border: 1px solid var(--grid); border-radius: 8px;
146 + padding: 14px 16px; overflow-x: auto; line-height: 1.5;
147 +}
148 +.md table { border-collapse: collapse; width: 100%; font-size: 14px; display: block; overflow-x: auto; }
149 +.md th, .md td { border: 1px solid var(--grid); padding: 6px 10px; text-align: left; vertical-align: top; }
150 +.md th { background: var(--page); }
151 +.md blockquote { border-left: 3px solid var(--accent); margin-left: 0; padding-left: 16px; color: var(--ink-2); }
152 +
153 +/* log */
154 +.log-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 20px; }
155 +.log-entry h3 { font-size: 15px; margin: 0 0 8px; }
156 +.log-entry .md { font-size: 13.5px; color: var(--ink-2); }
157 +
158 +/* results table / kv table / file lists */
159 +.results-table, .kv-table { border-collapse: collapse; width: 100%; font-size: 14.5px; background: var(--surface); }
160 +.results-table th, .results-table td, .kv-table th, .kv-table td {
161 + border: 1px solid var(--grid); padding: 8px 12px; text-align: left;
162 +}
163 +.results-table th { background: var(--page); }
164 +.kv-table th { background: var(--page); width: 180px; }
165 +.file-list { list-style: none; padding: 0; }
166 +.file-list li { padding: 6px 0; border-bottom: 1px solid var(--grid); }
167 +.tree { list-style: none; padding-left: 0; }
168 +.tree ul { list-style: none; padding-left: 20px; }
169 +.tree-root { margin-bottom: 14px; }
170 +.tree li { padding: 2px 0; font-size: 14.5px; }
171 +.mono-small { font-size: 12.5px; color: var(--muted); }
172 +.file-card { padding: 0; overflow: hidden; }
173 +.file-head { display: flex; justify-content: space-between; padding: 10px 16px; border-bottom: 1px solid var(--grid); background: var(--page); }
174 +.file-card .codeblock { border: 0; border-radius: 0; margin: 0; }
175 +
176 +/* chart */
177 +.chart-fig { margin: 18px 0 8px; position: relative; }
178 +.chart-fig svg { width: 100%; height: auto; }
179 +.chart-fig .grid { stroke: var(--grid); stroke-width: 1; }
180 +.chart-fig .axis { stroke: var(--baseline); stroke-width: 1; }
181 +.chart-fig .tick { fill: var(--muted); font-size: 11px; font-family: ui-monospace, Menlo, monospace; }
182 +.chart-fig .axis-title { fill: var(--ink-2); font-size: 12px; }
183 +.chart-fig .series-label { font-size: 12px; font-weight: 600; }
184 +.chart-fig .pt { cursor: pointer; }
185 +.chart-fig figcaption { font-size: 12.5px; color: var(--muted); margin-top: 4px; }
186 +.chart-tip {
187 + position: absolute; pointer-events: none; background: var(--ink); color: #fff;
188 + font-size: 12.5px; padding: 5px 10px; border-radius: 6px; white-space: nowrap; z-index: 5;
189 +}
190 +
191 +/* hljs light theme (subset, GitHub-like) */
192 +.hljs { color: #24292e; }
193 +.hljs-keyword, .hljs-meta .hljs-keyword { color: #d73a49; }
194 +.hljs-string, .hljs-attr { color: #032f62; }
195 +.hljs-comment, .hljs-quote { color: #6a737d; font-style: italic; }
196 +.hljs-number, .hljs-literal { color: #005cc5; }
197 +.hljs-title, .hljs-function .hljs-title { color: #6f42c1; }
198 +.hljs-built_in, .hljs-type { color: #e36209; }
199 +.hljs-section { color: #005cc5; font-weight: 600; }
200 +.hljs-params { color: #24292e; }
201 +.hljs-variable, .hljs-template-variable { color: #e36209; }
202 +
203 +/* footer */
204 +.site-footer {
205 + border-top: 1px solid var(--grid); margin-top: 48px; padding: 20px 0 32px;
206 + background: var(--surface); font-size: 13px; color: var(--ink-2);
207 +}
208 +.site-footer .wrap { display: flex; justify-content: space-between; flex-wrap: wrap; gap: 8px; }
209 +.footer-meta { color: var(--muted); }
210 +
211 +/* ---- professional document view: header, TOC, anchors -------------------- */
212 +.doc-head { margin: 28px 0 6px; }
213 +.doc-head-row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
214 +.doc-head h1 { font-size: 30px; letter-spacing: -0.02em; margin: 0; text-transform: none; }
215 +.doc-head h1::first-letter { text-transform: uppercase; }
216 +.doc-head-meta { color: var(--muted); font-size: 13px; margin: 6px 0 0; font-family: ui-monospace, Menlo, monospace; }
217 +.doc-layout { display: block; margin-top: 18px; }
218 +.doc-layout.has-toc { display: grid; grid-template-columns: 240px minmax(0, 1fr); gap: 32px; align-items: start; }
219 +.doc-layout .doc { margin: 0; max-width: 820px; }
220 +.toc {
221 + position: sticky; top: 76px; background: var(--surface); border: 1px solid var(--grid);
222 + border-radius: 10px; padding: 14px 16px; max-height: calc(100vh - 100px); overflow-y: auto;
223 +}
224 +.toc-title { font-size: 11px; text-transform: uppercase; letter-spacing: 0.07em; color: var(--muted); font-weight: 600; }
225 +.toc ul { list-style: none; margin: 8px 0 0; padding: 0; }
226 +.toc li { padding: 3px 0; line-height: 1.35; }
227 +.toc li a { color: var(--ink-2); font-size: 13px; display: block; }
228 +.toc li a:hover { color: var(--accent-dark); text-decoration: none; }
229 +.toc-d3 { padding-left: 14px !important; }
230 +.toc-d3 a { font-size: 12.5px !important; color: var(--muted) !important; }
231 +.hanchor { opacity: 0; margin-left: 8px; font-weight: 400; color: var(--muted); font-size: 0.85em; }
232 +h2:hover .hanchor, h3:hover .hanchor, h4:hover .hanchor { opacity: 1; text-decoration: none; }
233 +html { scroll-behavior: smooth; scroll-padding-top: 72px; }
234 +.card-head-row { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; flex-wrap: wrap; }
235 +
236 +/* ---- research-log timeline ----------------------------------------------- */
237 +.timeline { margin: 24px 0; max-width: 860px; }
238 +.tl-entry { display: grid; grid-template-columns: 24px minmax(0, 1fr); gap: 14px; }
239 +.tl-rail { position: relative; }
240 +.tl-rail::before {
241 + content: ""; position: absolute; left: 11px; top: 4px; bottom: -18px; width: 2px; background: var(--grid);
242 +}
243 +.tl-entry:last-child .tl-rail::before { bottom: auto; height: 24px; }
244 +.tl-dot {
245 + position: absolute; left: 5px; top: 6px; width: 14px; height: 14px; border-radius: 50%;
246 + background: var(--accent); border: 3px solid var(--page); box-shadow: 0 0 0 1px var(--grid);
247 +}
248 +.tl-card {
249 + background: var(--surface); border: 1px solid var(--grid); border-radius: 10px;
250 + padding: 16px 20px; margin-bottom: 18px; min-width: 0;
251 +}
252 +.tl-time { font-size: 12px; color: var(--muted); font-family: ui-monospace, Menlo, monospace; }
253 +.tl-card h3 { margin: 4px 0 10px; font-size: 16.5px; letter-spacing: -0.01em; }
254 +.md-compact { font-size: 14px; color: var(--ink-2); }
255 +.md-compact ul { padding-left: 18px; margin: 6px 0; }
256 +.md-compact li { margin: 3px 0; }
257 +.md-compact p { margin: 6px 0; }
258 +.md-compact strong { color: var(--ink); }
259 +.md-compact pre { font-size: 12px; padding: 10px 12px; }
260 +
261 +/* ---- smartphone adaptation ------------------------------------------------ */
262 +@media (max-width: 720px) {
263 + body { font-size: 15px; }
264 + .wrap { padding: 0 16px; }
265 + .header-row { height: auto; flex-direction: column; align-items: flex-start; gap: 2px; padding: 10px 0; }
266 + .site-nav { width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; flex-wrap: nowrap; padding-bottom: 4px; }
267 + .site-nav a { white-space: nowrap; padding: 6px 10px; font-size: 13.5px; }
268 + .site-header { position: static; }
269 + html { scroll-padding-top: 12px; }
270 + .hero { padding: 28px 0 4px; }
271 + .hero h1 { font-size: 26px; }
272 + .lede { font-size: 16px; }
273 + .lede-eq code { font-size: 11px; white-space: normal; word-break: break-word; }
274 + .tiles { grid-template-columns: repeat(2, 1fr); gap: 10px; margin: 20px 0; }
275 + .tile { padding: 14px 16px; }
276 + .tile-value { font-size: 26px; }
277 + .card { padding: 16px 16px; }
278 + .page-title { font-size: 24px; margin-top: 24px; }
279 + .doc-head h1 { font-size: 22px; }
280 + .doc-layout.has-toc { display: block; }
281 + .toc { position: static; max-height: none; margin-bottom: 16px; }
282 + .tl-entry { grid-template-columns: 18px minmax(0, 1fr); gap: 10px; }
283 + .tl-card { padding: 12px 14px; }
284 + .md h1 { font-size: 22px; } .md h2 { font-size: 18px; } .md h3 { font-size: 16px; }
285 + .md pre, .codeblock { font-size: 12px; padding: 10px 12px; }
286 + .results-table, .kv-table { font-size: 13px; display: block; overflow-x: auto; }
287 + .kv-table th { width: auto; }
288 + .file-head { flex-direction: column; gap: 2px; }
289 + .site-footer .wrap { flex-direction: column; }
290 + .chart-fig svg { min-width: 560px; }
291 + .chart-fig { overflow-x: auto; -webkit-overflow-scrolling: touch; }
292 +}
293 +@media (max-width: 420px) {
294 + .tiles { grid-template-columns: 1fr 1fr; }
295 + .hero h1 { font-size: 23px; }
296 +}
297 +
298 +/* ---- anomaly-atlas additions: honesty banner + confidence-level badges ---- */
299 +.honesty-banner {
300 + background: #fdf6ec;
301 + border-bottom: 1px solid #eadfc8;
302 + color: #5a4a2a;
303 + font-size: 0.82rem;
304 + line-height: 1.45;
305 + padding: 8px 0;
306 +}
307 +.honesty-banner strong { color: #4a3a1e; }
308 +.badge-level-0 { background: #f0efeb; color: #6b6b66; }
309 +.badge-level-1 { background: #e7f0fb; color: #2a5da8; }
310 +.badge-level-2 { background: #eaf6ef; color: #1e7a4d; }
311 +.badge-level-3 { background: #163a26; color: #eafff3; }
added web/server.js +393 −0
@@ -0,0 +1,393 @@
1 +// ============================================================================
2 +// Project : anomaly-atlas
3 +// File : web/server.js
4 +// Purpose : Public atlas platform — routes and pages (English, light)
5 +// Author : Simon-Pierre Boucher
6 +// Contact : contact@spboucher.ai
7 +// Data src : hfmarketdata.io (sole data source)
8 +// Created : 2026-08-12
9 +// Modified : 2026-08-12
10 +// Platform : macOS / Apple Silicon (arm64) — Node.js (deployed on MacLustr)
11 +// License : All rights reserved (research code)
12 +// ============================================================================
13 +"use strict";
14 +
15 +const path = require("path");
16 +const express = require("express");
17 +const C = require("./lib/content");
18 +const R = require("./lib/render");
19 +
20 +const app = express();
21 +const PORT = process.env.PORT || 8150;
22 +
23 +app.use("/static", express.static(path.join(__dirname, "public"), { maxAge: "1h" }));
24 +
25 +function page(res, opts) {
26 + res.send(R.layout({ ...opts, buildInfo: C.buildInfo() }));
27 +}
28 +
29 +function metaChips(data) {
30 + if (!data || !Object.keys(data).length) return "";
31 + const fields = ["document", "author", "created", "modified", "status"];
32 + const chips = fields
33 + .filter((f) => data[f])
34 + .map((f) => `<span class="chip"><span class="chip-k">${f}</span>${R.esc(data[f])}</span>`)
35 + .join("");
36 + return chips ? `<div class="chips">${chips}</div>` : "";
37 +}
38 +
39 +// ---------------------------------------------------------------- home
40 +app.get("/", (req, res) => {
41 + const s = C.stats();
42 + const log = C.parseLogEntries().slice(-3).reverse();
43 + const phases = [
44 + ["Phase 0.5 — Data reality check (Experiment A)", C.exists("research/data_source_profile.md") && (C.readText("research/data_source_profile.md") || "").length > 1200 ? "done" : "pending", "what hfmarketdata.io actually provides"],
45 + ["Phase 1 — Literature research (§4.1–4.6)", s.notes ? "in progress" : "pending", `${s.notes} theme notes · ${s.sources} sources`],
46 + ["Phase 2 — State-of-the-art map", (C.readText("research/state_of_the_art.md") || "").length > 1200 ? "done" : "pending", "per-anomaly epistemic status"],
47 + ["Phase 3 — Research gaps", s.gaps ? "done" : "pending", `${s.gaps || "≥20 target"} testable hypotheses with constructable artifact nulls`],
48 + ["Phase 4 — Candidate ranking", (C.readText("research/candidate_ranking.md") || "").length > 1200 ? "done" : "pending", "10-axis scoring, 3–5 candidates"],
49 + ["Phases 5–6 — Framework & micro-experiments A–H", s.experimentsDone ? "in progress" : "pending", `${s.experimentsDone}/${s.experiments} experiments completed`],
50 + ["Phases 7–11 — Candidates → methodology → atlas → novelty", "pending", "evidence-driven"],
51 + ];
52 + const phaseHtml = phases
53 + .map(
54 + ([name, st, detail]) => `<li class="phase ${st.replace(" ", "-")}">
55 + <span class="phase-dot"></span><div><strong>${name}</strong><span class="phase-detail">${detail}</span></div>
56 + <span class="badge badge-${st.replace(" ", "-")}">${st}</span></li>`
57 + )
58 + .join("");
59 + const logHtml = log
60 + .map((e) => {
61 + const [when, ...rest] = e.title.split(" — ");
62 + const excerpt = R.markdownToHtml(e.body.split("\n").slice(0, 6).join("\n"), "research/LOG.md").html;
63 + return `<article class="log-entry">
64 + <time class="tl-time">${R.esc(rest.length ? when : "")}</time>
65 + <h3>${R.esc(rest.join(" — ") || when)}</h3>
66 + <div class="md md-compact">${excerpt}</div></article>`;
67 + })
68 + .join("");
69 +
70 + const body = `
71 +<section class="hero">
72 + <p class="kicker">Independent statistics + market-microstructure research · Apple Silicon · hfmarketdata.io</p>
73 + <h1>Which market "anomalies" are real —<br>and which are artifacts?</h1>
74 + <p class="lede">A systematic, reproducible atlas of statistical regularities in open high-frequency
75 + market data — mean-reversion, lead-lag, calendar effects — where every claim survives (or visibly fails)
76 + out-of-sample testing, multiple-comparison correction, artifact nulls, and realistic transaction costs.
77 + Negative results are first-class findings.</p>
78 + <p class="lede-eq"><code>detectable in-sample ≠ reproducible out-of-sample ≠ robust to artifacts ≠ meaningful after costs</code></p>
79 +</section>
80 +
81 +<section class="tiles">
82 + <div class="tile"><span class="tile-value">${s.sources}</span><span class="tile-label">sources reviewed</span></div>
83 + <div class="tile"><span class="tile-value">${s.gaps}</span><span class="tile-label">hypotheses registered</span></div>
84 + <div class="tile"><span class="tile-value">${s.experimentsDone}<span class="tile-denom">/${s.experiments}</span></span><span class="tile-label">experiments completed</span></div>
85 + <div class="tile"><span class="tile-value">${s.findings}</span><span class="tile-label">atlas findings (Level ≥ 1)</span></div>
86 +</section>
87 +
88 +<section class="split">
89 + <div class="card">
90 + <h2>The confidence taxonomy</h2>
91 + <p>A finding only advances one level at a time, and only Level ≥ 1 is ever published.
92 + A large in-sample effect with zero out-of-sample survival is a <strong>negative result</strong> —
93 + published as one.</p>
94 + <ul class="phases">
95 + <li class="phase pending"><span class="phase-dot"></span><div><strong>Level 0 — in-sample only</strong><span class="phase-detail">scan output; never published as a finding</span></div></li>
96 + <li class="phase in-progress"><span class="phase-dot"></span><div><strong>Level 1 — corrected &amp; OOS</strong><span class="phase-detail">survives multiple-testing correction and a clean out-of-sample split, artifact null subtracted</span></div></li>
97 + <li class="phase in-progress"><span class="phase-dot"></span><div><strong>Level 2 — robust</strong><span class="phase-detail">+ robust to specification choices, sub-periods, instruments</span></div></li>
98 + <li class="phase done"><span class="phase-dot"></span><div><strong>Level 3 — cost-real &amp; held-out</strong><span class="phase-detail">+ economically nonzero after realistic costs, confirmed on the once-touched holdout</span></div></li>
99 + </ul>
100 + <a class="more" href="/atlas">Browse the atlas →</a>
101 + </div>
102 + <div class="card">
103 + <h2>Research phases</h2>
104 + <ul class="phases">${phaseHtml}</ul>
105 + </div>
106 +</section>
107 +
108 +<section class="card">
109 + <div class="card-head-row"><h2>Latest from the research log</h2>
110 + <a class="more" href="/doc/research/LOG.md">Full research log →</a></div>
111 + <div class="log-grid">${logHtml}</div>
112 +</section>`;
113 + page(res, { title: "Home", active: "Home", body });
114 +});
115 +
116 +// ---------------------------------------------------------------- atlas
117 +app.get("/atlas", (req, res) => {
118 + const findings = C.listFindings();
119 + const published = findings.filter((f) => f.level >= 1);
120 + const cards = published
121 + .map(
122 + (f) => `<a class="doc-card" href="/atlas/${f.id}/${f.version}">
123 + <h3>${R.esc(f.title)}${f.negative ? ' <span class="badge badge-pending">negative result</span>' : ""}</h3>
124 + <p>${R.esc(f.summary)}</p>${R.levelBadge(f.level)}</a>`
125 + )
126 + .join("");
127 + const empty = `<div class="card"><h2>No findings yet — and that is the point</h2>
128 + <p>The atlas publishes only Level ≥ 1 findings: effects that survive multiple-testing correction
129 + and a clean out-of-sample split with the artifact null subtracted. Nothing has earned that yet —
130 + the framework is being built so that nothing can enter without earning it. The
131 + <a href="/doc/research/artifact_taxonomy.md">artifact taxonomy</a> and the
132 + <a href="/doc/research/methodology.md">validation methodology</a> are primary deliverables in their own right.</p></div>`;
133 + const body = `<h1 class="page-title">The atlas</h1>
134 +<p class="lede-small">Confidence-labeled statistical regularities (and non-regularities) in hfmarketdata.io data.
135 +Every entry carries provenance — commit, config, data-manifest hash, hardware — and the exact command that
136 +regenerates it. Negative results are first-class entries.</p>
137 +${published.length ? `<div class="doc-grid">${cards}</div>` : empty}
138 +<h2 class="section-title">Foundations</h2>
139 +<div class="doc-grid">
140 + <a class="doc-card" href="/doc/research/artifact_taxonomy.md"><h3>Artifact taxonomy</h3>
141 + <p>The catalogue of artifacts in this dataset that masquerade as anomalies — often the most useful output.</p></a>
142 + <a class="doc-card" href="/doc/research/methodology.md"><h3>Validation methodology</h3>
143 + <p>The pre-registered protocol that turns a hypothesis into a confidence-labeled entry.</p></a>
144 +</div>`;
145 + page(res, { title: "Atlas", active: "Atlas", body });
146 +});
147 +
148 +app.get("/atlas/:id/:version", (req, res) => {
149 + const f = C.listFindings().find((x) => x.id === req.params.id && x.version === req.params.version);
150 + if (!f) return notFound(res);
151 + const prov = C.readJson(path.posix.join(f.rel, "provenance.json")) || {};
152 + const conf = C.readMarkdown(path.posix.join(f.rel, "confidence.md"));
153 + const provRows = Object.entries(prov)
154 + .map(([k, v]) => `<tr><th>${R.esc(k)}</th><td class="mono-small">${R.esc(typeof v === "string" ? v : JSON.stringify(v))}</td></tr>`)
155 + .join("");
156 + const body = `<p class="crumb"><a href="/atlas">Atlas</a> / ${R.esc(f.id)} / ${R.esc(f.version)}</p>
157 +<h1 class="page-title">${R.esc(f.title)}</h1>
158 +<p>${R.levelBadge(f.level)}${f.negative ? ' <span class="badge badge-pending">negative result</span>' : ""}</p>
159 +<p class="lede-small">${R.esc(f.summary)}</p>
160 +${conf ? `<section class="card"><h2>Confidence evidence</h2><div class="md">${R.markdownToHtml(conf.content, path.posix.join(f.rel, "confidence.md")).html}</div></section>` : ""}
161 +<section class="card"><h2>Finding payload</h2>
162 +<pre class="codeblock"><code class="hljs">${R.highlightFile(JSON.stringify(f.finding, null, 2), "x.json")}</code></pre></section>
163 +<section class="card"><h2>Provenance</h2><table class="kv-table">${provRows}</table></section>
164 +<p class="note-line">Past statistical regularity does not imply future returns. Not investment advice.</p>`;
165 + page(res, { title: f.title, active: "Atlas", body });
166 +});
167 +
168 +// ---------------------------------------------------------------- research index
169 +app.get("/research", (req, res) => {
170 + const docs = [
171 + ["research/LOG.md", "Research log", "Append-only, auditable record of every question, experiment, result, and decision."],
172 + ["research/data_source_profile.md", "Data source profile", "Phase 0.5 — what hfmarketdata.io actually provides, established empirically."],
173 + ["research/state_of_the_art.md", "State of the art", "Phase 2 — per-anomaly map with epistemic status: robust / decayed / disputed / likely-artifact."],
174 + ["research/artifact_taxonomy.md", "Artifact taxonomy", "The living catalogue of dataset artifacts that masquerade as anomalies (Q4)."],
175 + ["research/research_gaps.md", "Research gaps", "Phase 3 — ≥20 testable hypotheses, each with a constructable artifact null."],
176 + ["research/candidate_ranking.md", "Candidate ranking", "Phase 4 — 10-axis scoring and selected prototype candidates."],
177 + ["research/methodology.md", "Validation methodology", "Phase 9 — the pre-registered protocol behind every atlas entry."],
178 + ["research/bibliography.md", "Bibliography", "Every consulted source with URL and access date."],
179 + ["research/novelty_check.md", "Novelty check", "Phase 11 — assume not novel until evidence says otherwise (written last)."],
180 + ];
181 + const cards = docs
182 + .map(([rel, title, desc]) => {
183 + const ok = C.exists(rel);
184 + const thin = ok && (C.readText(rel) || "").length < 1200;
185 + return `<a class="doc-card ${ok ? "" : "disabled"}" href="${ok ? "/doc/" + rel : "#"}">
186 + <h3>${title}</h3><p>${desc}</p>${!ok ? '<span class="badge badge-pending">not yet written</span>' : thin ? '<span class="badge badge-pending">placeholder</span>' : ""}</a>`;
187 + })
188 + .join("");
189 + const notes = (C.listDir("research/notes") || [])
190 + .filter((f) => f.name.endsWith(".md") && f.name !== "README.md")
191 + .map((f) => {
192 + const md = C.readMarkdown(f.rel);
193 + const sources = md ? (md.content.match(/^- .*http/gm) || []).length : 0;
194 + const lines = md ? md.content.split("\n").length : 0;
195 + return `<a class="doc-card" href="/doc/${f.rel}"><h3>${R.esc(f.name.replace(".md", "").replace(/_/g, " "))}</h3>
196 + <p>Phase 1 literature notes — ${lines} lines.</p>
197 + <span class="badge badge-done">${sources} sources</span></a>`;
198 + })
199 + .join("");
200 + const body = `<h1 class="page-title">Research documents</h1>
201 +<div class="doc-grid">${cards}</div>
202 +${notes ? `<h2 class="section-title">Phase 1 literature notes</h2><div class="doc-grid">${notes}</div>` : ""}
203 +<p class="note-line">The project charter itself is public: <a href="/doc/CLAUDE.md">read the full research charter</a>.</p>`;
204 + page(res, { title: "Research", active: "Research", body });
205 +});
206 +
207 +// ---------------------------------------------------------------- markdown viewer
208 +app.get(/^\/doc\/(.+)$/, (req, res) => {
209 + const rel = req.params[0];
210 + const md = rel.endsWith(".md") ? C.readMarkdown(rel) : null;
211 + if (!md) return notFound(res);
212 + const crumb = `<p class="crumb"><a href="/research">Research</a> / ${R.esc(rel)}</p>`;
213 +
214 + if (rel === "research/LOG.md") {
215 + const entries = C.parseLogEntries();
216 + const body = `${crumb}
217 + ${R.docHeader({ ...md.data, document: "Research log" }, rel)}
218 + <p class="lede-small">Append-only, newest first — every question, experiment, result,
219 + interpretation, and decision, as required by the charter (§12). ${entries.length} entries.</p>
220 + ${R.logTimeline(entries, rel)}`;
221 + return page(res, { title: "Research log", active: "Research", body });
222 + }
223 +
224 + const { html, toc } = R.markdownToHtml(md.content, rel);
225 + const tocBox = R.tocHtml(toc);
226 + const body = `${crumb}${R.docHeader(md.data, rel)}
227 + <div class="doc-layout ${tocBox ? "has-toc" : ""}">
228 + ${tocBox}
229 + <article class="doc"><div class="md">${html}</div></article>
230 + </div>`;
231 + page(res, { title: md.data.document || rel, active: "Research", body });
232 +});
233 +
234 +// ---------------------------------------------------------------- experiments
235 +app.get("/experiments", (req, res) => {
236 + const exps = C.listExperiments();
237 + const cards = exps
238 + .map(
239 + (e) => `<a class="doc-card" href="/experiments/${e.id}">
240 + <h3>${R.esc(e.id)}</h3><p>${R.esc(e.purpose)}</p>
241 + <span class="badge badge-${e.status === "completed" ? "done" : e.status === "has results" ? "in-progress" : "pending"}">${e.status}</span>
242 + ${e.runs ? `<span class="runs">${e.runs} result run${e.runs > 1 ? "s" : ""}</span>` : ""}</a>`
243 + )
244 + .join("");
245 + const body = `<h1 class="page-title">Experiments</h1>
246 +<p class="lede-small">Every experiment carries a registered hypothesis with an explicit falsification criterion
247 +and its artifact null(s), a benchmark implementation, raw results, and an analysis. Detectors must first pass the
248 +synthetic-series tests — a detector that finds anomalies in a random walk is broken. Negative results are kept.</p>
249 +<div class="doc-grid">${cards}</div>`;
250 + page(res, { title: "Experiments", active: "Experiments", body });
251 +});
252 +
253 +app.get("/experiments/:id", (req, res) => {
254 + const exps = C.listExperiments();
255 + const exp = exps.find((e) => e.id === req.params.id);
256 + if (!exp) return notFound(res);
257 + const sections = [];
258 + for (const [file, title] of [["hypothesis.md", "Hypothesis"], ["analysis.md", "Analysis"], ["README.md", "README"]]) {
259 + const md = C.readMarkdown(path.posix.join(exp.rel, file));
260 + if (md && md.content.trim().length > 40) {
261 + sections.push(`<section class="card"><h2>${title}</h2>${metaChips(md.data)}
262 + <div class="md">${R.markdownToHtml(md.content, path.posix.join(exp.rel, file)).html}</div></section>`);
263 + }
264 + }
265 + const runs = C.listResultRuns().filter((r) => r.experiment === exp.id);
266 + const runsHtml = runs.length
267 + ? `<section class="card"><h2>Result runs</h2><ul class="file-list">` +
268 + runs.map((r) => r.files.map((f) => `<li><a href="/results/${f.rel}">${r.timestamp} / ${f.name}</a> <span class="mono-small">${(f.size / 1024).toFixed(1)} KiB</span></li>`).join("")).join("") +
269 + `</ul></section>`
270 + : "";
271 + const codeLink = C.exists(path.posix.join(exp.rel, "benchmark.py"))
272 + ? `<p><a class="more" href="/file/${exp.rel}/benchmark.py">View benchmark implementation (benchmark.py) →</a></p>`
273 + : "";
274 + const body = `<p class="crumb"><a href="/experiments">Experiments</a> / ${R.esc(exp.id)}</p>
275 +<h1 class="page-title">${R.esc(exp.id)}</h1><p class="lede-small">${R.esc(exp.purpose)}</p>
276 +${codeLink}${sections.join("")}${runsHtml}`;
277 + page(res, { title: exp.id, active: "Experiments", body });
278 +});
279 +
280 +// ---------------------------------------------------------------- results
281 +app.get("/results", (req, res) => {
282 + const runs = C.listResultRuns();
283 + const rows = runs
284 + .map(
285 + (r) => `<tr><td><a href="/experiments/${r.experiment}">${r.experiment}</a></td>
286 + <td class="mono-small">${r.timestamp}</td>
287 + <td>${r.files.map((f) => `<a href="/results/${f.rel}">${f.name}</a>`).join(" · ")}</td></tr>`
288 + )
289 + .join("");
290 + const body = `<h1 class="page-title">Raw results</h1>
291 +<p class="lede-small">Every result is reproducible from commit hash + config + data-manifest index + seed +
292 +hardware manifest, and each JSON embeds the manifest of the exact machine that produced it.</p>
293 +<table class="results-table"><thead><tr><th>Experiment</th><th>Run (UTC)</th><th>Files</th></tr></thead>
294 +<tbody>${rows || '<tr><td colspan="3">No result runs yet.</td></tr>'}</tbody></table>`;
295 + page(res, { title: "Results", active: "Results", body });
296 +});
297 +
298 +app.get(/^\/results\/(.+)$/, (req, res) => {
299 + const rel = req.params[0].startsWith("results/") ? req.params[0] : "results/" + req.params[0];
300 + const raw = C.readText(rel);
301 + if (raw === null) return notFound(res);
302 + let bodyContent;
303 + if (rel.endsWith(".json")) {
304 + const obj = C.readJson(rel);
305 + bodyContent = `<pre class="codeblock"><code class="hljs">${R.highlightFile(JSON.stringify(obj, null, 2), "x.json")}</code></pre>`;
306 + } else {
307 + bodyContent = `<pre class="codeblock"><code>${R.esc(raw.slice(0, 200000))}</code></pre>`;
308 + }
309 + const body = `<p class="crumb"><a href="/results">Results</a> / ${R.esc(rel)}</p><div class="card">${bodyContent}</div>`;
310 + page(res, { title: rel, active: "Results", body });
311 +});
312 +
313 +// ---------------------------------------------------------------- code browser
314 +const CODE_ROOTS = ["src", "tools", "benchmarks", "experiments", "Makefile", "pyproject.toml", "CITATION.cff"];
315 +app.get("/code", (req, res) => {
316 + const sections = CODE_ROOTS.map((root) => {
317 + if (!C.exists(root)) return "";
318 + const st = C.listDir(root);
319 + if (st === null) {
320 + return `<li><a href="/file/${root}">${root}</a></li>`;
321 + }
322 + const files = C.walk(root, (f) => C.isTextFile(f.rel) && !f.rel.includes("results/"));
323 + return `<li class="tree-root"><strong>${root}/</strong><ul>` +
324 + files.map((f) => `<li><a href="/file/${f.rel}">${f.rel.slice(root.length + 1)}</a></li>`).join("") +
325 + `</ul></li>`;
326 + }).join("");
327 + const body = `<h1 class="page-title">Code</h1>
328 +<p class="lede-small">Core library (<code>src/anomaly_atlas/</code>) — including the single
329 +<code>hf_client</code> every byte of data flows through — tooling, benchmark harness, and experiment
330 +implementations. Every file carries the project's author header and the hfmarketdata.io data-source attribution.</p>
331 +<ul class="tree">${sections}</ul>`;
332 + page(res, { title: "Code", active: "Code", body });
333 +});
334 +
335 +app.get(/^\/file\/(.+)$/, (req, res) => {
336 + const rel = req.params[0];
337 + if (!C.isTextFile(rel)) return notFound(res);
338 + const raw = C.readText(rel);
339 + if (raw === null) return notFound(res);
340 + const name = rel.split("/").pop();
341 + const body = `<p class="crumb"><a href="/code">Code</a> / ${R.esc(rel)}</p>
342 +<div class="card file-card"><div class="file-head"><span class="mono-small">${R.esc(rel)}</span>
343 +<span class="mono-small">${raw.split("\n").length} lines</span></div>
344 +<pre class="codeblock"><code class="hljs">${R.highlightFile(raw.slice(0, 400000), name)}</code></pre></div>`;
345 + page(res, { title: name, active: "Code", body });
346 +});
347 +
348 +// ---------------------------------------------------------------- about
349 +app.get("/about", (req, res) => {
350 + const body = `<h1 class="page-title">About this project</h1>
351 +<div class="card md">
352 +<p><strong>anomaly-atlas</strong> is an independent research project by
353 +<strong>Simon-Pierre Boucher</strong> (<a href="mailto:contact@spboucher.ai">contact@spboucher.ai</a>) that asks:
354 +given only open high-frequency market data from <a href="https://www.hfmarketdata.io">hfmarketdata.io</a>
355 +(the sole data source), which statistical regularities — mean-reversion, lead-lag relationships,
356 +calendar/seasonal effects — are <em>real</em>, and which are artifacts of bid-ask bounce, stale or
357 +non-synchronized timestamps, survivorship, look-ahead, or microstructure noise?</p>
358 +<p>The methodology is deliberately strict: pre-specified hypotheses and splits, artifact nulls that every
359 +detector must beat, mandatory multiple-testing correction, a holdout touched exactly once, realistic
360 +transaction-cost models, an append-only research log, hardware manifests embedded in every result, and no
361 +result reported from an uncommitted tree. Detectors are validated on synthetic series first — a detector that
362 +finds anomalies in a pure random walk is broken. The full methodology is codified in the
363 +<a href="/doc/CLAUDE.md">research charter</a>.</p>
364 +<h2>What counts as a real result</h2>
365 +<ul>
366 +<li>A regularity reaching Level 3: corrected, out-of-sample, robust, cost-real, confirmed on the once-touched holdout.</li>
367 +<li>A rigorous artifact taxonomy for this dataset — letting others avoid mistaking artifacts for signals.</li>
368 +<li>A clean demonstration that a widely-cited anomaly does NOT survive honest testing here (negative result).</li>
369 +<li>A methodological contribution: a better artifact null or honest test for short-horizon effects on bar data.</li>
370 +<li>A measured survival curve: what fraction of naively-significant effects remains after each layer of rigor.</li>
371 +</ul>
372 +<h2>What this is not</h2>
373 +<p>Not a trading system, not trading advice, not a claim of arbitrage or "free money". The honest answer
374 +"nothing survives" is an acceptable — and publishable — outcome. Do not assume Level-3 findings exist;
375 +the research establishes the truth either way.</p>
376 +<h2>Platform</h2>
377 +<p>Everything runs on a consumer Apple Silicon Mac: DuckDB + parquet on the internal NVMe for out-of-core
378 +columnar queries, NumPy/Accelerate for the math. This site is generated from the repository itself —
379 +no hand-typed numbers.</p>
380 +</div>`;
381 + page(res, { title: "About", active: "About", body });
382 +});
383 +
384 +// ---------------------------------------------------------------- misc
385 +app.get("/health", (req, res) => res.json({ ok: true, app: "anomaly-atlas-web", author: "Simon-Pierre Boucher", data_source: "hfmarketdata.io" }));
386 +
387 +function notFound(res) {
388 + res.status(404);
389 + page(res, { title: "Not found", active: "", body: `<div class="card"><h1>404</h1><p>That page does not exist. <a href="/">Back to home</a>.</p></div>` });
390 +}
391 +app.use((req, res) => notFound(res));
392 +
393 +app.listen(PORT, () => console.log(`anomaly-atlas-web listening on :${PORT}`));
added web/sync-content.sh +46 −0
@@ -0,0 +1,46 @@
1 +#!/bin/zsh
2 +# =============================================================================
3 +# Project : anomaly-atlas
4 +# File : web/sync-content.sh
5 +# Purpose : Snapshot the research repo into web/content/ + build-info.json
6 +# Author : Simon-Pierre Boucher
7 +# Contact : contact@spboucher.ai
8 +# Data src : hfmarketdata.io (sole data source)
9 +# Created : 2026-08-12
10 +# Modified : 2026-08-12
11 +# Platform : macOS / Apple Silicon (arm64)
12 +# License : All rights reserved (research code)
13 +# =============================================================================
14 +set -euo pipefail
15 +
16 +REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
17 +CONTENT="$REPO_ROOT/web/content"
18 +
19 +mkdir -p "$CONTENT"
20 +# NOTE: rsync applies filters in order — excludes MUST precede the broad
21 +# includes, or large artifacts slip through.
22 +rsync -a --delete \
23 + --exclude='*.parquet' --exclude='*.duckdb' --exclude='*.log' \
24 + --exclude='__pycache__' --exclude='.git' --exclude='.venv' \
25 + --exclude='node_modules' --exclude='web' --exclude='data' \
26 + --include='CLAUDE.md' --include='README.md' --include='CITATION.cff' \
27 + --include='Makefile' --include='pyproject.toml' \
28 + --include='research/***' --include='experiments/***' --include='benchmarks/***' \
29 + --include='src/***' --include='tools/***' --include='results/***' \
30 + --include='atlas/***' --include='docs/***' \
31 + --exclude='*' \
32 + "$REPO_ROOT/" "$CONTENT/"
33 +
34 +COMMIT=$(git -C "$REPO_ROOT" rev-parse HEAD 2>/dev/null || echo "unknown")
35 +COMMITS=$(git -C "$REPO_ROOT" rev-list --count HEAD 2>/dev/null || echo 0)
36 +cat > "$CONTENT/build-info.json" <<EOF
37 +{
38 + "author": "Simon-Pierre Boucher",
39 + "contact": "contact@spboucher.ai",
40 + "data_source": "hfmarketdata.io",
41 + "commit": "$COMMIT",
42 + "commits": $COMMITS,
43 + "generated": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
44 +}
45 +EOF
46 +echo "content synced at commit ${COMMIT:0:7} ($COMMITS commits)"
47