SPB Git

spb/wp40_uqo

Public MIT

WP40 RAID: Phase 0 audit + Phase 1 foundation (C++20 core, CPU backend, calendar, OLS/robust SE, realized measures, GARCH QML, nanobind, CLI, tests, docs)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Simon-Pierre Boucher committed (Nov 2, 2026) parent 2ceeede Browse files

102 changed files +15,235 −21

102 changed files
added .github/workflows/ci.yml +22 −0
@@ -0,0 +1,22 @@
1+name: ci
2+on: [push, pull_request]
3+jobs:
4+ cpu-linux:
5+ runs-on: ubuntu-24.04
6+ steps:
7+ - uses: actions/checkout@v4
8+ - run: sudo apt-get update && sudo apt-get install -y cmake ninja-build libeigen3-dev nlohmann-json3-dev catch2 libcurl4-openssl-dev
9+ - run: cmake --preset cpu-only -DRAID_WERROR=OFF
10+ - run: cmake --build --preset cpu-only
11+ - run: ctest --preset cpu-only
12+ - run: bash scripts/check_no_secrets.sh
13+ macos-metal:
14+ runs-on: macos-15
15+ steps:
16+ - uses: actions/checkout@v4
17+ - run: brew install cmake ninja eigen apache-arrow catch2 nlohmann-json
18+ - run: cmake --preset macos-release
19+ - run: cmake --build --preset macos-release
20+ - run: ctest --preset macos-release
21+ - run: ./build/macos-release/raid devices
22+ - run: ./build/macos-release/raid validate datasets/fixtures/synthetic_bars_1m.csv
modified .gitignore +8 −21
@@ -1,5 +1,4 @@
1−# 1+# Build
2−Build
3 build/ 2 build/
4 build-*/ 3 build-*/
5 cmake-build-*/ 4 cmake-build-*/
@@ -13,38 +12,27 @@ wheelhouse/
13 *.so 12 *.so
14 *.air 13 *.air
15 *.metallib 14 *.metallib
16−# 15+# Python
17−Python
18 __pycache__/ 16 __pycache__/
19 *.pyc 17 *.pyc
20 .pytest_cache/ 18 .pytest_cache/
21 .hypothesis/ 19 .hypothesis/
22 .venv/ 20 .venv/
23 .ruff_cache/ 21 .ruff_cache/
24−# 22+# Data — FirstRate private license: never commit real bars
25−Data
26−—
27−FirstRate
28−private
29−license:
30−never
31−commit
32−real
33−bars
34 datasets/cache/ 23 datasets/cache/
35 datasets/real/ 24 datasets/real/
36 data/ 25 data/
26+experiments/results/raw/
37 *.parquet 27 *.parquet
28+*.arrow
38 !datasets/fixtures/*.parquet 29 !datasets/fixtures/*.parquet
39 !datasets/synthetic/*.parquet 30 !datasets/synthetic/*.parquet
40−experiments/results/raw/*.arrow 31+# Secrets
41−#
42−Secrets
43 .env 32 .env
44 *.key 33 *.key
45 api-key*.txt 34 api-key*.txt
46−# 35+# LaTeX
47−LaTeX
48 paper/*.aux 36 paper/*.aux
49 paper/*.log 37 paper/*.log
50 paper/*.out 38 paper/*.out
@@ -58,8 +46,7 @@ paper/*.blg
58 paper/*.synctex.gz 46 paper/*.synctex.gz
59 paper/*.run.xml 47 paper/*.run.xml
60 paper/*.bcf 48 paper/*.bcf
61−# 49+# macOS
62−macOS
63 .DS_Store 50 .DS_Store
64 Icon? 51 Icon?
65 logs/ 52 logs/
added CITATION.cff +23 −0
@@ -0,0 +1,23 @@
1+cff-version: 1.2.0
2+message: "If you use RAID, please cite the working paper."
3+title: "RAID: Research Architecture for Intraday Data"
4+version: 0.1.0
5+date-released: 2026-11-02
6+authors:
7+ - family-names: Boucher
8+ given-names: Simon-Pierre
9+ email: simon-pierre.boucher@uqo.ca
10+ affiliation: "Université du Québec en Outaouais, Département des sciences administratives"
11+license: MIT
12+repository-code: "https://git.spboucher.ai/wp40_uqo"
13+keywords: [high-frequency econometrics, realized volatility, GARCH, HAR, Apple Metal, GPU, C++20, reproducibility]
14+preferred-citation:
15+ type: report
16+ title: "RAID: A Metal-Accelerated C++ Framework for Reproducible High-Frequency Financial Econometrics"
17+ authors:
18+ - family-names: Boucher
19+ given-names: Simon-Pierre
20+ institution:
21+ name: "Université du Québec en Outaouais"
22+ number: "40"
23+ year: 2026
added CMakeLists.txt +189 −0
@@ -0,0 +1,189 @@
1+cmake_minimum_required(VERSION 3.28)
2+project(raid VERSION 0.1.0 LANGUAGES CXX)
3+
4+# ---------------------------------------------------------------------------
5+# RAID — Research Architecture for Intraday Data (UQO WP 40)
6+# C++20 core + CPU backend (portable) + optional Metal backend (macOS arm64).
7+# ---------------------------------------------------------------------------
8+set(CMAKE_CXX_STANDARD 20)
9+set(CMAKE_CXX_STANDARD_REQUIRED ON)
10+set(CMAKE_CXX_EXTENSIONS OFF)
11+set(CMAKE_POSITION_INDEPENDENT_CODE ON)
12+set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
13+if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
14+ set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
15+endif()
16+
17+option(RAID_ENABLE_METAL "Build the Metal backend (macOS only)" ${APPLE})
18+option(RAID_WITH_ARROW "Build the Arrow/Parquet reader (requires Apache Arrow C++)" ON)
19+option(RAID_WITH_ACCELERATE "Use Apple Accelerate BLAS/LAPACK through Eigen (macOS only)" ${APPLE})
20+option(RAID_BUILD_TESTS "Build Catch2 tests" ON)
21+option(RAID_BUILD_BENCH "Build benchmark executables" ON)
22+option(RAID_BUILD_CLI "Build the raid command-line tool" ON)
23+option(RAID_BUILD_PYTHON "Build the nanobind Python extension" OFF)
24+option(RAID_NATIVE_ARCH "Tune for the host CPU (-mcpu=native)" ON)
25+option(RAID_WERROR "Treat warnings as errors" OFF)
26+
27+list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
28+include(GNUInstallDirs)
29+
30+# ---- dependencies ----------------------------------------------------------
31+find_package(Eigen3 REQUIRED CONFIG)
32+find_package(nlohmann_json 3.11 REQUIRED CONFIG)
33+find_package(CURL REQUIRED) # system libcurl (macOS SDK / Linux)
34+find_package(Threads REQUIRED)
35+if(RAID_WITH_ARROW)
36+ find_package(Arrow CONFIG QUIET)
37+ find_package(Parquet CONFIG QUIET)
38+ if(NOT Arrow_FOUND OR NOT Parquet_FOUND)
39+ message(WARNING "RAID_WITH_ARROW=ON but Arrow/Parquet not found; building without Arrow")
40+ set(RAID_WITH_ARROW OFF)
41+ endif()
42+endif()
43+
44+# ---- compile options -------------------------------------------------------
45+add_library(raid_options INTERFACE)
46+target_compile_options(raid_options INTERFACE
47+ $<$<CXX_COMPILER_ID:Clang,AppleClang,GNU>:-Wall -Wextra -Wpedantic -Wshadow -Wconversion -Wno-sign-conversion>
48+ $<$<AND:$<CXX_COMPILER_ID:Clang,AppleClang,GNU>,$<BOOL:${RAID_WERROR}>>:-Werror>
49+ $<$<AND:$<CXX_COMPILER_ID:Clang,AppleClang,GNU>,$<CONFIG:Release>>:-O3>
50+)
51+if(RAID_NATIVE_ARCH AND CMAKE_SYSTEM_PROCESSOR MATCHES "arm64|aarch64")
52+ target_compile_options(raid_options INTERFACE -mcpu=native)
53+endif()
54+# Floating-point semantics: strict IEEE (no -ffast-math) so that CPU FP64 is a true reference.
55+target_compile_options(raid_options INTERFACE $<$<CXX_COMPILER_ID:Clang,AppleClang>:-ffp-model=precise>)
56+
57+# ---- core library ----------------------------------------------------------
58+file(GLOB_RECURSE RAID_CORE_SOURCES CONFIGURE_DEPENDS
59+ src/core/*.cpp src/data/*.cpp src/statistics/*.cpp src/estimators/*.cpp src/models/*.cpp backends/cpu/*.cpp)
60+if(NOT RAID_WITH_ARROW)
61+ list(FILTER RAID_CORE_SOURCES EXCLUDE REGEX ".*parquet_arrow\\.cpp$")
62+endif()
63+add_library(raid ${RAID_CORE_SOURCES})
64+add_library(raid::raid ALIAS raid)
65+target_include_directories(raid PUBLIC
66+ $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
67+ $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)
68+target_link_libraries(raid PUBLIC Eigen3::Eigen nlohmann_json::nlohmann_json Threads::Threads CURL::libcurl raid_options)
69+target_compile_definitions(raid PUBLIC RAID_VERSION="${PROJECT_VERSION}")
70+if(RAID_WITH_ARROW)
71+ target_link_libraries(raid PUBLIC Arrow::arrow_shared Parquet::parquet_shared)
72+ target_compile_definitions(raid PUBLIC RAID_WITH_ARROW=1)
73+endif()
74+if(APPLE AND RAID_WITH_ACCELERATE)
75+ target_compile_definitions(raid PUBLIC RAID_WITH_ACCELERATE=1 EIGEN_USE_BLAS=1 ACCELERATE_NEW_LAPACK=1 ACCELERATE_LAPACK_ILP64=0)
76+ target_link_libraries(raid PUBLIC "-framework Accelerate")
77+endif()
78+
79+# ---- Metal backend ---------------------------------------------------------
80+if(RAID_ENABLE_METAL)
81+ if(NOT APPLE)
82+ message(FATAL_ERROR "RAID_ENABLE_METAL requires macOS")
83+ endif()
84+ file(GLOB _raid_metal_probe CONFIGURE_DEPENDS backends/metal/host/*.mm)
85+ if(NOT _raid_metal_probe)
86+ message(STATUS "Metal backend sources not present yet (Phase 3): building without Metal")
87+ set(RAID_ENABLE_METAL OFF)
88+ endif()
89+endif()
90+if(RAID_ENABLE_METAL)
91+ enable_language(OBJCXX)
92+ set(CMAKE_OBJCXX_STANDARD 20)
93+ file(GLOB RAID_METAL_HOST CONFIGURE_DEPENDS backends/metal/host/*.mm backends/metal/host/*.cpp)
94+ file(GLOB RAID_METAL_SHADERS CONFIGURE_DEPENDS backends/metal/shaders/*.metal)
95+ add_library(raid_metal ${RAID_METAL_HOST})
96+ target_link_libraries(raid_metal PUBLIC raid "-framework Metal" "-framework Foundation")
97+ target_compile_options(raid_metal PRIVATE -fobjc-arc)
98+ target_compile_definitions(raid PUBLIC RAID_ENABLE_METAL=1)
99+ # Embed the MSL sources as a string (runtime compilation fallback / cold-compile benchmark)
100+ set(RAID_MSL_BUNDLE "${CMAKE_CURRENT_BINARY_DIR}/raid_msl_bundle.cpp")
101+ add_custom_command(OUTPUT ${RAID_MSL_BUNDLE}
102+ COMMAND ${CMAKE_COMMAND} -DOUT=${RAID_MSL_BUNDLE} "-DSHADERS=${RAID_METAL_SHADERS}" -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/EmbedMSL.cmake
103+ DEPENDS ${RAID_METAL_SHADERS} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/EmbedMSL.cmake COMMENT "Embedding MSL sources")
104+ target_sources(raid_metal PRIVATE ${RAID_MSL_BUNDLE})
105+ # Optional offline metallib (requires the Metal Toolchain). Missing toolchain => runtime compile only.
106+ find_program(RAID_XCRUN xcrun)
107+ if(RAID_XCRUN AND RAID_METAL_SHADERS)
108+ execute_process(COMMAND ${RAID_XCRUN} -sdk macosx metal --version RESULT_VARIABLE _mt_rc OUTPUT_QUIET ERROR_QUIET)
109+ if(_mt_rc EQUAL 0)
110+ set(RAID_METALLIB "${CMAKE_CURRENT_BINARY_DIR}/raid.metallib")
111+ set(_airs)
112+ foreach(s ${RAID_METAL_SHADERS})
113+ get_filename_component(n ${s} NAME_WE)
114+ add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${n}.air
115+ COMMAND ${RAID_XCRUN} -sdk macosx metal -std=metal3.2 -O2 -c ${s} -o ${CMAKE_CURRENT_BINARY_DIR}/${n}.air
116+ DEPENDS ${s} COMMENT "metal ${n}")
117+ list(APPEND _airs ${CMAKE_CURRENT_BINARY_DIR}/${n}.air)
118+ endforeach()
119+ add_custom_command(OUTPUT ${RAID_METALLIB} COMMAND ${RAID_XCRUN} -sdk macosx metallib ${_airs} -o ${RAID_METALLIB} DEPENDS ${_airs} COMMENT "metallib")
120+ add_custom_target(raid_metallib ALL DEPENDS ${RAID_METALLIB})
121+ add_dependencies(raid_metal raid_metallib)
122+ target_compile_definitions(raid_metal PRIVATE RAID_METALLIB_PATH="${RAID_METALLIB}")
123+ message(STATUS "Metal toolchain found: building raid.metallib")
124+ else()
125+ message(STATUS "Metal toolchain not found: runtime MSL compilation only")
126+ endif()
127+ endif()
128+endif()
129+
130+# ---- CLI --------------------------------------------------------------------
131+if(RAID_BUILD_CLI)
132+ add_executable(raid_cli cli/raid.cpp)
133+ set_target_properties(raid_cli PROPERTIES OUTPUT_NAME raid)
134+ target_link_libraries(raid_cli PRIVATE raid)
135+ if(RAID_ENABLE_METAL)
136+ target_link_libraries(raid_cli PRIVATE raid_metal)
137+ endif()
138+endif()
139+
140+# ---- examples -----------------------------------------------------------------
141+add_executable(example_minimal examples/cpp/minimal.cpp)
142+target_link_libraries(example_minimal PRIVATE raid)
143+if(RAID_ENABLE_METAL)
144+ target_link_libraries(example_minimal PRIVATE raid_metal)
145+endif()
146+
147+# ---- tests ------------------------------------------------------------------
148+if(RAID_BUILD_TESTS)
149+ find_package(Catch2 3 CONFIG REQUIRED)
150+ include(CTest)
151+ include(Catch)
152+ file(GLOB_RECURSE RAID_TEST_SOURCES CONFIGURE_DEPENDS tests/unit/*.cpp tests/property/*.cpp tests/reference/*.cpp tests/simulation/*.cpp tests/stress/*.cpp tests/integration/*.cpp)
153+ add_executable(raid_tests ${RAID_TEST_SOURCES})
154+ target_link_libraries(raid_tests PRIVATE raid Catch2::Catch2WithMain)
155+ target_compile_definitions(raid_tests PRIVATE RAID_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
156+ if(RAID_ENABLE_METAL)
157+ target_link_libraries(raid_tests PRIVATE raid_metal)
158+ endif()
159+ catch_discover_tests(raid_tests)
160+endif()
161+
162+# ---- benchmarks ---------------------------------------------------------------
163+if(RAID_BUILD_BENCH)
164+ file(GLOB RAID_BENCH_SOURCES CONFIGURE_DEPENDS benchmarks/micro/*.cpp benchmarks/end_to_end/*.cpp benchmarks/accuracy/*.cpp benchmarks/scalability/*.cpp)
165+ foreach(b ${RAID_BENCH_SOURCES})
166+ get_filename_component(n ${b} NAME_WE)
167+ add_executable(bench_${n} ${b})
168+ target_link_libraries(bench_${n} PRIVATE raid)
169+ if(RAID_ENABLE_METAL)
170+ target_link_libraries(bench_${n} PRIVATE raid_metal)
171+ endif()
172+ endforeach()
173+endif()
174+
175+# ---- Python -------------------------------------------------------------------
176+if(RAID_BUILD_PYTHON)
177+ add_subdirectory(bindings/python)
178+endif()
179+
180+# ---- install -----------------------------------------------------------------
181+install(TARGETS raid raid_options EXPORT raidTargets LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
182+if(RAID_ENABLE_METAL)
183+ install(TARGETS raid_metal EXPORT raidTargets LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
184+endif()
185+if(RAID_BUILD_CLI)
186+ install(TARGETS raid_cli RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
187+endif()
188+install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
189+install(EXPORT raidTargets NAMESPACE raid:: DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/raid)
added CMakePresets.json +25 −0
@@ -0,0 +1,25 @@
1+{
2+ "version": 6,
3+ "configurePresets": [
4+ { "name": "base", "hidden": true, "generator": "Ninja", "binaryDir": "${sourceDir}/build/${presetName}",
5+ "cacheVariables": { "CMAKE_EXPORT_COMPILE_COMMANDS": "ON" } },
6+ { "name": "macos-release", "inherits": "base", "displayName": "macOS arm64 Release (CPU + Metal + Arrow)",
7+ "cacheVariables": { "CMAKE_BUILD_TYPE": "Release", "RAID_ENABLE_METAL": "ON", "RAID_WITH_ARROW": "ON", "CMAKE_PREFIX_PATH": "/opt/homebrew" } },
8+ { "name": "macos-debug", "inherits": "macos-release", "displayName": "macOS Debug + sanitizers",
9+ "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug", "CMAKE_CXX_FLAGS": "-fsanitize=address,undefined -fno-omit-frame-pointer", "CMAKE_EXE_LINKER_FLAGS": "-fsanitize=address,undefined", "RAID_NATIVE_ARCH": "OFF" } },
10+ { "name": "cpu-only", "inherits": "base", "displayName": "Portable CPU-only build (Linux/macOS)",
11+ "cacheVariables": { "CMAKE_BUILD_TYPE": "Release", "RAID_ENABLE_METAL": "OFF", "RAID_WITH_ARROW": "OFF", "RAID_WITH_ACCELERATE": "OFF", "RAID_NATIVE_ARCH": "OFF" } },
12+ { "name": "bundle", "inherits": "base", "displayName": "Self-contained bundle for cluster nodes (E7)",
13+ "cacheVariables": { "CMAKE_BUILD_TYPE": "Release", "RAID_ENABLE_METAL": "ON", "RAID_WITH_ARROW": "OFF", "RAID_NATIVE_ARCH": "OFF", "RAID_BUILD_TESTS": "OFF", "CMAKE_OSX_DEPLOYMENT_TARGET": "26.0", "CMAKE_PREFIX_PATH": "/opt/homebrew", "CMAKE_CXX_FLAGS": "-mcpu=apple-m1" } }
14+ ],
15+ "buildPresets": [
16+ { "name": "macos-release", "configurePreset": "macos-release" },
17+ { "name": "macos-debug", "configurePreset": "macos-debug" },
18+ { "name": "cpu-only", "configurePreset": "cpu-only" },
19+ { "name": "bundle", "configurePreset": "bundle" }
20+ ],
21+ "testPresets": [
22+ { "name": "macos-release", "configurePreset": "macos-release", "output": { "outputOnFailure": true } },
23+ { "name": "cpu-only", "configurePreset": "cpu-only", "output": { "outputOnFailure": true } }
24+ ]
25+}
added README.md +114 −0
@@ -0,0 +1,114 @@
1+<!-- Author: Simon-Pierre Boucher — simon-pierre.boucher@uqo.ca -->
2+# ⚡ RAID — Research Architecture for Intraday Data
3+
4+**UQO Working Paper No. 40** — *A Metal-Accelerated C++ Framework for Reproducible High-Frequency Financial Econometrics*
5+
6+[![Paper](https://img.shields.io/badge/paper-in%20progress-lightgrey?logo=latex&logoColor=white)](paper/)
7+[![C++20](https://img.shields.io/badge/C%2B%2B-20-00599C?logo=cplusplus&logoColor=white)](CMakeLists.txt)
8+[![Metal](https://img.shields.io/badge/Metal-4%20(FP32)-000000?logo=apple&logoColor=white)](backends/metal/)
9+[![Python](https://img.shields.io/badge/python-3.12%2B%20nanobind-3776AB?logo=python&logoColor=white)](pyproject.toml)
10+[![Data](https://img.shields.io/badge/data-FinData%20OS%20%C2%B7%20FirstRate%20(private)-4051b5)](docs/audit.md)
11+[![Institution](https://img.shields.io/badge/UQO-D%C3%A9pt.%20des%20sciences%20administratives-16365c)](https://uqo.ca)
12+[![Author](https://img.shields.io/badge/author-Simon--Pierre%20Boucher-16365c)](mailto:simon-pierre.boucher@uqo.ca)
13+
14+> **TL;DR** — RAID asks whether heterogeneous high-frequency econometric workloads (realized measures, rolling/batched
15+> regressions, HAR, GARCH-family QML, block bootstrap) can be turned into batched, fused, streaming computation on Apple
16+> Silicon **without** losing estimator accuracy, inference validity or reproducibility. It is a C++20 library with a FP64
17+> CPU reference backend, an optional FP32 Metal backend (Apple GPUs have no FP64), nanobind Python bindings, a CLI, a
18+> FinData OS client, and a benchmark protocol that reports break-even points and the cases where the CPU wins.
19+> Status: Phase 1 (foundation) — see `RESEARCH_STATUS.md`. No performance numbers are claimed yet.
20+
21+---
22+
23+## 📌 Status
24+
25+| Item | Value |
26+|---|---|
27+| Version | 0.1.0 (November 2026) — Phases 0–1 |
28+| SSRN | not yet submitted |
29+| Journal | targets: J. Financial Econometrics, J. Financial Data Science, J. Computational Finance |
30+| Code & data | CPU core + tests build and pass on macOS 27 / Xcode 27.2; synthetic fixtures only in git; real bars via FinData OS (private license) |
31+| Ledger | `RESEARCH_STATUS.md` · `docs/PROGRESS.md` · `docs/DECISIONS.md` · `docs/risks.md` |
32+
33+## 🎯 What this paper does
34+
35+Three falsifiable questions. **RQ1** At what data and batch sizes does Metal beat an optimized multicore CPU, once I/O,
36+shader compilation, synchronization and transfers are counted? **RQ2** Does batched estimation raise throughput while
37+preserving statistical coverage and convergence? **RQ3** Can an Arrow/Parquet-to-estimator pipeline materially reduce
38+copies, memory pressure and end-to-end latency? Negative results are published. Tier A methods (returns, calendar-aware
39+aggregation, RV/BPV/RSV/RQ/jumps/rCov, OLS/WLS/ridge with HC/HAC/cluster inference, HAR family, GARCH/GJR/EGARCH with
40+Gaussian/Student-t QML, block bootstrap) are validated against R `highfrequency`/`rugarch` and Python
41+`statsmodels`/`arch`; GPU results are validated against the FP64 CPU reference with per-method tolerances.
42+
43+## 🏆 Headline results
44+
45+None yet — benchmarks (E1–E7) run in Phase 6; every number in the paper will point to a record under
46+`experiments/results/` listed in `experiments/manifests/`.
47+
48+## 🗂 Repository layout
49+
50+```
51+wp40_raid/
52+├── include/raid/{core,data,statistics,estimators,models,backend}/ # public C++20 headers
53+├── src/… backends/cpu/ backends/metal/{host,shaders}/ # implementations (Metal: Phase 3)
54+├── bindings/python/ python/raid/ cli/ # nanobind module, Python API, `raid` CLI
55+├── tests/{unit,property,reference,simulation,stress,integration}/ # Catch2 (reference = vs statsmodels/arch/highfrequency)
56+├── benchmarks/ experiments/{configs,scripts,results,manifests}/ # harness + tracked artifacts (Phase 6)
57+├── datasets/{synthetic,fixtures}/ # synthetic only; real data cached outside git
58+├── docs/ paper/ scripts/ .github/workflows/
59+```
60+
61+## 🚀 Quick start
62+
63+```bash
64+brew install cmake ninja eigen apache-arrow catch2 nlohmann-json # macOS
65+cmake --preset macos-release && cmake --build --preset macos-release # CPU + Metal + Arrow
66+ctest --preset macos-release # unit + reference tests
67+./build/macos-release/raid devices
68+./build/macos-release/raid validate datasets/fixtures/synthetic_bars_1m.csv
69+./build/macos-release/raid estimate realized datasets/fixtures/synthetic_bars_1m.csv --backend auto
70+uv venv --python 3.12 .venv && uv pip install --python .venv/bin/python -e ".[dev]" # Python package (nanobind)
71+```
72+
73+Linux / CPU-only: `cmake --preset cpu-only && cmake --build --preset cpu-only && ctest --preset cpu-only`.
74+
75+## 🗃 Data
76+
77+Market data come from the owner's **FinData OS** lakehouse (`https://findata.spboucher.ai/api/v1`, FirstRate Data under
78+a private-use license): 1-minute OHLCV bars (bar-open timestamps, extended 04:00–20:00 ET, regular 09:30–16:00 with the
79+16:00 closing print, zero-volume minutes absent at the source), dataset versions pinned via `X-Dataset-Version`. Raw
80+bars are never committed; `datasets/fixtures/` contains synthetic bars generated by `scripts/make_fixtures.py`.
81+
82+## 🔬 Methodology
83+
84+See `docs/methods/registry.md` (one entry per method: equations, conventions, reference software and tolerance) and
85+`docs/architecture/overview.md`.
86+
87+## 🖼 Figures & tables inventory
88+
89+To be generated in Phase 6–7 from `experiments/results/`.
90+
91+## ⚠️ Limitations
92+
93+Apple Metal has no FP64: GPU paths are FP32 with compensated reductions and FP64 combination on the CPU, and are only
94+valid where the documented tolerance is met. No tick/quote data: microstructure-noise and order-flow methods are out of
95+scope. Energy is reported only when measured.
96+
97+## 📖 Citation
98+
99+```bibtex
100+@techreport{boucher2026raid,
101+ title = {RAID: A Metal-Accelerated C++ Framework for Reproducible High-Frequency Financial Econometrics},
102+ author = {Boucher, Simon-Pierre},
103+ institution = {Université du Québec en Outaouais, Département des sciences administratives},
104+ type = {UQO Working Paper}, number = {40}, year = {2026}
105+}
106+```
107+
108+## 👤 Author & contact
109+
110+Simon-Pierre Boucher — simon-pierre.boucher@uqo.ca — Département des sciences administratives, UQO.
111+
112+## 📝 Notes
113+
114+Directory name `wp40_raid` (charter requirement) instead of the usual slug; git remote `gitsrv:srv/git/wp40_uqo.git`.
added RESEARCH_STATUS.md +40 −0
@@ -0,0 +1,40 @@
1+# RESEARCH_STATUS.md — RAID (UQO WP 40)
2+
3+Last update: 2026-11-02 (Phase 1 passed). Legend: ✅ verified by tests/artifacts · 🧪 implemented, validation pending ·
4+🧭 experimental · ⛔ blocked · ⏳ planned · ✖ not implemented (deliberately).
5+
6+## Phases
7+| Phase | Gate | Status | Evidence |
8+|---|---|---|---|
9+| 0 Audit | scope & feasibility memo | ✅ passed 2026-11-02 | `docs/audit.md`, `docs/capabilities.md`, `docs/novelty_matrix.md`, `docs/risks.md`, `docs/PROGRESS.md` |
10+| 1 Foundation | clean build + tests + docs | ✅ passed 2026-11-02 | `ctest` 26/26 (314 assertions), `pytest` 7/7, `docs/PROGRESS.md` Phase 1 |
11+| 2 Data | offline synthetic tests + opt-in live tests | ⏳ | — |
12+| 3 Numerical primitives | accuracy + measured break-even | ⏳ | — |
13+| 4 Econometrics (Tier A) | references + Monte Carlo | ⏳ | — |
14+| 5 Extensions (Tier B) | per-feature specs + tests | ⏳ | — |
15+| 6 Benchmarking E1–E7 | every claim traceable | ⏳ | — |
16+| 7 Manuscript | reproducible build, no placeholders | ⏳ | — |
17+| 8 Independent review | clean-checkout reproduction | ⏳ | — |
18+
19+## Methods registry (summary; full entries in `docs/methods/registry.md` from Phase 4)
20+| Method | Tier | CPU FP64 | Metal FP32 | Reference software | Status |
21+|---|---|---|---|---|---|
22+| returns (log/arith), lag/lead, rolling windows, calendar-aware aggregation | A | ✅ | ⏳ (Phase 3) | numpy; exchange_calendars (calendar exact 2000–2026) | ✅ CPU |
23+| RV, BPV, RSV±, RQ, jump proxy, TriPV, rCov/rCorr | A | ✅ (numpy truth 1e−12) | ⏳ (Phase 3) | highfrequency 1.0.3 (R comparison Phase 4) | ✅ CPU |
24+| OLS/WLS/ridge, HC0–3, HAC, cluster, rolling OLS | A | ✅ (statsmodels 1e−9) | ⏳ (sufficient stats, Phase 3) | statsmodels 0.15, scipy 1.18 | ✅ CPU |
25+| HAR-RV, log-HAR, HAR-X, HARQ | A | ⏳ | ⏳ | highfrequency `HARmodel`, statsmodels | ⏳ |
26+| GARCH(1,1), GJR, EGARCH (N, t), QMLE/Hessian/OPG SE, forecasts | A | ✅ (arch 8.0: loglik 5e−4, params 5 %) | ⏳ (batched likelihood, Phase 3) | arch 8.0 ✅, rugarch 1.5.6 (Phase 4) | ✅ CPU |
27+| Bootstrap (stationary / circular block), Monte Carlo | A | ⏳ | ⏳ | arch.bootstrap | ⏳ |
28+| VAR, GFEVD, Diebold–Yilmaz | B | ⏳ | ⏳ | statsmodels VAR | ⏳ |
29+| Realized GARCH, HEAVY, DCC, PCA/shrinkage, Kalman | B | ✖ until Tier A passes | | | ⏳ |
30+| Pre-averaging, realized kernels, MSRV, BNS test | C | ✖ (1-min bars) | | highfrequency | 🧭 only if validated |
31+| Hawkes, order-flow, LOB methods | C | ✖ | ✖ | — | ✖ no admissible data (bars only) |
32+
33+## Blockers
34+- None at Phase 0. Seven cluster nodes unreachable (m2m16, M2M32, M2M32b, M4M36, m4ma, m4mb, m4mc): E7 reports them as
35+ not tested unless they come back.
36+
37+## Next safe action
38+Phase 2: FinData client (C++ libcurl: catalog, bars Arrow pages, exports with polling/download, provenance, cache,
39+token bucket, retries), `raid data` CLI, offline tests with a recorded OpenAPI/fixture server, opt-in live tests
40+(`RAID_LIVE=1`), aggregate-vs-FinData-5m check.
added backends/cpu/cpu_backend.cpp +115 −0
@@ -0,0 +1,115 @@
1+// CPU reference backend: FP64, Neumaier-compensated per-series reductions, parallel over series.
2+#include "raid/backend/backend.hpp"
3+#include "raid/core/thread_pool.hpp"
4+#include "raid/core/error.hpp"
5+#include "raid/statistics/reductions.hpp"
6+#include "raid/statistics/rolling.hpp"
7+#include "raid/models/garch.hpp"
8+#include <chrono>
9+#include <thread>
10+#include <cmath>
11+#ifdef __APPLE__
12+#include <sys/sysctl.h>
13+#endif
14+
15+namespace raid::backend {
16+
17+namespace {
18+struct Timer { std::chrono::steady_clock::time_point t0 = std::chrono::steady_clock::now(); f64 s() const { return std::chrono::duration<f64>(std::chrono::steady_clock::now() - t0).count(); } };
19+void fill(KernelStats* st, const char* k, const Timer& t, usize bin, usize bout) { if (!st) return; st->kernel = k; st->backend = raid::Backend::cpu; st->precision = Precision::fp64; st->wall_seconds = t.s(); st->bytes_in = bin; st->bytes_out = bout; }
20+}
21+
22+class CpuBackend final : public Backend {
23+ public:
24+ explicit CpuBackend(unsigned threads) : pool_(threads == 0 ? nullptr : std::make_unique<ThreadPool>(threads)) {}
25+ std::string name() const override { return "cpu"; }
26+ raid::Backend kind() const override { return raid::Backend::cpu; }
27+ bool available() const override { return true; }
28+ Device device() const override {
29+ Device d; d.kind = raid::Backend::cpu; d.supports_fp64 = true; d.unified_memory = true; d.compute_units = static_cast<int>(std::thread::hardware_concurrency());
30+#ifdef __APPLE__
31+ char buf[256]; size_t n = sizeof buf; if (sysctlbyname("machdep.cpu.brand_string", buf, &n, nullptr, 0) == 0) d.name = std::string(buf, n - 1); else d.name = "cpu";
32+ i64 mem = 0; n = sizeof mem; if (sysctlbyname("hw.memsize", &mem, &n, nullptr, 0) == 0) d.memory_bytes = static_cast<u64>(mem);
33+#else
34+ d.name = "cpu";
35+#endif
36+ d.details = "threads=" + std::to_string(pool().size());
37+ return d;
38+ }
39+ ThreadPool& pool() const { return pool_ ? *pool_ : ThreadPool::global(); }
40+
41+ template <class F> std::vector<f64> per_series(const Batch& b, F f) {
42+ std::vector<f64> out(b.n_series());
43+ pool().parallel_for(b.n_series(), [&](usize i0, usize i1) { for (usize i = i0; i < i1; ++i) out[i] = f(b.series(i)); });
44+ return out;
45+ }
46+
47+ std::vector<f64> sum_squares(const Batch& b, KernelStats* st) override {
48+ Timer t; auto r = per_series(b, [](std::span<const f64> x) { return stats::sum_squares(x); }); fill(st, "sum_squares", t, b.values.size_bytes(), r.size() * 8); return r;
49+ }
50+ std::vector<f64> sum_abs_adjacent(const Batch& b, KernelStats* st) override {
51+ Timer t; auto r = per_series(b, [](std::span<const f64> x) { f64 s = 0, c = 0; for (usize i = 1; i < x.size(); ++i) { const f64 v = std::abs(x[i]) * std::abs(x[i - 1]), tt = s + v; if (std::abs(s) >= std::abs(v)) c += (s - tt) + v; else c += (v - tt) + s; s = tt; } return s + c; });
52+ fill(st, "sum_abs_adjacent", t, b.values.size_bytes(), r.size() * 8); return r;
53+ }
54+ void semivariances(const Batch& b, std::vector<f64>& neg, std::vector<f64>& pos, KernelStats* st) override {
55+ Timer t; neg.assign(b.n_series(), 0); pos.assign(b.n_series(), 0);
56+ pool().parallel_for(b.n_series(), [&](usize i0, usize i1) { for (usize i = i0; i < i1; ++i) { f64 sn = 0, cn = 0, sp = 0, cp = 0; for (f64 v : b.series(i)) { const f64 q = v * v; if (v < 0) { const f64 tt = sn + q; if (std::abs(sn) >= q) cn += (sn - tt) + q; else cn += (q - tt) + sn; sn = tt; } else if (v > 0) { const f64 tt = sp + q; if (std::abs(sp) >= q) cp += (sp - tt) + q; else cp += (q - tt) + sp; sp = tt; } } neg[i] = sn + cn; pos[i] = sp + cp; } });
57+ fill(st, "semivariances", t, b.values.size_bytes(), neg.size() * 16);
58+ }
59+ std::vector<f64> sum_fourth(const Batch& b, KernelStats* st) override {
60+ Timer t; auto r = per_series(b, [](std::span<const f64> x) { f64 s = 0, c = 0; for (f64 v : x) { const f64 q = v * v * v * v, tt = s + q; if (std::abs(s) >= q) c += (s - tt) + q; else c += (q - tt) + s; s = tt; } return s + c; });
61+ fill(st, "sum_fourth", t, b.values.size_bytes(), r.size() * 8); return r;
62+ }
63+ std::vector<f64> rolling_sum_squares(const Batch& b, usize window, KernelStats* st) override {
64+ Timer t; std::vector<f64> out(b.values.size());
65+ pool().parallel_for(b.n_series(), [&](usize i0, usize i1) { for (usize i = i0; i < i1; ++i) { auto x = b.series(i); std::vector<f64> sq(x.size()); for (usize j = 0; j < x.size(); ++j) sq[j] = x[j] * x[j]; auto r = stats::rolling_sum(sq, stats::RollingOptions{window, 0, 1024}); std::copy(r.begin(), r.end(), out.begin() + b.offsets[i]); } });
66+ fill(st, "rolling_sum_squares", t, b.values.size_bytes(), out.size() * 8); return out;
67+ }
68+ void regression_stats(std::span<const f64> x, std::span<const f64> y, std::span<const i64> offsets, usize k, std::vector<f64>& xtx, std::vector<f64>& xty, std::vector<f64>& yty, KernelStats* st) override {
69+ Timer t; const usize ns = offsets.size() - 1; xtx.assign(ns * k * k, 0); xty.assign(ns * k, 0); yty.assign(ns, 0);
70+ pool().parallel_for(ns, [&](usize i0, usize i1) { for (usize s = i0; s < i1; ++s) { const usize b0 = static_cast<usize>(offsets[s]), n = static_cast<usize>(offsets[s + 1] - offsets[s]);
71+ std::vector<f64> acc(k * k, 0), accc(k * k, 0), ay(k, 0), ayc(k, 0); f64 syy = 0, cyy = 0;
72+ for (usize r = 0; r < n; ++r) { const f64* xr = x.data() + (b0 + r) * k; const f64 yr = y[b0 + r];
73+ for (usize a = 0; a < k; ++a) { for (usize c = a; c < k; ++c) { const f64 v = xr[a] * xr[c], tt = acc[a * k + c] + v; if (std::abs(acc[a * k + c]) >= std::abs(v)) accc[a * k + c] += (acc[a * k + c] - tt) + v; else accc[a * k + c] += (v - tt) + acc[a * k + c]; acc[a * k + c] = tt; }
74+ const f64 v = xr[a] * yr, tt = ay[a] + v; if (std::abs(ay[a]) >= std::abs(v)) ayc[a] += (ay[a] - tt) + v; else ayc[a] += (v - tt) + ay[a]; ay[a] = tt; }
75+ const f64 q = yr * yr, tt = syy + q; if (std::abs(syy) >= q) cyy += (syy - tt) + q; else cyy += (q - tt) + syy; syy = tt; }
76+ for (usize a = 0; a < k; ++a) for (usize c = a; c < k; ++c) { const f64 v = acc[a * k + c] + accc[a * k + c]; xtx[s * k * k + a * k + c] = v; xtx[s * k * k + c * k + a] = v; }
77+ for (usize a = 0; a < k; ++a) xty[s * k + a] = ay[a] + ayc[a];
78+ yty[s] = syy + cyy; } });
79+ fill(st, "regression_stats", t, x.size_bytes() + y.size_bytes(), (xtx.size() + xty.size() + yty.size()) * 8);
80+ }
81+ std::vector<f64> garch_nll_batch(const Batch& returns, std::span<const f64> params, usize n_params, usize p, int model_code, f64 nu_fixed, KernelStats* st) override {
82+ Timer t; const usize ns = returns.n_series(); std::vector<f64> out(ns * n_params);
83+ pool().parallel_for(ns * n_params, [&](usize i0, usize i1) { for (usize idx = i0; idx < i1; ++idx) { const usize s = idx / n_params, q = idx % n_params; const f64* th = params.data() + (s * n_params + q) * p; out[idx] = models::garch_nll(returns.series(s), std::span<const f64>(th, p), static_cast<models::GarchModel>(model_code), nu_fixed); } });
84+ fill(st, "garch_nll_batch", t, returns.values.size_bytes() + params.size_bytes(), out.size() * 8); return out;
85+ }
86+ private:
87+ std::unique_ptr<ThreadPool> pool_;
88+};
89+
90+std::shared_ptr<Backend> make_cpu_backend(unsigned threads) { return std::make_shared<CpuBackend>(threads); }
91+
92+std::shared_ptr<Backend> resolve(const ExecutionPlan& plan, std::string* reason) {
93+ auto why = [&](const std::string& s) { if (reason) *reason = s; };
94+ if (plan.backend == raid::Backend::cpu) { why("cpu requested"); return make_cpu_backend(plan.threads); }
95+#ifdef RAID_ENABLE_METAL
96+ auto m = make_metal_backend(plan.precision);
97+ if (m && m->available()) { why(plan.backend == raid::Backend::metal ? "metal requested" : "auto: metal available"); return m; }
98+ if (plan.backend == raid::Backend::metal && !plan.allow_fallback) throw RaidError(ErrorCode::unsupported, "Metal backend requested but unavailable and fallback disabled");
99+ why("metal unavailable: falling back to cpu");
100+#else
101+ if (plan.backend == raid::Backend::metal && !plan.allow_fallback) throw RaidError(ErrorCode::unsupported, "RAID built without Metal support");
102+ why(plan.backend == raid::Backend::metal ? "built without Metal: falling back to cpu" : "auto: cpu (no Metal in this build)");
103+#endif
104+ return make_cpu_backend(plan.threads);
105+}
106+
107+std::vector<Device> list_devices() {
108+ std::vector<Device> d; d.push_back(make_cpu_backend()->device());
109+#ifdef RAID_ENABLE_METAL
110+ if (auto m = make_metal_backend()) if (m->available()) d.push_back(m->device());
111+#endif
112+ return d;
113+}
114+
115+} // namespace raid::backend
added bindings/python/CMakeLists.txt +9 −0
@@ -0,0 +1,9 @@
1+find_package(Python 3.11 COMPONENTS Interpreter Development.Module REQUIRED)
2+execute_process(COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT)
3+find_package(nanobind CONFIG REQUIRED)
4+nanobind_add_module(_core NB_STATIC raid_py.cpp)
5+target_link_libraries(_core PRIVATE raid)
6+if(RAID_ENABLE_METAL)
7+ target_link_libraries(_core PRIVATE raid_metal)
8+endif()
9+install(TARGETS _core LIBRARY DESTINATION raid)
added bindings/python/raid_py.cpp +102 −0
@@ -0,0 +1,102 @@
1+// nanobind bindings for RAID. Arrays are passed as NumPy views (no copy for contiguous float64/int64), the GIL is
2+// released around every heavy call, and results come back as dicts (JSON-like) plus NumPy arrays for vectors.
3+#include <nanobind/nanobind.h>
4+#include <nanobind/ndarray.h>
5+#include <nanobind/stl/string.h>
6+#include <nanobind/stl/vector.h>
7+#include <nanobind/stl/optional.h>
8+#include <nlohmann/json.hpp>
9+#include "raid/core/types.hpp"
10+#include "raid/core/error.hpp"
11+#include "raid/backend/backend.hpp"
12+#include "raid/estimators/ols.hpp"
13+#include "raid/estimators/realized.hpp"
14+#include "raid/models/garch.hpp"
15+#include "raid/data/bar_series.hpp"
16+#include "raid/data/calendar.hpp"
17+#include "raid/data/returns.hpp"
18+
19+namespace nb = nanobind;
20+using namespace raid;
21+using json = nlohmann::json;
22+using F64 = nb::ndarray<const double, nb::c_contig, nb::device::cpu>;
23+using I64 = nb::ndarray<const int64_t, nb::c_contig, nb::device::cpu>;
24+
25+namespace {
26+nb::object json_to_py(const json& j) {
27+ switch (j.type()) {
28+ case json::value_t::null: return nb::none();
29+ case json::value_t::boolean: return nb::bool_(j.get<bool>());
30+ case json::value_t::number_integer: return nb::int_(j.get<long long>());
31+ case json::value_t::number_unsigned: return nb::int_(j.get<unsigned long long>());
32+ case json::value_t::number_float: return nb::float_(j.get<double>());
33+ case json::value_t::string: return nb::str(j.get<std::string>().c_str());
34+ case json::value_t::array: { nb::list l; for (auto& v : j) l.append(json_to_py(v)); return l; }
35+ case json::value_t::object: { nb::dict d; for (auto& [k, v] : j.items()) d[k.c_str()] = json_to_py(v); return d; }
36+ default: return nb::none();
37+ }
38+}
39+nb::ndarray<nb::numpy, double> to_np(std::vector<double> v) {
40+ auto* data = new std::vector<double>(std::move(v));
41+ nb::capsule owner(data, [](void* p) noexcept { delete static_cast<std::vector<double>*>(p); });
42+ return nb::ndarray<nb::numpy, double>(data->data(), {data->size()}, owner);
43+}
44+nb::ndarray<nb::numpy, int64_t> to_np_i64(std::vector<int64_t> v) {
45+ auto* data = new std::vector<int64_t>(std::move(v));
46+ nb::capsule owner(data, [](void* p) noexcept { delete static_cast<std::vector<int64_t>*>(p); });
47+ return nb::ndarray<nb::numpy, int64_t>(data->data(), {data->size()}, owner);
48+}
49+} // namespace
50+
51+NB_MODULE(_core, m) {
52+ m.doc() = "RAID compiled core (C++20, CPU + optional Metal)";
53+ m.attr("__version__") = RAID_VERSION;
54+ nb::exception<RaidError>(m, "RaidError");
55+
56+ m.def("devices", []() { json out = json::array(); for (auto& d : backend::list_devices()) out.push_back(d.to_json()); return json_to_py(out); });
57+
58+ m.def("ols", [](F64 X, F64 y, const std::string& cov, int hac_lags, std::optional<I64> clusters, double ridge, std::vector<usize> unpen, std::optional<F64> weights, std::vector<std::string> names) {
59+ RAID_REQUIRE(X.ndim() == 2 && y.ndim() == 1 && X.shape(0) == y.shape(0), invalid_argument, "X (n,k), y (n,)");
60+ const Eigen::Index n = static_cast<Eigen::Index>(X.shape(0)), k = static_cast<Eigen::Index>(X.shape(1));
61+ Eigen::Map<const Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>> Xm(X.data(), n, k);
62+ Eigen::Map<const Eigen::VectorXd> ym(y.data(), n);
63+ est::OlsOptions o; o.cov.type = stats::covtype_from_string(cov); o.cov.hac_lags = hac_lags; o.ridge_lambda = ridge; o.unpenalized = unpen; o.names = names;
64+ std::vector<i64> cl; if (clusters) { cl.assign(clusters->data(), clusters->data() + clusters->shape(0)); o.cov.clusters = cl; }
65+ std::vector<f64> w; if (weights) { w.assign(weights->data(), weights->data() + weights->shape(0)); o.weights = w; }
66+ Eigen::MatrixXd Xd = Xm; json out; { nb::gil_scoped_release rel; auto r = est::ols(Xd, ym, o); out = r.estimate.to_json(); }
67+ return json_to_py(out);
68+ }, nb::arg("X"), nb::arg("y"), nb::arg("cov") = "classical", nb::arg("hac_lags") = -1, nb::arg("clusters") = nb::none(), nb::arg("ridge_lambda") = 0.0, nb::arg("unpenalized") = std::vector<usize>{}, nb::arg("weights") = nb::none(), nb::arg("names") = std::vector<std::string>{});
69+
70+ m.def("realized_measures", [](F64 r, I64 off, const std::string& backend_s, bool bpv_fs, bool deterministic) {
71+ est::RealizedOptions o; o.plan.backend = backend_from_string(backend_s); o.bpv_finite_sample = bpv_fs; o.plan.deterministic = deterministic;
72+ backend::Batch b{std::span<const f64>(r.data(), r.shape(0)), std::span<const i64>(off.data(), off.shape(0))};
73+ est::RealizedMeasures mres; { nb::gil_scoped_release rel; mres = est::realized_measures_batch(b, o); }
74+ nb::dict d; d["rv"] = to_np(mres.rv); d["bpv"] = to_np(mres.bpv); d["rsv_neg"] = to_np(mres.rsv_neg); d["rsv_pos"] = to_np(mres.rsv_pos); d["rq"] = to_np(mres.rq); d["jump"] = to_np(mres.jump); d["tripv"] = to_np(mres.tripv); d["ret"] = to_np(mres.daily_return);
75+ d["backend"] = std::string(to_string(mres.stats.backend)); d["precision"] = std::string(to_string(mres.stats.precision)); d["wall_seconds"] = mres.stats.wall_seconds; d["device_seconds"] = mres.stats.device_seconds; d["note"] = mres.stats.note; d["warnings"] = mres.warnings;
76+ return d;
77+ }, nb::arg("returns"), nb::arg("offsets"), nb::arg("backend") = "auto", nb::arg("bpv_finite_sample") = true, nb::arg("deterministic") = true);
78+
79+ m.def("garch_fit", [](F64 r, const std::string& model, const std::string& dist, int horizon, const std::string& cov, int max_iter, double tol) {
80+ models::GarchFitOptions o; o.model = model == "gjr" ? models::GarchModel::gjr : (model == "egarch" ? models::GarchModel::egarch : models::GarchModel::garch);
81+ o.dist = dist == "student_t" || dist == "t" ? models::GarchDist::student_t : models::GarchDist::gaussian; o.forecast_horizon = horizon; o.cov = cov; o.max_iter = max_iter; o.tol = tol;
82+ std::span<const f64> rs(r.data(), r.shape(0)); json out; { nb::gil_scoped_release rel; out = models::garch_fit(rs, o).to_json(); }
83+ return json_to_py(out);
84+ }, nb::arg("returns"), nb::arg("model") = "garch", nb::arg("dist") = "gaussian", nb::arg("horizon") = 0, nb::arg("cov") = "qmle", nb::arg("max_iter") = 500, nb::arg("tol") = 1e-8);
85+
86+ m.def("validate_bars", [](I64 t, F64 o, F64 h, F64 l, F64 c, F64 v, const std::string& symbol, const std::string& interval, const std::string& session) {
87+ BarSeries b; b.symbol = symbol; b.interval = interval_from_string(interval); const usize n = t.shape(0); b.reserve(n);
88+ for (usize i = 0; i < n; ++i) b.push_back(Timestamp(t.data()[i]), o.data()[i], h.data()[i], l.data()[i], c.data()[i], v.data()[i]);
89+ json out; { nb::gil_scoped_release rel; auto st = b.normalize(); MarketCalendar cal; auto q = b.validate(cal, session == "extended" ? SessionPolicy::extended : SessionPolicy::regular); q.duplicates = st.duplicates_removed; q.unsorted = st.reordered; out = q.to_json(); }
90+ return json_to_py(out);
91+ });
92+
93+ m.def("calendar_sessions", [](const std::string& a, const std::string& b) { MarketCalendar cal; std::vector<std::string> out; for (auto d : cal.sessions(date_from_iso(a), date_from_iso(b))) out.push_back(d.iso()); return out; });
94+
95+ m.def("intraday_returns", [](I64 t, F64 c, const std::string& kind) {
96+ BarSeries b; b.interval = Interval::m1; const usize n = t.shape(0); b.reserve(n);
97+ for (usize i = 0; i < n; ++i) b.push_back(Timestamp(t.data()[i]), c.data()[i], c.data()[i], c.data()[i], c.data()[i], 0.0);
98+ data::IntradayReturns ir; { nb::gil_scoped_release rel; b.normalize(); MarketCalendar cal; auto reg = b.filter_session(cal, SessionPolicy::regular); ir = data::intraday_returns(reg, kind == "arithmetic" ? data::ReturnKind::arithmetic : data::ReturnKind::log); }
99+ nb::dict d; d["r"] = to_np(ir.r); d["offsets"] = to_np_i64(ir.session_offsets); std::vector<std::string> s; for (auto dd : ir.session_dates) s.push_back(dd.iso()); d["sessions"] = s; d["overnight"] = to_np(ir.overnight); d["close_to_close"] = to_np(ir.close_to_close); d["open_to_close"] = to_np(ir.open_to_close);
100+ return d;
101+ });
102+}
added cli/raid.cpp +120 −0
@@ -0,0 +1,120 @@
1+// raid — command-line tool: devices | validate | estimate | data | benchmark (Phase 1: devices, validate, estimate on CSV/Parquet).
2+#include <cstdio>
3+#include <cstring>
4+#include <iostream>
5+#include <fstream>
6+#include <string>
7+#include <vector>
8+#include <map>
9+#include <nlohmann/json.hpp>
10+#include "raid/core/types.hpp"
11+#include "raid/core/error.hpp"
12+#include "raid/core/manifest.hpp"
13+#include "raid/core/hash.hpp"
14+#include "raid/backend/backend.hpp"
15+#include "raid/data/io.hpp"
16+#include "raid/data/calendar.hpp"
17+#include "raid/data/returns.hpp"
18+#include "raid/estimators/realized.hpp"
19+#include "raid/estimators/ols.hpp"
20+#include "raid/models/garch.hpp"
21+#ifdef RAID_WITH_FINDATA
22+#include "raid/data/findata.hpp"
23+#endif
24+
25+using namespace raid;
26+using json = nlohmann::json;
27+
28+namespace {
29+void usage() {
30+ std::fprintf(stderr,
31+ "raid %s — Research Architecture for Intraday Data (UQO WP 40)\n"
32+ " raid devices list backends/devices (JSON)\n"
33+ " raid validate <bars.csv|.parquet> [--session regular|extended] quality report per symbol (JSON)\n"
34+ " raid estimate realized <bars> [--backend auto|cpu|metal] [--bpv-no-fs] per-session realized measures\n"
35+ " raid estimate garch <bars> [--model garch|gjr|egarch] [--dist gaussian|student_t] [--horizon H] [--returns daily|intraday]\n"
36+ " raid estimate ols <csv y,x1,x2...> [--cov classical|hc0..hc3|hac|cluster] [--lags L]\n"
37+ " raid data ... FinData access (Phase 2)\n"
38+ " raid benchmark ... benchmark harness (Phase 3+)\n", RAID_VERSION);
39+}
40+std::string arg(const std::vector<std::string>& a, const std::string& key, const std::string& def = "") { for (usize i = 0; i + 1 < a.size(); ++i) if (a[i] == key) return a[i + 1]; return def; }
41+bool flag(const std::vector<std::string>& a, const std::string& key) { for (auto& s : a) if (s == key) return true; return false; }
42+
43+std::map<std::string, BarSeries> load_bars(const std::string& path) {
44+ if (path.size() > 4 && path.substr(path.size() - 4) == ".csv") return data::read_bars_csv(path);
45+#ifdef RAID_WITH_ARROW
46+ if (path.size() > 8 && path.substr(path.size() - 8) == ".parquet") return data::read_bars_parquet(path).series;
47+ if (path.size() > 6 && path.substr(path.size() - 6) == ".arrow") return data::read_bars_arrow_ipc(path).series;
48+#endif
49+ throw RaidError(ErrorCode::io, "unsupported input format (csv"
50+#ifdef RAID_WITH_ARROW
51+ ", parquet, arrow"
52+#endif
53+ "): " + path);
54+}
55+
56+int cmd_devices() {
57+ json out = json::array(); for (auto& d : backend::list_devices()) out.push_back(d.to_json());
58+ json j = {{"devices", out}, {"host", host_info()}}; std::cout << j.dump(1) << "\n"; return 0;
59+}
60+
61+int cmd_validate(const std::vector<std::string>& a) {
62+ if (a.empty()) { usage(); return 2; }
63+ auto bars = load_bars(a[0]); MarketCalendar cal;
64+ const auto policy = arg(a, "--session", "regular") == "extended" ? SessionPolicy::extended : SessionPolicy::regular;
65+ json out = json::array();
66+ for (auto& [sym, b] : bars) { auto bb = b; auto st = bb.normalize(); auto q = bb.validate(cal, policy); q.duplicates = st.duplicates_removed; q.unsorted = st.reordered; out.push_back(q.to_json()); }
67+ std::cout << json({{"file", a[0]}, {"sha256", Sha256::of_file(a[0])}, {"reports", out}}).dump(1) << "\n"; return 0;
68+}
69+
70+int cmd_estimate(const std::vector<std::string>& a) {
71+ if (a.size() < 2) { usage(); return 2; }
72+ const std::string what = a[0], path = a[1];
73+ ExecutionPlan plan; plan.backend = backend_from_string(arg(a, "--backend", "auto"));
74+ if (what == "realized") {
75+ auto bars = load_bars(path); MarketCalendar cal; json out = json::object();
76+ est::RealizedOptions ro; ro.plan = plan; ro.bpv_finite_sample = !flag(a, "--bpv-no-fs");
77+ for (auto& [sym, b0] : bars) { auto b = b0; b.normalize(); auto reg = b.filter_session(cal, SessionPolicy::regular); auto ir = data::intraday_returns(reg); auto m = est::realized_measures(ir, ro);
78+ json rows = json::array(); for (usize k = 0; k < m.sessions.size(); ++k) rows.push_back({{"session", m.sessions[k].iso()}, {"n", m.n_returns[k]}, {"rv", m.rv[k]}, {"bpv", m.bpv[k]}, {"rsv_neg", m.rsv_neg[k]}, {"rsv_pos", m.rsv_pos[k]}, {"rq", m.rq[k]}, {"jump", m.jump[k]}, {"tripv", m.tripv[k]}, {"ret", m.daily_return[k]}});
79+ out[sym] = {{"measures", rows}, {"backend", std::string(to_string(m.stats.backend))}, {"precision", std::string(to_string(m.stats.precision))}, {"wall_seconds", m.stats.wall_seconds}, {"note", m.stats.note}, {"warnings", m.warnings}}; }
80+ std::cout << out.dump(1) << "\n"; return 0;
81+ }
82+ if (what == "garch") {
83+ auto bars = load_bars(path); MarketCalendar cal; json out = json::object();
84+ models::GarchFitOptions go; const std::string mdl = arg(a, "--model", "garch"); go.model = mdl == "gjr" ? models::GarchModel::gjr : (mdl == "egarch" ? models::GarchModel::egarch : models::GarchModel::garch);
85+ go.dist = arg(a, "--dist", "gaussian") == "student_t" ? models::GarchDist::student_t : models::GarchDist::gaussian; go.forecast_horizon = std::stoi(arg(a, "--horizon", "0")); go.plan = plan;
86+ const bool intraday = arg(a, "--returns", "daily") == "intraday";
87+ for (auto& [sym, b0] : bars) { auto b = b0; b.normalize(); auto reg = b.filter_session(cal, SessionPolicy::regular); auto ir = data::intraday_returns(reg);
88+ std::vector<f64> r; if (intraday) r = ir.r; else for (f64 v : ir.close_to_close) if (std::isfinite(v)) r.push_back(v);
89+ try { auto e = models::garch_fit(r, go); out[sym] = e.to_json(); } catch (const RaidError& ex) { out[sym] = {{"error", ex.what()}}; } }
90+ std::cout << out.dump(1) << "\n"; return 0;
91+ }
92+ if (what == "ols") {
93+ std::ifstream f(path); RAID_REQUIRE(f.good(), io, "cannot open " + path); std::string line; std::getline(f, line);
94+ std::vector<std::string> names; { usize s = 0; for (usize i = 0; i <= line.size(); ++i) if (i == line.size() || line[i] == ',') { names.push_back(line.substr(s, i - s)); s = i + 1; } }
95+ std::vector<std::vector<f64>> rows; while (std::getline(f, line)) { if (line.empty()) continue; std::vector<f64> r; usize s = 0; for (usize i = 0; i <= line.size(); ++i) if (i == line.size() || line[i] == ',') { r.push_back(std::stod(line.substr(s, i - s))); s = i + 1; } rows.push_back(r); }
96+ const usize n = rows.size(), k = names.size() - 1; Eigen::MatrixXd X(n, k + 1); Eigen::VectorXd y(n);
97+ for (usize i = 0; i < n; ++i) { y[static_cast<Eigen::Index>(i)] = rows[i][0]; X(static_cast<Eigen::Index>(i), 0) = 1.0; for (usize j = 0; j < k; ++j) X(static_cast<Eigen::Index>(i), static_cast<Eigen::Index>(j + 1)) = rows[i][j + 1]; }
98+ est::OlsOptions oo; oo.cov.type = stats::covtype_from_string(arg(a, "--cov", "classical")); oo.cov.hac_lags = std::stoi(arg(a, "--lags", "-1")); oo.names = {"const"}; for (usize j = 1; j < names.size(); ++j) oo.names.push_back(names[j]);
99+ std::cout << est::ols(X, y, oo).estimate.to_json().dump(1) << "\n"; return 0;
100+ }
101+ usage(); return 2;
102+}
103+} // namespace
104+
105+int main(int argc, char** argv) {
106+ std::vector<std::string> a(argv + 1, argv + argc);
107+ if (a.empty()) { usage(); return 2; }
108+ try {
109+ const std::string cmd = a[0]; a.erase(a.begin());
110+ if (cmd == "devices") return cmd_devices();
111+ if (cmd == "validate") return cmd_validate(a);
112+ if (cmd == "estimate") return cmd_estimate(a);
113+#ifdef RAID_WITH_FINDATA
114+ if (cmd == "data") return raid::findata::cli_main(a);
115+#endif
116+ if (cmd == "benchmark") { std::fprintf(stderr, "benchmark: use the bench_* executables (Phase 3+)\n"); return 2; }
117+ usage(); return 2;
118+ } catch (const RaidError& e) { std::fprintf(stderr, "raid error: %s\n", e.what()); return 1; }
119+ catch (const std::exception& e) { std::fprintf(stderr, "error: %s\n", e.what()); return 1; }
120+}
added cmake/EmbedMSL.cmake +9 −0
@@ -0,0 +1,9 @@
1+# Concatenates all .metal sources into one C++ translation unit exposing raid::metal::msl_bundle().
2+set(_out "namespace raid { namespace metal { const char* msl_bundle() { return R\"RAIDMSL(\n")
3+set(_sep "")
4+foreach(s ${SHADERS})
5+ file(READ ${s} _c)
6+ string(APPEND _out "// ---- ${s}\n${_c}\n")
7+endforeach()
8+string(APPEND _out ")RAIDMSL\"; } } }\n")
9+file(WRITE ${OUT} "${_out}")
added datasets/fixtures/garch_returns.csv +3001 −0
Large diff (3,001 lines) is not loaded by default to keep the page fast.Load diff
added datasets/fixtures/ols_design.csv +501 −0
Large diff (501 lines) is not loaded by default to keep the page fast.Load diff
added datasets/fixtures/reference_values.json +205 −0
Large diff (206 lines) is not loaded by default to keep the page fast.Load diff
added datasets/fixtures/synthetic_bars_1m.csv +6818 −0
Large diff (6,818 lines) is not loaded by default to keep the page fast.Load diff
added datasets/fixtures/synthetic_bars_1m.expected.json +96 −0
Large diff (97 lines) is not loaded by default to keep the page fast.Load diff
added docs/DECISIONS.md +14 −0
Large diff (14 lines) is not loaded by default to keep the page fast.Load diff
added docs/PROGRESS.md +56 −0
Large diff (56 lines) is not loaded by default to keep the page fast.Load diff
added docs/api/cpp.md +28 −0
Large diff (28 lines) is not loaded by default to keep the page fast.Load diff
added docs/architecture/overview.md +44 −0
Large diff (44 lines) is not loaded by default to keep the page fast.Load diff
added docs/audit.md +136 −0
Large diff (136 lines) is not loaded by default to keep the page fast.Load diff
added docs/capabilities.md +41 −0
Large diff (41 lines) is not loaded by default to keep the page fast.Load diff
added docs/evidence/findata_openapi_2026-11-02.json +1 −0
Large diff (2 lines) is not loaded by default to keep the page fast.Load diff
added docs/evidence/nodes/M1M32.txt +15 −0
Large diff (15 lines) is not loaded by default to keep the page fast.Load diff
added docs/evidence/nodes/M2M32.txt +2 −0
Large diff (2 lines) is not loaded by default to keep the page fast.Load diff
added docs/evidence/nodes/M2M32b.txt +2 −0
Large diff (2 lines) is not loaded by default to keep the page fast.Load diff
added docs/evidence/nodes/M2M32c.txt +15 −0
Large diff (15 lines) is not loaded by default to keep the page fast.Load diff
added docs/evidence/nodes/M2U64.txt +15 −0
Large diff (15 lines) is not loaded by default to keep the page fast.Load diff
added docs/evidence/nodes/M3BA16.txt +15 −0
Large diff (15 lines) is not loaded by default to keep the page fast.Load diff
added docs/evidence/nodes/M3BA24.txt +15 −0
Large diff (15 lines) is not loaded by default to keep the page fast.Load diff
added docs/evidence/nodes/M3U96a.txt +15 −0
Large diff (15 lines) is not loaded by default to keep the page fast.Load diff
added docs/evidence/nodes/M3U96b.txt +15 −0
Large diff (15 lines) is not loaded by default to keep the page fast.Load diff
added docs/evidence/nodes/M4BP36.txt +15 −0
Large diff (15 lines) is not loaded by default to keep the page fast.Load diff
added docs/evidence/nodes/M4BP48.txt +7 −0
Large diff (7 lines) is not loaded by default to keep the page fast.Load diff
added docs/evidence/nodes/M4M36.txt +2 −0
Large diff (2 lines) is not loaded by default to keep the page fast.Load diff
added docs/evidence/nodes/M4M64a.txt +15 −0
Large diff (15 lines) is not loaded by default to keep the page fast.Load diff
added docs/evidence/nodes/M4M64b.txt +15 −0
Large diff (15 lines) is not loaded by default to keep the page fast.Load diff
added docs/evidence/nodes/M4M64c.txt +15 −0
Large diff (15 lines) is not loaded by default to keep the page fast.Load diff
added docs/evidence/nodes/M5M36.txt +15 −0
Large diff (15 lines) is not loaded by default to keep the page fast.Load diff
added docs/evidence/nodes/m2m16.txt +2 −0
Large diff (2 lines) is not loaded by default to keep the page fast.Load diff
added docs/evidence/nodes/m2m8a.txt +15 −0
Large diff (15 lines) is not loaded by default to keep the page fast.Load diff
added docs/evidence/nodes/m2m8b.txt +15 −0
Large diff (15 lines) is not loaded by default to keep the page fast.Load diff
added docs/evidence/nodes/m4ma.txt +2 −0
Large diff (2 lines) is not loaded by default to keep the page fast.Load diff
added docs/evidence/nodes/m4mb.txt +2 −0
Large diff (2 lines) is not loaded by default to keep the page fast.Load diff
added docs/evidence/nodes/m4mc.txt +2 −0
Large diff (2 lines) is not loaded by default to keep the page fast.Load diff
added docs/evidence/nodes/m4md.txt +15 −0
Large diff (15 lines) is not loaded by default to keep the page fast.Load diff
added docs/evidence/nodes/m4ml.txt +15 −0
Large diff (15 lines) is not loaded by default to keep the page fast.Load diff
added docs/evidence/nodes/m4mm.txt +15 −0
Large diff (15 lines) is not loaded by default to keep the page fast.Load diff
added docs/evidence/nodes/m4mn.txt +15 −0
Large diff (15 lines) is not loaded by default to keep the page fast.Load diff
added docs/evidence/nodes/m5ma.txt +15 −0
Large diff (15 lines) is not loaded by default to keep the page fast.Load diff
added docs/evidence/nodes/m6ma.txt +15 −0
Large diff (15 lines) is not loaded by default to keep the page fast.Load diff
added docs/evidence/nodes/m6mb.txt +15 −0
Large diff (15 lines) is not loaded by default to keep the page fast.Load diff
added docs/literature/crossref_resolved.json +396 −0
Large diff (397 lines) is not loaded by default to keep the page fast.Load diff
added docs/methods/registry.md +40 −0
Large diff (40 lines) is not loaded by default to keep the page fast.Load diff
added docs/novelty_matrix.md +57 −0
Large diff (57 lines) is not loaded by default to keep the page fast.Load diff
added docs/risks.md +22 −0
Large diff (22 lines) is not loaded by default to keep the page fast.Load diff
added examples/cpp/minimal.cpp +23 −0
Large diff (23 lines) is not loaded by default to keep the page fast.Load diff
added examples/python/minimal.py +15 −0
Large diff (15 lines) is not loaded by default to keep the page fast.Load diff
added experiments/manifests/environment_MacBookPro-de-simon-pierre-2.json +33 −0
Large diff (34 lines) is not loaded by default to keep the page fast.Load diff
added include/raid/backend/backend.hpp +71 −0
Large diff (71 lines) is not loaded by default to keep the page fast.Load diff
added include/raid/core/error.hpp +33 −0
Large diff (33 lines) is not loaded by default to keep the page fast.Load diff
added include/raid/core/estimate.hpp +62 −0
Large diff (62 lines) is not loaded by default to keep the page fast.Load diff
added include/raid/core/hash.hpp +29 −0
Large diff (29 lines) is not loaded by default to keep the page fast.Load diff
added include/raid/core/log.hpp +28 −0
Large diff (28 lines) is not loaded by default to keep the page fast.Load diff
added include/raid/core/manifest.hpp +33 −0
Large diff (33 lines) is not loaded by default to keep the page fast.Load diff
added include/raid/core/plan.hpp +45 −0
Large diff (45 lines) is not loaded by default to keep the page fast.Load diff
added include/raid/core/rng.hpp +71 −0
Large diff (71 lines) is not loaded by default to keep the page fast.Load diff
added include/raid/core/thread_pool.hpp +40 −0
Large diff (40 lines) is not loaded by default to keep the page fast.Load diff
added include/raid/core/types.hpp +120 −0
Large diff (120 lines) is not loaded by default to keep the page fast.Load diff
added include/raid/estimators/ols.hpp +58 −0
Large diff (58 lines) is not loaded by default to keep the page fast.Load diff
added include/raid/estimators/realized.hpp +54 −0
Large diff (54 lines) is not loaded by default to keep the page fast.Load diff
added include/raid/models/garch.hpp +64 −0
Large diff (64 lines) is not loaded by default to keep the page fast.Load diff
added include/raid/statistics/covariance.hpp +32 −0
Large diff (32 lines) is not loaded by default to keep the page fast.Load diff
added include/raid/statistics/reductions.hpp +84 −0
Large diff (84 lines) is not loaded by default to keep the page fast.Load diff
added include/raid/statistics/rolling.hpp +30 −0
Large diff (30 lines) is not loaded by default to keep the page fast.Load diff
added pyproject.toml +33 −0
Large diff (33 lines) is not loaded by default to keep the page fast.Load diff
added python/raid/__init__.py +23 −0
Large diff (23 lines) is not loaded by default to keep the page fast.Load diff
added python/raid/api.py +78 −0
Large diff (78 lines) is not loaded by default to keep the page fast.Load diff
added python/tests/test_api.py +60 −0
Large diff (60 lines) is not loaded by default to keep the page fast.Load diff
added scripts/check_no_secrets.sh +9 −0
Large diff (9 lines) is not loaded by default to keep the page fast.Load diff
added scripts/make_fixtures.py +90 −0
Large diff (90 lines) is not loaded by default to keep the page fast.Load diff
added scripts/record_environment.sh +28 −0
Large diff (28 lines) is not loaded by default to keep the page fast.Load diff
added src/core/estimate.cpp +40 −0
Large diff (40 lines) is not loaded by default to keep the page fast.Load diff
added src/core/hash.cpp +66 −0
Large diff (66 lines) is not loaded by default to keep the page fast.Load diff
added src/core/log.cpp +35 −0
Large diff (35 lines) is not loaded by default to keep the page fast.Load diff
added src/core/manifest.cpp +76 −0
Large diff (76 lines) is not loaded by default to keep the page fast.Load diff
added src/core/thread_pool.cpp +68 −0
Large diff (68 lines) is not loaded by default to keep the page fast.Load diff
added src/core/types.cpp +85 −0
Large diff (85 lines) is not loaded by default to keep the page fast.Load diff
added src/estimators/ols.cpp +180 −0
Large diff (180 lines) is not loaded by default to keep the page fast.Load diff
added src/estimators/realized.cpp +57 −0
Large diff (57 lines) is not loaded by default to keep the page fast.Load diff
added src/models/garch_fit.cpp +147 −0
Large diff (147 lines) is not loaded by default to keep the page fast.Load diff
added src/models/garch_likelihood.cpp +124 −0
Large diff (124 lines) is not loaded by default to keep the page fast.Load diff
added src/statistics/covariance.cpp +75 −0
Large diff (75 lines) is not loaded by default to keep the page fast.Load diff
added src/statistics/reductions.cpp +48 −0
Large diff (48 lines) is not loaded by default to keep the page fast.Load diff
added src/statistics/rolling.cpp +111 −0
Large diff (111 lines) is not loaded by default to keep the page fast.Load diff
added tests/reference/test_reference_values.cpp +75 −0
Large diff (75 lines) is not loaded by default to keep the page fast.Load diff
added tests/unit/test_calendar.cpp +49 −0
Large diff (49 lines) is not loaded by default to keep the page fast.Load diff
added tests/unit/test_core.cpp +48 −0
Large diff (48 lines) is not loaded by default to keep the page fast.Load diff
added tests/unit/test_ols.cpp +44 −0
Large diff (44 lines) is not loaded by default to keep the page fast.Load diff
added tests/unit/test_realized_garch.cpp +54 −0
Large diff (54 lines) is not loaded by default to keep the page fast.Load diff
added tests/unit/test_stats.cpp +39 −0
Large diff (40 lines) is not loaded by default to keep the page fast.Load diff
Menu

Theme