SPB Git

spb/wp46_uqo

Public

WP46: reporting (plot style, figures, tables), paper skeleton, build_paper, tier C analysis, data manifests

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

40 changed files +9,391 −0

added model_cards/aparch11.md +39 −0
@@ -0,0 +1,39 @@
1 +# Model card — `aparch11` (APARCH(1,1), asymmetric power ARCH)
2 +
3 +## 1. Identity
4 +- **model_id:** `aparch11` · **family:** power · **tier:** A · **status:** implemented · **impl:** arch 8.0.0 (`APARCH(p=1, o=1, q=1)`, δ estimated)
5 +- **Label:** APARCH(1,1) · **distributions:** normal, t, ged, skewt
6 +- **Seminal citation:** `ding1993long`
7 +- **Other references:** `ling2002necessary` (moment conditions), `he1999properties`, `giot2003value` (skewed-t APARCH for VaR), `brownlees2011practical`, `hansen2005forecast`.
8 +
9 +## 2. Definition (arch parameterisation)
10 +$$r_t = \mu + \varepsilon_t,\ \varepsilon_t = \sigma_t z_t,\qquad \sigma_t^{\delta} = \omega + \alpha\big(|\varepsilon_{t-1}| - \gamma\,\varepsilon_{t-1}\big)^{\delta} + \beta\,\sigma_{t-1}^{\delta} ,$$
11 +`aparch_recursion`: pre-sample shock $= \sqrt{\text{backcast}}$ (variance backcast) and $\sigma_0^\delta$ term $= \text{backcast}^{\delta/2}$; $\sigma_t^2 = (\sigma_t^\delta)^{2/\delta}$, clamped to arch's variance bounds. Innovations: normal, t, GED, Hansen skew-t.
12 +
13 +## 3. Parameter space and admissibility
14 +- Parameters: `mu, omega, alpha[1], gamma[1], beta[1], delta` (+ distribution).
15 +- arch bounds: $\omega \in [0, 10v]$ with $v = \max(\overline{|\varepsilon|^{0.5}}, \overline{\varepsilon^2})$, $\alpha \in [0,1]$, $\gamma \in [-0.9997, 0.9997]$, $\beta \in [0,1]$, $\delta \in [0.05, 4]$; linear constraints $\omega, \alpha \ge 0$, $|\gamma| < 0.9997$, $\beta \ge 0$, $0.05 < \delta < 4$, $\alpha + \beta < 1$ (arch's generic persistence constraint, **not** the Ding–Granger–Engle stationarity condition $\alpha E[(|z| - \gamma z)^\delta] + \beta < 1$, `ling2002necessary`).
16 +- Admissibility flag: **always `True`** (no model-specific check in `ArchModel.fit`).
17 +
18 +## 4. Nesting and relations
19 +Nests `garch11` (δ = 2, γ = 0), `gjr11` (δ = 2; with $\alpha_{GJR} = \alpha(1-\gamma)^2$, $\gamma_{GJR} = \alpha[(1+\gamma)^2 - (1-\gamma)^2]$), `avgarch11` (δ = 1, γ = 0), `tarch11` (δ = 1), Higgins–Bera NARCH (γ = 0, free δ). Literature-matrix caveat: "multi-step forecast of h not closed-form when δ ≠ 2".
20 +
21 +## 5. Estimation
22 +arch `ConstantMean` + `APARCH(1,1,1)` + distribution; SLSQP (`maxiter` 1000) with the bounds/constraints of §3; starting values from arch's grid α ∈ {0.03, 0.05, 0.08, 0.15} × γ ∈ {−0.5, 0, 0.5} × (α+β) ∈ {0.8, 0.9, 0.95, 0.975, 0.99} × δ ∈ {0.5, 1.2, 1.8}, best Gaussian log-likelihood; variance backcast (0.94ⁱ weights on ε²); ≤ 2 restarts (`ArchModel.fit`). Point estimates only.
23 +
24 +## 6. Forecasting
25 +h = 1 analytic (recursion, then $(\cdot)^{2/\delta}$). **h > 1 by simulation** (arch raises for analytic APARCH multi-step): 2,000 paths of $\sigma^\delta_{t+k}$ via `APARCH._simulate_paths` (shock $= z\,\sigma$, $(|{\rm shock}| - \gamma\,{\rm shock})^\delta$ feeds the next step), $\hat\sigma^2_{t+k|t}$ = path mean of $(\sigma^\delta_{t+k})^{2/\delta}$. Normal: seeded (46 + origin); t/GED/skew-t: arch's unseeded fitted-distribution simulator (`_discrepancies.md`). Cumulative = sum over k ≤ h. Method: `simulation` (h > 1).
26 +
27 +## 7. Implementation
28 +- `src/wp46/models/arch_models.py` (`SPECS["aparch11"]`), arch 8.0.0 (`sheppard2025arch`).
29 +- Cross-check (`results/metrics/rugarch_crosscheck.json`, rugarch `apARCH`, t): −3372.7593 (ours) vs −3372.8489 (rugarch), **difference +0.0896** (ours higher; δ = 1.81, γ = 0.232, ν = 7.02 on GJR-t data).
30 +- Tests: `tests/replication/test_rugarch.py` (fit recorded in the JSON); pilot `aparch11__t`: 207/207 refits `ok`, 1,032/1,032 forecasts `ok` on CL.
31 +
32 +## 8. Known limitations / failure modes
33 +δ and γ are jointly weakly identified (flat likelihood ridges; δ can run to the bounds 0.05/4 or the persistence constraint binds); γ bound ±0.9997; simulation forecasts with Monte-Carlo error, unseeded for non-normal laws; no admissibility flag beyond optimizer constraints; stationarity under the estimated (γ, δ) is not verified.
34 +
35 +## 9. Computational footprint
36 +Most expensive arch-backed estimation (6–8 parameters, 180 grid starting values evaluated before SLSQP): pilot median fit time per refit (t) 0.0321 s (CL) / 0.0406 s (EURUSD), max 0.07 s; forecasts by simulation (22 × 2,000 draws per origin).
37 +
38 +## 10. Literature expectations
39 +`ding1993long` found δ ≈ 1.4 on S&P 500 with strong asymmetry; `hansen2005forecast` report APARCH among the best on IBM; `giot2003value` favour skewed-t APARCH for VaR; `brownlees2011practical` include APARCH among the top asymmetric models. Expectation: competitive with GJR/EGARCH at h = 1 on equities (H1) at the highest estimation and forecasting cost of Tier A (H5), with the largest failure/boundary rate.
added model_cards/avgarch11.md +39 −0
@@ -0,0 +1,39 @@
1 +# Model card — `avgarch11` (AVGARCH(1,1), Taylor–Schwert absolute-value GARCH)
2 +
3 +## 1. Identity
4 +- **model_id:** `avgarch11` · **family:** power · **tier:** A · **status:** implemented · **impl:** arch 8.0.0 (`GARCH(p=1, o=0, q=1, power=1.0)`, arch name "AVGARCH")
5 +- **Label:** AVGARCH(1,1) (Taylor-Schwert) · **distributions:** normal, t, ged, skewt
6 +- **Seminal citation:** `taylor2008modelling` (2nd ed. of Taylor 1986; the 1986 first edition has no verified record — see `docs/literature_matrix.md`, Unverified) and `schwert1989why`.
7 +- **Other references:** `ding1993long` (APARCH nests it, δ = 1, γ = 0), `zakoian1994threshold`.
8 +
9 +## 2. Definition (arch, power = 1)
10 +$$r_t = \mu + \varepsilon_t,\ \varepsilon_t = \sigma_t z_t,\qquad \sigma_t = \omega + \alpha\,|\varepsilon_{t-1}| + \beta\,\sigma_{t-1} ,$$
11 +pre-sample $|\varepsilon|$ and $\sigma$ replaced by the power-1 backcast ($\sum_{i<75} w_i|\varepsilon_i|$, $w_i \propto 0.94^i$). Innovations: normal, t, GED, Hansen skew-t.
12 +
13 +## 3. Parameter space and admissibility
14 +- Parameters: `mu, omega, alpha[1], beta[1]` (+ distribution).
15 +- arch bounds/constraints: $\omega \in [10^{-8}v_1, 10v_1]$ ($v_1 = \overline{|\varepsilon|}$), $\alpha, \beta \in [0,1]$, $\omega > 0$, $\alpha, \beta \ge 0$, $\alpha + \beta < 1$ (arch's generic constraint; the stationarity of $\sigma_t$ requires $\alpha E|z| + \beta < 1$, which is implied).
16 +- Admissibility flag: **always `True`** (no check in `ArchModel.fit` for `avgarch11`).
17 +
18 +## 4. Nesting and relations
19 +`aparch11` with δ = 1, γ = 0; `tarch11` with γ = 0. Not nested in `garch11` (power 1 vs 2). Symmetric; the only symmetric power-1 model of the registry.
20 +
21 +## 5. Estimation
22 +arch SLSQP (`maxiter` 1000), grid starting values, power-1 backcast, ≤ 2 restarts (`ArchModel.fit`). Point estimates only.
23 +
24 +## 6. Forecasting
25 +h = 1 analytic ($\hat\sigma_{t+1|t}^2 = (\omega + \alpha|\varepsilon_t| + \beta\sigma_t)^2$); h > 1 by **simulation** (2,000 paths of $\sigma_{t+k}$ via `GARCH._simulate_paths`, power 1; variance forecast = path mean of $\sigma_{t+k}^2$; note $E[\sigma_{t+k}^2] \ne (E\sigma_{t+k})^2$, which is why no analytic rule is used). Normal: seeded (46 + origin); t/GED/skew-t: arch's unseeded distribution simulator (`_discrepancies.md`). Cumulative = sum over k ≤ h. Method: `simulation` for h > 1.
26 +
27 +## 7. Implementation
28 +- `src/wp46/models/arch_models.py` (`SPECS["avgarch11"]`), arch 8.0.0.
29 +- Cross-check: none (not in the rugarch fixture script; rugarch `fGARCH(AVGARCH)` was not run).
30 +- Tests: none dedicated.
31 +
32 +## 8. Known limitations / failure modes
33 +No admissibility flag beyond optimizer constraints; simulation forecasts (Monte-Carlo error, unseeded for non-normal laws); boundary optima as for GARCH(1,1).
34 +
35 +## 9. Computational footprint
36 +Estimation like `garch11`; simulation forecasts (22 × 2,000 draws per origin). No pilot timing.
37 +
38 +## 10. Literature expectations
39 +`taylor2008modelling`/`schwert1989why` introduced absolute-value dynamics as more robust to outliers; `ding1993long` found δ ≈ 1.4 for S&P 500 (between AVGARCH and GARCH); `hansen2005forecast` include TS-GARCH variants without finding them superior to GARCH(1,1). Expectation: similar to GARCH(1,1), possibly more robust in stressed regimes (H6), at a higher forecasting cost (H5).
added model_cards/egarch11.md +39 −0
@@ -0,0 +1,39 @@
1 +# Model card — `egarch11` (EGARCH(1,1))
2 +
3 +## 1. Identity
4 +- **model_id:** `egarch11` · **family:** asymmetric · **tier:** A · **status:** implemented · **impl:** arch 8.0.0 (`EGARCH(p=1, o=1, q=1)`)
5 +- **Label:** EGARCH(1,1) · **distributions:** normal, t, ged, skewt
6 +- **Seminal citation:** `nelson1991conditional`
7 +- **Other references:** `straumann2006quasi` (EGARCH QMLE theory), `harvey2013dynamic`/`harvey2014egarch` (Beta-t-EGARCH, score-driven relative), `hansen2005forecast`, `brownlees2011practical`.
8 +
9 +## 2. Definition (arch parameterisation)
10 +$$r_t = \mu + \varepsilon_t,\ \varepsilon_t = \sigma_t z_t,\ e_t = \varepsilon_t/\sigma_t,\qquad \ln\sigma_t^2 = \omega + \alpha\big(|e_{t-1}| - \sqrt{2/\pi}\big) + \gamma\,e_{t-1} + \beta\,\ln\sigma_{t-1}^2 .$$
11 +Here **arch's `alpha[1]` multiplies the magnitude term and `gamma[1]` the sign term** (Nelson writes $g(z) = \theta z + \gamma(|z| - E|z|)$; rugarch writes α on the sign and γ on the magnitude — the cross-check confirms the swap: our α = 0.1565 ↔ rugarch γ₁ = 0.1565, our γ = −0.0444 ↔ rugarch α₁ = −0.0444). The centring constant is $\sqrt{2/\pi} = E|z|$ for the normal only; for t/GED/skew-t $E|z| \ne \sqrt{2/\pi}$ and the difference $\alpha(E|z| - \sqrt{2/\pi})$ is absorbed by ω (observationally equivalent, but ω is not Nelson's ω). Pre-sample: $\ln\sigma_0^2 = \omega + \beta\cdot\ln(\text{backcast})$ (the α, γ terms are omitted at t = 0). $\ln\sigma_t^2$ is capped at `LNSIGMA_MAX` and $\sigma_t^2$ clamped to arch's variance bounds. Innovations: normal, t, GED, Hansen skew-t.
12 +
13 +## 3. Parameter space and admissibility
14 +- Parameters: `mu, omega, alpha[1], gamma[1], beta[1]` (+ distribution).
15 +- arch bounds: $\omega \in [\ln v - \ln 10^4, \ln v + \ln 10^4]$ ($v = \overline{\varepsilon^2}$), $\alpha, \gamma \in (-\infty, \infty)$, $\beta \in [0, 1]$; constraint $\beta < 1$. arch therefore excludes $\beta < 0$ (Nelson's condition is $|\beta| < 1$).
16 +- Admissibility flag (`ArchModel.fit`): $|\beta| < 1$ (log-variance stationarity). No positivity constraints are needed (log specification). Note the literature matrix caveat: stationarity is $|\beta| < 1$, not $\alpha + \beta < 1$.
17 +
18 +## 4. Nesting and relations
19 +Does not nest `garch11` (log vs level). γ = 0 gives a symmetric log-GARCH; the Beta-t-EGARCH of Harvey is the score-driven counterpart (not in the registry; `gas_t11` is the variance-level GAS). Exponential form of the asymmetry shared with `realegarch11` (unsupported).
20 +
21 +## 5. Estimation
22 +arch `ConstantMean` + `EGARCH(1,1,1)` + distribution; SLSQP (`maxiter` 1000) with the bounds of §3; starting values from arch's grid (α ∈ {0.01, 0.05, 0.1, 0.2}, γ ∈ {−0.1, 0, 0.1}, β ∈ {0.5, 0.7, 0.9, 0.98}, ω = (1−β)·ln v̄; best Gaussian log-likelihood); backcast = log of the exponentially weighted mean of the first min(75, T) squared residuals; ≤ 2 restarts (`ArchModel.fit`). Point estimates only; daily filtering with `am.fix(params)`.
23 +
24 +## 6. Forecasting
25 +h = 1: analytic, $\hat\sigma^2_{t+1|t} = \exp(\omega + \alpha(|e_t| - \sqrt{2/\pi}) + \gamma e_t + \beta\ln\sigma_t^2)$. **h > 1: simulation** (`SPECS["egarch11"]["analytic"] = False`; arch has no analytic EGARCH multi-step): 2,000 paths, $\ln\sigma^2_{t+k} = \omega + \alpha(|z_{t+k-1}| - \sqrt{2/\pi}) + \gamma z_{t+k-1} + \beta\ln\sigma^2_{t+k-1}$ with simulated standardised shocks, $\hat\sigma^2_{t+k|t}$ = path mean of $\exp(\ln\sigma^2_{t+k})$. Normal: seeded `default_rng(46 + origin).standard_normal`; t/GED/skew-t: arch's fitted-distribution simulator, **unseeded** (`_discrepancies.md`). Cumulative = sum over k ≤ h. Method: `simulation` (h > 1).
26 +
27 +## 7. Implementation
28 +- `src/wp46/models/arch_models.py` (`SPECS["egarch11"]`), arch 8.0.0 (`sheppard2025arch`).
29 +- Cross-check (`results/metrics/rugarch_crosscheck.json`, rugarch `eGARCH`, t): −3376.6704 (ours) vs −3376.6833 (rugarch), **difference +0.0129**; coefficients agree after the α/γ label swap (β 0.96868 vs 0.96871, ν 6.98 vs 6.99); ω differs (−0.0110 vs −0.0171) because rugarch uses the distribution-specific $E|z|$ in the centring (observational equivalence through ω).
30 +- Tests: `tests/replication/test_rugarch.py` (fit recorded; no assertion on EGARCH beyond the JSON); pilot `egarch11__t` 1,032/1,032 forecasts `ok` on CL, 207/207 refits `ok`.
31 +
32 +## 8. Known limitations / failure modes
33 +β frequently close to 1 (log-variance near unit root) → explosive multi-step simulations are bounded only by `LNSIGMA_MAX`/variance bounds; multi-step forecasts carry Monte-Carlo error and, for non-normal laws, are unseeded; $E[\sigma^2_{t+k}]$ is a mean of log-normal-like paths and is sensitive to tail draws (large variance of the path mean at h = 22); the centring constant mismatch for non-normal laws is harmless for the fit but makes `omega` non-comparable across distributions.
34 +
35 +## 9. Computational footprint
36 +Estimation cheap (pilot median fit time per refit, t: 0.0088 s CL / 0.0095 s EURUSD); forecasting by simulation (22 × 2,000 draws per origin) is the dominant cost.
37 +
38 +## 10. Literature expectations
39 +`hansen2005forecast`: EGARCH-type leverage models among those that beat GARCH(1,1) on IBM; `brownlees2011practical` rank EGARCH/asymmetric models best for US equities; `nelson1991conditional` originally with GED innovations. Expectation: strong at h = 1 on equities (H1), weaker at h = 22 where the simulation mean of exponentials inflates forecasts; no gain expected on FX (`hansen2005forecast`).
added model_cards/figarch11.md +40 −0
@@ -0,0 +1,40 @@
1 +# Model card — `figarch11` (FIGARCH(1,d,1))
2 +
3 +## 1. Identity
4 +- **model_id:** `figarch11` · **family:** fractional · **tier:** A · **status:** implemented · **impl:** arch 8.0.0 (`FIGARCH(p=1, q=1, truncation=1000)`)
5 +- **Label:** FIGARCH(1,d,1) · **distributions:** normal, t, ged, skewt · registry note: "arch truncates the ARCH(∞) representation at 1000 lags"
6 +- **Seminal citation:** `baillie1996fractionally`
7 +- **Other references:** `conrad2006inequality` (necessary and sufficient positivity), `ding1996modeling` (long memory in volatility), `bollerslev1996modeling` (FIEGARCH), `davidson2004moment` (HYGARCH); `chung1999estimating` is **unverified and not in the .bib** (literature matrix).
8 +
9 +## 2. Definition (arch parameterisation)
10 +Model: $[1 - \beta L]\,\sigma_t^2 = \omega + [1 - \beta L - (1 - \phi L)(1-L)^d]\,\varepsilon_t^2$, estimated through the truncated ARCH(∞) representation
11 +$$\sigma_t^2 = \frac{\omega}{1-\beta} + \sum_{i=1}^{1000}\lambda_i\,\varepsilon_{t-i}^2,\qquad \lambda_1 = \phi - \beta + d,\ \ \delta_1 = d,\ \ \delta_j = \frac{j-1-d}{j}\,\delta_{j-1},\ \ \lambda_j = \beta\lambda_{j-1} + \delta_j - \phi\,\delta_{j-1} .$$
12 +Lags beyond the available sample use the backcast (`figarch_recursion`: $\sigma_t^2 = \tilde\omega + (\sum_{i > t}\lambda_i)\,\text{backcast} + \sum_{i \le \min(t,1000)}\lambda_i\varepsilon_{t-i}^2$). Innovations: normal, t, GED, Hansen skew-t.
13 +
14 +## 3. Parameter space and admissibility
15 +- Parameters: `mu, omega, phi, d, beta` (+ distribution).
16 +- arch bounds: $\omega \in [0, 10v]$, $\phi \in [0, 0.5]$, $d \in [0, 1-\epsilon]$, $\beta \in [0, 1-\epsilon]$; linear constraints $\omega > 0$, $0 \le d \le 1$, $0 \le \phi \le (1-d)/2$, $0 \le \beta \le d + \phi$ (sufficient positivity conditions of the (1,d,1) case as coded in arch; the necessary and sufficient set of `conrad2006inequality` is larger).
17 +- Admissibility flag (`ArchModel.fit`): $0 \le d \le 1$. FIGARCH is strictly but not covariance stationary for d > 0 (infinite unconditional variance), so no $\alpha+\beta<1$-type flag applies.
18 +
19 +## 4. Nesting and relations
20 +d = 0 gives GARCH(1,1) with $\alpha = \phi - \beta$ (…) and d = 1 the IGARCH-type unit root; `igarch11` and `garch11` are the end points. `rm2006` and HAR are non-estimated / regression analogues of hyperbolic memory. FIEGARCH/FIAPARCH/HYGARCH (unsupported) are its extensions.
21 +
22 +## 5. Estimation
23 +arch `ConstantMean` + `FIGARCH(1,1)` + distribution; SLSQP (`maxiter` 1000) with the bounds/constraints of §3; starting values from arch's grid d ∈ {0.2, 0.5, 0.7} × φ-ratio ∈ {0.2, 0.5, 0.8} × β-ratio ∈ {0.1, 0.5, 0.9}, $\omega = (1-\beta)\,\overline{\varepsilon^2}\,(1 - \sum_i\lambda_i)$, best Gaussian log-likelihood; backcast = exponentially weighted mean of the first 75 squared residuals; ≤ 2 restarts (`ArchModel.fit`). Point estimates only. Each likelihood evaluation convolves 1,000 weights over the 1,000-day window.
24 +
25 +## 6. Forecasting
26 +arch `FIGARCH._analytic_forecast`: the ARCH(∞) recursion is iterated forward with future $\varepsilon^2$ replaced by their forecasts, $\hat\sigma^2_{t+k|t} = \tilde\omega + \sum_{i=1}^{1000}\lambda_i\,\widetilde{\varepsilon^2}_{t+k-i}$ ($\widetilde{\varepsilon^2}_s = \varepsilon_s^2$ for $s \le t$, $= \hat\sigma^2_{s|t}$ for $s > t$; pre-sample lags = backcast). Exact under $E z^2 = 1$. Cumulative = sum over k ≤ h. Method: `analytic`.
27 +
28 +## 7. Implementation
29 +- `src/wp46/models/arch_models.py` (`SPECS["figarch11"]`), arch 8.0.0 (`sheppard2025arch`).
30 +- Cross-check (`results/metrics/rugarch_crosscheck.json`, rugarch `fiGARCH`, t): −3383.4506 (ours) vs −3381.9798 (rugarch), **difference −1.4708** (ours lower) — the only cross-check outside ±0.3; attributed to different truncation / constraint handling between arch and rugarch (D-14, `progress.md` open issue). Estimates: d = 0.546, φ = 0.227, β = 0.693, ν = 6.45 (arch). Reported as an implementation caveat; not resolved.
31 +- Tests: `tests/replication/test_rugarch.py` (fit recorded, no assertion on FIGARCH). No simulation-recovery test.
32 +
33 +## 8. Known limitations / failure modes
34 +ω is small and poorly identified (literature matrix); d near 0 or 1 hits the bounds; the sufficient constraints exclude admissible parameter sets; the 1,000-lag truncation inside a 1,000-day window means every in-sample variance uses the backcast for the oldest lags; the 1.47 log-likelihood discrepancy with rugarch is unexplained; infinite unconditional variance makes long-horizon forecasts grow slowly without bound.
35 +
36 +## 9. Computational footprint
37 +Analytic but O(T × 1000) per likelihood evaluation (the heaviest analytic model of Tier A); forecasts are O(22 × 1000). Not in the pilot.
38 +
39 +## 10. Literature expectations
40 +`baillie1996fractionally` and `ding1996modeling` document hyperbolic decay in volatility autocorrelations; `hansen2005forecast` include FIGARCH among models that do not beat GARCH(1,1) at the 1-day horizon; `lamoureux1990persistence`/`hillebrand2005neglecting` warn that breaks mimic long memory. Pre-registered H4 (persistence structure) predicts gains, if any, at h = 22 rather than h = 1.
added model_cards/garchm11.md +39 −0
@@ -0,0 +1,39 @@
1 +# Model card — `garchm11` (GARCH-in-mean(1,1))
2 +
3 +## 1. Identity
4 +- **model_id:** `garchm11` · **family:** classical · **tier:** A · **status:** implemented · **impl:** custom (`src/wp46/models/custom.py::GARCHM11`)
5 +- **Label:** GARCH-M(1,1) · **distributions:** normal, t, ged, skewt
6 +- **Seminal citation:** `engle1987estimating` (Engle, Lilien & Robins 1987, ARCH-M)
7 +- **Other references:** `bollerslev1986generalized`; literature matrix §a.1 ("mean and variance parameters not separable → affects multi-step forecasts of cumulative returns").
8 +
9 +## 2. Definition
10 +$$r_t = \mu + \delta\,\sigma_t^2 + \varepsilon_t,\quad \varepsilon_t = \sigma_t z_t,\qquad \sigma_t^2 = \omega + \alpha\,\varepsilon_{t-1}^2 + \beta\,\sigma_{t-1}^2 ,$$
11 +with the risk-premium function $g(\sigma_t^2) = \sigma_t^2$ (variance in the mean, not $\sigma_t$ or $\log\sigma_t^2$). Recursion `rec_garchm`: $\sigma_0^2 = \omega + (\alpha+\beta)s_0$, $\varepsilon_t = r_t - \mu - \delta\sigma_t^2$ computed jointly with $\sigma_t^2$. Innovations: normal, t, GED, Hansen skew-t.
12 +
13 +## 3. Parameter space and admissibility
14 +- Parameters: `mu, delta, omega, alpha, beta` (+ distribution). `delta` is unconstrained.
15 +- Reparameterisation: $\omega = e^{\theta}$, persistence $s = \alpha + \beta = \sigma(\theta)$, share $\alpha/s = \sigma(\theta')$.
16 +- Admissibility flag: $\omega > 0$, $\alpha, \beta \ge 0$, $\alpha + \beta < 1$ (variance equation as GARCH(1,1)).
17 +
18 +## 4. Nesting and relations
19 +Nests `garch11` ($\delta = 0$). Only registry model whose conditional mean depends on the variance; its variance recursion is GARCH(1,1) but the residuals differ ($\varepsilon_t$ depends on δ).
20 +
21 +## 5. Estimation
22 +Custom MLE with a model-specific objective (`GARCHM11._negll`): $-\sum_t[\log f_z(\varepsilon_t/\sigma_t) - \tfrac12\log\sigma_t^2]$ where $(\sigma_t^2, \varepsilon_t)$ come from `rec_garchm`. L-BFGS-B (maxiter 2000) from three starts (δ, ω, α, β) ∈ {(0, 0.05v, 0.05, 0.90), (0.02, 0.02v, 0.10, 0.88), (−0.02, 0.2v, 0.15, 0.65)}, Nelder–Mead polish on failure, μ start = sample mean, backcast $s_0$ from the demeaned returns (computed once with μ = sample mean, i.e. ignoring the in-mean term, and reused for filtering/forecasting). Point estimates only; no standard errors.
23 +
24 +## 6. Forecasting
25 +`GARCHM11.forecast` recomputes $(\sigma_t^2, \varepsilon_t)$ with `rec_garchm`, then $\hat\sigma^2_{t+1|t} = \omega + \alpha\varepsilon_t^2 + \beta\sigma_t^2$ and $\hat\sigma^2_{t+k|t} = \omega + (\alpha+\beta)\hat\sigma^2_{t+k-1|t}$ (identical to GARCH(1,1)); mean forecast $\hat\mu_{t+1} = \mu + \delta\hat\sigma^2_{t+1|t}$ (used for VaR). Cumulative $h$-day variance = sum of per-step variances (the variance of the h-day return induced by the time-varying mean $\delta\sigma^2_{t+k}$ is ignored, as for all models). Method: `analytic`.
26 +
27 +## 7. Implementation
28 +- `src/wp46/models/custom.py::GARCHM11` (numba `rec_garchm`).
29 +- Cross-check: **none** — `garchm11` is not in `results/metrics/rugarch_crosscheck.json` (rugarch `archm` was not included in `tests/replication/rugarch_crosscheck.R`).
30 +- Tests: **no unit, simulation or integration test references `GARCHM11`** (it is absent from `tests/unit/test_custom_models.py` imports and parametrisations). This is a gap relative to CLAUDE.md rule 6 — recorded in `_discrepancies.md`.
31 +
32 +## 8. Known limitations / failure modes
33 +δ is weakly identified on daily data (risk-premium effects are second order; literature matrix: "low priority in a variance-forecast benchmark"); the joint mean/variance likelihood can be flat in δ and produce spurious large |δ| with near-integrated variance. The backcast ignores δ (minor). Positivity of $\sigma_t^2$ is guaranteed by the reparameterisation.
34 +
35 +## 9. Computational footprint
36 +Analytic; 5–7 parameters; the recursion must recompute residuals at each likelihood evaluation (same cost order as `garch11` custom). Not in the pilot.
37 +
38 +## 10. Literature expectations
39 +`engle1987estimating` motivate ARCH-M for term premia, not for variance forecasting; the literature matrix states the mean effect on h-day variance is second order. Expectation: variance forecasts essentially equal to GARCH(1,1); any difference comes from estimation noise in δ (H5: more failures, no accuracy gain).
added model_cards/gjr11.md +42 −0
@@ -0,0 +1,42 @@
1 +# Model card — `gjr11` (GJR-GARCH(1,1))
2 +
3 +## 1. Identity
4 +- **model_id:** `gjr11` · **family:** asymmetric · **tier:** A · **status:** implemented · **impl:** arch 8.0.0 (`GARCH(p=1, o=1, q=1)`); a custom twin `gjr11_custom` (`src/wp46/models/custom.py::GJR11`) exists for cross-checks and simulation tests only.
5 +- **Label:** GJR-GARCH(1,1) · **distributions:** normal, t, ged, skewt
6 +- **Seminal citation:** `glosten1993relation`
7 +- **Other references:** `engle1993measuring` (news-impact curve, sign-bias tests), `zakoian1994threshold` (TGARCH — not the same model, see `tarch11`), `ling2002stationarity` (moments of asymmetric GARCH), `hansen2005forecast`, `brownlees2011practical`, `bollerslev2010glossary` (naming: EViews "TARCH" = GJR).
8 +
9 +## 2. Definition (arch parameterisation)
10 +$$r_t = \mu + \varepsilon_t,\ \varepsilon_t = \sigma_t z_t,\qquad \sigma_t^2 = \omega + \alpha\,\varepsilon_{t-1}^2 + \gamma\,\varepsilon_{t-1}^2\,\mathbb 1\{\varepsilon_{t-1} < 0\} + \beta\,\sigma_{t-1}^2 .$$
11 +arch `garch_recursion` with power 2: pre-sample terms use the backcast for $\varepsilon^2$ and $\sigma^2$ and $0.5\times$ backcast for the asymmetric term. Innovations: normal, t, GED, Hansen skew-t.
12 +
13 +## 3. Parameter space and admissibility
14 +- Parameters: `mu, omega, alpha[1], gamma[1], beta[1]` (+ distribution).
15 +- arch bounds: $\omega \in [10^{-8}v, 10v]$, $\alpha \in [0,1]$, $\gamma \in [-1, 2]$, $\beta \in [0,1]$; linear constraints $\omega > 0$, $\alpha \ge 0$, $\alpha + \gamma \ge 0$, $\beta \ge 0$, $\alpha + \tfrac12\gamma + \beta < 1$ (arch uses the symmetric weight ½ in the optimizer constraint for every distribution).
16 +- Admissibility flag (`ArchModel.fit`): $\alpha + \beta + 0.5\gamma < 1$ — the covariance-stationarity condition under a **symmetric** innovation law. For the skew-t the exact condition is $\alpha + \beta + \kappa\gamma < 1$ with $\kappa = E[z^2\mathbb 1\{z<0\}] \neq 0.5$ (`distributions.neg_half_moment`); it is used by the custom twin (`GJR11.admissible`) but **not** by the registry implementation (recorded in `_discrepancies.md`).
17 +- Custom twin reparameterisation: $\omega = e^{\theta}$, persistence $s = \sigma(\theta)$, softmax shares of $s$ among $(\alpha, \kappa\gamma, \beta)$.
18 +
19 +## 4. Nesting and relations
20 +Nests `garch11` (γ = 0). Special case of `aparch11` with δ = 2 and $\gamma_{APARCH}$ mapped to the GJR indicator form only approximately (APARCH uses $(|\varepsilon| - \gamma\varepsilon)^\delta$, which for δ = 2 is $(1\mp\gamma)^2\varepsilon^2$: GJR corresponds to $\alpha_{GJR} = \alpha(1-\gamma)^2$, $\alpha_{GJR}+\gamma_{GJR} = \alpha(1+\gamma)^2$). Not the same as `tarch11` (threshold on σ, power 1). Same news-impact asymmetry as `nagarch11`/`qgarch11` but with a kink rather than a shift.
21 +
22 +## 5. Estimation
23 +arch `ConstantMean` + `GARCH(1,1,1)` + distribution; SLSQP (`fit(disp="off", options={"maxiter": 1000})`) with the bounds/constraints of §3; starting values from arch's grid (α ∈ {0.01, 0.05, 0.1, 0.2}, γ same grid, persistence ∈ {0.5, 0.7, 0.9, 0.98}; best Gaussian log-likelihood); backcast = exponentially weighted mean of the first min(75, T) squared residuals (0.94ⁱ); ≤ 2 restarts (previous estimate × (0.9 + 0.2(i+1)/3), maxiter 2000) when `convergence_flag ≠ 0`. Only point estimates are used in the rolling benchmark; daily filtering with `am.fix(params)`.
24 +
25 +## 6. Forecasting
26 +arch `GARCH._analytic_forecast` (power 2): $\hat\sigma^2_{t+1|t} = \omega + \alpha\varepsilon_t^2 + \gamma\varepsilon_t^2\mathbb 1\{\varepsilon_t<0\} + \beta\sigma_t^2$ and, for $k \ge 2$,
27 +$$\hat\sigma^2_{t+k|t} = \omega + (\alpha + 0.5\,\gamma + \beta)\,\hat\sigma^2_{t+k-1|t},$$
28 +because arch sets the future asymmetric term to $0.5\times$ the variance forecast (`_asym_resids[h+m] = sqrt(0.5 * forecast)`). This is exact for symmetric $z$ (normal, t, GED) and **approximate for the skew-t** (exact weight $\kappa = E[z^2\mathbb 1\{z<0\}]$, > 0.5 for λ < 0). The custom twin implements $\hat\sigma^2_{t+k|t} = \omega + (\alpha + \kappa\gamma + \beta)\hat\sigma^2_{t+k-1|t}$ with κ from `neg_half_moment` (D-09 describes this κ-version; the scored `gjr11` is the arch version). Cumulative = sum over k ≤ h. Method: `analytic`.
29 +
30 +## 7. Implementation
31 +- `src/wp46/models/arch_models.py` (`SPECS["gjr11"]`), arch 8.0.0 (`sheppard2025arch`); custom twin `custom.py::GJR11` (`model_id = "gjr11_custom"`, not in the registry).
32 +- Cross-check (`results/metrics/rugarch_crosscheck.json`, rugarch `gjrGARCH`, t): −3372.9536 (ours, arch) vs −3372.9905 (rugarch), **difference +0.0369**; parameters agree to 4 decimals (α 0.04267/0.04264, γ 0.06203/0.06202, β 0.90036/0.90040, ν 7.002/7.010).
33 +- Tests: `tests/replication/test_rugarch.py` (arch GJR vs rugarch, assert ours ≥ rugarch − 1); custom twin: `tests/unit/test_custom_models.py::test_nesting_and_admissibility[GJR11]`, `test_no_lookahead_in_forecasts`, `test_edge_cases_constant_and_extreme` (spike of 60 % at t = 700), `tests/simulation/test_recovery.py::test_gjr_recovery_median_over_replications` (12 seeded replications, T = 4,000, t(6) innovations; median within 0.02–0.03 of (0.04, 0.03, 0.10, 0.88)); driver: `tests/integration/test_rolling_driver.py::test_driver_tier_b_and_failure_flags` (`gjr11`, t). The fixture of the rugarch cross-check is itself GJR-t data.
34 +
35 +## 8. Known limitations / failure modes
36 +α often converges to the zero bound on equities (all news impact loaded on γ) — a boundary optimum flagged by SLSQP; near-integrated $\alpha + \gamma/2 + \beta$; the symmetric-κ forecast/admissibility rule under skew-t (above); the optimizer bound $\gamma \le 2$. Positivity requires $\alpha + \gamma \ge 0$ (imposed).
37 +
38 +## 9. Computational footprint
39 +Analytic; 5–7 parameters. Pilot median fit time per refit (t innovations): 0.0081 s (CL), 0.0101 s (EURUSD).
40 +
41 +## 10. Literature expectations
42 +`hansen2005forecast`: on IBM, models with a leverage effect (GJR among them) significantly outperform GARCH(1,1); on DM/$ they do not. `brownlees2011practical`, `awartani2005predicting`: asymmetric GARCH best overall for equity indices, especially in stressed periods. Pre-registered H1 (asymmetry) and H6 (regimes) concern this model directly; the matrix notes the GJR/TGARCH naming confusion that the registry resolves by keeping both definitions.
added model_cards/igarch11.md +39 −0
@@ -0,0 +1,39 @@
1 +# Model card — `igarch11` (Integrated GARCH(1,1))
2 +
3 +## 1. Identity
4 +- **model_id:** `igarch11` · **family:** classical · **tier:** A · **status:** implemented · **impl:** custom (`src/wp46/models/custom.py::IGARCH11`)
5 +- **Label:** IGARCH(1,1) · **distributions:** normal, t, ged, skewt
6 +- **Seminal citation:** `engle1986modelling` (Engle & Bollerslev 1986)
7 +- **Other references:** `nelson1990stationarity` (strict stationarity of IGARCH), `lumsdaine1996consistency`, `lee1994asymptotic`, `jensen2004asymptotic` (QMLE under unit persistence); `lamoureux1990persistence`, `mikosch2004nonstationarities`, `hillebrand2005neglecting` (spurious IGARCH from breaks); `jpmorgan1996riskmetrics` (EWMA, nested).
8 +
9 +## 2. Definition
10 +$$r_t = \mu + \varepsilon_t,\ \varepsilon_t = \sigma_t z_t,\qquad \sigma_t^2 = \omega + \alpha\,\varepsilon_{t-1}^2 + (1-\alpha)\,\sigma_{t-1}^2 ,$$
11 +i.e. GARCH(1,1) with $\beta = 1 - \alpha$ imposed (`rec_garch(eps, omega, alpha, 1 - alpha, s0)`), $\sigma_0^2 = \omega + s_0$ ($s_0$ = backcast, since $\alpha + \beta = 1$). Innovations: normal, t(ν), GED(ν), Hansen skew-t(η, λ) (standardised, `distributions.py`).
12 +
13 +## 3. Parameter space and admissibility
14 +- Parameters: `mu, omega, alpha` (+ `nu` | `eta, lambda`).
15 +- Reparameterisation (unconstrained θ): $\omega = e^{\theta_0}$, $\alpha = \sigma(\theta_1) \in (0,1)$ (logistic); t: $\nu = 2.05 + e^{\theta}$; GED: $\nu = 0.3 + e^{\theta}$; skew-t: $\eta = 2.05 + e^{\theta}$, $\lambda = 0.99\tanh(\theta')$.
16 +- Admissibility flag: $\omega > 0$ and $0 < \alpha < 1$. No covariance stationarity (unconditional variance does not exist); strictly stationary for $\alpha < 1$ (`nelson1990stationarity`).
17 +
18 +## 4. Nesting and relations
19 +Boundary case of `garch11` ($\alpha + \beta = 1$); nests `ewma094` ($\omega = 0$, $\alpha = 0.06$ — registry `ewma094: nested_in: igarch11`). Variance forecasts grow linearly in the horizon (unit root in $\varepsilon_t^2$). Related to `figarch11` with d = 1.
20 +
21 +## 5. Estimation
22 +Custom MLE (`CustomModel.fit`): objective $-\sum_t[\log f_z(z_t) - \tfrac12\log\sigma_t^2]$ with numba recursion; L-BFGS-B (maxiter 2000, ftol 1e-10, gtol 1e-6) on θ from three starting vectors (ω, α) ∈ {(0.01 v, 0.06), (0.003 v, 0.03), (0.03 v, 0.12)} with $v = \operatorname{var}(r)$ (at least two starts are tried; the third only if the second fails), Nelder–Mead polish (maxiter 4000) if L-BFGS-B does not report success; μ starts at the sample mean; distribution starts ν = 6 / 1.5 / (6, −0.05). Initial variance: arch-style backcast of the demeaned returns ($\sum_{i<75} w_i (r_i - \bar r)^2$, $w_i \propto 0.94^i$), stored in `aux["backcast"]`. `converged` = optimizer success with finite objective; inadmissible optima kept and flagged. No standard errors; point estimates only.
23 +
24 +## 6. Forecasting
25 +`IGARCH11.step_forecast`: $\hat\sigma^2_{t+1|t} = \omega + \alpha\varepsilon_t^2 + (1-\alpha)\sigma_t^2$, $\hat\sigma^2_{t+k|t} = \omega + \hat\sigma^2_{t+k-1|t}$ for $k = 2..22$ (linear growth by ω per day). Cumulative $h$-day variance $= h\hat\sigma^2_{t+1|t} + \omega h(h-1)/2$. Method: `analytic`.
26 +
27 +## 7. Implementation
28 +- `src/wp46/models/custom.py::IGARCH11` (custom, numba `rec_garch`), standardised densities from `src/wp46/models/distributions.py` (equal to arch's to 1e-12, `tests/unit/test_distributions.py`).
29 +- Cross-check (`results/metrics/rugarch_crosscheck.json`, rugarch `iGARCH`, Student-t, shared fixture): log-likelihood −3385.4670 (ours) vs −3385.5171 (rugarch), **difference +0.0502**.
30 +- Tests: `tests/replication/test_rugarch.py` (asserts ours ≥ rugarch − 1.0); `tests/unit/test_custom_models.py::test_nesting_and_admissibility[IGARCH11]` (convergence, admissibility, positive finite 22-step forecasts; excluded from the "nests GARCH(1,1)" log-likelihood assertion). No simulation-recovery test.
31 +
32 +## 8. Known limitations / failure modes
33 +Imposes unit persistence even when the data are stationary: on the GJR-t fixture the IGARCH likelihood is 6.0 units below GARCH(1,1) (−3385.47 vs −3379.46). Forecast variance grows without bound with h (h = 22 cumulative variance inflated by $\omega \cdot 231$). $\omega$ near zero makes the model indistinguishable from EWMA. Linear growth makes long-horizon forecasts sensitive to $\hat\omega$.
34 +
35 +## 9. Computational footprint
36 +Analytic recursion and forecasts; 3–5 parameters; L-BFGS-B with numerical gradients. Not in the pilot; cost comparable to `garch11` custom estimation (a few milliseconds per refit on a 1,000-day window).
37 +
38 +## 10. Literature expectations
39 +Near-integrated GARCH estimates are common in long daily samples (`engle1986modelling`, `lamoureux1990persistence`); `hansen2005forecast` include IGARCH among the models that do not beat GARCH(1,1). Expectation: close to GARCH(1,1) at h = 1 for highly persistent instruments, worse at h = 22 because of the absent mean reversion (literature matrix §a.1).
added model_cards/nagarch11.md +41 −0
@@ -0,0 +1,41 @@
1 +# Model card — `nagarch11` (NAGARCH(1,1), Engle–Ng nonlinear asymmetric GARCH)
2 +
3 +## 1. Identity
4 +- **model_id:** `nagarch11` · **family:** asymmetric · **tier:** A · **status:** implemented · **impl:** custom (`src/wp46/models/custom.py::NAGARCH11`)
5 +- **Label:** NAGARCH(1,1) (Engle-Ng) · **distributions:** normal, t, ged, skewt
6 +- **Seminal citation:** `engle1993measuring`
7 +- **Other references:** `higgins1992class` (NARCH — a *different* "NGARCH", literature-matrix caveat #3), `glosten1993relation`, `sentana1995quadratic` (QGARCH, the other shifted/linear-asymmetry form), `brownlees2011practical` (NGARCH in their comparison).
8 +
9 +## 2. Definition
10 +$$r_t = \mu + \varepsilon_t,\ \varepsilon_t = \sigma_t z_t,\qquad \sigma_t^2 = \omega + \alpha\big(\varepsilon_{t-1} - \theta\,\sigma_{t-1}\big)^2 + \beta\,\sigma_{t-1}^2 ,$$
11 +`rec_nagarch` with $\sigma_0^2 = \omega + [\alpha(1+\theta^2) + \beta]\,s_0$ ($s_0$ = backcast). Equivalent to rugarch's fGARCH/NAGARCH $\sigma_t^2 = \omega + \alpha\sigma_{t-1}^2(z_{t-1} - \eta)^2 + \beta\sigma_{t-1}^2$ with $\eta = \theta$. Innovations: normal, t, GED, Hansen skew-t.
12 +
13 +## 3. Parameter space and admissibility
14 +- Parameters: `mu, omega, alpha, theta, beta` (+ distribution).
15 +- Reparameterisation: $\omega = e^{\theta_0}$; persistence $s = \alpha(1+\theta^2) + \beta = \sigma(\theta_1) \in (0,1)$; share $w = \alpha(1+\theta^2)/s = \sigma(\theta_2)$; $\theta$ unconstrained. Hence $\alpha = s w/(1+\theta^2)$, $\beta = s(1-w)$.
16 +- Admissibility flag: $\omega > 0$, $\alpha, \beta \ge 0$, $\alpha(1+\theta^2) + \beta < 1$ (covariance stationarity under $E z = 0$, $E z^2 = 1$ — holds for all four laws, including Hansen's skew-t which is standardised to zero mean and unit variance).
17 +
18 +## 4. Nesting and relations
19 +Nests `garch11` (θ = 0). News-impact curve = GARCH parabola shifted to the right by $\theta\sigma_{t-1}$ (asymmetry via a shift, not a kink as in `gjr11`); QGARCH is the linear-term version ($\gamma\varepsilon_{t-1}$ instead of the shift, same quadratic form with $\gamma = -2\alpha\theta\sigma_{t-1}$ varying over time).
20 +
21 +## 5. Estimation
22 +Custom MLE (`CustomModel.fit`): L-BFGS-B on θ (maxiter 2000, ftol 1e-10, gtol 1e-6) from three starts (ω, α, θ, β) ∈ {(0.05v, 0.05, 0.5, 0.88), (0.02v, 0.08, 1.0, 0.85), (0.1v, 0.1, 0.0, 0.8)}, Nelder–Mead polish on failure; μ start = sample mean, ν/η/λ starts 6 / 1.5 / (6, −0.05); backcast $s_0$ = arch-style exponentially weighted mean of the first 75 squared demeaned returns. Point estimates only.
23 +
24 +## 6. Forecasting
25 +`NAGARCH11.step_forecast`: $\hat\sigma^2_{t+1|t} = \omega + \alpha(\varepsilon_t - \theta\sigma_t)^2 + \beta\sigma_t^2$; for $k \ge 2$,
26 +$$\hat\sigma^2_{t+k|t} = \omega + \big[\alpha(1+\theta^2) + \beta\big]\,\hat\sigma^2_{t+k-1|t},$$
27 +exact because $E[(\varepsilon - \theta\sigma)^2 \mid \sigma^2] = \sigma^2(1 + \theta^2)$ when $E z = 0$, $E z^2 = 1$. Cumulative = sum over k ≤ h. Method: `analytic`.
28 +
29 +## 7. Implementation
30 +- `src/wp46/models/custom.py::NAGARCH11` (numba `rec_nagarch`).
31 +- Cross-check (`results/metrics/rugarch_crosscheck.json`, rugarch `fGARCH` submodel `NAGARCH`, t): −3373.3123 (ours) vs −3373.3505 (rugarch), **difference +0.0382**.
32 +- Tests: `tests/replication/test_rugarch.py` (assert ours ≥ rugarch − 1); `tests/unit/test_custom_models.py::test_nesting_and_admissibility[NAGARCH11]` (log-likelihood ≥ GARCH(1,1) − 0.5, admissible, positive 22-step forecasts), `test_no_lookahead_in_forecasts`; `tests/simulation/test_recovery.py::test_nagarch_recovery` (T = 5,000, normal, θ = 0.8 recovered within 0.3, β within 0.05); pilot `nagarch11__t`: 207/207 refits `ok` on CL.
33 +
34 +## 8. Known limitations / failure modes
35 +θ is weakly identified when α is small (the shift multiplies α); large |θ| with tiny α yields flat likelihoods and slow L-BFGS-B progress (pilot max fit time 0.17 s on CL vs median 0.009 s). Near-integrated persistence as in GARCH. The sigmoid reparameterisation cannot return exactly $s = 1$ (inadmissible optima can only arise from numerical failure).
36 +
37 +## 9. Computational footprint
38 +Analytic; 5–7 parameters. Pilot median fit time per refit (t): 0.0089 s (CL), 0.0218 s (EURUSD).
39 +
40 +## 10. Literature expectations
41 +`engle1993measuring` show the NAGARCH news-impact curve fits Japanese equity data better than GARCH; `brownlees2011practical` include NGARCH among the best performers on US equities; `hansen2005forecast` find leverage specifications superior on IBM only. Expectation: comparable to GJR/EGARCH at h = 1 on equities (H1); exact analytic multi-step forecasts are an advantage over the simulation-based asymmetric models at h = 22 (H5).
added model_cards/qgarch11.md +40 −0
@@ -0,0 +1,40 @@
1 +# Model card — `qgarch11` (QGARCH(1,1), Sentana's quadratic GARCH)
2 +
3 +## 1. Identity
4 +- **model_id:** `qgarch11` · **family:** asymmetric · **tier:** A · **status:** implemented · **impl:** custom (`src/wp46/models/custom.py::QGARCH11`)
5 +- **Label:** QGARCH(1,1) (Sentana) · **distributions:** normal, t, ged, skewt
6 +- **Seminal citation:** `sentana1995quadratic`
7 +- **Other references:** `engle1993measuring` (NAGARCH, the shifted form), `glosten1993relation`.
8 +
9 +## 2. Definition
10 +$$r_t = \mu + \varepsilon_t,\ \varepsilon_t = \sigma_t z_t,\qquad \sigma_t^2 = \omega + \alpha\,\varepsilon_{t-1}^2 + \gamma\,\varepsilon_{t-1} + \beta\,\sigma_{t-1}^2 ,$$
11 +`rec_qgarch` with $\sigma_0^2 = \omega + (\alpha+\beta)s_0$ and a floor $\sigma_t^2 \ge 10^{-12}$ inside the recursion. Innovations: normal, t, GED, Hansen skew-t.
12 +
13 +## 3. Parameter space and admissibility
14 +- Parameters: `mu, omega, alpha, gamma, beta` (+ distribution).
15 +- Positivity: $\omega + \alpha\varepsilon^2 + \gamma\varepsilon > 0$ for all ε ⇔ $\gamma^2 < 4\alpha\omega$ (minimum of the quadratic at $\varepsilon = -\gamma/(2\alpha)$ equals $\omega - \gamma^2/(4\alpha)$).
16 +- Reparameterisation: $\omega = e^{\theta_0}$, $s = \alpha+\beta = \sigma(\theta_1)$, $\alpha = s\,\sigma(\theta_2)$, $\gamma = 2\sqrt{\alpha\omega}\,\tanh(\theta_3)$ (so the positivity condition holds by construction).
17 +- Admissibility flag: $\omega > 0$, $\alpha > 0$ (strict), $\beta \ge 0$, $\alpha + \beta < 1$, $\gamma^2 < 4\alpha\omega$.
18 +
19 +## 4. Nesting and relations
20 +Nests `garch11` (γ = 0). Linear-asymmetry counterpart of `nagarch11` (news-impact parabola shifted by $-\gamma/(2\alpha)$, a constant shift in ε rather than one proportional to σ). Sentana's general quadratic ARCH includes cross terms not used here.
21 +
22 +## 5. Estimation
23 +Custom MLE (`CustomModel.fit`): L-BFGS-B on θ from three starts (ω, α, γ, β) ∈ {(0.05v, 0.06, −0.3√(0.05v·0.06), 0.90), (0.03v, 0.10, 0, 0.87), (0.1v, 0.1, −0.5√(0.1v·0.1), 0.8)}, Nelder–Mead polish on failure; μ start = sample mean; backcast $s_0$ arch-style. Point estimates only.
24 +
25 +## 6. Forecasting
26 +`QGARCH11.step_forecast`: $\hat\sigma^2_{t+1|t} = \max(\omega + \alpha\varepsilon_t^2 + \gamma\varepsilon_t + \beta\sigma_t^2,\ 10^{-12})$; for $k \ge 2$, $\hat\sigma^2_{t+k|t} = \omega + (\alpha+\beta)\hat\sigma^2_{t+k-1|t}$ (uses $E[\varepsilon_{t+j}\mid\mathcal F_t] = 0$, exact for all four zero-mean laws). Cumulative = sum over k ≤ h. Method: `analytic`.
27 +
28 +## 7. Implementation
29 +- `src/wp46/models/custom.py::QGARCH11` (numba `rec_qgarch`).
30 +- Cross-check: **none** — QGARCH is in neither arch 8.0.0 nor rugarch 1.5-6; not in `results/metrics/rugarch_crosscheck.json`. The registry marks it `custom (validated vs arch/rugarch)` by the generic `impl` comment, but no library reference exists for this architecture (recorded in `_discrepancies.md`; it is the only scored custom architecture without any library counterpart besides `gas_t11` and `garchx_rv11`).
31 +- Tests: `tests/unit/test_custom_models.py::test_nesting_and_admissibility[QGARCH11]` (converged, admissible, log-likelihood ≥ GARCH(1,1) − 0.5, positive finite 22-step forecasts). No simulation-recovery test, no rugarch test.
32 +
33 +## 8. Known limitations / failure modes
34 +The positivity bound $\gamma^2 < 4\alpha\omega$ is restrictive when ω is small (highly persistent series): γ is then forced toward zero and the model collapses to GARCH(1,1), so the asymmetry cannot be large on persistent equity series. Admissibility requires α > 0 strictly (α → 0 makes γ → 0 by construction). Floors at 1e-12 silently mask any numerical negativity.
35 +
36 +## 9. Computational footprint
37 +Analytic; 5–7 parameters; same cost order as `nagarch11`. Not in the pilot.
38 +
39 +## 10. Literature expectations
40 +`sentana1995quadratic` shows the quadratic form captures asymmetry with a linear term; the literature matrix lists QGARCH as "custom (if time permits)" and notes the positivity restriction. `hansen2005forecast` do not include QGARCH. Expectation: between GARCH(1,1) and NAGARCH on equities; the positivity constraint is the main practical limitation (H5: more boundary optima).
added model_cards/tarch11.md +39 −0
@@ -0,0 +1,39 @@
1 +# Model card — `tarch11` (TGARCH(1,1), Zakoïan threshold on the conditional standard deviation)
2 +
3 +## 1. Identity
4 +- **model_id:** `tarch11` · **family:** asymmetric · **tier:** A · **status:** implemented · **impl:** arch 8.0.0 (`GARCH(p=1, o=1, q=1, power=1.0)`, arch name "TARCH/ZARCH")
5 +- **Label:** TGARCH(1,1) (Zakoian, threshold on sigma) · **distributions:** normal, t, ged, skewt
6 +- **Seminal citation:** `zakoian1994threshold`
7 +- **Other references:** `glosten1993relation` (GJR, the **different** variance-threshold model), `ding1993long` (APARCH nests it with δ = 1), `taylor2008modelling`/`schwert1989why` (absolute-value dynamics), `bollerslev2010glossary` (definition caveat #1 of the literature matrix: "TGARCH ≠ GJR").
8 +
9 +## 2. Definition (arch parameterisation, power = 1)
10 +$$r_t = \mu + \varepsilon_t,\ \varepsilon_t = \sigma_t z_t,\qquad \sigma_t = \omega + \alpha\,|\varepsilon_{t-1}| + \gamma\,|\varepsilon_{t-1}|\,\mathbb 1\{\varepsilon_{t-1} < 0\} + \beta\,\sigma_{t-1} .$$
11 +Mapping to Zakoïan's $\sigma_t = \omega + \alpha^+\varepsilon_{t-1}^+ - \alpha^-\varepsilon_{t-1}^- + \beta\sigma_{t-1}$ ($\varepsilon^+ = \max(\varepsilon,0)$, $\varepsilon^- = \min(\varepsilon,0)$): $\alpha^+ = \alpha$, $\alpha^- = \alpha + \gamma$. arch's recursion runs on $\sigma_t^{1}$ (`fresids = |ε|`), then squares; pre-sample terms use the power-1 backcast (weighted mean of $|\varepsilon|$) and $0.5\times$ it for the asymmetric term. Innovations: normal, t, GED, Hansen skew-t.
12 +
13 +## 3. Parameter space and admissibility
14 +- Parameters: `mu, omega, alpha[1], gamma[1], beta[1]` (+ distribution).
15 +- arch bounds: $\omega \in [10^{-8}v_1, 10v_1]$ with $v_1 = \overline{|\varepsilon|}$, $\alpha \in [0,1]$, $\gamma \in [-1, 2]$, $\beta \in [0,1]$; constraints $\omega > 0$, $\alpha \ge 0$, $\alpha+\gamma \ge 0$ (so $\alpha^- \ge 0$), $\beta \ge 0$, $\alpha + \tfrac12\gamma + \beta < 1$. The last constraint is arch's generic GARCH constraint applied to power-1 coefficients; it is **not** the second-order stationarity condition of the TGARCH (which involves $E[(\alpha|z| + \gamma|z|\mathbb 1\{z<0\} + \beta)^2] < 1$, see `he1999properties`/`ling2002necessary` for the APARCH family).
16 +- Admissibility flag: **always `True`** in `ArchModel.fit` (no model-specific check for `tarch11`; only the optimizer constraints apply). Recorded in `_discrepancies.md`.
17 +
18 +## 4. Nesting and relations
19 +APARCH with δ = 1 and $\gamma_{APARCH}$ mapped via $(1-\gamma_A)\alpha_A = \alpha$, $(1+\gamma_A)\alpha_A = \alpha + \gamma$. Nests `avgarch11` (γ = 0). Not nested in and not nesting `gjr11`/`garch11` (different power). Same sign asymmetry as GJR but on σ, which makes the variance response to a shock quadratic in $|\varepsilon|$ with a kink at zero.
20 +
21 +## 5. Estimation
22 +arch `ConstantMean` + `GARCH(1,1,1,power=1.0)` + distribution, SLSQP (`maxiter` 1000), grid starting values (same grid as GJR, target scaled to the power-1 moment), power-1 backcast, ≤ 2 restarts (`ArchModel.fit`). Point estimates only.
23 +
24 +## 6. Forecasting
25 +h = 1: analytic ($\hat\sigma_{t+1|t}$ from the recursion, squared). **h > 1: simulation** (`SPECS["tarch11"]["analytic"] = False`; arch raises for analytic multi-step when power ≠ 2): 2,000 paths of $\sigma_{t+k}$ via `GARCH._simulate_paths` with power 1 (shock $= z\,\sigma$, $|{\rm shock}|$ and its negative part feed the next step), forecast $\hat\sigma^2_{t+k|t}$ = mean over paths of $\sigma_{t+k}^2$. Shocks: for the normal, `np.random.default_rng(seed).standard_normal` with seed = 46 + origin index (reproducible); **for t/GED/skew-t, `rng=None` → arch draws from the fitted distribution with an unseeded generator** (`Distribution(seed=None)` → `default_rng()`), so multi-step forecasts are not bit-reproducible (recorded in `_discrepancies.md`). Cumulative = sum over k ≤ h. Method recorded: `simulation` (h > 1).
26 +
27 +## 7. Implementation
28 +- `src/wp46/models/arch_models.py` (`SPECS["tarch11"]`), arch 8.0.0.
29 +- Cross-check: rugarch `fGARCH(TGARCH)`-t was fitted on the fixture (`results/metrics/rugarch_crosscheck.json["rugarch"]["tgarch11_t"]`, log-likelihood −3378.2657, converged) **but no arch fit of `tarch11` was computed in `tests/replication/test_rugarch.py`, so no comparison exists** (the `comparison` block has no `tgarch11_t` entry). Gap recorded in `_discrepancies.md`.
30 +- Tests: none dedicated (not in the pilot).
31 +
32 +## 8. Known limitations / failure modes
33 +Boundary optima at α = 0 (all asymmetry on γ); the power-1 recursion is less conditioned for very small |ε| (percent units mitigate); simulation forecasts carry Monte-Carlo error (2,000 paths) and, for non-normal laws, are not seeded; no admissibility flag beyond the optimizer constraints.
34 +
35 +## 9. Computational footprint
36 +Estimation like GJR; forecasts by simulation (22 × 2,000 draws per origin) — one of the slower forecast steps with `aparch11`, `egarch11`, `avgarch11`. No pilot timing.
37 +
38 +## 10. Literature expectations
39 +`zakoian1994threshold` and `hansen2005forecast` (who include "TGARCH"/"TS-GARCH" power-1 variants) report that absolute-value/threshold specifications are competitive on equities; the matrix flags the definitional ambiguity rather than a ranking. Expectation: close to GJR and APARCH on equities (H1), with the simulation step adding cost (H5).
added paper/appendices/data_provenance.tex +1 −0
@@ -0,0 +1 @@
1 +% appendix data_provenance placeholder
added paper/appendices/extra_tables.tex +1 −0
@@ -0,0 +1 @@
1 +% appendix extra_tables placeholder
added paper/appendices/formulas.tex +1 −0
@@ -0,0 +1 @@
1 +% appendix formulas placeholder
added paper/appendices/verification.tex +1 −0
@@ -0,0 +1 @@
1 +% appendix verification placeholder
added paper/figures/fig_coverage.pdf +0 −0

Binary file not shown.

added paper/figures/fig_timeline.pdf +0 −0

Binary file not shown.

added paper/figures/fig_volseries.pdf +0 −0

Binary file not shown.

added paper/main.tex +88 −0
@@ -0,0 +1,88 @@
1 +% ============================================================================
2 +% UQO Working Paper No. 46 — Four Decades of Conditional Volatility Modeling
3 +% ============================================================================
4 +\documentclass[12pt,letterpaper]{article}
5 +\usepackage[utf8]{inputenc}
6 +\usepackage[T1]{fontenc}
7 +\usepackage[english]{babel}
8 +\usepackage[letterpaper,top=1in,bottom=1in,left=1in,right=1in]{geometry}
9 +\usepackage{amsmath,amsthm}
10 +\usepackage{mathtools}
11 +\allowdisplaybreaks
12 +\usepackage{newtxtext}
13 +\usepackage[varvw,cmintegrals,cmbraces]{newtxmath}
14 +\usepackage{bm}
15 +\usepackage{dsfont}
16 +\usepackage{microtype}
17 +\usepackage{setspace}
18 +\onehalfspacing
19 +\usepackage{booktabs,tabularx,multirow,threeparttable,longtable,adjustbox,rotating}
20 +\usepackage{siunitx}
21 +\sisetup{input-symbols = {()}, table-format = -1.3, parse-numbers = false}
22 +\usepackage{graphicx,subcaption,float}
23 +\usepackage[dvipsnames]{xcolor}
24 +\usepackage[pdftitle={Four Decades of Conditional Volatility Modeling: A Comprehensive Empirical Benchmark Across Assets, Frequencies, and Market Regimes},
25 + pdfauthor={Simon-Pierre Boucher}, colorlinks=true, linkcolor=NavyBlue, citecolor=NavyBlue, urlcolor=NavyBlue, breaklinks=true]{hyperref}
26 +\usepackage[authoryear,round,semicolon]{natbib}
27 +\bibliographystyle{aer}
28 +\usepackage[font=small,labelfont=bf,labelsep=period,justification=justified,singlelinecheck=false]{caption}
29 +\usepackage{fancyhdr}
30 +\pagestyle{fancy}\fancyhf{}\renewcommand{\headrulewidth}{0pt}\fancyfoot[C]{\thepage}
31 +\usepackage{titlesec}
32 +\titleformat{\section}{\large\bfseries}{\thesection.}{0.5em}{}
33 +\titleformat{\subsection}{\normalsize\bfseries}{\thesubsection.}{0.5em}{}
34 +\titleformat{\subsubsection}{\normalsize\itshape}{\thesubsubsection.}{0.5em}{}
35 +\usepackage[toc,page]{appendix}
36 +\usepackage{enumitem}
37 +\setlist{nosep,leftmargin=*}
38 +\theoremstyle{definition}
39 +\newtheorem{hypothesis}{Hypothesis}
40 +
41 +% ============================================================================
42 +% METADATA (numbers.tex is generated by scripts/build_paper.py — never edited by hand)
43 +% ============================================================================
44 +\input{numbers}
45 +\newcommand{\WPnumber}{46}
46 +\newcommand{\WPtitle}{Four Decades of Conditional Volatility Modeling: A Comprehensive Empirical Benchmark Across Assets, Frequencies, and Market Regimes}
47 +\newcommand{\WPsubtitle}{Does Anything Beat GARCH(1,1) Once Information Sets, Failures and Compute Are Priced In?}
48 +\newcommand{\WPdate}{November 2026}
49 +\newcommand{\WPversion}{1.0}
50 +\newcommand{\WPabstract}{\input{sections/abstract}}
51 +\newcommand{\WPkeywords}{volatility forecasting, GARCH, realized volatility, model confidence set, pre-registration, forecast evaluation, tail risk}
52 +\newcommand{\WPjel}{C22, C52, C53, C58, G17}
53 +\newcommand{\WPauthor}{Simon-Pierre Boucher}
54 +\newcommand{\WPaffiliation}{Département des sciences administratives\\Université du Québec en Outaouais}
55 +\newcommand{\WPemail}{simon-pierre.boucher@uqo.ca}
56 +\newcommand{\WPaddress}{Gatineau -- Pavillon Alexandre-Taché\\283, boulevard Alexandre-Taché\\Gatineau, Québec, Canada J9A 1L8}
57 +
58 +% --- Notation ---
59 +\newcommand{\E}{\mathbb{E}}
60 +\newcommand{\Var}{\mathrm{Var}}
61 +\newcommand{\Prob}{\mathbb{P}}
62 +\newcommand{\ind}[1]{\mathds{1}\!\left[#1\right]}
63 +\newcommand{\RV}{\mathrm{RV}}
64 +\newcommand{\QLIKE}{\mathrm{QLIKE}}
65 +\newcommand{\F}{\mathcal{F}}
66 +
67 +\begin{document}
68 +\input{sections/titlepage}
69 +\input{sections/introduction}
70 +\input{sections/literature}
71 +\input{sections/taxonomy}
72 +\input{sections/data}
73 +\input{sections/design}
74 +\input{sections/results_daily}
75 +\input{sections/results_realized}
76 +\input{sections/results_tail_multivariate}
77 +\input{sections/results_heterogeneity}
78 +\input{sections/frontier}
79 +\input{sections/conclusion}
80 +\bibliography{references}
81 +\clearpage
82 +\begin{appendices}
83 +\input{appendices/formulas}
84 +\input{appendices/data_provenance}
85 +\input{appendices/verification}
86 +\input{appendices/extra_tables}
87 +\end{appendices}
88 +\end{document}
added paper/numbers.tex +1 −0
@@ -0,0 +1 @@
1 +% generated by scripts/build_paper.py
added paper/sections/abstract.tex +1 −0
@@ -0,0 +1 @@
1 +% abstract — generated draft placeholder (to be written after the benchmark)
added paper/sections/conclusion.tex +1 −0
@@ -0,0 +1 @@
1 +% conclusion — generated draft placeholder (to be written after the benchmark)
added paper/sections/data.tex +1 −0
@@ -0,0 +1 @@
1 +% data — generated draft placeholder (to be written after the benchmark)
added paper/sections/design.tex +1 −0
@@ -0,0 +1 @@
1 +% design — generated draft placeholder (to be written after the benchmark)
added paper/sections/frontier.tex +1 −0
@@ -0,0 +1 @@
1 +% frontier — generated draft placeholder (to be written after the benchmark)
added paper/sections/introduction.tex +1 −0
@@ -0,0 +1 @@
1 +% introduction — generated draft placeholder (to be written after the benchmark)
added paper/sections/literature.tex +1 −0
@@ -0,0 +1 @@
1 +% literature — generated draft placeholder (to be written after the benchmark)
added paper/sections/results_daily.tex +1 −0
@@ -0,0 +1 @@
1 +% results_daily — generated draft placeholder (to be written after the benchmark)
added paper/sections/results_heterogeneity.tex +1 −0
@@ -0,0 +1 @@
1 +% results_heterogeneity — generated draft placeholder (to be written after the benchmark)
added paper/sections/results_realized.tex +1 −0
@@ -0,0 +1 @@
1 +% results_realized — generated draft placeholder (to be written after the benchmark)
added paper/sections/results_tail_multivariate.tex +1 −0
@@ -0,0 +1 @@
1 +% results_tail_multivariate — generated draft placeholder (to be written after the benchmark)
added paper/sections/taxonomy.tex +1 −0
@@ -0,0 +1 @@
1 +% taxonomy — generated draft placeholder (to be written after the benchmark)
added results/manifests/figure_audit.csv +5 −0
@@ -0,0 +1,5 @@
1 +figure,sources,n,note
2 +fig_timeline.pdf,configs/model_registry.yaml,26,
3 +fig_coverage.pdf,results/manifests/panels_manifest.json,42,
4 +fig_volseries.pdf,panels/SPY.parquet;panels/CL.parquet;panels/EURUSD.parquet;panels/BTC.parquet,4,
5 +fig_timeline.pdf,configs/model_registry.yaml,26,
added results/manifests/ingest_manifest.json +7930 −0
Large diff (7,931 lines) is not loaded by default to keep the page fast.Load diff
added results/manifests/panels_manifest.json +464 −0
Large diff (465 lines) is not loaded by default to keep the page fast.Load diff
added scripts/analyze_tier_c.py +54 −0
Large diff (54 lines) is not loaded by default to keep the page fast.Load diff
added scripts/build_paper.py +120 −0
Large diff (120 lines) is not loaded by default to keep the page fast.Load diff
added src/wp46/reporting/__init__.py +0 −0
Large diff (0 lines) is not loaded by default to keep the page fast.Load diff
added src/wp46/reporting/figures.py +234 −0
Large diff (234 lines) is not loaded by default to keep the page fast.Load diff
added src/wp46/reporting/plotstyle.py +40 −0
Large diff (40 lines) is not loaded by default to keep the page fast.Load diff
added src/wp46/reporting/tables.py +42 −0
Large diff (43 lines) is not loaded by default to keep the page fast.Load diff
Menu

Theme