@krzysztofsztompka/garch 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +116 -7
- package/dist/cjs/diagnostics.d.ts +29 -0
- package/dist/cjs/diagnostics.js +154 -0
- package/dist/cjs/diagnostics.js.map +1 -0
- package/dist/cjs/fit.d.ts +12 -3
- package/dist/cjs/fit.js +106 -76
- package/dist/cjs/fit.js.map +1 -1
- package/dist/cjs/forecast.js +1 -2
- package/dist/cjs/forecast.js.map +1 -1
- package/dist/cjs/index.d.ts +10 -6
- package/dist/cjs/index.js +29 -9
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/inference.d.ts +9 -2
- package/dist/cjs/inference.js +10 -4
- package/dist/cjs/inference.js.map +1 -1
- package/dist/cjs/jobs.d.ts +10 -2
- package/dist/cjs/jobs.js +12 -3
- package/dist/cjs/jobs.js.map +1 -1
- package/dist/cjs/model.d.ts +20 -10
- package/dist/cjs/model.js +49 -36
- package/dist/cjs/model.js.map +1 -1
- package/dist/cjs/node.d.ts +8 -42
- package/dist/cjs/node.js +31 -198
- package/dist/cjs/node.js.map +1 -1
- package/dist/cjs/optimize.js +1 -3
- package/dist/cjs/optimize.js.map +1 -1
- package/dist/cjs/pool.d.ts +84 -0
- package/dist/cjs/pool.js +257 -0
- package/dist/cjs/pool.js.map +1 -0
- package/dist/cjs/quality.d.ts +1 -1
- package/dist/cjs/quality.js +1 -1
- package/dist/cjs/quality.js.map +1 -1
- package/dist/cjs/risk.d.ts +82 -0
- package/dist/cjs/risk.js +165 -0
- package/dist/cjs/risk.js.map +1 -0
- package/dist/cjs/simulate.d.ts +35 -0
- package/dist/cjs/simulate.js +143 -0
- package/dist/cjs/simulate.js.map +1 -0
- package/dist/cjs/skewt.d.ts +45 -0
- package/dist/cjs/skewt.js +107 -0
- package/dist/cjs/skewt.js.map +1 -0
- package/dist/cjs/special.d.ts +13 -0
- package/dist/cjs/special.js +130 -0
- package/dist/cjs/special.js.map +1 -0
- package/dist/cjs/validation.d.ts +8 -2
- package/dist/cjs/validation.js +15 -12
- package/dist/cjs/validation.js.map +1 -1
- package/dist/cjs/web-worker-file.d.ts +17 -0
- package/dist/cjs/web-worker-file.js +14 -0
- package/dist/cjs/web-worker-file.js.map +1 -0
- package/dist/cjs/web.d.ts +30 -0
- package/dist/cjs/web.js +56 -0
- package/dist/cjs/web.js.map +1 -0
- package/dist/cjs/weighted.d.ts +8 -5
- package/dist/cjs/weighted.js +17 -12
- package/dist/cjs/weighted.js.map +1 -1
- package/dist/cjs/worker-core.d.ts +13 -0
- package/dist/cjs/worker-core.js +22 -0
- package/dist/cjs/worker-core.js.map +1 -0
- package/dist/cjs/worker-web.d.ts +1 -0
- package/dist/cjs/worker-web.js +10 -0
- package/dist/cjs/worker-web.js.map +1 -0
- package/dist/cjs/worker.js +3 -18
- package/dist/cjs/worker.js.map +1 -1
- package/dist/esm/diagnostics.d.ts +29 -0
- package/dist/esm/diagnostics.js +148 -0
- package/dist/esm/diagnostics.js.map +1 -0
- package/dist/esm/fit.d.ts +12 -3
- package/dist/esm/fit.js +106 -76
- package/dist/esm/fit.js.map +1 -1
- package/dist/esm/forecast.js +2 -3
- package/dist/esm/forecast.js.map +1 -1
- package/dist/esm/index.d.ts +10 -6
- package/dist/esm/index.js +7 -3
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/inference.d.ts +9 -2
- package/dist/esm/inference.js +10 -4
- package/dist/esm/inference.js.map +1 -1
- package/dist/esm/jobs.d.ts +10 -2
- package/dist/esm/jobs.js +12 -3
- package/dist/esm/jobs.js.map +1 -1
- package/dist/esm/model.d.ts +20 -10
- package/dist/esm/model.js +42 -32
- package/dist/esm/model.js.map +1 -1
- package/dist/esm/node.d.ts +8 -42
- package/dist/esm/node.js +26 -194
- package/dist/esm/node.js.map +1 -1
- package/dist/esm/optimize.js +1 -3
- package/dist/esm/optimize.js.map +1 -1
- package/dist/esm/pool.d.ts +84 -0
- package/dist/esm/pool.js +246 -0
- package/dist/esm/pool.js.map +1 -0
- package/dist/esm/quality.d.ts +1 -1
- package/dist/esm/quality.js +1 -1
- package/dist/esm/quality.js.map +1 -1
- package/dist/esm/risk.d.ts +82 -0
- package/dist/esm/risk.js +155 -0
- package/dist/esm/risk.js.map +1 -0
- package/dist/esm/simulate.d.ts +35 -0
- package/dist/esm/simulate.js +140 -0
- package/dist/esm/simulate.js.map +1 -0
- package/dist/esm/skewt.d.ts +45 -0
- package/dist/esm/skewt.js +99 -0
- package/dist/esm/skewt.js.map +1 -0
- package/dist/esm/special.d.ts +13 -0
- package/dist/esm/special.js +122 -0
- package/dist/esm/special.js.map +1 -0
- package/dist/esm/validation.d.ts +8 -2
- package/dist/esm/validation.js +14 -12
- package/dist/esm/validation.js.map +1 -1
- package/dist/esm/web-worker-file.d.ts +17 -0
- package/dist/esm/web-worker-file.js +4 -0
- package/dist/esm/web.d.ts +30 -0
- package/dist/esm/web.js +43 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/esm/weighted.d.ts +8 -5
- package/dist/esm/weighted.js +16 -11
- package/dist/esm/weighted.js.map +1 -1
- package/dist/esm/worker-core.d.ts +13 -0
- package/dist/esm/worker-core.js +19 -0
- package/dist/esm/worker-core.js.map +1 -0
- package/dist/esm/worker-web.d.ts +1 -0
- package/dist/esm/worker-web.js +8 -0
- package/dist/esm/worker-web.js.map +1 -0
- package/dist/esm/worker.js +3 -18
- package/dist/esm/worker.js.map +1 -1
- package/package.json +12 -1
- package/src/diagnostics.ts +144 -0
- package/src/fit.ts +146 -79
- package/src/forecast.ts +2 -2
- package/src/index.ts +56 -15
- package/src/inference.ts +20 -5
- package/src/jobs.ts +22 -5
- package/src/model.ts +53 -35
- package/src/node.ts +43 -221
- package/src/optimize.ts +3 -11
- package/src/pool.ts +309 -0
- package/src/quality.ts +3 -2
- package/src/risk.ts +205 -0
- package/src/simulate.ts +165 -0
- package/src/skewt.ts +134 -0
- package/src/special.ts +112 -0
- package/src/validation.ts +24 -11
- package/src/web-worker-file.ts +21 -0
- package/src/web.ts +86 -0
- package/src/weighted.ts +30 -18
- package/src/worker-core.ts +22 -0
- package/src/worker-web.ts +13 -0
- package/src/worker.ts +3 -18
package/README.md
CHANGED
|
@@ -10,7 +10,8 @@ npm install @krzysztofsztompka/garch
|
|
|
10
10
|
```
|
|
11
11
|
|
|
12
12
|
Node ≥ 20. The core (`@krzysztofsztompka/garch`) runs anywhere; the worker pool
|
|
13
|
-
|
|
13
|
+
comes as `@krzysztofsztompka/garch/node` (Node's `worker_threads`) and
|
|
14
|
+
`@krzysztofsztompka/garch/web` (Web Workers, ES modules only).
|
|
14
15
|
|
|
15
16
|
## Quick start
|
|
16
17
|
|
|
@@ -40,7 +41,10 @@ What it does not do, deliberately:
|
|
|
40
41
|
- **No prices and no percent.** It takes returns in whatever units it is given.
|
|
41
42
|
- **No interpretation.** When σ̂ is worth quoting, or what a regime means, is the caller's.
|
|
42
43
|
- **No trimming.** `quantizedUntil` says where a coarse-grid stretch of history ends; whether to fit from there is the caller's.
|
|
43
|
-
- **No EGARCH with Student-t innovations.** Its expected variance two steps ahead is infinite (below).
|
|
44
|
+
- **No EGARCH with Student-t or skewed-t innovations.** Its expected variance two steps ahead is infinite (below).
|
|
45
|
+
- **No AR or ARMA mean.** The mean is a constant or zero. Daily returns have next to no autocorrelation to model; a caller who needs it can filter the returns first and fit with `mean: 'zero'`.
|
|
46
|
+
- **No GED innovations.** Normal, t and skewed t cover thin tails, fat tails and a skew; GED adds a tail shape between normal and t that daily equity returns rarely ask for.
|
|
47
|
+
- **No GARCH(p, q) beyond (1, 1).** Higher orders rarely beat (1, 1) out of sample, and they need a different stationarity parameterisation; every model here is (1, 1).
|
|
44
48
|
|
|
45
49
|
## The model
|
|
46
50
|
|
|
@@ -49,8 +53,10 @@ What it does not do, deliberately:
|
|
|
49
53
|
r_t = μ + e_t, e_t ~ N(0, s_t)
|
|
50
54
|
s_t = ω + α·e²_{t−1} + γ·e²_{t−1}·[e_{t−1} < 0] + β·s_{t−1} (γ = 0 for garch)
|
|
51
55
|
|
|
56
|
+
- **A zero mean** (`mean: 'zero'`, `arch`'s `mean='Zero'`) holds μ at 0 for returns already demeaned; every model takes it, and so do the walk-forward, the pool and the weighted forecast. It matches `arch` on the fixture series.
|
|
52
57
|
- **Backcast as in `arch`.** The recursion starts from an exponentially weighted (0.94) mean of the first 75 squared residuals.
|
|
53
58
|
- **Analytic forecasts.** s_{T+k} = ω + (α + γ/2 + β)·s_{T+k−1}.
|
|
59
|
+
- **AIC and BIC** as `arch` computes them, −2·ln L + 2k and −2·ln L + k·ln n, with k the free parameters (`parameterCount`): μ, ω, α, β, plus γ for gjr and egarch, plus ν for t unless it is held on a bound.
|
|
54
60
|
- **Robust standard errors.** Bollerslev–Wooldridge, `arch`'s `cov_type="robust"`: analytic scores and a Hessian taken by differencing the analytic gradient.
|
|
55
61
|
|
|
56
62
|
### Student-t innovations (`dist: 't'`)
|
|
@@ -64,6 +70,20 @@ parameter with its analytic gradient and robust standard error.
|
|
|
64
70
|
- **A ν the data push past a bound is held there.** Free, the logit of ν runs off and the fit never converges. The fit is refitted with ν fixed on the bound (the constrained optimum) and reports `nuBound: 'lower' | 'upper'`. A held ν has no standard error.
|
|
65
71
|
- **Exact-zero returns break it.** Prices recorded on a coarse grid make most days an exact zero: cents on a stock worth $0.08 split-adjusted, or a penny stock's cents times a 1:100 reverse split. On an exact zero the variance can run to 0 while the likelihood rises without bound. The normal likelihood punishes that on the next non-zero day (e²/s); the t likelihood only logarithmically, so the fit degenerates to ω ≈ 0 and μ ≈ 0, **and can still report `converged`**. `quantizedUntil(returns)` returns the index after the last 250-return window with more than 20 % exact zeros; fit t from there.
|
|
66
72
|
|
|
73
|
+
### Skewed-t innovations (`dist: 'skewt'`)
|
|
74
|
+
|
|
75
|
+
Hansen's skewed t, `arch`'s `SkewStudent`: standardised to mean 0 and variance
|
|
76
|
+
1, with ν (`arch`'s η) and λ ∈ (−1, 1); λ < 0 leans the mass left, a fatter
|
|
77
|
+
loss tail. Each side of the density is half a standardised t, so its CDF,
|
|
78
|
+
quantile and partial moments E[zᵏ·1{z < x}] are closed forms over the
|
|
79
|
+
incomplete beta; they agree with `arch`'s to 1e-14. The gradient is analytic
|
|
80
|
+
in λ and ν as well, λ has a robust standard error and p-value, and ν has the
|
|
81
|
+
same floor and bound handling as under t.
|
|
82
|
+
|
|
83
|
+
- **GJR's asymmetry under a skew.** A down move's share of a unit shock's variance, κ = E[z²·1{z < 0}], is ½ only for a symmetric innovation; under a skewed t it is computed exactly (`downShare`, `skewDownShare`). `persistence` and the forecast use κ: s_{T+k} = ω + (α + κγ + β)·s_{T+k−1}. `arch` uses ½ there whatever the distribution, so for a skewed GJR its analytic forecast and this one differ by that much; the simulated paths here agree with this one. The search space keeps `arch`'s constraint α + γ/2 + β < 1.
|
|
84
|
+
- **Against `arch`** on SPY: garch and gjr fits to ~1e-5, with an equal or higher likelihood, and λ's and γ's p-values within 2 %.
|
|
85
|
+
- **VaR and ES** take λ (`tailRisk({ ..., nu, lambda })`), and `simulate` draws skewed-t innovations when the parameters carry λ.
|
|
86
|
+
|
|
67
87
|
### EGARCH (`model: 'egarch'`)
|
|
68
88
|
|
|
69
89
|
`arch`'s `vol="EGARCH", p=1, o=1, q=1` with normal innovations:
|
|
@@ -74,9 +94,39 @@ parameter with its analytic gradient and robust standard error.
|
|
|
74
94
|
- **The recursion starts from ln of the backcast**, with no shock term at t = 0, as in `arch`. `GarchFit.backcast` holds that log.
|
|
75
95
|
- **Parameter space.** β = σ(x2) ∈ (0, 1); ω = (1 − β)·(ln var(r) + x1); α and γ are free. `arch` lets β run in (−1, 1); a negative β is left out here. The gradient is analytic, through the recursion of d ln s.
|
|
76
96
|
- **The forecast is exact, not simulated.** E[s_{T+k}] = exp(m_k)·Π_{j<k−1} E[exp(βʲ·g(z))], with m_k = ω + β·m_{k−1}. Under the normal, E[exp(a|z| + bz)] = e^{(a+b)²/2}Φ(a+b) + e^{(a−b)²/2}Φ(a−b). Under Student-t that expectation is infinite for every a > 0, which is why a t EGARCH is refused (`GarchInputError`).
|
|
77
|
-
- **Against `arch` 8.0.0** (`test/fixtures/arch-egarch-2026-10-01.json`): the same optimum to ~1e-5 in α, γ, β, and an equal or slightly higher log-likelihood. γ's robust p-value agrees within 1 %. The closed-form forecast agrees with `arch`'s 200 000 simulated paths to 0.
|
|
97
|
+
- **Against `arch` 8.0.0** (`test/fixtures/arch-egarch-2026-10-01.json`): the same optimum to ~1e-5 in α, γ, β, and an equal or slightly higher log-likelihood. γ's robust p-value agrees within 1 %. The closed-form forecast agrees with `arch`'s 200 000 simulated paths (seeded) to 0.02 % and 0.08 % at 20 steps.
|
|
78
98
|
- **Its walk-forward baseline is symmetric GARCH,** like GJR's: is the asymmetry worth it?
|
|
79
99
|
|
|
100
|
+
### Simulation
|
|
101
|
+
|
|
102
|
+
`simulate` runs the model's own recursion forward: `paths` paths of `steps`
|
|
103
|
+
returns, from where a fit stands (its last variance and residual, so step 1's
|
|
104
|
+
variance is the one-step forecast) or from the unconditional variance. The
|
|
105
|
+
innovations are normal, or Student-t scaled to unit variance when the
|
|
106
|
+
parameters carry ν; an EGARCH with t is refused, as in fitting. One seeded
|
|
107
|
+
stream (xoshiro128**) feeds every path, so a seed gives the same paths on any
|
|
108
|
+
platform. The paths' mean variance converges to `forecastVariance`.
|
|
109
|
+
|
|
110
|
+
### Residual diagnostics
|
|
111
|
+
|
|
112
|
+
`standardizedResiduals(fit, returns)` gives z_t = (r_t − μ)/√s_t. `ljungBox`
|
|
113
|
+
(on z for the mean, on z² for the variance) and `archLm` (Engle's LM test on
|
|
114
|
+
z) say whether the model left anything behind; each returns the statistic, its
|
|
115
|
+
χ² degrees of freedom and the p-value. They follow statsmodels'
|
|
116
|
+
`acorr_ljungbox` and `het_arch` and agree with them on the fixture series to
|
|
117
|
+
1e-3; `chiSquareSurvival` agrees with scipy to 1e-12.
|
|
118
|
+
|
|
119
|
+
### Value at risk
|
|
120
|
+
|
|
121
|
+
VaR and ES are positive losses in the returns' units at a tail probability
|
|
122
|
+
`level` (0.01 for 99 %). `tailRisk` gives the one-step answer in closed form
|
|
123
|
+
from μ, s_{T+1} and the innovations (normal, or standardised t with ν); its
|
|
124
|
+
quantiles and tail means agree with scipy. Over several steps the cumulative
|
|
125
|
+
return under GARCH is not normal, so `simulatedTailRisk` reads VaR and ES off
|
|
126
|
+
`simulate`'s paths. `varBacktest` walks a one-step VaR forward and
|
|
127
|
+
`coverageTest` grades the violations: Kupiec's unconditional coverage,
|
|
128
|
+
Christoffersen's independence, and both together.
|
|
129
|
+
|
|
80
130
|
## The optimiser, and why it has six starts
|
|
81
131
|
|
|
82
132
|
BFGS with an analytic gradient runs in an unconstrained space where every point
|
|
@@ -115,8 +165,28 @@ tasks share one deadline: past it, or on one task's refusal, its queued tasks
|
|
|
115
165
|
are dropped and its running workers terminated, and the pool carries on. The
|
|
116
166
|
heaviest segments (the longest train windows) are dispatched first.
|
|
117
167
|
|
|
118
|
-
|
|
119
|
-
|
|
168
|
+
`npm run bench` measures it on SPY's closes from the `arch` fixture (8 469
|
|
169
|
+
returns, horizon 20) and asserts the pool's answer equals the in-process one.
|
|
170
|
+
On an i7-13700K, Node 24, 23 workers, one core → pool:
|
|
171
|
+
|
|
172
|
+
| job | one core | pool |
|
|
173
|
+
|---|---|---|
|
|
174
|
+
| garch | 15.8 s | 2.3 s |
|
|
175
|
+
| gjr | 42.4 s | 4.0 s |
|
|
176
|
+
| egarch | 51.7 s | 3.9 s |
|
|
177
|
+
| weighted (`-- --weighted`) | 122 s | 11.2 s |
|
|
178
|
+
|
|
179
|
+
### In the browser
|
|
180
|
+
|
|
181
|
+
`@krzysztofsztompka/garch/web` is the same pool on Web Workers: `GarchWebPool`
|
|
182
|
+
(default: every logical core but one) and the same calls —
|
|
183
|
+
`volatilityForecastInWorkers`, `fitsAtInWorkers`, `weightedForecastInWorkers`,
|
|
184
|
+
`runInWorker` — with the in-process answer. The worker is started as
|
|
185
|
+
`new Worker(new URL('./worker-web.js', import.meta.url), { type: 'module' })`,
|
|
186
|
+
written out literally, so Vite and webpack bundle it without configuration;
|
|
187
|
+
both were checked in Chrome. A bundler that needs the worker its own way passes
|
|
188
|
+
`new GarchWebPool(size, () => new Worker(...))`. Node's pool and this one share
|
|
189
|
+
one implementation of the queue, deadline and cancellation (`WorkerPool`).
|
|
120
190
|
|
|
121
191
|
## Several models, weighted by their past loss
|
|
122
192
|
|
|
@@ -156,15 +226,30 @@ npm `garch`'s variance forecast collapses to ≈ 0 at single origins (QLIKE 10¹
|
|
|
156
226
|
## API
|
|
157
227
|
|
|
158
228
|
```ts
|
|
159
|
-
import { egarchForecast, fitGarch, forecastVariance, quantizedUntil, walkForwardValidation, volatilityForecast, fitsAt } from '@krzysztofsztompka/garch';
|
|
229
|
+
import { archLm, egarchForecast, fitGarch, forecastVariance, ljungBox, quantizedUntil, simulate, simulatedTailRisk, standardizedResiduals, tailRisk, varBacktest, walkForwardValidation, volatilityForecast, fitsAt } from '@krzysztofsztompka/garch';
|
|
160
230
|
import { fitsAtInWorkers, runInWorker, volatilityForecastInWorkers, weightedForecastInWorkers } from '@krzysztofsztompka/garch/node';
|
|
161
231
|
|
|
162
232
|
const fit = fitGarch(returns, { model: 'gjr', inference: true });
|
|
163
233
|
fit.modes; // every optimum reached, best first
|
|
234
|
+
fit.aic; fit.bic; // as arch reports them; a ν held on a bound is not counted
|
|
164
235
|
fitGarch(returns, { model: 'egarch', inference: true }); // γ < 0: leverage; normal innovations only
|
|
236
|
+
fitGarch(returns, { model: 'gjr', mean: 'zero' }); // μ held at 0
|
|
237
|
+
fitGarch(returns, { model: 'gjr', dist: 'skewt', inference: true }); // → params.nu, params.lambda; λ < 0: a fatter loss tail
|
|
165
238
|
fitGarch(returns.slice(quantizedUntil(returns)), { dist: 't' }); // Student-t on the fine-grained history
|
|
166
239
|
// → params.nu, nuBound: 'lower' | 'upper' | null
|
|
167
240
|
|
|
241
|
+
// paths forward from where the fit stands (seeded); a FitAt from fitsAt spreads in as it is
|
|
242
|
+
simulate({ model: 'gjr', params: fit.params, lastVariance: fit.conditionalVariance.at(-1), lastResidual: fit.lastResidual,
|
|
243
|
+
steps: 20, paths: 10_000, seed: 1 }); // → { returns, variance }: Float64Array per path
|
|
244
|
+
|
|
245
|
+
const z = standardizedResiduals(fit, returns);
|
|
246
|
+
ljungBox(z.map((v) => v * v), 10); // → { statistic, df, pValue }: ARCH left in the residuals?
|
|
247
|
+
archLm(z, 10);
|
|
248
|
+
|
|
249
|
+
tailRisk({ mean: fit.params.mu, variance: forecastVariance(fit, 1)[0], nu: fit.params.nu, level: 0.01 });
|
|
250
|
+
simulatedTailRisk(simulate({ ... , steps: 10, paths: 100_000, seed: 1 }), 0.01); // 10-day VaR, ES
|
|
251
|
+
varBacktest(returns, { model: 'gjr', dist: 't', level: 0.01, step: 1 }); // → violations, rate, Kupiec, Christoffersen
|
|
252
|
+
|
|
168
253
|
// Node only: the walk-forward's segments across a pool of workers — the in-process answer, faster
|
|
169
254
|
await volatilityForecastInWorkers({ returns, model: 'gjr', horizon: 20, rivalsWithin: 1.92 }, { timeoutMs: 120_000 });
|
|
170
255
|
await fitsAtInWorkers({ returns, ends: [999, 1499] });
|
|
@@ -184,19 +269,34 @@ reject with one of three errors, so a caller can word each:
|
|
|
184
269
|
|
|
185
270
|
`npm test` compiles `src/` and `test/` with tsc and runs them with `node --test`.
|
|
186
271
|
There is no test framework dependency.
|
|
272
|
+
`npm run coverage` (Node ≥ 22.8) adds Node's own coverage of the compiled
|
|
273
|
+
code and fails under 95 % of lines, 85 % of branches or 80 % of functions; CI
|
|
274
|
+
runs it on Node 22 and 24. Code that runs only inside a worker is counted where
|
|
275
|
+
the tests also run it in-process.
|
|
187
276
|
|
|
188
277
|
- **The analytic gradient** matches its finite difference.
|
|
189
278
|
- **Simulated paths.** Parameters are recovered on simulated GARCH and GJR paths. A real leverage term is significant and an absent one is not.
|
|
190
279
|
- **Every fit is a valid, stationary model.** Inputs that cannot be fitted are refused.
|
|
191
280
|
- **Forecasts** decay to the unconditional variance.
|
|
192
281
|
- **Walk-forward windows** never leak from test into train.
|
|
193
|
-
- **
|
|
282
|
+
- **Simulation.** A seed gives the same paths; the paths' mean variance matches the analytic forecast for GJR (normal and t) and EGARCH, and their innovations have unit variance.
|
|
283
|
+
- **Value at risk.** Normal and t quantiles, VaR and ES against scipy; the one-step VaR read off simulated paths; Kupiec and Christoffersen statistics against a hand computation; a walked-forward VaR covers at its level when the model is the truth.
|
|
284
|
+
- **Residual diagnostics.** Ljung–Box and ARCH-LM against statsmodels on the fixture series; the χ² tail against scipy; ARCH found in a GARCH path and none left in its standardised residuals.
|
|
285
|
+
- **Against `arch`.** `test/fixtures/arch-2026-09-25.json` holds real IOVA and SPY closes (to 2026-09-23) and `arch`'s own answers. The package must match the parameters and σ̂ to 1e-3 / 1e-4, γ's p-value to 2 %, and IOVA's walk-forward QLIKE to 1e-3.
|
|
286
|
+
- **The fixtures are regenerated, not hand-written.** `scripts/arch-fixtures/generate.py` reads the closes from the fixture and rewrites `arch`'s answers in both files, with the versions in its `requirements.txt` (`arch` 8.0.0) and a seeded simulation, recording both in each file's `source`:
|
|
287
|
+
|
|
288
|
+
```bash
|
|
289
|
+
python3 -m venv .venv && .venv/bin/pip install -r scripts/arch-fixtures/requirements.txt
|
|
290
|
+
.venv/bin/python scripts/arch-fixtures/generate.py
|
|
291
|
+
```
|
|
194
292
|
- **Worker round trip.** The same answer as in-process, an input refusal kept as an input refusal, and a timeout that terminates the worker.
|
|
195
293
|
- **The modes.** Best first, the fit being the first; the warm start's optimum marked; rivals only within the asked distance.
|
|
196
294
|
- **Student-t.** The gradient against finite differences, ν included. ν is recovered on a simulated t-GARCH path. A ν below the floor is held and converges, and its standard error is NaN. Robust SE come with a free ν.
|
|
295
|
+
- **Skewed t.** `arch`'s `SkewStudent` quantiles and partial moments to 1e-12; the gradient against finite differences, ν and λ included; ν and λ recovered on a simulated path; SPY's garch and gjr fits against `arch`'s; the skewed VaR and ES against the model's own paths; the simulated variance against the forecast with the exact down share.
|
|
197
296
|
- **Weighting.** Equal weights before the burn-in; the better model dominates after it; changing a forecast whose window ends after an origin does not move that origin's weights; the pool's answer equals the in-process one.
|
|
198
297
|
- **EGARCH.** The gradient against finite differences. A simulated path's parameters, leverage sign included. The closed-form forecast against its own Monte Carlo. `arch` 8.0.0's optimum, γ p-value and simulated forecast. A t EGARCH refused.
|
|
199
298
|
- **Quantized history.** On IOVA's frozen closes: the cut lands where the reverse-split cents end, t degenerates before it (ω ≈ 0 while `converged`), and fits cleanly after it.
|
|
299
|
+
- **The web pool.** Through a Web Worker made of a worker thread: the walk-forward identical to in-process, an input refusal and a deadline carried as on Node.
|
|
200
300
|
- **The pool.** The walk-forward and `fitsAt` bit-identical to in-process on pools of 1 and 3 workers; one refused job fails its whole call; the pool keeps working after a call timed out.
|
|
201
301
|
|
|
202
302
|
## Build
|
|
@@ -205,6 +305,15 @@ There is no test framework dependency.
|
|
|
205
305
|
npm install && npm run build # → dist/cjs and dist/esm, each with .d.ts
|
|
206
306
|
```
|
|
207
307
|
|
|
308
|
+
## Releasing
|
|
309
|
+
|
|
310
|
+
Move `CHANGELOG.md`'s `Unreleased` entries under the new version, set
|
|
311
|
+
`package.json`'s version to match, commit, then push a tag `v<version>`. The
|
|
312
|
+
`release` workflow refuses a tag that differs from the version or has no
|
|
313
|
+
changelog section; otherwise it tests, publishes to npm with provenance and
|
|
314
|
+
makes the GitHub Release from that section. npm trusts the workflow as a
|
|
315
|
+
trusted publisher (`uhlryk/garch`, `release.yml`), so no token is stored.
|
|
316
|
+
|
|
208
317
|
## Licence
|
|
209
318
|
|
|
210
319
|
MIT
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { GarchFit } from './fit.js';
|
|
2
|
+
/**
|
|
3
|
+
* Is anything left for the model to explain? A fitted model's standardised
|
|
4
|
+
* residuals z_t = (r_t − μ) / √s_t should be serially uncorrelated, and so
|
|
5
|
+
* should z²_t — what remains of an ARCH effect shows up there.
|
|
6
|
+
*/
|
|
7
|
+
/** z_t = (r_t − μ) / √s_t for every observation of the fit. */
|
|
8
|
+
export declare function standardizedResiduals(fit: GarchFit, returns: ArrayLike<number>): Float64Array;
|
|
9
|
+
export type TestResult = {
|
|
10
|
+
readonly statistic: number;
|
|
11
|
+
/** Degrees of freedom of the χ² it is referred to. */
|
|
12
|
+
readonly df: number;
|
|
13
|
+
readonly pValue: number;
|
|
14
|
+
};
|
|
15
|
+
/** P(χ²_df > x), computed in the upper tail (no 1 − P cancellation for a large statistic). */
|
|
16
|
+
export declare function chiSquareSurvival(x: number, df: number): number;
|
|
17
|
+
/**
|
|
18
|
+
* Ljung–Box Q = n(n + 2)·Σ_{k ≤ lags} ρ̂²_k / (n − k) against χ²(lags − modelDf),
|
|
19
|
+
* as statsmodels' `acorr_ljungbox` (autocorrelations of the demeaned series
|
|
20
|
+
* over its total sum of squares). Run it on z for the mean, on z² for what
|
|
21
|
+
* remains of the variance dynamics.
|
|
22
|
+
*/
|
|
23
|
+
export declare function ljungBox(series: ArrayLike<number>, lags: number, modelDf?: number): TestResult;
|
|
24
|
+
/**
|
|
25
|
+
* Engle's ARCH-LM test: e²_t regressed on a constant and e²_{t−1} … e²_{t−lags};
|
|
26
|
+
* LM = (n − lags)·R² against χ²(lags), as statsmodels' `het_arch`. On the
|
|
27
|
+
* standardised residuals, a small p-value says the model left ARCH behind.
|
|
28
|
+
*/
|
|
29
|
+
export declare function archLm(series: ArrayLike<number>, lags: number): TestResult;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.standardizedResiduals = standardizedResiduals;
|
|
4
|
+
exports.chiSquareSurvival = chiSquareSurvival;
|
|
5
|
+
exports.ljungBox = ljungBox;
|
|
6
|
+
exports.archLm = archLm;
|
|
7
|
+
const inference_js_1 = require("./inference.js");
|
|
8
|
+
const special_js_1 = require("./special.js");
|
|
9
|
+
/**
|
|
10
|
+
* Is anything left for the model to explain? A fitted model's standardised
|
|
11
|
+
* residuals z_t = (r_t − μ) / √s_t should be serially uncorrelated, and so
|
|
12
|
+
* should z²_t — what remains of an ARCH effect shows up there.
|
|
13
|
+
*/
|
|
14
|
+
/** z_t = (r_t − μ) / √s_t for every observation of the fit. */
|
|
15
|
+
function standardizedResiduals(fit, returns) {
|
|
16
|
+
const s = fit.conditionalVariance;
|
|
17
|
+
if (returns.length !== s.length)
|
|
18
|
+
throw new RangeError(`the fit has ${s.length} variances, the returns are ${returns.length}`);
|
|
19
|
+
const z = new Float64Array(s.length);
|
|
20
|
+
for (let t = 0; t < s.length; t += 1)
|
|
21
|
+
z[t] = (returns[t] - fit.params.mu) / Math.sqrt(s[t]);
|
|
22
|
+
return z;
|
|
23
|
+
}
|
|
24
|
+
/** The regularised lower incomplete gamma P(a, x): its series below a + 1, its continued fraction above. */
|
|
25
|
+
function lowerGamma(a, x) {
|
|
26
|
+
if (x <= 0)
|
|
27
|
+
return 0;
|
|
28
|
+
const front = Math.exp(-x + a * Math.log(x) - (0, special_js_1.logGamma)(a));
|
|
29
|
+
if (x < a + 1) {
|
|
30
|
+
let term = 1 / a;
|
|
31
|
+
let sum = term;
|
|
32
|
+
for (let n = 1; n < 1000; n += 1) {
|
|
33
|
+
term *= x / (a + n);
|
|
34
|
+
sum += term;
|
|
35
|
+
if (Math.abs(term) < Math.abs(sum) * 1e-16)
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
return sum * front;
|
|
39
|
+
}
|
|
40
|
+
return 1 - upperFraction(a, x) * front;
|
|
41
|
+
}
|
|
42
|
+
/** Lentz's continued fraction for Γ(a, x) / (e^{−x} x^a / Γ(a)). */
|
|
43
|
+
function upperFraction(a, x) {
|
|
44
|
+
const tiny = 1e-300;
|
|
45
|
+
let b = x + 1 - a;
|
|
46
|
+
let c = 1 / tiny;
|
|
47
|
+
let d = 1 / b;
|
|
48
|
+
let h = d;
|
|
49
|
+
for (let i = 1; i < 1000; i += 1) {
|
|
50
|
+
const an = -i * (i - a);
|
|
51
|
+
b += 2;
|
|
52
|
+
d = an * d + b;
|
|
53
|
+
if (Math.abs(d) < tiny)
|
|
54
|
+
d = tiny;
|
|
55
|
+
c = b + an / c;
|
|
56
|
+
if (Math.abs(c) < tiny)
|
|
57
|
+
c = tiny;
|
|
58
|
+
d = 1 / d;
|
|
59
|
+
const delta = d * c;
|
|
60
|
+
h *= delta;
|
|
61
|
+
if (Math.abs(delta - 1) < 1e-16)
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
return h;
|
|
65
|
+
}
|
|
66
|
+
/** P(χ²_df > x), computed in the upper tail (no 1 − P cancellation for a large statistic). */
|
|
67
|
+
function chiSquareSurvival(x, df) {
|
|
68
|
+
if (!(df > 0))
|
|
69
|
+
throw new RangeError(`df must be positive, got ${df}`);
|
|
70
|
+
if (!(x > 0))
|
|
71
|
+
return 1;
|
|
72
|
+
const a = df / 2;
|
|
73
|
+
const h = x / 2;
|
|
74
|
+
if (h < a + 1)
|
|
75
|
+
return 1 - lowerGamma(a, h);
|
|
76
|
+
return upperFraction(a, h) * Math.exp(-h + a * Math.log(h) - (0, special_js_1.logGamma)(a));
|
|
77
|
+
}
|
|
78
|
+
const checkLags = (lags, n) => {
|
|
79
|
+
if (!(Number.isInteger(lags) && lags >= 1))
|
|
80
|
+
throw new RangeError(`lags must be a positive integer, got ${lags}`);
|
|
81
|
+
if (lags >= n)
|
|
82
|
+
throw new RangeError(`${lags} lags need more than ${n} observations`);
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Ljung–Box Q = n(n + 2)·Σ_{k ≤ lags} ρ̂²_k / (n − k) against χ²(lags − modelDf),
|
|
86
|
+
* as statsmodels' `acorr_ljungbox` (autocorrelations of the demeaned series
|
|
87
|
+
* over its total sum of squares). Run it on z for the mean, on z² for what
|
|
88
|
+
* remains of the variance dynamics.
|
|
89
|
+
*/
|
|
90
|
+
function ljungBox(series, lags, modelDf = 0) {
|
|
91
|
+
const n = series.length;
|
|
92
|
+
checkLags(lags, n);
|
|
93
|
+
if (!(Number.isInteger(modelDf) && modelDf >= 0 && modelDf < lags))
|
|
94
|
+
throw new RangeError(`modelDf must be an integer in [0, lags), got ${modelDf}`);
|
|
95
|
+
let mean = 0;
|
|
96
|
+
for (let t = 0; t < n; t += 1)
|
|
97
|
+
mean += series[t];
|
|
98
|
+
mean /= n;
|
|
99
|
+
let ss = 0;
|
|
100
|
+
for (let t = 0; t < n; t += 1)
|
|
101
|
+
ss += (series[t] - mean) ** 2;
|
|
102
|
+
let q = 0;
|
|
103
|
+
for (let k = 1; k <= lags; k += 1) {
|
|
104
|
+
let c = 0;
|
|
105
|
+
for (let t = k; t < n; t += 1)
|
|
106
|
+
c += (series[t] - mean) * (series[t - k] - mean);
|
|
107
|
+
q += (c / ss) ** 2 / (n - k);
|
|
108
|
+
}
|
|
109
|
+
const statistic = n * (n + 2) * q;
|
|
110
|
+
const df = lags - modelDf;
|
|
111
|
+
return { statistic, df, pValue: chiSquareSurvival(statistic, df) };
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Engle's ARCH-LM test: e²_t regressed on a constant and e²_{t−1} … e²_{t−lags};
|
|
115
|
+
* LM = (n − lags)·R² against χ²(lags), as statsmodels' `het_arch`. On the
|
|
116
|
+
* standardised residuals, a small p-value says the model left ARCH behind.
|
|
117
|
+
*/
|
|
118
|
+
function archLm(series, lags) {
|
|
119
|
+
const n = series.length;
|
|
120
|
+
checkLags(lags, n);
|
|
121
|
+
const m = lags + 1;
|
|
122
|
+
const xtx = Array.from({ length: m }, () => new Array(m).fill(0));
|
|
123
|
+
const xty = new Array(m).fill(0);
|
|
124
|
+
const row = new Array(m);
|
|
125
|
+
let sumY = 0;
|
|
126
|
+
let sumY2 = 0;
|
|
127
|
+
for (let t = lags; t < n; t += 1) {
|
|
128
|
+
const y = series[t] ** 2;
|
|
129
|
+
row[0] = 1;
|
|
130
|
+
for (let k = 1; k <= lags; k += 1)
|
|
131
|
+
row[k] = series[t - k] ** 2;
|
|
132
|
+
for (let i = 0; i < m; i += 1) {
|
|
133
|
+
xty[i] += row[i] * y;
|
|
134
|
+
for (let j = 0; j <= i; j += 1)
|
|
135
|
+
xtx[i][j] += row[i] * row[j];
|
|
136
|
+
}
|
|
137
|
+
sumY += y;
|
|
138
|
+
sumY2 += y * y;
|
|
139
|
+
}
|
|
140
|
+
for (let i = 0; i < m; i += 1)
|
|
141
|
+
for (let j = i + 1; j < m; j += 1)
|
|
142
|
+
xtx[i][j] = xtx[j][i];
|
|
143
|
+
const inverse = (0, inference_js_1.invert)(xtx);
|
|
144
|
+
if (!inverse)
|
|
145
|
+
throw new RangeError('the lagged squares are collinear');
|
|
146
|
+
const beta = inverse.map((r) => r.reduce((a, v, j) => a + v * xty[j], 0));
|
|
147
|
+
const nobs = n - lags;
|
|
148
|
+
// R² = (β'X'y − n·ȳ²) / (y'y − n·ȳ²)
|
|
149
|
+
const explained = beta.reduce((a, b, i) => a + b * xty[i], 0) - (sumY * sumY) / nobs;
|
|
150
|
+
const total = sumY2 - (sumY * sumY) / nobs;
|
|
151
|
+
const statistic = nobs * (explained / total);
|
|
152
|
+
return { statistic, df: lags, pValue: chiSquareSurvival(statistic, lags) };
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=diagnostics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diagnostics.js","sourceRoot":"","sources":["../../src/diagnostics.ts"],"names":[],"mappings":"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"}
|
package/dist/cjs/fit.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { type Distribution, type GarchModel, type GarchParams } from './model.js';
|
|
1
|
+
import { type Distribution, type GarchModel, type GarchParams, type MeanModel } from './model.js';
|
|
2
2
|
export declare class GarchInputError extends Error {
|
|
3
3
|
}
|
|
4
4
|
export type FitOptions = {
|
|
5
5
|
readonly model?: GarchModel;
|
|
6
|
+
/** A fitted constant (default) or zero — μ held at 0. */
|
|
7
|
+
readonly mean?: MeanModel;
|
|
6
8
|
/** The innovations: normal (default, `arch`'s) or Student-t with ν fitted. */
|
|
7
9
|
readonly dist?: Distribution;
|
|
8
10
|
/** The lowest ν a t fit may take (default `T_NU_MIN`); must exceed 2. */
|
|
@@ -18,10 +20,17 @@ export type Inference = {
|
|
|
18
20
|
};
|
|
19
21
|
export type GarchFit = {
|
|
20
22
|
readonly model: GarchModel;
|
|
23
|
+
readonly mean: MeanModel;
|
|
21
24
|
readonly converged: boolean;
|
|
22
25
|
readonly params: GarchParams;
|
|
23
26
|
readonly persistence: number;
|
|
24
27
|
readonly logLikelihood: number;
|
|
28
|
+
/** The free parameters the criteria count: μ (constant mean), ω, α, β, γ (gjr, egarch), ν (t, unless held on a bound). */
|
|
29
|
+
readonly parameterCount: number;
|
|
30
|
+
/** −2·ln L + 2k, as `arch` reports it. */
|
|
31
|
+
readonly aic: number;
|
|
32
|
+
/** −2·ln L + k·ln n, as `arch` reports it. */
|
|
33
|
+
readonly bic: number;
|
|
25
34
|
/** s_t for every observation, in the returns' units squared. */
|
|
26
35
|
readonly conditionalVariance: Float64Array;
|
|
27
36
|
/** e_T, the last residual. */
|
|
@@ -55,8 +64,8 @@ export type GarchMode = {
|
|
|
55
64
|
/**
|
|
56
65
|
* The default floor on ν. `arch` allows 2.05, but a t variance is
|
|
57
66
|
* scale²·ν/(ν − 2): near 2 it is barely defined and its forecast explodes —
|
|
58
|
-
*
|
|
59
|
-
* not converging and the forecast worse than naive. At 4 the kurtosis is
|
|
67
|
+
* fitted freely, AAPL and IOVA came out at ν̂ ≈ 2.1–2.5, most of their early
|
|
68
|
+
* origins not converging and the forecast worse than naive. At 4 the kurtosis is
|
|
60
69
|
* still infinite but the variance forecast is stable.
|
|
61
70
|
*/
|
|
62
71
|
export declare const T_NU_MIN = 4;
|