SPB Git

spb/wp46_uqo

Public

WP46: synthetic fixture, data READMEs

Simon-Pierre Boucher committed (Nov 9, 2026) parent dcc6e1c Browse files

7 changed files +83 −5

added data/README.md +7 −0
@@ -0,0 +1,7 @@
1+# data/
2+
3+- `raw/`, `interim/`, `processed/`: ignored by git (FirstRate Data private licence). The live cache lives outside the repository
4+ (`~/wp46-cache/{bars,panels,rcov}` on the data node and compute nodes); see `docs/replication_guide.md`.
5+- `manifests/`: nothing here — all manifests are under `results/manifests/` (audit, ingest with dataset versions and SHA-256, panels, jobs).
6+- `synthetic/`: licence-free simulated panels (`scripts/make_synthetic.py`) in the exact panel schema (`docs/data_dictionary.md`), to run the
7+ pipeline without credentials: `WP46_PANELS=data/synthetic .venv/bin/python scripts/run_benchmark.py --jobs <jobs for SYN1,SYN2> --out results/forecasts/synthetic`.
added data/raw/README.md +5 −0
@@ -0,0 +1,5 @@
1+# data/raw — provenance (no files committed)
2+Source: FinData OS (`https://findata.spboucher.ai/api/v1`), FirstRate Data one-minute and daily bars. Extraction 2026-11-09 by
3+`scripts/ingest_bars.py` (exports chunked by ≤ 5 years; dataset ids, versions, row counts and SHA-256 per file in
4+`results/manifests/ingest_manifest.json`; ≈ 230 M one-minute rows, ≈ 10 GB Parquet). Access requires the owner's API key; the data may
5+not be redistributed. Audit of the inventory: `docs/findata_audit.md`.
added data/synthetic/SYN1.parquet +0 −0

Binary file not shown.

added data/synthetic/SYN2.parquet +0 −0

Binary file not shown.

