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 −#
2 −Build
1 +# Build
3 2 build/
4 3 build-*/
5 4 cmake-build-*/
@@ -13,38 +12,27 @@ wheelhouse/
13 12 *.so
14 13 *.air
15 14 *.metallib
16 −#
17 −Python
15 +# Python
18 16 __pycache__/
19 17 *.pyc
20 18 .pytest_cache/
21 19 .hypothesis/
22 20 .venv/
23 21 .ruff_cache/
24 −#
25 −Data
26 −—
27 −FirstRate
28 −private
29 −license:
30 −never
31 −commit
32 −real
33 −bars
22 +# Data — FirstRate private license: never commit real bars
34 23 datasets/cache/
35 24 datasets/real/
36 25 data/
26 +experiments/results/raw/
37 27 *.parquet
28 +*.arrow
38 29 !datasets/fixtures/*.parquet
39 30 !datasets/synthetic/*.parquet
40 −experiments/results/raw/*.arrow
41 −#
42 −Secrets
31 +# Secrets
43 32 .env
44 33 *.key
45 34 api-key*.txt
46 −#
47 −LaTeX
35 +# LaTeX
48 36 paper/*.aux
49 37 paper/*.log
50 38 paper/*.out
@@ -58,8 +46,7 @@ paper/*.blg
58 46 paper/*.synctex.gz
59 47 paper/*.run.xml
60 48 paper/*.bcf
61 −#
62 −macOS
49 +# macOS
63 50 .DS_Store
64 51 Icon?
65 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