Bootstrap: full research skeleton (CLAUDE.md §3, localvm-research layout) + web platform
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[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 & 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 & 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 | ||