added data/synthetic/manifest.json +18 −0
@@ -0,0 +1,18 @@
1+{
2+ "SYN1": {
3+ "asset_class": "synthetic",
4+ "symbol": "SYN1",
5+ "rows": 5500,
6+ "first": "2004-01-02",
7+ "last": "2025-01-30",
8+ "seed": 100
9+ },
10+ "SYN2": {
11+ "asset_class": "synthetic",
12+ "symbol": "SYN2",
13+ "rows": 5500,
14+ "first": "2004-01-02",
15+ "last": "2025-01-30",
16+ "seed": 101
17+ }
18+}
\ No newline at end of file
added scripts/make_synthetic.py +49 −0
@@ -0,0 +1,49 @@
1+#!/usr/bin/env python3
2+"""Synthetic fixture (licence-free): two simulated instruments with GJR-GARCH-t returns and a noisy realized proxy, written
3+in the panel format so that the whole pipeline (forecasts -> losses -> analysis -> exhibits) runs without FinData access.
4+usage: make_synthetic.py [--out data/synthetic] ; then WP46_PANELS=data/synthetic scripts/run_benchmark.py ...
5+"""
6+from __future__ import annotations
7+
8+import argparse
9+import json
10+from pathlib import Path
11+
12+import numpy as np
13+import pandas as pd
14+
15+ROOT = Path(__file__).resolve().parents[1]
16+
17+
18+def simulate(T: int, seed: int, om=0.03, al=0.03, ga=0.08, be=0.90, nu=6.0) -> pd.DataFrame:
19+ rng = np.random.default_rng(seed)
20+ z = rng.standard_t(nu, T) * np.sqrt((nu - 2) / nu)
21+ s = np.empty(T); e = np.empty(T); s[0] = om / (1 - al - be - ga / 2)
22+ for t in range(T):
23+ if t > 0:
24+ s[t] = om + al * e[t - 1] ** 2 + ga * e[t - 1] ** 2 * (e[t - 1] < 0) + be * s[t - 1]
25+ e[t] = np.sqrt(s[t]) * z[t]
26+ r = 0.02 + e
27+ rv = s * rng.lognormal(-0.05, 0.3, T) * 0.7 # intraday part (70 % of the daily variance on average)
28+ on = np.sqrt(0.3 * s) * rng.standard_normal(T) # overnight return
29+ dates = pd.bdate_range("2004-01-02", periods=T)
30+ return pd.DataFrame({"session": dates, "close": 100 * np.exp(np.cumsum(r) / 100), "open": np.nan, "r": r, "r_on": on, "r_oc": r - on,
31+ "valid_rm": True, "expected_bars": 391, "rv_1m": rv * 1.1, "rv_5m": rv, "rv_5m_ss": rv, "bv_5m": rv * 0.95,
32+ "rsv_pos_5m": rv / 2, "rsv_neg_5m": rv / 2, "medrv_5m": rv * 0.9, "n_bars": 391, "n_5m": 78})
33+
34+
35+def main() -> None:
36+ ap = argparse.ArgumentParser(); ap.add_argument("--out", default=str(ROOT / "data" / "synthetic")); ap.add_argument("--T", type=int, default=5500)
37+ a = ap.parse_args()
38+ out = Path(a.out); out.mkdir(parents=True, exist_ok=True)
39+ man = {}
40+ for i, sym in enumerate(["SYN1", "SYN2"]):
41+ df = simulate(a.T, 100 + i)
42+ df.to_parquet(out / f"{sym}.parquet", index=False)
43+ man[sym] = {"asset_class": "synthetic", "symbol": sym, "rows": int(len(df)), "first": str(df.session.min().date()), "last": str(df.session.max().date()), "seed": 100 + i}
44+ (out / "manifest.json").write_text(json.dumps(man, indent=1))
45+ print("synthetic panels:", list(man))
46+
47+
48+if __name__ == "__main__":
49+ main()
modified tests/replication/rugarch_crosscheck.R +4 −5
@@ -37,16 +37,15 @@ if (requireNamespace("GAS", quietly = TRUE)) {
37 suppressPackageStartupMessages(library(GAS)) 37 suppressPackageStartupMessages(library(GAS))
38 gs <- UniGASSpec(Dist = "std", ScalingType = "Inv", GASPar = list(location = FALSE, scale = TRUE, shape = FALSE)) 38 gs <- UniGASSpec(Dist = "std", ScalingType = "Inv", GASPar = list(location = FALSE, scale = TRUE, shape = FALSE))
39 gf <- tryCatch(UniGASFit(gs, r), error = function(e) NULL) 39 gf <- tryCatch(UniGASFit(gs, r), error = function(e) NULL)
40− if (!is.null(gf)) out2$gas_t <- list(converged = TRUE, loglik = gf@Estimates$lLik, coef = as.list(gf@Estimates$lParList), aic = gf@GOF$AIC) else out2$gas_t <- list(converged = FALSE) 40+ out2$gas_t <- tryCatch(if (!is.null(gf)) list(converged = TRUE, loglik = as.numeric(gf@Estimates$lLik), coef = as.list(gf@Estimates$lParList)) else list(converged = FALSE), error = function(e) list(converged = FALSE, error = conditionMessage(e)))
41 } 41 }
42 if (requireNamespace("MSGARCH", quietly = TRUE)) { 42 if (requireNamespace("MSGARCH", quietly = TRUE)) {
43 suppressPackageStartupMessages(library(MSGARCH)) 43 suppressPackageStartupMessages(library(MSGARCH))
44 ms <- CreateSpec(variance.spec = list(model = c("sGARCH", "sGARCH")), distribution.spec = list(distribution = c("norm", "norm")), switch.spec = list(do.mix = FALSE, K = 2)) 44 ms <- CreateSpec(variance.spec = list(model = c("sGARCH", "sGARCH")), distribution.spec = list(distribution = c("norm", "norm")), switch.spec = list(do.mix = FALSE, K = 2))
45 mf <- tryCatch(FitML(ms, data = r - mean(r)), error = function(e) NULL) 45 mf <- tryCatch(FitML(ms, data = r - mean(r)), error = function(e) NULL)
46− if (!is.null(mf)) { 46+ out2$msgarch2_n <- tryCatch(if (!is.null(mf)) { pr <- predict(mf, nahead = 22, do.return.draw = FALSE)
47− pr <- predict(mf, nahead = 22, do.return.draw = FALSE) 47+ list(converged = TRUE, loglik = as.numeric(mf$loglik), par = as.list(mf$par), vol_forecast = as.numeric(pr$vol), mean_removed = mean(r)) } else list(converged = FALSE),
48− out2$msgarch2_n <- list(converged = TRUE, loglik = mf$loglik, par = as.list(mf$par), vol_forecast = as.numeric(pr$vol), mean_removed = mean(r)) 48+ error = function(e) list(converged = FALSE, error = conditionMessage(e)))
49− } else out2$msgarch2_n <- list(converged = FALSE)
50 } 49 }
51 out <- c(out, out2) 50 out <- c(out, out2)
52 write_json(out, a[2], auto_unbox = TRUE, digits = NA) 51 write_json(out, a[2], auto_unbox = TRUE, digits = NA)
53 52
Menu

Theme