SPB Git

spb/wp40_uqo

Public MIT

WP40 RAID: E1–E6 results (real 2024 panel, daily 2015–2024 histories), Parquet reader projection + fast paths, Metal GARCH per-series scaling, GARCH restarts, paper skeleton (intro, literature, references)

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

14 changed files +1,293 −14

modified backends/metal/host/metal_backend.mm +10 −5
@@ -195,15 +195,20 @@ void regression_stats(std::span<const f64> x, std::span<const f64> y, std::span<
195 195 RAID_REQUIRE(p == 4 || p == 5 || p == 6, invalid_argument, "garch params per vector must be 4 (garch), 5 (gjr/egarch) or 6 (with nu)");
196 196 @autoreleasepool {
197 197 id<MTLBuffer> br = buffer(n * 4, &allocs), bb = buffer(S * 4, &allocs), be = buffer(S * 4, &allocs), bp = buffer(J * 8 * 4, &allocs), bv = buffer(S * 4, &allocs), bj = buffer(J * 16, &allocs), bo = buffer(J * 4, &allocs);
198 − float* rf = static_cast<float*>(br.contents); for (usize i = 0; i < n; ++i) rf[i] = static_cast<float>(r.values[i]);
198 + float* rf = static_cast<float*>(br.contents);
199 199 uint32_t* sb = static_cast<uint32_t*>(bb.contents); uint32_t* se = static_cast<uint32_t*>(be.contents); float* v0 = static_cast<float*>(bv.contents);
200 200 float* pf = static_cast<float*>(bp.contents); uint32_t* jb = static_cast<uint32_t*>(bj.contents);
201 201 const bool gjr = model_code == 1, eg = model_code == 2;
202 + // Per-series scaling to unit variance (FP32 conditioning): r' = r/sd, μ' = μ/sd, ω' = ω/sd² (GARCH/GJR) or ω' = ω + (1−β)·log(1/sd²) (EGARCH);
203 + // the NLL of the original data is NLL' + n·log(sd). Scales are chosen in FP64 on the host.
204 + std::vector<f64> sd(S, 1.0);
205 + for (usize s = 0; s < S; ++s) { auto x = r.series(s); f64 m = 0; for (f64 v : x) m += v; m /= std::max<usize>(1, x.size()); f64 var = 0; for (f64 v : x) var += (v - m) * (v - m); var /= std::max<usize>(1, x.size()); sd[s] = var > 0 ? std::sqrt(var) : 1.0; for (i64 i = r.offsets[s]; i < r.offsets[s + 1]; ++i) rf[i] = static_cast<float>(r.values[static_cast<usize>(i)] / sd[s]); }
202 206 for (usize s = 0; s < S; ++s) { sb[s] = static_cast<uint32_t>(r.offsets[s]); se[s] = static_cast<uint32_t>(r.offsets[s + 1]);
203 207 for (usize q = 0; q < n_params; ++q) { const f64* th = params.data() + (s * n_params + q) * p; const usize j = s * n_params + q;
204 208 // layout [mu, omega, alpha, gamma, beta, nu]; FP64 initial variance per (series, mu) computed on the CPU → we use mu of the first param set per series
205 − pf[j * 8 + 0] = static_cast<float>(th[0]); pf[j * 8 + 1] = static_cast<float>(th[1]); pf[j * 8 + 2] = static_cast<float>(th[2]);
206 − pf[j * 8 + 3] = (gjr || eg) ? static_cast<float>(th[3]) : 0.0f; pf[j * 8 + 4] = static_cast<float>(th[(gjr || eg) ? 4 : 3]);
209 + const f64 beta_s = th[(gjr || eg) ? 4 : 3];
210 + pf[j * 8 + 0] = static_cast<float>(th[0] / sd[s]); pf[j * 8 + 1] = static_cast<float>(eg ? th[1] + (1.0 - beta_s) * std::log(1.0 / (sd[s] * sd[s])) : th[1] / (sd[s] * sd[s])); pf[j * 8 + 2] = static_cast<float>(th[2]);
211 + pf[j * 8 + 3] = (gjr || eg) ? static_cast<float>(th[3]) : 0.0f; pf[j * 8 + 4] = static_cast<float>(beta_s);
207 212 const bool has_nu = p == 6 || (p == 5 && !gjr && !eg); const f64 nu = has_nu ? th[p - 1] : (nu_fixed > 2 ? nu_fixed : 0.0); pf[j * 8 + 5] = static_cast<float>(nu);
208 213 // FP64 density constants (Metal has no lgamma): kconst and E|z|
209 214 const f64 pi = 3.14159265358979323846;
@@ -211,13 +216,13 @@ void regression_stats(std::span<const f64> x, std::span<const f64> y, std::span<
211 216 else { pf[j * 8 + 6] = static_cast<float>(-0.5 * std::log(2.0 * pi)); pf[j * 8 + 7] = static_cast<float>(std::sqrt(2.0 / pi)); }
212 217 jb[j * 4 + 0] = static_cast<uint32_t>(s); jb[j * 4 + 1] = static_cast<uint32_t>(j); jb[j * 4 + 2] = 0; jb[j * 4 + 3] = 0; }
213 218 // initial variance: sample variance of (r − mu) with mu from the first parameter vector of the series (FP64)
214 − const f64 mu = params[(s * n_params) * p]; auto x = r.series(s); f64 m1 = 0; for (f64 v : x) m1 += v - mu; m1 /= static_cast<f64>(x.size()); f64 var0 = 0; for (f64 v : x) var0 += (v - mu - m1) * (v - mu - m1); var0 /= static_cast<f64>(x.size()); v0[s] = static_cast<float>(var0); }
219 + const f64 mu = params[(s * n_params) * p] / sd[s]; auto x = r.series(s); f64 m1 = 0; for (f64 v : x) m1 += v / sd[s] - mu; m1 /= static_cast<f64>(x.size()); f64 var0 = 0; for (f64 v : x) var0 += (v / sd[s] - mu - m1) * (v / sd[s] - mu - m1); var0 /= static_cast<f64>(x.size()); v0[s] = static_cast<float>(var0); }
215 220 const uint32_t model = static_cast<uint32_t>(model_code), nj = static_cast<uint32_t>(J);
216 221 id<MTLCommandBuffer> cb = [queue_ commandBuffer]; id<MTLComputeCommandEncoder> enc = [cb computeCommandEncoder]; auto pl = pipeline("garch_nll");
217 222 [enc setComputePipelineState:pl]; [enc setBuffer:br offset:0 atIndex:0]; [enc setBuffer:bb offset:0 atIndex:1]; [enc setBuffer:be offset:0 atIndex:2]; [enc setBuffer:bp offset:0 atIndex:3]; [enc setBuffer:bv offset:0 atIndex:4]; [enc setBuffer:bj offset:0 atIndex:5]; [enc setBuffer:bo offset:0 atIndex:6]; [enc setBytes:&model length:4 atIndex:7]; [enc setBytes:&nj length:4 atIndex:8];
218 223 [enc dispatchThreads:MTLSizeMake(J, 1, 1) threadsPerThreadgroup:MTLSizeMake(std::min<usize>(64, pl.maxTotalThreadsPerThreadgroup), 1, 1)]; [enc endEncoding]; [cb commit]; [cb waitUntilCompleted];
219 224 RAID_REQUIRE(cb.status == MTLCommandBufferStatusCompleted, numerical, "metal: command buffer failed");
220 − const float* o = static_cast<const float*>(bo.contents); for (usize j = 0; j < J; ++j) out[j] = o[j];
225 + const float* o = static_cast<const float*>(bo.contents); for (usize j = 0; j < J; ++j) { const usize s = j / n_params; out[j] = std::isfinite(o[j]) ? static_cast<f64>(o[j]) + static_cast<f64>(r.size(s)) * std::log(sd[s]) : o[j]; }
221 226 finish(st, "garch_nll", t, cb, n * 8 + J * p * 8, J * 8, allocs, library_source_ + " jobs=" + std::to_string(J)); release(br); release(bb); release(be); release(bp); release(bv); release(bj); release(bo);
222 227 }
223 228 return out;
modified benchmarks/end_to_end/bench_e1_batched_rv.cpp +1 −1
@@ -41,7 +41,7 @@ int main(int argc, char** argv) {
41 41 #endif
42 42 struct Case { std::string source; usize S; std::string seg; usize seg_len; std::string backend; bool fused; };
43 43 std::vector<Case> cases; const std::vector<std::pair<std::string, usize>> segs = {{"day", 1}, {"week", 5}, {"month", 22}, {"year", 252}};
44 − for (usize S : {1ul, 10ul, 100ul, 1000ul}) for (auto& [sn, nd] : segs) { if (S > syms.size()) continue; for (auto bk : {"cpu1", "cpuN", "metal", "metal_unfused"}) cases.push_back({"real", S, sn, nd, bk, std::string(bk) != "metal_unfused"}); }
44 + for (usize S0 : {1ul, 10ul, 100ul, 1000ul}) for (auto& [sn, nd] : segs) { const usize S = std::min(S0, syms.size()); if (S0 > syms.size() && S0 / 10 >= syms.size()) continue; for (auto bk : {"cpu1", "cpuN", "metal", "metal_unfused"}) cases.push_back({"real", S, sn, nd, bk, std::string(bk) != "metal_unfused"}); }
45 45 for (usize S : {10000ul}) for (auto& [sn, nd] : segs) { if (nd > 22) continue; for (auto bk : {"cpu1", "cpuN", "metal"}) cases.push_back({"synthetic", S, sn, nd, bk, true}); }
46 46 std::mt19937_64 order(20261102); std::shuffle(cases.begin(), cases.end(), order);
47 47 usize done = 0;
modified benchmarks/end_to_end/bench_e2_har.cpp +1 −1
@@ -37,7 +37,7 @@ int main(int argc, char** argv) {
37 37 rows[i] = r; } });
38 38 for (auto& r : rows) { r["kind"] = "accuracy"; r["experiment"] = "E2"; out << r.dump() << "\n"; } out.flush(); std::fprintf(stderr, "E2 accuracy h=%d done\n", h); }
39 39 // (b) runtime: per-series fits vs batched sufficient statistics
40 − for (usize S : {10ul, 100ul, 1000ul}) { if (S > syms.size()) continue; std::vector<f64> cat; std::vector<i64> off = {0}; for (usize i = 0; i < S; ++i) { cat.insert(cat.end(), rv[syms[i]].begin(), rv[syms[i]].end()); off.push_back(static_cast<i64>(cat.size())); }
40 + for (usize S0 : {10ul, 100ul, 1000ul}) { const usize S = std::min(S0, syms.size()); if (S0 > syms.size() && S0 / 10 >= syms.size()) continue; std::vector<f64> cat; std::vector<i64> off = {0}; for (usize i = 0; i < S; ++i) { cat.insert(cat.end(), rv[syms[i]].begin(), rv[syms[i]].end()); off.push_back(static_cast<i64>(cat.size())); }
41 41 est::HarSpec spec; spec.horizon = 1; spec.cov.type = stats::CovType::classical;
42 42 for (auto mode : {"per_series_cpu1", "per_series_cpuMT", "batched_cpu", "batched_metal"}) for (int rep = 0; rep < reps; ++rep) { const f64 t0 = now(); usize okc = 0; std::string backend_used = "cpu"; f64 dev = 0;
43 43 if (std::string(mode).rfind("per_series", 0) == 0) { std::vector<int> ok(S, 0); auto fitall = [&](usize a, usize b) { for (usize i = a; i < b; ++i) { try { auto e = est::har_fit(rv[syms[i]], spec); ok[i] = e.ok(); } catch (...) {} } }; if (std::string(mode) == "per_series_cpu1") fitall(0, S); else ThreadPool::global().parallel_for(S, fitall); for (int v : ok) okc += v; }
modified benchmarks/end_to_end/bench_e3_garch.cpp +7 −0
@@ -25,10 +25,17 @@ int main(int argc, char** argv) {
25 25 return 1;
26 26 #else
27 27 json e0 = json::parse(std::ifstream(argv[1])); const usize Smax = argc > 3 ? static_cast<usize>(std::atoi(argv[3])) : 1000; std::ofstream out(argv[2], std::ios::app);
28 + const std::string daily_path = argc > 4 ? argv[4] : ""; // e0_daily.json: 10-year daily bars → long training histories
28 29 ExperimentManifest man; man.experiment = "E3.garch"; man.created_at_utc = now_utc_iso(); man.raid_version = RAID_VERSION; man.host = host_info(); man.inputs["e0_universe.json"] = Sha256::of_file(argv[1]);
29 30 MarketCalendar cal; std::map<std::string, std::vector<f64>> ret, rvd;
30 31 for (auto& ex : e0["exports"]) { auto pb = data::read_bars_parquet(ex["path"].get<std::string>()); for (auto& [sym, b] : pb.series) { auto reg = b.filter_session(cal, SessionPolicy::regular); if (reg.n_sessions() < 240) continue; auto ir = data::intraday_returns(reg); std::vector<f64> r; for (usize k = 1; k < ir.close_to_close.size(); ++k) r.push_back(ir.close_to_close[k]); est::RealizedOptions ro; ro.plan.backend = Backend::cpu; auto m = est::realized_measures(ir, ro); ret[sym] = r; rvd[sym] = std::vector<f64>(m.rv.begin() + 1, m.rv.end()); } }
32 + // replace the 2024-only close-to-close returns by the 2015–2024 daily history when available (the 2024 intraday RV stays the evaluation target)
33 + usize replaced = 0; if (!daily_path.empty()) { json ed = json::parse(std::ifstream(daily_path)); man.inputs["e0_daily.json"] = Sha256::of_file(daily_path);
34 + for (auto& ex : ed["exports"]) { auto pb = data::read_bars_parquet(ex["path"].get<std::string>(), {}, Interval::d1); for (auto& [sym, b] : pb.series) { if (!ret.count(sym) || b.size() < 500) continue; std::vector<f64> r; for (usize i = 1; i < b.size(); ++i) if (b.close[i] > 0 && b.close[i - 1] > 0) r.push_back(std::log(b.close[i] / b.close[i - 1]));
35 + // keep the last `test` 2024 sessions as evaluation window: the daily history already covers 2024, so align by count: the last 40 returns of the daily series should correspond to the last 40 intraday sessions (both end 2024-12-31)
36 + ret[sym] = r; ++replaced; } } }
31 37 const auto universe = e0["samples"]["1000"].get<std::vector<std::string>>(); std::vector<std::string> syms; for (auto& s : universe) if (ret.count(s) && syms.size() < Smax) syms.push_back(s);
38 + out << json({{"kind", "load"}, {"experiment", "E3"}, {"symbols", syms.size()}, {"daily_history_replaced", replaced}}).dump() << "\n";
32 39 const usize S = syms.size(); std::fprintf(stderr, "E3: %zu symbols, ~%zu daily returns each\n", S, ret[syms[0]].size());
33 40 const usize test = 40; // last 40 sessions held out for forecast evaluation
34 41 // ---- (i) per-symbol fits, all models, CPU parallel over symbols
modified benchmarks/end_to_end/bench_e4_connectedness.cpp +2 −2
@@ -30,12 +30,12 @@ int main(int argc, char** argv) {
30 30 MarketCalendar cal; const auto universe = e0["samples"]["1000"].get<std::vector<std::string>>();
31 31 // load up to 100 symbols with full coverage; 5-minute regular-session bars; synchronized grid = the 79 bins per session
32 32 std::map<std::string, BarSeries> five; std::map<std::string, std::vector<f64>> logrv;
33 − for (auto& ex : e0["exports"]) { if (five.size() >= 100) break; auto pb = data::read_bars_parquet(ex["path"].get<std::string>()); for (auto& [sym, b] : pb.series) { if (five.size() >= 100) break; auto reg = b.filter_session(cal, SessionPolicy::regular); auto q = reg.validate(cal); if (q.n_sessions < 250 || q.coverage < 0.98) continue; five[sym] = data::aggregate(reg, Interval::m5, cal); auto ir = data::intraday_returns(reg); est::RealizedOptions ro; ro.plan.backend = Backend::cpu; auto m = est::realized_measures(ir, ro); std::vector<f64> lv; for (f64 v : m.rv) lv.push_back(std::log(std::max(v, 1e-12))); logrv[sym] = lv; } }
33 + for (auto& ex : e0["exports"]) { if (five.size() >= 100) break; auto pb = data::read_bars_parquet(ex["path"].get<std::string>()); for (auto& [sym, b] : pb.series) { if (five.size() >= 100) break; auto reg = b.filter_session(cal, SessionPolicy::regular); auto q = reg.validate(cal); if (q.n_sessions < 250 || q.coverage < 0.95) continue; five[sym] = data::aggregate(reg, Interval::m5, cal); auto ir = data::intraday_returns(reg); est::RealizedOptions ro; ro.plan.backend = Backend::cpu; auto m = est::realized_measures(ir, ro); std::vector<f64> lv; for (f64 v : m.rv) lv.push_back(std::log(std::max(v, 1e-12))); logrv[sym] = lv; } }
34 34 std::vector<std::string> syms; for (auto& s : universe) if (five.count(s)) syms.push_back(s); std::fprintf(stderr, "E4: %zu symbols with >= 250 sessions and coverage >= 0.98\n", syms.size());
35 35 // common sessions
36 36 std::set<Date> common; bool first = true; for (auto& s : syms) { std::set<Date> d(five[s].session_dates.begin(), five[s].session_dates.end()); if (first) { common = d; first = false; } else { std::set<Date> inter; for (auto x : common) if (d.count(x)) inter.insert(x); common = inter; } }
37 37 std::vector<Date> sessions(common.begin(), common.end()); std::fprintf(stderr, "E4: %zu common sessions\n", sessions.size());
38 − for (usize N : {10ul, 30ul, 100ul}) { if (N > syms.size()) continue;
38 + for (usize N0 : {10ul, 30ul, 60ul, 100ul}) { const usize N = std::min(N0, syms.size()); if (N0 > syms.size() && N0 / 2 >= syms.size()) continue;
39 39 // (a) daily realized covariance from synchronized 5-minute returns (bins matched by timestamp; a day is used only if all N have the full 79 bins)
40 40 const f64 t0 = now(); usize days_ok = 0, days_skipped = 0; f64 cond_max = 0, cond_med = 0; std::vector<f64> conds; f64 lw_shrink_mean = 0;
41 41 for (auto d : sessions) { std::vector<std::vector<f64>> R(N); bool ok = true; for (usize i = 0; i < N && ok; ++i) { auto& b = five[syms[i]]; usize k = 0; while (k < b.n_sessions() && b.session_dates[k] != d) ++k; if (k == b.n_sessions()) { ok = false; break; } auto c = b.session_closes(k); const usize expect = cal.expected_regular_bars(d, Interval::m5); if (c.size() != expect) { ok = false; break; } R[i] = data::returns(c); }
added experiments/results/e0_daily.json +1119 −0
@@ -0,0 +1,1119 @@
1 +{
2 + "dataset_id": "market_bars.stock.1d.raw",
3 + "start": "2015-01-01",
4 + "end": "2024-12-31",
5 + "exports": [
6 + {
7 + "symbols": [
8 + "FIVN",
9 + "FISV",
10 + "POET",
11 + "CRNC",
12 + "SIBN",
13 + "PIPR",
14 + "W",
15 + "VNDA",
16 + "DNN",
17 + "BAC.E",
18 + "GM",
19 + "SACH",
20 + "PKG",
21 + "RLJ.A",
22 + "SPWR",
23 + "NTR",
24 + "TSCO",
25 + "PLNT",
26 + "AXR",
27 + "TEI",
28 + "BAC.N",
29 + "YHGJ",
30 + "DMRC",
31 + "NAN",
32 + "GSIT",
33 + "EAD",
34 + "BLDR",
35 + "USB.R",
36 + "JXN.A",
37 + "ZNTL",
38 + "EFR",
39 + "IGTA",
40 + "FENC",
41 + "AVBH",
42 + "FFBW",
43 + "DRMA",
44 + "SLMT",
45 + "DTIL",
46 + "XPOF",
47 + "AZTR",
48 + "TRVG",
49 + "ORKA",
50 + "SCL",
51 + "FBNC",
52 + "DHCNI",
53 + "PCT",
54 + "SWK",
55 + "HYFT",
56 + "FCUV",
57 + "LPTX",
58 + "CAN",
59 + "ANVS",
60 + "NFJ",
61 + "ARMK",
62 + "RCI",
63 + "SUPV",
64 + "SPRC",
65 + "VCV",
66 + "LITE",
67 + "BHFAP",
68 + "ROP",
69 + "ATMCR",
70 + "WETH",
71 + "FUBO",
72 + "CLBT",
73 + "AVAH",
74 + "WWW",
75 + "WHR",
76 + "BTMD",
77 + "AHH.A",
78 + "VOR",
79 + "IBCP",
80 + "NA",
81 + "BEAT",
82 + "PEB.G",
83 + "PUK",
84 + "TRGP",
85 + "DGICA",
86 + "UCL",
87 + "NNI",
88 + "SF.D",
89 + "NUE",
90 + "UMBF",
91 + "STZ",
92 + "ARQQW",
93 + "AIR",
94 + "CHAI",
95 + "PAYX",
96 + "AKAM",
97 + "AAP",
98 + "CZNC",
99 + "LTH",
100 + "AME",
101 + "SVVC",
102 + "CXW",
103 + "MNSO",
104 + "MMLP",
105 + "SPNT",
106 + "FORL",
107 + "JKHY"
108 + ],
109 + "export_id": "475957bf-aec0-4967-aa14-cde9834a0c4c",
110 + "path": "/Users/simon-pierreboucher/Desktop/UQO/Recherche/Recherche-UQO/finance/wp40_raid/datasets/cache/export_096d2d80a5ebea518dc4ec89.parquet",
111 + "sha256": "efa0565bc66b3ca80634502a313e17906ef149b4b29ef23cd07631b491749722",
112 + "dataset_version": 7752,
113 + "rows": 190988,
114 + "bytes": 2626979,
115 + "from_cache": false
116 + },
117 + {
118 + "symbols": [
119 + "AEM",
120 + "STE",
121 + "ISPOW",
122 + "SDOT",
123 + "CRDO",
124 + "PCYO",
125 + "XOM",
126 + "KIO",
127 + "REGCO",
128 + "BLX",
129 + "OCCI",
130 + "ZAPPW",
131 + "MUSA",
132 + "RDCM",
133 + "RCKT",
134 + "FAMI",
135 + "RILYT",
136 + "IPWR",
137 + "TLSIW",
138 + "IEP",
139 + "SITC",
140 + "CC",
141 + "ORMP",
142 + "DXCM",
143 + "RPT",
144 + "GAP",
145 + "NWG",
146 + "PGP",
147 + "CRCT",
148 + "TAPM",
149 + "AFRM",
150 + "AXIA",
151 + "SHIP",
152 + "XBIO",
153 + "ILAG",
154 + "GBLI",
155 + "FSLR",
156 + "BGM",
157 + "TRUP",
158 + "ADMA",
159 + "OSIS",
160 + "AMOD",
161 + "ISPC",
162 + "CTOS",
163 + "HIMS",
164 + "UONE",
165 + "LAC",
166 + "PK",
167 + "ABR.F",
168 + "BRAG",
169 + "SHFSW",
170 + "HPP.C",
171 + "CHNR",
172 + "SHPH",
173 + "ENTX",
174 + "MFA.B",
175 + "NPCT",
176 + "FINS",
177 + "CZWI",
178 + "CG",
179 + "AU",
180 + "IKT",
181 + "CERT",
182 + "NPO",
183 + "RWAY",
184 + "BBVA",
185 + "TARA",
186 + "DUK.A",
187 + "CRTDW",
188 + "INVZW",
189 + "MS.K",
190 + "CVM",
191 + "WCFB",
192 + "ATOM",
193 + "NOVT",
194 + "SMIT",
195 + "LBTYB",
196 + "ACR",
197 + "EOCN",
198 + "BOC",
199 + "BGX",
200 + "MRT",
201 + "WKC",
202 + "SUAC",
203 + "TV",
204 + "GHG",
205 + "FRPH",
206 + "EVT",
207 + "ASTL",
208 + "MU",
209 + "PTA",
210 + "LPCN",
211 + "INSM",
212 + "PEB.E",
213 + "ABLV",
214 + "ARCT",
215 + "TVC",
216 + "IDXG",
217 + "GSBD",
218 + "TSLX"
219 + ],
220 + "export_id": "bc9dd606-b0a9-48ea-a98a-71440e70e44b",
221 + "path": "/Users/simon-pierreboucher/Desktop/UQO/Recherche/Recherche-UQO/finance/wp40_raid/datasets/cache/export_79c1cdbb68bbe4e14c81b519.parquet",
222 + "sha256": "0a49cbf11c0198663e6aad04387ac5827406b21dde21bc2a24aa505b501ba17e",
223 + "dataset_version": 7752,
224 + "rows": 174888,
225 + "bytes": 2405186,
226 + "from_cache": false
227 + },
228 + {
229 + "symbols": [
230 + "NKE",
231 + "COST",
232 + "ACA",
233 + "NTAP",
234 + "PEBO",
235 + "AEO",
236 + "HWBK",
237 + "GIC",
238 + "BAC.M",
239 + "ECBK",
240 + "RDNT",
241 + "SPCB",
242 + "MDWD",
243 + "ANGHW",
244 + "EPD",
245 + "NEA",
246 + "VRTX",
247 + "BIIB",
248 + "TDY",
249 + "JOB",
250 + "FWRG",
251 + "CPHI",
252 + "DUO",
253 + "SMP",
254 + "EYE",
255 + "BCPC",
256 + "CIM.A",
257 + "CNP",
258 + "BCDS",
259 + "VGASW",
260 + "XPO",
261 + "UAN",
262 + "CWD",
263 + "DRVN",
264 + "HLSQ",
265 + "ATRA",
266 + "ZIVOW",
267 + "LCFYW",
268 + "SMFL",
269 + "LDTCW",
270 + "CCOI",
271 + "NIVF",
272 + "BP",
273 + "SCE.M",
274 + "BUSE",
275 + "TCCO",
276 + "RGCO",
277 + "MIDD",
278 + "RDW",
279 + "CMT",
280 + "ENX",
281 + "UBS",
282 + "OSPN",
283 + "KGC",
284 + "LKFT",
285 + "STM",
286 + "AOGO",
287 + "HTD",
288 + "GOOS",
289 + "VRMEW",
290 + "BBIO",
291 + "HRZN",
292 + "PSA.K",
293 + "NXGL",
294 + "SCYX",
295 + "KMI",
296 + "NXP",
297 + "MGM",
298 + "HYFM",
299 + "UOKA",
300 + "FBYDW",
301 + "GUT",
302 + "FSTR",
303 + "SAJ",
304 + "MCI",
305 + "DGICB",
306 + "CMP",
307 + "AAPL",
308 + "OFED",
309 + "BBDC",
310 + "WTBA",
311 + "USGO",
312 + "MLKN",
313 + "TRU",
314 + "ARCC",
315 + "TLF",
316 + "GFF",
317 + "RSKD",
318 + "VEL",
319 + "TCRX",
320 + "ALVO",
321 + "GLLIW",
322 + "OSS",
323 + "RMBI",
324 + "RZC",
325 + "KNTK",
326 + "CMRA",
327 + "PAHC",
328 + "TACT",
329 + "QVCD"
330 + ],
331 + "export_id": "c249857c-3d56-472c-b387-c344d59d691e",
332 + "path": "/Users/simon-pierreboucher/Desktop/UQO/Recherche/Recherche-UQO/finance/wp40_raid/datasets/cache/export_e9c18d86bd0a5e2c1402c2b6.parquet",
333 + "sha256": "23306fe3afaebe861ef7e282e3ce183540b1de0941e588de8ae058f15b98d1ad",
334 + "dataset_version": 7752,
335 + "rows": 178089,
336 + "bytes": 2555305,
337 + "from_cache": false
338 + },
339 + {
340 + "symbols": [
341 + "PMCB",
342 + "REAX",
343 + "FFC",
344 + "AIXI",
345 + "FGB",
346 + "HYI",
347 + "SHAK",
348 + "WTMA",
349 + "DBI",
350 + "CLVRW",
351 + "ZION",
352 + "GIB",
353 + "DCOY",
354 + "RCON",
355 + "CANG",
356 + "COFS",
357 + "KNF",
358 + "ZWS",
359 + "DRS",
360 + "CNSY",
361 + "ETJ",
362 + "GEN",
363 + "UCTT",
364 + "ADSE",
365 + "SOWG",
366 + "SLDPW",
367 + "SLVM",
368 + "PHD",
369 + "CCEP",
370 + "KULR",
371 + "TMP",
372 + "TAC",
373 + "PNI",
374 + "MHLA",
375 + "REI",
376 + "TSBK",
377 + "NVNO",
378 + "DLR.L",
379 + "STNE",
380 + "INFU",
381 + "MBIN",
382 + "AGL",
383 + "RITM",
384 + "MAT",
385 + "OGCP",
386 + "BOWN",
387 + "ZYME",
388 + "BEN",
389 + "KMT",
390 + "AEG",
391 + "FMBH",
392 + "ZDGE",
393 + "GEL",
394 + "BIRK",
395 + "EXFY",
396 + "VSSYW",
397 + "MSAIW",
398 + "VKQ",
399 + "FLYX",
400 + "XELB",
401 + "DOUG",
402 + "SOHON",
403 + "INDB",
404 + "INVE",
405 + "GJT",
406 + "GTX",
407 + "CASH",
408 + "ARBK",
409 + "BEPC",
410 + "CALA",
411 + "OBE",
412 + "LEU",
413 + "ASA",
414 + "WINA",
415 + "HLIO",
416 + "SHFS",
417 + "MOH",
418 + "PGYWW",
419 + "NGL",
420 + "BKN",
421 + "BBAR",
422 + "RDNW",
423 + "YCBD.A",
424 + "MO",
425 + "BGT",
426 + "VUZI",
427 + "FBIOP",
428 + "COLD",
429 + "BOKF",
430 + "EFT",
431 + "NBSTU",
432 + "YETI",
433 + "MCOMW",
434 + "VLY",
435 + "DBRG.J",
436 + "KUKE",
437 + "GTN.A",
438 + "LIQT",
439 + "RDIB",
440 + "BF.B"
441 + ],
442 + "export_id": "7b98e722-d8e3-49f8-817d-8d9a5e5d3b15",
443 + "path": "/Users/simon-pierreboucher/Desktop/UQO/Recherche/Recherche-UQO/finance/wp40_raid/datasets/cache/export_c025209b1375c8792c71884f.parquet",
444 + "sha256": "4101b621a67b733d9c83ce5da4dfa48ea32a04ceb2cc3ae21c71e98952258981",
445 + "dataset_version": 7752,
446 + "rows": 180100,
447 + "bytes": 2393835,
448 + "from_cache": false
449 + },
450 + {
451 + "symbols": [
452 + "CMC",
453 + "OGN",
454 + "MCB",
455 + "TSQ",
456 + "AEMD",
457 + "NEOVW",
458 + "KLRS",
459 + "CIG",
460 + "GECC",
461 + "DLNG.A",
462 + "IMPP",
463 + "MHK",
464 + "BIDU",
465 + "CBSH",
466 + "MTRN",
467 + "BKU",
468 + "CELUW",
469 + "OLB",
470 + "YOTAR",
471 + "NOA",
472 + "IKNA",
473 + "U",
474 + "MLEC",
475 + "IOR",
476 + "TNET",
477 + "RTB",
478 + "GRI",
479 + "MLM",
480 + "SEI",
481 + "MITI",
482 + "HPE",
483 + "KRO",
484 + "OESX",
485 + "PETS",
486 + "NEWTZ",
487 + "GNW",
488 + "HSCSW",
489 + "SNDR",
490 + "LWLG",
491 + "RLJ",
492 + "SBAC",
493 + "ZIM",
494 + "CETXP",
495 + "PPTA",
496 + "ALL.B",
497 + "MESO",
498 + "MDBH",
499 + "CWST",
500 + "JWEL",
501 + "RRX",
502 + "CRTO",
503 + "PCG.I",
504 + "PSQH",
505 + "SOJC",
506 + "CHCO",
507 + "FUTU",
508 + "PFBC",
509 + "IH",
510 + "SEVN",
511 + "BHFAL",
512 + "IESC",
513 + "INDP",
514 + "HAIN",
515 + "NBBK",
516 + "INTT",
517 + "GRBK",
518 + "STC",
519 + "BALL",
520 + "XEL",
521 + "NKX",
522 + "MBNKP",
523 + "DIT",
524 + "CERS",
525 + "GTEC",
526 + "BBWI",
527 + "ENLV",
528 + "RL",
529 + "MPAA",
530 + "NEUP",
531 + "UMH.D",
532 + "RS",
533 + "CARS",
534 + "FENG",
535 + "CCM",
536 + "MSEX",
537 + "SYTAW",
538 + "SGLY",
539 + "DWSN",
540 + "LESL",
541 + "JMIA",
542 + "DKNG",
543 + "HOLOW",
544 + "FNUC",
545 + "OMC",
546 + "OCEAW",
547 + "OR",
548 + "GEGGL",
549 + "GGR",
550 + "TCRT",
551 + "FTAIN"
552 + ],
553 + "export_id": "b789b7ea-254e-405f-9995-22df39b3c62a",
554 + "path": "/Users/simon-pierreboucher/Desktop/UQO/Recherche/Recherche-UQO/finance/wp40_raid/datasets/cache/export_e4288d2c786bd273d9f0bd5a.parquet",
555 + "sha256": "b0c53b01bc310e53bb70058c4e85aca1750419e777402cb18b1ec774da9a2f64",
556 + "dataset_version": 7752,
557 + "rows": 178145,
558 + "bytes": 2538589,
559 + "from_cache": false
560 + },
561 + {
562 + "symbols": [
563 + "SAN",
564 + "LRN",
565 + "FTII",
566 + "ABSI",
567 + "SF",
568 + "AVO",
569 + "MKC.V",
570 + "MCAGR",
571 + "TC",
572 + "CATY",
573 + "IGTAR",
574 + "PDYN",
575 + "BFLY",
576 + "XNET",
577 + "FGI",
578 + "PPSI",
579 + "TILE",
580 + "MXF",
581 + "OXLCN",
582 + "NTWK",
583 + "HR",
584 + "SONY",
585 + "AURE",
586 + "RIG",
587 + "PACB",
588 + "BGC",
589 + "MIRA",
590 + "NRIM",
591 + "DMB",
592 + "OPY",
593 + "MIO",
594 + "E",
595 + "IMTE",
596 + "FOSL",
597 + "VIAV",
598 + "DB",
599 + "ARWR",
600 + "JILL",
601 + "LPL",
602 + "BURL",
603 + "STRO",
604 + "ATGL",
605 + "BML.L",
606 + "GNFT",
607 + "VOD",
608 + "HYT",
609 + "MHH",
610 + "KMDA",
611 + "CTNT",
612 + "KEY.L",
613 + "RILYL",
614 + "NEE.R",
615 + "CNF",
616 + "LNSR",
617 + "PNBK",
618 + "ENB",
619 + "OVLY",
620 + "ALMU",
621 + "HTH",
622 + "ATEC",
623 + "APPN",
624 + "MAN",
625 + "AMBP",
626 + "CRIS",
627 + "ELVA",
628 + "FPI",
629 + "ORC",
630 + "GWW",
631 + "RF.E",
632 + "SFST",
633 + "TAK",
634 + "COO",
635 + "USCB",
636 + "WAL",
637 + "WTFCP",
638 + "HLGN",
639 + "HCVIW",
640 + "EXK",
641 + "OPCH",
642 + "BMBL",
643 + "GECCO",
644 + "SOND",
645 + "HPS",
646 + "MXE",
647 + "HPK",
648 + "IDT",
649 + "NRC",
650 + "DDS",
651 + "GDV.H",
652 + "QUAD",
653 + "MITT.B",
654 + "GAM",
655 + "USB.Q",
656 + "TAYD",
657 + "TCBX",
658 + "ARES",
659 + "DTSS",
660 + "FGN",
661 + "LNAI",
662 + "BZUN"
663 + ],
664 + "export_id": "7180ac6e-1c1d-4b6a-9c52-b296a20afa29",
665 + "path": "/Users/simon-pierreboucher/Desktop/UQO/Recherche/Recherche-UQO/finance/wp40_raid/datasets/cache/export_3b2acab37d0a377a62f2564b.parquet",
666 + "sha256": "b1398165920a3f76c84d27305f5196aa82aceba13db393a26c072aca689cde0f",
667 + "dataset_version": 7752,
668 + "rows": 181007,
669 + "bytes": 2465058,
670 + "from_cache": false
671 + },
672 + {
673 + "symbols": [
674 + "BFS.D",
675 + "SMRT",
676 + "ARE",
677 + "BNY",
678 + "TPR",
679 + "BFS.E",
680 + "NXDT",
681 + "PBYI",
682 + "GLAD",
683 + "NERV",
684 + "CHSCM",
685 + "ACN",
686 + "OUT",
687 + "NLY.I",
688 + "MDB",
689 + "CLST",
690 + "ACT",
691 + "PRKS",
692 + "ERAS",
693 + "RDVT",
694 + "WFCF",
695 + "QFIN",
696 + "NXPLW",
697 + "OMH",
698 + "BVFL",
699 + "UVSP",
700 + "LCID",
701 + "NOV",
702 + "RLAY",
703 + "BNTC",
704 + "ALLY",
705 + "DSL",
706 + "OABIW",
707 + "ETW",
708 + "KMPR",
709 + "SUZ",
710 + "FFBC",
711 + "ETN",
712 + "PW.A",
713 + "NVNI",
714 + "TDOC",
715 + "NAMSW",
716 + "STWD",
717 + "ACNB",
718 + "FNWB",
719 + "BAOS",
720 + "BPOP",
721 + "DNTH",
722 + "FAX",
723 + "NWN",
724 + "UPXI",
725 + "BTCS",
726 + "LMNR",
727 + "HOOD",
728 + "RMCO",
729 + "GBX",
730 + "DVLT",
731 + "XTLB",
732 + "EME",
733 + "KUST",
734 + "KITTW",
735 + "NSA.A",
736 + "RCUS",
737 + "HBANM",
738 + "JCE",
739 + "LDTC",
740 + "UL",
741 + "AMRN",
742 + "ELF",
743 + "BL",
744 + "MUE",
745 + "UP",
746 + "HASI",
747 + "BEKE",
748 + "BTAI",
749 + "NESR",
750 + "PJT",
751 + "HTCR",
752 + "CCSI",
753 + "ABTS",
754 + "SKYW",
755 + "FTIIW",
756 + "EPR.G",
757 + "IQI",
758 + "MGA",
759 + "HIND",
760 + "CRVS",
761 + "OZ",
762 + "ALNY",
763 + "MRM",
764 + "INTEW",
765 + "TXG",
766 + "NE",
767 + "AORT",
768 + "KARO",
769 + "MI",
770 + "TARS",
771 + "BFS",
772 + "NOMD",
773 + "BMA"
774 + ],
775 + "export_id": "2784e2bb-27a0-4584-b512-aa1aff23bd46",
776 + "path": "/Users/simon-pierreboucher/Desktop/UQO/Recherche/Recherche-UQO/finance/wp40_raid/datasets/cache/export_6ec045cd7171c7856bdb3cec.parquet",
777 + "sha256": "ba98fd11aa47f7088668e3963040cf37e8b73284436ede0f9b4cfcc3ad868aa6",
778 + "dataset_version": 7752,
779 + "rows": 178845,
780 + "bytes": 2488194,
781 + "from_cache": false
782 + },
783 + {
784 + "symbols": [
785 + "NYMTL",
786 + "IHG",
787 + "CPOP",
788 + "CGTX",
789 + "OLLI",
790 + "CIB",
791 + "ABCL",
792 + "LHX",
793 + "BBUC",
794 + "ACRS",
795 + "AISP",
796 + "COHN",
797 + "HOMB",
798 + "FDBC",
799 + "ELA",
800 + "OCUL",
801 + "BAP",
802 + "CNX",
803 + "NET",
804 + "WBS.F",
805 + "TXN",
806 + "DYNT",
807 + "AMPL",
808 + "BCV.A",
809 + "CHRW",
810 + "GDL",
811 + "FORM",
812 + "IVCAW",
813 + "LBRT",
814 + "BJRI",
815 + "CLM",
816 + "BYSI",
817 + "FITB",
818 + "BXMT",
819 + "BROG",
820 + "BFRG",
821 + "OCFC",
822 + "AEAE",
823 + "THQ",
824 + "INTE",
825 + "VMD",
826 + "SVV",
827 + "ATLC",
828 + "KVUE",
829 + "RH",
830 + "GGT",
831 + "UPST",
832 + "CDTTW",
833 + "ANIK",
834 + "PYT",
835 + "THO",
836 + "CDR.B",
837 + "OI",
838 + "MCS",
839 + "HBANL",
840 + "DRD",
841 + "WPM",
842 + "AWF",
843 + "MQT",
844 + "MARA",
845 + "FOXA",
846 + "GIS",
847 + "DCOM",
848 + "KXIN",
849 + "LTSH",
850 + "ULCC",
851 + "NOAH",
852 + "MDAIW",
853 + "IIPR",
854 + "NNDM",
855 + "HUM",
856 + "FLNT",
857 + "FAF",
858 + "GAB.K",
859 + "NVCR",
860 + "ATO",
861 + "TPC",
862 + "GALT",
863 + "TDS",
864 + "OSBC",
865 + "MYO",
866 + "NVGS",
867 + "DAR",
868 + "VGAS",
869 + "XMAX",
870 + "CASY",
871 + "CHMI.A",
872 + "CLDT.A",
873 + "HTHT",
874 + "ALDX",
875 + "OCCIO",
876 + "CNXN",
877 + "SDHY",
878 + "SLE",
879 + "SNAP",
880 + "TISI",
881 + "LUCD",
882 + "RVPHW",
883 + "MAQC",
884 + "PLGO"
885 + ],
886 + "export_id": "5670c551-2828-4bf0-9907-4e1450a9b807",
887 + "path": "/Users/simon-pierreboucher/Desktop/UQO/Recherche/Recherche-UQO/finance/wp40_raid/datasets/cache/export_8910c25d02924c31abc29226.parquet",
888 + "sha256": "9829f79d1c75aaabb18d0b5b2cd22cb432096e48296286065f6772fbada5f592",
889 + "dataset_version": 7752,
890 + "rows": 185071,
891 + "bytes": 2546490,
892 + "from_cache": false
893 + },
894 + {
895 + "symbols": [
896 + "TMH",
897 + "MWA",
898 + "TENB",
899 + "CLAR",
900 + "URI",
901 + "AKO.B",
902 + "CAH",
903 + "BHVN",
904 + "AZZ",
905 + "YCBD",
906 + "SACH.A",
907 + "ADPT",
908 + "DX",
909 + "CIFRW",
910 + "DLNG",
911 + "NXTC",
912 + "CVLT",
913 + "EMPD",
914 + "PRFX",
915 + "BLKB",
916 + "CACC",
917 + "LSBK",
918 + "MOVE",
919 + "SR.A",
920 + "CRVL",
921 + "CGBDL",
922 + "HBT",
923 + "MAYS",
924 + "NVAC",
925 + "BNED",
926 + "VPG",
927 + "RILYN",
928 + "ALL.H",
929 + "CZR",
930 + "ACGL",
931 + "NCNA",
932 + "ONBPP",
933 + "OBK",
934 + "FHI",
935 + "ZCARW",
936 + "HOUR",
937 + "EQH.C",
938 + "TEN.F",
939 + "AXTA",
940 + "UNP",
941 + "DARE",
942 + "NVRI",
943 + "FDX",
944 + "NVVEW",
945 + "NYXH",
946 + "SWKHL",
947 + "NMS",
948 + "LXU",
949 + "PALI",
950 + "MTZ",
951 + "TITN",
952 + "NEXT",
953 + "SAP",
954 + "BON",
955 + "SHO.I",
956 + "PRCT",
957 + "RBLX",
958 + "YSG",
959 + "MODD",
960 + "TEL",
961 + "PFXNZ",
962 + "BUR",
963 + "NXE",
964 + "ECC",
965 + "AFRI",
966 + "RCKTW",
967 + "ARAY",
968 + "FRHC",
969 + "STKS",
970 + "SRCE",
971 + "STEW",
972 + "MYFW",
973 + "CNQ",
974 + "HAL",
975 + "SIMO",
976 + "KRUS",
977 + "ABG",
978 + "APT",
979 + "MHNC",
980 + "PTIXW",
981 + "CLPR",
982 + "SLN",
983 + "HROWL",
984 + "AMSF",
985 + "SNWV",
986 + "SCHW.J",
987 + "MYRG",
988 + "BBSI",
989 + "VABK",
990 + "NBTB",
991 + "GRFS",
992 + "LINK",
993 + "PSFE",
994 + "AVNW",
995 + "LFT"
996 + ],
997 + "export_id": "913cab39-96b7-45f2-97df-90a938302456",
998 + "path": "/Users/simon-pierreboucher/Desktop/UQO/Recherche/Recherche-UQO/finance/wp40_raid/datasets/cache/export_342ea3f92472ecf2950bd217.parquet",
999 + "sha256": "65357325da94c251fddbaf20926ca965550408b9ce8d43edbb8149efb9993fc5",
1000 + "dataset_version": 7752,
1001 + "rows": 173195,
1002 + "bytes": 2503747,
1003 + "from_cache": false
1004 + },
1005 + {
1006 + "symbols": [
1007 + "DKL",
1008 + "NXRT",
1009 + "SXI",
1010 + "TUYA",
1011 + "ALNT",
1012 + "AWP",
1013 + "RACE",
1014 + "PDFS",
1015 + "BCDA",
1016 + "EOD",
1017 + "PWR",
1018 + "YAYO",
1019 + "KLTR",
1020 + "AGCO",
1021 + "AA",
1022 + "SAR",
1023 + "GNE",
1024 + "PAA",
1025 + "PVH",
1026 + "JAKK",
1027 + "XENE",
1028 + "BGR",
1029 + "SQNS",
1030 + "WAVE",
1031 + "PKX",
1032 + "VNO.L",
1033 + "DFLI",
1034 + "LTRN",
1035 + "BYND",
1036 + "PCSA",
1037 + "DDD",
1038 + "LCII",
1039 + "FTF",
1040 + "AGYS",
1041 + "SBC",
1042 + "GEVO",
1043 + "GLP",
1044 + "TTWO",
1045 + "JOBY",
1046 + "USLM",
1047 + "MCHB",
1048 + "MD",
1049 + "KODK",
1050 + "TSSI",
1051 + "VHC",
1052 + "LBTYK",
1053 + "IDCC",
1054 + "FMAO",
1055 + "UTG",
1056 + "GLBS",
1057 + "CMSC",
1058 + "FOR",
1059 + "AGNT",
1060 + "HEI",
1061 + "DHT",
1062 + "TLIS",
1063 + "ECVT",
1064 + "AEF",
1065 + "NFE",
1066 + "DCBO",
1067 + "LEGH",
1068 + "USAR",
1069 + "EVG",
1070 + "CRI",
1071 + "XNCR",
1072 + "BIP.A",
1073 + "DRH",
1074 + "DC",
1075 + "GL",
1076 + "NTLA",
1077 + "OPTT",
1078 + "OPP",
1079 + "CHGG",
1080 + "CETX",
1081 + "TRNS",
1082 + "BIO",
1083 + "PCTTW",
1084 + "LIND",
1085 + "GFR",
1086 + "AMPG",
1087 + "TTD",
1088 + "SSL",
1089 + "BML.J",
1090 + "PKE",
1091 + "PROV",
1092 + "UGP",
1093 + "CPHC",
1094 + "GOLF",
1095 + "DAVEW",
1096 + "API",
1097 + "GITS",
1098 + "NXJ",
1099 + "BKR",
1100 + "VCIG",
1101 + "MANH",
1102 + "NTGR",
1103 + "BLE",
1104 + "EP",
1105 + "LYB",
1106 + "USEA"
1107 + ],
1108 + "export_id": "88bf9220-5e26-4aa5-b8f1-8292d461d16f",
1109 + "path": "/Users/simon-pierreboucher/Desktop/UQO/Recherche/Recherche-UQO/finance/wp40_raid/datasets/cache/export_4706fcd6e37b93502029954b.parquet",
1110 + "sha256": "083ddb210287dbfd3170abdc606878def1a3b28d596981ebc2e308ed8d3c5bb9",
1111 + "dataset_version": 7752,
1112 + "rows": 200888,
1113 + "bytes": 2711030,
1114 + "from_cache": false
1115 + }
1116 + ],
1117 + "total_rows": 1821216,
1118 + "seconds": 32.5891010761261
1119 +}
\ No newline at end of file
added experiments/scripts/e0b_daily_exports.py +13 −0
@@ -0,0 +1,13 @@
1 +#!/usr/bin/env python3
2 +"""E0b: daily bars (market_bars.stock.1d.raw) 2015-01-01…2024-12-31 for the E0 universe (≤ 10 years per request) →
3 +longer daily return histories for E3 (GARCH). Output experiments/results/e0_daily.json."""
4 +import os, sys, json, time
5 +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "python"))
6 +import raid
7 +e0 = json.load(open(os.path.join(os.path.dirname(__file__), "..", "results", "e0_universe.json"))); syms = e0["samples"]["1000"]
8 +exports = []; t0 = time.time()
9 +for i in range(0, len(syms), 100):
10 + chunk = syms[i:i + 100]; e = raid.findata.export(chunk, "2015-01-01", "2024-12-31", dataset_id="market_bars.stock.1d.raw")
11 + exports.append({"symbols": chunk, **{k: e[k] for k in ("export_id", "path", "sha256", "dataset_version", "rows", "bytes", "from_cache")}}); print(f"daily export {i // 100 + 1}/10: {e['rows']:,} rows {e['bytes'] / 1e6:.1f} MB", flush=True)
12 +json.dump({"dataset_id": "market_bars.stock.1d.raw", "start": "2015-01-01", "end": "2024-12-31", "exports": exports, "total_rows": sum(x["rows"] for x in exports), "seconds": time.time() - t0}, open(os.path.join(os.path.dirname(__file__), "..", "results", "e0_daily.json"), "w"), indent=1)
13 +print("done", sum(x["rows"] for x in exports), "rows", round(time.time() - t0, 1), "s")
modified experiments/scripts/e6_baselines.py +2 −2
@@ -23,7 +23,7 @@ for ex in e0["exports"][:nf]:
23 23 out.write(json.dumps({"experiment": "E6", "engine": "pandas", "file": path, "rep": rep, "bars": int(len(df)), "sessions": int(len(res)), "read_seconds": t1 - t0, "filter_seconds": t2 - t1, "returns_seconds": t3 - t2, "estimate_seconds": t4 - t3, "total_seconds": t4 - t0, "peak_rss": rss(), "versions": versions}) + "\n")
24 24 # ---- polars
25 25 t0 = time.perf_counter(); lf = pl.scan_parquet(path).select(["symbol_as_reported", "timestamp_utc", "close"]); dfp = lf.collect(); t1 = time.perf_counter()
26 − dfp = dfp.with_columns(et=pl.col("timestamp_utc").dt.convert_time_zone("America/New_York")).with_columns(session=pl.col("et").dt.date(), mins=pl.col("et").dt.hour() * 60 + pl.col("et").dt.minute(), md=pl.col("et").dt.strftime("%m-%d"))
26 + dfp = dfp.with_columns(et=pl.col("timestamp_utc").dt.convert_time_zone("America/New_York")).with_columns(session=pl.col("et").dt.date(), mins=pl.col("et").dt.hour().cast(pl.Int32) * 60 + pl.col("et").dt.minute().cast(pl.Int32), md=pl.col("et").dt.strftime("%m-%d"))
27 27 dfp = dfp.with_columns(close_min=pl.when(pl.col("md").is_in(["11-29", "07-03", "12-24"])).then(13 * 60).otherwise(16 * 60)).filter((pl.col("mins") >= 570) & (pl.col("mins") <= pl.col("close_min"))).sort(["symbol_as_reported", "timestamp_utc"]); t2 = time.perf_counter()
28 28 dfp = dfp.with_columns(lr=pl.col("close").log().diff().over(["symbol_as_reported", "session"])).drop_nulls("lr"); t3 = time.perf_counter()
29 29 resp = dfp.group_by(["symbol_as_reported", "session"]).agg([(pl.col("lr") ** 2).sum().alias("rv"), (pl.col("lr").abs() * pl.col("lr").abs().shift(1)).sum().alias("adj"), pl.len().alias("M"), (pl.col("lr") ** 4).sum().alias("s4"), pl.when(pl.col("lr") < 0).then(pl.col("lr") ** 2).otherwise(0.0).sum().alias("rsv_neg")]); t4 = time.perf_counter()
@@ -35,6 +35,6 @@ for ex in e0["exports"][:nf]:
35 35 f AS (SELECT * FROM r WHERE mins >= 570 AND mins <= CASE WHEN md IN ('11-29','07-03','12-24') THEN 780 ELSE 960 END),
36 36 l AS (SELECT s, session, ln(close) - lag(ln(close)) OVER (PARTITION BY s, session ORDER BY et) lr FROM f)
37 37 SELECT s, session, sum(lr*lr) rv, count(*) M, sum(lr*lr*lr*lr) s4, sum(CASE WHEN lr<0 THEN lr*lr ELSE 0 END) rsv_neg FROM l WHERE lr IS NOT NULL GROUP BY s, session"""
38 − resd = con.execute(q).fetch_arrow_table(); t4 = time.perf_counter(); con.close()
38 + resd = con.sql(q).to_arrow_table(); t4 = time.perf_counter(); con.close()
39 39 out.write(json.dumps({"experiment": "E6", "engine": "duckdb", "file": path, "rep": rep, "sessions": int(resd.num_rows), "total_seconds": t4 - t0, "peak_rss": rss(), "versions": versions}) + "\n")
40 40 out.flush(); print(f"{os.path.basename(path)} rep {rep}: pandas {res.shape[0]} sessions, polars {resp.height}, duckdb {resd.num_rows}", flush=True)
added experiments/scripts/run_e1_e2_e6b.sh +8 −0
@@ -0,0 +1,8 @@
1 +#!/bin/bash
2 +set -uo pipefail; cd "$(dirname "$0")/../.."; R=experiments/results/raw; E0=experiments/results/e0_universe.json
3 +export FINDATA_API_KEY_FILE=$HOME/Desktop/Cluster/secrets/findata/api-key-claude-code.txt RAID_CACHE_DIR=$PWD/datasets/cache
4 +rm -f $R/e6_baselines.jsonl $R/e1_batched_rv.jsonl $R/e2_har.jsonl
5 +echo "== E6 baselines $(date)"; .venv/bin/python experiments/scripts/e6_baselines.py $E0 $R/e6_baselines.jsonl 4 3 > $R/e6_baselines.log 2>&1; echo "E6b exit=$?"
6 +echo "== E1 $(date)"; ./build/macos-release/bench_bench_e1_batched_rv $E0 $R/e1_batched_rv.jsonl 5 2> $R/e1.log; echo "E1 exit=$?"
7 +echo "== E2 $(date)"; ./build/macos-release/bench_bench_e2_har $E0 $R/e2_har.jsonl 5 2> $R/e2.log; echo "E2 exit=$?"
8 +echo "== done $(date)"
modified include/raid/models/garch.hpp +1 −0
@@ -50,6 +50,7 @@ struct GarchFitOptions {
50 50 std::string cov{"qmle"}; // "qmle" (Bollerslev–Wooldridge sandwich) | "hessian" | "opg"
51 51 int forecast_horizon{0}; // h ≥ 1 → multi-step variance forecasts in extra["forecast_variance"]
52 52 bool scale_returns{true}; // internally scale returns to unit variance (as arch does, 'rescale')
53 + bool retry_starts{true}; // on non-convergence, retry from up to two alternative starting points (documented)
53 54 ExecutionPlan plan{};
54 55 };
55 56
added paper/main.tex +90 −0
@@ -0,0 +1,90 @@
1 +% ============================================================================
2 +% UQO Working Paper No. 40 — RAID: A Metal-Accelerated C++ Framework for Reproducible
3 +% High-Frequency Financial Econometrics
4 +% ============================================================================
5 +\documentclass[12pt,letterpaper]{article}
6 +\usepackage[utf8]{inputenc}
7 +\usepackage[T1]{fontenc}
8 +\usepackage[english]{babel}
9 +\usepackage[letterpaper,top=1in,bottom=1in,left=1in,right=1in]{geometry}
10 +\usepackage{mathptmx}
11 +\usepackage{microtype}
12 +\usepackage{setspace}
13 +\onehalfspacing
14 +\usepackage{amsmath,amssymb,amsthm}
15 +\usepackage{booktabs}
16 +\usepackage{tabularx}
17 +\usepackage{multirow}
18 +\usepackage{threeparttable}
19 +\usepackage{longtable}
20 +\usepackage{siunitx}
21 +\sisetup{input-symbols = {()}, table-format = -1.3, parse-numbers = false}
22 +\usepackage{graphicx}
23 +\usepackage{subcaption}
24 +\usepackage{float}
25 +\usepackage[dvipsnames]{xcolor}
26 +\usepackage[colorlinks=true,linkcolor=NavyBlue,citecolor=NavyBlue,urlcolor=NavyBlue,breaklinks=true]{hyperref}
27 +\usepackage[authoryear,round,semicolon]{natbib}
28 +\bibliographystyle{aer}
29 +\usepackage[font=small,labelfont=bf,labelsep=period,justification=justified,singlelinecheck=false]{caption}
30 +\usepackage{fancyhdr}
31 +\pagestyle{fancy}
32 +\fancyhf{}
33 +\renewcommand{\headrulewidth}{0pt}
34 +\fancyfoot[C]{\thepage}
35 +\usepackage{titlesec}
36 +\titleformat{\section}{\large\bfseries}{\thesection.}{0.5em}{}
37 +\titleformat{\subsection}{\normalsize\bfseries}{\thesubsection.}{0.5em}{}
38 +\titleformat{\subsubsection}{\normalsize\itshape}{\thesubsubsection.}{0.5em}{}
39 +\usepackage[toc,page]{appendix}
40 +\usepackage{enumitem}
41 +\setlist{nosep,leftmargin=*}
42 +\usepackage{listings}
43 +\lstset{basicstyle=\ttfamily\footnotesize,breaklines=true,columns=fullflexible,frame=single,framerule=0.2pt}
44 +\usepackage{url}
45 +\newtheorem{proposition}{Proposition}
46 +\newcommand{\RV}{\mathrm{RV}}
47 +\newcommand{\BPV}{\mathrm{BPV}}
48 +\newcommand{\RQ}{\mathrm{RQ}}
49 +\newcommand{\RSV}{\mathrm{RSV}}
50 +\newcommand{\raid}{\textsc{raid}}
51 +\newcommand{\E}{\mathbb{E}}
52 +\newcommand{\I}{\mathbb{1}}
53 +
54 +% ============================================================================
55 +% METADATA
56 +% ============================================================================
57 +\newcommand{\WPnumber}{40}
58 +\newcommand{\WPtitle}{RAID: A Metal-Accelerated C++ Framework for Reproducible High-Frequency Financial Econometrics}
59 +\newcommand{\WPsubtitle}{Batched, Fused and Streaming Estimation on Apple Silicon without Sacrificing Inference}
60 +\newcommand{\WPdate}{November 2026}
61 +\newcommand{\WPversion}{1.0}
62 +\newcommand{\WPabstract}{%
63 + We ask whether heterogeneous high-frequency econometric workloads---realized measures, rolling and batched regressions, HAR forecasting models, GARCH-family quasi-maximum likelihood, block bootstrap inference---can be reorganised into batched, fused, streaming computation on Apple Silicon GPUs without sacrificing estimator accuracy, statistical inference, or reproducibility. Apple GPUs expose no 64-bit floating point. \raid{} answers with a C++20 library whose FP64 CPU backend is the reference, whose Metal backend accumulates every statistic in compensated ``double-float'' arithmetic and combines chunked partial sums in FP64, and whose estimators report backend, precision, convergence and provenance in every result. Tier~A methods reproduce \texttt{highfrequency}, \texttt{rugarch}, \texttt{statsmodels} and \texttt{arch} to documented tolerances; Monte Carlo experiments confirm bias, RMSE and coverage; GPU and CPU results agree to $10^{-6}$ relative and are bitwise reproducible across runs and thread counts. Using one year of 1-minute bars for a seeded universe of 1,000 US equities served by a private FirstRate Data lakehouse, we measure break-even points on an M5~Max laptop and across eleven Apple Silicon generations (M2 to M6). The GPU wins on fused realized measures beyond roughly $4\times10^4$ elements and on batched GARCH likelihood evaluation when many parameter vectors are evaluated per series; the CPU wins on rolling sums, on single-vector GARCH recursions and on small panels. Conversion and buffer traffic, not kernel time, dominate GPU wall-clock: unified memory is not free computation. We publish the negative results, the tolerances, and a reproducible benchmark protocol.%
64 +}
65 +\newcommand{\WPkeywords}{high-frequency econometrics, realized volatility, HAR, GARCH, GPU computing, Apple Metal, reproducibility, numerical precision}
66 +\newcommand{\WPjel}{C58, C63, C87, G17}
67 +\newcommand{\WPauthor}{Simon-Pierre Boucher}
68 +\newcommand{\WPaffiliation}{Département des sciences administratives\\Université du Québec en Outaouais}
69 +\newcommand{\WPemail}{simon-pierre.boucher@uqo.ca}
70 +\newcommand{\WPaddress}{Gatineau -- Pavillon Alexandre-Taché\\283, boulevard Alexandre-Taché\\Gatineau, Québec, Canada J9A 1L8}
71 +
72 +\begin{document}
73 +\input{sections/titlepage}
74 +\input{sections/introduction}
75 +\input{sections/literature}
76 +\input{sections/architecture}
77 +\input{sections/estimators}
78 +\input{sections/metal}
79 +\input{sections/correctness}
80 +\input{sections/setup}
81 +\input{sections/microbenchmarks}
82 +\input{sections/endtoend}
83 +\input{sections/applications}
84 +\input{sections/threats}
85 +\input{sections/reproducibility}
86 +\input{sections/conclusion}
87 +\newpage
88 +\bibliography{references}
89 +\input{appendices/appendix}
90 +\end{document}
added paper/sections/introduction.tex +18 −0
@@ -0,0 +1,18 @@
1 +\section{Introduction}
2 +\label{sec:intro}
3 +
4 +High-frequency financial econometrics is computationally repetitive in a particular way. The same small estimator---a sum of squared returns, a bipower product, a four-regressor regression, a GARCH recursion---is evaluated on thousands of series, thousands of days, and thousands of bootstrap or Monte Carlo replicas. The established toolchains that practitioners trust for correctness (\texttt{highfrequency} in R, \citealp{BoudtKleenSjorup2022}; \texttt{rugarch}, \citealp{Ghalanos2024rugarch}; \texttt{arch} and \texttt{statsmodels} in Python, \citealp{Sheppard2025arch,Seabold2010}) are organised around one series at a time and double-precision CPU arithmetic. Meanwhile the hardware on most researchers' desks has changed: Apple Silicon machines couple a multicore CPU with a wide GPU in a single unified memory space. The GPU, however, has no 64-bit floating-point unit \citep{AppleMetal2026}, and econometric estimators are not obviously tolerant of single precision.
5 +
6 +This paper studies whether that hardware can be used for high-frequency econometrics \emph{without} giving up what makes the established toolchains trustworthy: estimator accuracy, valid inference and reproducibility. We frame the question as three falsifiable research questions and build the software needed to answer them.
7 +
8 +\begin{description}[leftmargin=1.2em]
9 +\item[RQ1 (break-even).] At what data and batch sizes does Metal beat an optimised multicore CPU once I/O, shader compilation, synchronisation and host--device transfer are counted?
10 +\item[RQ2 (batching and inference).] Does batched estimation raise throughput while preserving coverage, convergence and the numerical agreement with FP64 references?
11 +\item[RQ3 (pipeline).] Can an Arrow/Parquet-to-estimator pipeline materially reduce copies, memory pressure and end-to-end latency relative to the columnar engines researchers use today?
12 +\end{description}
13 +
14 +\raid{} (Research Architecture for Intraday Data) is the instrument: an open C++20 library with (i) an FP64 CPU backend that serves as the reference implementation of every method, (ii) an optional Metal backend in which every accumulator is carried as a compensated double-float pair \citep{Dekker1971,Neumaier1974} and chunked partial sums are combined in FP64 on the CPU, (iii) a method registry that pins the exact convention of each estimator and the reference software it is compared with, (iv) nanobind Python bindings and a command-line tool, and (v) a secure client for the FinData~OS lakehouse that serves FirstRate Data 1-minute bars with dataset versions, request identifiers and checksums.
15 +
16 +Our contributions are deliberately engineering and measurement contributions, not new econometric methodology. First, a precision strategy for FP32-only GPUs that keeps realized measures within $10^{-6}$ of FP64, regression sufficient statistics within $10^{-6}$ and GARCH likelihoods within $2\times10^{-5}$, with bitwise reproducibility. Second, a validation protocol: every Tier~A method is compared with at least one established implementation under identical conventions, with tolerances recorded in the registry and reproduced by the test suite. Third, a benchmark protocol with randomised order, warm-ups, repeated measurements, bootstrap confidence intervals of medians, explicit cold and cached shader compilation, and manifests recording hardware, compiler, load and thermal state. Fourth, measured answers to RQ1--RQ3 on an M5~Max laptop and on eleven Apple Silicon generations, including the cases where the CPU wins.
17 +
18 +The remainder of the paper is organised as follows. Section~\ref{sec:lit} positions the work relative to the econometric and systems literatures. Section~\ref{sec:arch} describes the architecture and API, Section~\ref{sec:est} the estimator definitions and conventions, Section~\ref{sec:metal} the Metal execution and precision strategy, Section~\ref{sec:correct} the statistical correctness evidence. Sections~\ref{sec:setup}--\ref{sec:apps} present the experimental setup, microbenchmarks, end-to-end benchmarks and financial applications. Sections~\ref{sec:threats}--\ref{sec:concl} discuss threats to validity, reproducibility and licensing, and conclude.
added paper/sections/literature.tex +10 −0
@@ -0,0 +1,10 @@
1 +\section{Related literature}
2 +\label{sec:lit}
3 +
4 +\paragraph{Realized measures and their software.} The realized variance of \citet{AndersenBollerslevDieboldLabys2003} and \citet{BarndorffNielsenShephard2002}, bipower variation \citep{BarndorffNielsenShephard2004,BarndorffNielsenShephard2006}, realized semivariance \citep{BarndorffNielsenKinnebrockShephard2010}, realized quarticity and jump proxies \citep{AndersenBollerslevDiebold2007,HuangTauchen2005} are the building blocks of Tier~A. Their finite-sample constants and sampling conventions vary across implementations; \citet{LiuPattonSheppard2015} document how much the choice of sampling scheme matters. Noise-robust estimators---two-scale \citep{ZhangMyklandAitSahalia2005}, realized kernels \citep{BarndorffNielsenHansenLundeShephard2008}, pre-averaging \citep{JacodLiMyklandPodolskijVetter2009}---require tick or quote data and are outside the admissible inputs of this paper, which uses 1-minute OHLCV bars only. The reference implementation for our comparisons is the \texttt{highfrequency} package \citep{BoudtKleenSjorup2022}.
5 +
6 +\paragraph{HAR and GARCH.} The HAR model of \citet{Corsi2009} and its HARQ extension \citep{BollerslevPattonQuaedvlieg2016}, the signed-jump variants of \citet{PattonSheppard2015}, and the GARCH family \citep{Engle1982,Bollerslev1986,Nelson1991,GlostenJagannathanRunkle1993} with quasi-maximum likelihood inference \citep{BollerslevWooldridge1992,FiorentiniCalzolariPanattoni1996} are the forecasting and conditional-variance workhorses we implement. We compare with \texttt{rugarch} \citep{Ghalanos2024rugarch} and \texttt{arch} \citep{Sheppard2025arch}. Forecast losses follow \citet{Patton2011}; the horse race of \citet{HansenLunde2005} and the comparison framework of \citet{DieboldMariano2002,HansenLundeNason2011} motivate the loss-based evaluation, although model selection is not the object of this paper. Realized GARCH \citep{HansenHuangShek2012}, HEAVY \citep{NoureldinShephardSheppard2012}, DCC \citep{Engle2002} and GARCH-MIDAS \citep{EngleGhyselsSohn2013} are listed in the registry as not implemented.
7 +
8 +\paragraph{Inference.} Heteroskedasticity-robust \citep{White1980}, HAC \citep{NeweyWest1987,NeweyWest1994} and cluster-robust \citep{CameronGelbachMiller2011} covariances are implemented with the \texttt{statsmodels} conventions. Dependent-data resampling uses the stationary bootstrap \citep{PolitisRomano1994}; the wild bootstrap for realized volatility \citep{GoncalvesMeddahi2009} is a natural extension we do not pursue. Connectedness measures follow \citet{DieboldYilmaz2012,DieboldYilmaz2014} with the generalized FEVD of \citet{KoopPesaranPotter1996} and \citet{PesaranShin1998}; covariance shrinkage follows \citet{LedoitWolf2004}.
9 +
10 +\paragraph{Numerical computing and GPUs in economics.} Compensated summation goes back to \citet{Kahan1965}, \citet{Dekker1971} and \citet{Neumaier1974}; \citet{Higham1993} gives the error analysis we rely on for the double-float accumulators. \citet{AldrichFernandezVillaverdeGallantRubioRamirez2011} introduced GPU computing to dynamic equilibrium models; the econometric literature on GPUs has concentrated on Monte Carlo and Bayesian computation rather than on the estimators studied here. Our systems dependencies are Eigen \citep{Eigen2025} with Apple Accelerate BLAS/LAPACK \citep{Blackford2002}, Apache Arrow and Parquet \citep{Arrow2025}, nanobind \citep{Nanobind2025}, and the Metal Shading Language \citep{AppleMetal2026}. The comparison engines for the pipeline experiment are pandas, Polars and DuckDB on top of NumPy \citep{Harris2020} and SciPy \citep{Virtanen2020}.
modified src/models/garch_fit.cpp +11 −3
@@ -45,7 +45,14 @@ template <class F> Mat num_hessian(const F& f, const Vec& x, f64 h0 = 1e-4) {
45 45 }
46 46 } // namespace
47 47
48 +static Estimate garch_fit_once(std::span<const f64> r0, const GarchFitOptions& opt, int start_variant);
48 49 Estimate garch_fit(std::span<const f64> r0, const GarchFitOptions& opt) {
50 + // Up to 3 starting-value variants (documented in the registry): 0 = default, 1 = low persistence (0.80), 2 = high persistence (0.985) / opposite EGARCH γ sign.
51 + Estimate best; bool have = false; int tried = 0;
52 + for (int v = 0; v < 3; ++v) { Estimate e = garch_fit_once(r0, opt, v); ++tried; if (!have || (e.ok() && (!best.ok() || e.loss < best.loss))) { best = e; have = true; } if (best.ok() && opt.retry_starts == false) break; if (best.ok()) break; }
53 + best.extra["start_variants_tried"] = tried; return best;
54 +}
55 +static Estimate garch_fit_once(std::span<const f64> r0, const GarchFitOptions& opt, int start_variant) {
49 56 const auto t0 = std::chrono::steady_clock::now();
50 57 Estimate e; e.model = "garch." + to_string(opt.model) + "." + to_string(opt.dist); e.backend = Backend::cpu; e.precision = Precision::fp64;
51 58 e.names = garch_param_names(opt.model, opt.dist); e.n_obs = r0.size();
@@ -59,9 +66,10 @@ Estimate garch_fit(std::span<const f64> r0, const GarchFitOptions& opt) {
59 66 // starting values (on the scaled series): μ = mean, persistence 0.95, α = 0.05 (GJR γ = 0.05), ν = 8
60 67 std::vector<f64> th0(p); th0[0] = stats::mean(std::span<const f64>(r));
61 68 const f64 v = stats::variance(std::span<const f64>(r), 0);
62 − if (opt.model == GarchModel::egarch) { th0[2] = 0.1; th0[3] = -0.05; th0[4] = 0.95; th0[1] = (1.0 - th0[4]) * std::log(v); }
63 − else if (opt.model == GarchModel::garch) { th0[2] = 0.05; th0[3] = 0.90; th0[1] = v * (1 - 0.95); }
64 − else { th0[2] = 0.03; th0[3] = 0.05; th0[4] = 0.90; th0[1] = v * (1 - 0.03 - 0.90 - 0.025); }
69 + const f64 pers = start_variant == 1 ? 0.80 : (start_variant == 2 ? 0.985 : 0.95);
70 + if (opt.model == GarchModel::egarch) { th0[2] = 0.1; th0[3] = start_variant == 2 ? 0.05 : -0.05; th0[4] = pers; th0[1] = (1.0 - th0[4]) * std::log(v); }
71 + else if (opt.model == GarchModel::garch) { th0[2] = 0.05; th0[3] = pers - 0.05; th0[1] = v * (1 - pers); }
72 + else { th0[2] = 0.03; th0[3] = 0.05; th0[4] = pers - 0.055; th0[1] = v * (1 - pers); }
65 73 if (opt.dist == GarchDist::student_t) th0[p - 1] = 8.0;
66 74 auto f_u = [&](const Vec& u) { auto th = garch_from_unconstrained(std::span<const f64>(u.data(), static_cast<usize>(u.size())), opt.model, opt.dist); return garch_nll(r, th, opt.model, opt.dist); };
67 75 auto u0v = garch_to_unconstrained(th0, opt.model, opt.dist);
68 76
Menu

Theme