garch 1.0.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/LICENSE +21 -0
- package/README.md +207 -0
- package/build/index.cjs +528 -0
- package/build/index.mjs +516 -0
- package/package.json +47 -0
- package/types.d.ts +197 -0
package/build/index.mjs
ADDED
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
function nelderMead(fn, x0, options = {}) {
|
|
2
|
+
const { maxIter = 1000, tol = 1e-8, alpha = 1, // reflection
|
|
3
|
+
gamma = 2, // expansion
|
|
4
|
+
rho = 0.5, // contraction
|
|
5
|
+
sigma = 0.5, // shrink
|
|
6
|
+
} = options;
|
|
7
|
+
const n = x0.length;
|
|
8
|
+
// Initialize simplex
|
|
9
|
+
const simplex = [x0.slice()];
|
|
10
|
+
for (let i = 0; i < n; i++) {
|
|
11
|
+
const point = x0.slice();
|
|
12
|
+
const delta = point[i] === 0 ? 0.00025 : point[i] * 0.05;
|
|
13
|
+
point[i] += delta;
|
|
14
|
+
simplex.push(point);
|
|
15
|
+
}
|
|
16
|
+
let values = simplex.map(fn);
|
|
17
|
+
let iterations = 0;
|
|
18
|
+
let converged = false;
|
|
19
|
+
for (iterations = 0; iterations < maxIter; iterations++) {
|
|
20
|
+
// Sort simplex by function values
|
|
21
|
+
const indices = values.map((_, i) => i).sort((a, b) => values[a] - values[b]);
|
|
22
|
+
const sortedSimplex = indices.map(i => simplex[i]);
|
|
23
|
+
const sortedValues = indices.map(i => values[i]);
|
|
24
|
+
for (let i = 0; i <= n; i++) {
|
|
25
|
+
simplex[i] = sortedSimplex[i];
|
|
26
|
+
values[i] = sortedValues[i];
|
|
27
|
+
}
|
|
28
|
+
// Check convergence
|
|
29
|
+
const range = values[n] - values[0];
|
|
30
|
+
if (range < tol) {
|
|
31
|
+
converged = true;
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
// Centroid of all points except worst
|
|
35
|
+
const centroid = new Array(n).fill(0);
|
|
36
|
+
for (let i = 0; i < n; i++) {
|
|
37
|
+
for (let j = 0; j < n; j++) {
|
|
38
|
+
centroid[j] += simplex[i][j] / n;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// Reflection
|
|
42
|
+
const reflected = centroid.map((c, j) => c + alpha * (c - simplex[n][j]));
|
|
43
|
+
const fr = fn(reflected);
|
|
44
|
+
if (fr < values[0]) {
|
|
45
|
+
// Expansion
|
|
46
|
+
const expanded = centroid.map((c, j) => c + gamma * (reflected[j] - c));
|
|
47
|
+
const fe = fn(expanded);
|
|
48
|
+
if (fe < fr) {
|
|
49
|
+
simplex[n] = expanded;
|
|
50
|
+
values[n] = fe;
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
simplex[n] = reflected;
|
|
54
|
+
values[n] = fr;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
else if (fr < values[n - 1]) {
|
|
58
|
+
simplex[n] = reflected;
|
|
59
|
+
values[n] = fr;
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
// Contraction
|
|
63
|
+
if (fr < values[n]) {
|
|
64
|
+
// Outside contraction
|
|
65
|
+
const contracted = centroid.map((c, j) => c + rho * (reflected[j] - c));
|
|
66
|
+
const fc = fn(contracted);
|
|
67
|
+
if (fc <= fr) {
|
|
68
|
+
simplex[n] = contracted;
|
|
69
|
+
values[n] = fc;
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
// Shrink
|
|
73
|
+
shrink(simplex, values, sigma, fn, n);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
// Inside contraction
|
|
78
|
+
const contracted = centroid.map((c, j) => c + rho * (simplex[n][j] - c));
|
|
79
|
+
const fc = fn(contracted);
|
|
80
|
+
if (fc < values[n]) {
|
|
81
|
+
simplex[n] = contracted;
|
|
82
|
+
values[n] = fc;
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
// Shrink
|
|
86
|
+
shrink(simplex, values, sigma, fn, n);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
x: simplex[0],
|
|
93
|
+
fx: values[0],
|
|
94
|
+
iterations,
|
|
95
|
+
converged,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function shrink(simplex, values, sigma, fn, n) {
|
|
99
|
+
for (let i = 1; i <= n; i++) {
|
|
100
|
+
for (let j = 0; j < n; j++) {
|
|
101
|
+
simplex[i][j] = simplex[0][j] + sigma * (simplex[i][j] - simplex[0][j]);
|
|
102
|
+
}
|
|
103
|
+
values[i] = fn(simplex[i]);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Calculate log returns from candles
|
|
109
|
+
*/
|
|
110
|
+
function calculateReturns(candles) {
|
|
111
|
+
const returns = [];
|
|
112
|
+
for (let i = 1; i < candles.length; i++) {
|
|
113
|
+
if (candles[i].close <= 0 || candles[i - 1].close <= 0) {
|
|
114
|
+
throw new Error(`Invalid close price at index ${i}`);
|
|
115
|
+
}
|
|
116
|
+
returns.push(Math.log(candles[i].close / candles[i - 1].close));
|
|
117
|
+
}
|
|
118
|
+
return returns;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Calculate log returns from price array
|
|
122
|
+
*/
|
|
123
|
+
function calculateReturnsFromPrices(prices) {
|
|
124
|
+
const returns = [];
|
|
125
|
+
for (let i = 1; i < prices.length; i++) {
|
|
126
|
+
if (prices[i] <= 0 || prices[i - 1] <= 0) {
|
|
127
|
+
throw new Error(`Invalid price at index ${i}`);
|
|
128
|
+
}
|
|
129
|
+
returns.push(Math.log(prices[i] / prices[i - 1]));
|
|
130
|
+
}
|
|
131
|
+
return returns;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Calculate sample variance (mean-zero assumption)
|
|
135
|
+
*/
|
|
136
|
+
function sampleVariance(returns) {
|
|
137
|
+
return returns.reduce((sum, r) => sum + r * r, 0) / returns.length;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Calculate sample variance with mean adjustment
|
|
141
|
+
*/
|
|
142
|
+
function sampleVarianceWithMean(returns) {
|
|
143
|
+
const mean = returns.reduce((sum, r) => sum + r, 0) / returns.length;
|
|
144
|
+
return returns.reduce((sum, r) => sum + (r - mean) ** 2, 0) / (returns.length - 1);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Check for leverage effect (asymmetry in volatility)
|
|
148
|
+
*/
|
|
149
|
+
function checkLeverageEffect(returns) {
|
|
150
|
+
const negative = returns.filter(r => r < 0);
|
|
151
|
+
const positive = returns.filter(r => r > 0);
|
|
152
|
+
if (negative.length === 0 || positive.length === 0) {
|
|
153
|
+
return {
|
|
154
|
+
negativeVol: 0,
|
|
155
|
+
positiveVol: 0,
|
|
156
|
+
ratio: 1,
|
|
157
|
+
recommendation: 'garch',
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
const negativeVol = Math.sqrt(negative.reduce((s, r) => s + r * r, 0) / negative.length);
|
|
161
|
+
const positiveVol = Math.sqrt(positive.reduce((s, r) => s + r * r, 0) / positive.length);
|
|
162
|
+
const ratio = negativeVol / positiveVol;
|
|
163
|
+
return {
|
|
164
|
+
negativeVol,
|
|
165
|
+
positiveVol,
|
|
166
|
+
ratio,
|
|
167
|
+
recommendation: ratio > 1.2 ? 'egarch' : 'garch',
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Expected value of |Z| where Z ~ N(0,1)
|
|
172
|
+
* E[|Z|] = sqrt(2/π)
|
|
173
|
+
*/
|
|
174
|
+
const EXPECTED_ABS_NORMAL = Math.sqrt(2 / Math.PI);
|
|
175
|
+
/**
|
|
176
|
+
* Calculate AIC (Akaike Information Criterion)
|
|
177
|
+
*/
|
|
178
|
+
function calculateAIC(logLikelihood, numParams) {
|
|
179
|
+
return 2 * numParams - 2 * logLikelihood;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Calculate BIC (Bayesian Information Criterion)
|
|
183
|
+
*/
|
|
184
|
+
function calculateBIC(logLikelihood, numParams, numObs) {
|
|
185
|
+
return numParams * Math.log(numObs) - 2 * logLikelihood;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* GARCH(1,1) model
|
|
190
|
+
*
|
|
191
|
+
* σ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁
|
|
192
|
+
*
|
|
193
|
+
* where:
|
|
194
|
+
* - ω (omega) > 0: constant term
|
|
195
|
+
* - α (alpha) ≥ 0: ARCH parameter (reaction to shocks)
|
|
196
|
+
* - β (beta) ≥ 0: GARCH parameter (persistence)
|
|
197
|
+
* - α + β < 1: stationarity condition
|
|
198
|
+
*/
|
|
199
|
+
class Garch {
|
|
200
|
+
returns;
|
|
201
|
+
periodsPerYear;
|
|
202
|
+
initialVariance;
|
|
203
|
+
constructor(data, options = {}) {
|
|
204
|
+
this.periodsPerYear = options.periodsPerYear ?? 252;
|
|
205
|
+
if (data.length < 50) {
|
|
206
|
+
throw new Error('Need at least 50 data points for GARCH estimation');
|
|
207
|
+
}
|
|
208
|
+
// Determine if input is candles or prices
|
|
209
|
+
if (typeof data[0] === 'number') {
|
|
210
|
+
this.returns = calculateReturnsFromPrices(data);
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
this.returns = calculateReturns(data);
|
|
214
|
+
}
|
|
215
|
+
this.initialVariance = sampleVariance(this.returns);
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Calibrate GARCH(1,1) parameters using Maximum Likelihood Estimation
|
|
219
|
+
*/
|
|
220
|
+
fit(options = {}) {
|
|
221
|
+
const { maxIter = 1000, tol = 1e-8 } = options;
|
|
222
|
+
const returns = this.returns;
|
|
223
|
+
const n = returns.length;
|
|
224
|
+
const initVar = this.initialVariance;
|
|
225
|
+
// Negative log-likelihood function
|
|
226
|
+
function negLogLikelihood(params) {
|
|
227
|
+
const [omega, alpha, beta] = params;
|
|
228
|
+
// Constraints
|
|
229
|
+
if (omega <= 1e-12)
|
|
230
|
+
return 1e10;
|
|
231
|
+
if (alpha < 0 || beta < 0)
|
|
232
|
+
return 1e10;
|
|
233
|
+
if (alpha + beta >= 0.9999)
|
|
234
|
+
return 1e10;
|
|
235
|
+
let variance = initVar;
|
|
236
|
+
let ll = 0;
|
|
237
|
+
for (let i = 0; i < n; i++) {
|
|
238
|
+
if (i > 0) {
|
|
239
|
+
variance = omega + alpha * returns[i - 1] ** 2 + beta * variance;
|
|
240
|
+
}
|
|
241
|
+
if (variance <= 1e-12)
|
|
242
|
+
return 1e10;
|
|
243
|
+
// Gaussian log-likelihood (dropping constant)
|
|
244
|
+
ll += Math.log(variance) + (returns[i] ** 2) / variance;
|
|
245
|
+
}
|
|
246
|
+
return ll / 2;
|
|
247
|
+
}
|
|
248
|
+
// Initial guesses
|
|
249
|
+
const omega0 = initVar * 0.05;
|
|
250
|
+
const alpha0 = 0.1;
|
|
251
|
+
const beta0 = 0.85;
|
|
252
|
+
const result = nelderMead(negLogLikelihood, [omega0, alpha0, beta0], { maxIter, tol });
|
|
253
|
+
const [omega, alpha, beta] = result.x;
|
|
254
|
+
const persistence = alpha + beta;
|
|
255
|
+
const unconditionalVariance = omega / (1 - persistence);
|
|
256
|
+
const annualizedVol = Math.sqrt(unconditionalVariance * this.periodsPerYear) * 100;
|
|
257
|
+
const logLikelihood = -result.fx * 2;
|
|
258
|
+
const numParams = 3;
|
|
259
|
+
return {
|
|
260
|
+
params: {
|
|
261
|
+
omega,
|
|
262
|
+
alpha,
|
|
263
|
+
beta,
|
|
264
|
+
persistence,
|
|
265
|
+
unconditionalVariance,
|
|
266
|
+
annualizedVol,
|
|
267
|
+
},
|
|
268
|
+
diagnostics: {
|
|
269
|
+
logLikelihood,
|
|
270
|
+
aic: calculateAIC(logLikelihood, numParams),
|
|
271
|
+
bic: calculateBIC(logLikelihood, numParams, n),
|
|
272
|
+
iterations: result.iterations,
|
|
273
|
+
converged: result.converged,
|
|
274
|
+
},
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Calculate conditional variance series given parameters
|
|
279
|
+
*/
|
|
280
|
+
getVarianceSeries(params) {
|
|
281
|
+
const { omega, alpha, beta } = params;
|
|
282
|
+
const variance = [];
|
|
283
|
+
for (let i = 0; i < this.returns.length; i++) {
|
|
284
|
+
if (i === 0) {
|
|
285
|
+
variance.push(this.initialVariance);
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
const v = omega + alpha * this.returns[i - 1] ** 2 + beta * variance[i - 1];
|
|
289
|
+
variance.push(v);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return variance;
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Forecast variance forward
|
|
296
|
+
*/
|
|
297
|
+
forecast(params, steps = 1) {
|
|
298
|
+
const { omega, alpha, beta } = params;
|
|
299
|
+
const variance = [];
|
|
300
|
+
// Get last variance
|
|
301
|
+
const varianceSeries = this.getVarianceSeries(params);
|
|
302
|
+
const lastVariance = varianceSeries[varianceSeries.length - 1];
|
|
303
|
+
const lastReturn = this.returns[this.returns.length - 1];
|
|
304
|
+
// One-step ahead
|
|
305
|
+
let v = omega + alpha * lastReturn ** 2 + beta * lastVariance;
|
|
306
|
+
variance.push(v);
|
|
307
|
+
// Multi-step ahead (converges to unconditional variance)
|
|
308
|
+
for (let h = 1; h < steps; h++) {
|
|
309
|
+
v = omega + (alpha + beta) * v;
|
|
310
|
+
variance.push(v);
|
|
311
|
+
}
|
|
312
|
+
return {
|
|
313
|
+
variance,
|
|
314
|
+
volatility: variance.map(v => Math.sqrt(v)),
|
|
315
|
+
annualized: variance.map(v => Math.sqrt(v * this.periodsPerYear) * 100),
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Get the return series
|
|
320
|
+
*/
|
|
321
|
+
getReturns() {
|
|
322
|
+
return [...this.returns];
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Get initial variance estimate
|
|
326
|
+
*/
|
|
327
|
+
getInitialVariance() {
|
|
328
|
+
return this.initialVariance;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Convenience function to calibrate GARCH(1,1) from candles
|
|
333
|
+
*/
|
|
334
|
+
function calibrateGarch(data, options = {}) {
|
|
335
|
+
const model = new Garch(data, options);
|
|
336
|
+
return model.fit(options);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* EGARCH(1,1) model (Nelson, 1991)
|
|
341
|
+
*
|
|
342
|
+
* ln(σ²ₜ) = ω + α·(|zₜ₋₁| - E[|z|]) + γ·zₜ₋₁ + β·ln(σ²ₜ₋₁)
|
|
343
|
+
*
|
|
344
|
+
* where:
|
|
345
|
+
* - zₜ = εₜ/σₜ (standardized residual)
|
|
346
|
+
* - ω (omega): constant term
|
|
347
|
+
* - α (alpha): magnitude effect
|
|
348
|
+
* - γ (gamma): leverage effect (typically negative)
|
|
349
|
+
* - β (beta): persistence
|
|
350
|
+
* - E[|z|] = √(2/π) for standard normal
|
|
351
|
+
*/
|
|
352
|
+
class Egarch {
|
|
353
|
+
returns;
|
|
354
|
+
periodsPerYear;
|
|
355
|
+
initialVariance;
|
|
356
|
+
constructor(data, options = {}) {
|
|
357
|
+
this.periodsPerYear = options.periodsPerYear ?? 252;
|
|
358
|
+
if (data.length < 50) {
|
|
359
|
+
throw new Error('Need at least 50 data points for EGARCH estimation');
|
|
360
|
+
}
|
|
361
|
+
if (typeof data[0] === 'number') {
|
|
362
|
+
this.returns = calculateReturnsFromPrices(data);
|
|
363
|
+
}
|
|
364
|
+
else {
|
|
365
|
+
this.returns = calculateReturns(data);
|
|
366
|
+
}
|
|
367
|
+
this.initialVariance = sampleVariance(this.returns);
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Calibrate EGARCH(1,1) parameters using Maximum Likelihood Estimation
|
|
371
|
+
*/
|
|
372
|
+
fit(options = {}) {
|
|
373
|
+
const { maxIter = 1000, tol = 1e-8 } = options;
|
|
374
|
+
const returns = this.returns;
|
|
375
|
+
const n = returns.length;
|
|
376
|
+
const initLogVar = Math.log(this.initialVariance);
|
|
377
|
+
function negLogLikelihood(params) {
|
|
378
|
+
const [omega, alpha, gamma, beta] = params;
|
|
379
|
+
// EGARCH allows negative gamma, but beta should ensure stationarity
|
|
380
|
+
if (Math.abs(beta) >= 0.9999)
|
|
381
|
+
return 1e10;
|
|
382
|
+
let logVariance = initLogVar;
|
|
383
|
+
let variance = Math.exp(logVariance);
|
|
384
|
+
let ll = 0;
|
|
385
|
+
for (let i = 0; i < n; i++) {
|
|
386
|
+
if (i > 0) {
|
|
387
|
+
const sigma = Math.sqrt(variance);
|
|
388
|
+
const z = returns[i - 1] / sigma;
|
|
389
|
+
logVariance = omega
|
|
390
|
+
+ alpha * (Math.abs(z) - EXPECTED_ABS_NORMAL)
|
|
391
|
+
+ gamma * z
|
|
392
|
+
+ beta * logVariance;
|
|
393
|
+
// Prevent extreme values
|
|
394
|
+
logVariance = Math.max(-50, Math.min(50, logVariance));
|
|
395
|
+
variance = Math.exp(logVariance);
|
|
396
|
+
}
|
|
397
|
+
if (variance <= 1e-12 || !isFinite(variance))
|
|
398
|
+
return 1e10;
|
|
399
|
+
ll += Math.log(variance) + (returns[i] ** 2) / variance;
|
|
400
|
+
}
|
|
401
|
+
return ll / 2;
|
|
402
|
+
}
|
|
403
|
+
// Initial guesses
|
|
404
|
+
// omega approximates log of unconditional variance when other params are small
|
|
405
|
+
const omega0 = initLogVar * 0.1;
|
|
406
|
+
const alpha0 = 0.1;
|
|
407
|
+
const gamma0 = -0.05; // Negative for typical leverage effect
|
|
408
|
+
const beta0 = 0.95;
|
|
409
|
+
const result = nelderMead(negLogLikelihood, [omega0, alpha0, gamma0, beta0], { maxIter, tol });
|
|
410
|
+
const [omega, alpha, gamma, beta] = result.x;
|
|
411
|
+
// For EGARCH, unconditional variance: E[ln(σ²)] = ω/(1-β)
|
|
412
|
+
// So E[σ²] ≈ exp(ω/(1-β)) when α and γ effects average out
|
|
413
|
+
const unconditionalLogVar = omega / (1 - beta);
|
|
414
|
+
const unconditionalVariance = Math.exp(unconditionalLogVar);
|
|
415
|
+
const annualizedVol = Math.sqrt(unconditionalVariance * this.periodsPerYear) * 100;
|
|
416
|
+
const logLikelihood = -result.fx * 2;
|
|
417
|
+
const numParams = 4;
|
|
418
|
+
return {
|
|
419
|
+
params: {
|
|
420
|
+
omega,
|
|
421
|
+
alpha,
|
|
422
|
+
gamma,
|
|
423
|
+
beta,
|
|
424
|
+
persistence: beta, // In EGARCH, persistence is primarily driven by beta
|
|
425
|
+
unconditionalVariance,
|
|
426
|
+
annualizedVol,
|
|
427
|
+
leverageEffect: gamma,
|
|
428
|
+
},
|
|
429
|
+
diagnostics: {
|
|
430
|
+
logLikelihood,
|
|
431
|
+
aic: calculateAIC(logLikelihood, numParams),
|
|
432
|
+
bic: calculateBIC(logLikelihood, numParams, n),
|
|
433
|
+
iterations: result.iterations,
|
|
434
|
+
converged: result.converged,
|
|
435
|
+
},
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Calculate conditional variance series given parameters
|
|
440
|
+
*/
|
|
441
|
+
getVarianceSeries(params) {
|
|
442
|
+
const { omega, alpha, gamma, beta } = params;
|
|
443
|
+
const variance = [];
|
|
444
|
+
let logVariance = Math.log(this.initialVariance);
|
|
445
|
+
for (let i = 0; i < this.returns.length; i++) {
|
|
446
|
+
if (i === 0) {
|
|
447
|
+
variance.push(this.initialVariance);
|
|
448
|
+
}
|
|
449
|
+
else {
|
|
450
|
+
const sigma = Math.sqrt(variance[i - 1]);
|
|
451
|
+
const z = this.returns[i - 1] / sigma;
|
|
452
|
+
logVariance = omega
|
|
453
|
+
+ alpha * (Math.abs(z) - EXPECTED_ABS_NORMAL)
|
|
454
|
+
+ gamma * z
|
|
455
|
+
+ beta * logVariance;
|
|
456
|
+
logVariance = Math.max(-50, Math.min(50, logVariance));
|
|
457
|
+
variance.push(Math.exp(logVariance));
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
return variance;
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Forecast variance forward
|
|
464
|
+
*
|
|
465
|
+
* Note: EGARCH forecasts are more complex because they depend on
|
|
466
|
+
* the path of shocks. This provides an approximation assuming
|
|
467
|
+
* expected values of future shocks.
|
|
468
|
+
*/
|
|
469
|
+
forecast(params, steps = 1) {
|
|
470
|
+
const { omega, alpha, gamma, beta } = params;
|
|
471
|
+
const variance = [];
|
|
472
|
+
const varianceSeries = this.getVarianceSeries(params);
|
|
473
|
+
const lastVariance = varianceSeries[varianceSeries.length - 1];
|
|
474
|
+
const lastReturn = this.returns[this.returns.length - 1];
|
|
475
|
+
// One-step ahead using actual last return
|
|
476
|
+
const sigma = Math.sqrt(lastVariance);
|
|
477
|
+
const z = lastReturn / sigma;
|
|
478
|
+
let logVariance = omega
|
|
479
|
+
+ alpha * (Math.abs(z) - EXPECTED_ABS_NORMAL)
|
|
480
|
+
+ gamma * z
|
|
481
|
+
+ beta * Math.log(lastVariance);
|
|
482
|
+
variance.push(Math.exp(logVariance));
|
|
483
|
+
// Multi-step: assume E[z] = 0, E[|z|] = √(2/π)
|
|
484
|
+
// So the α and γ terms contribute 0 on average
|
|
485
|
+
for (let h = 1; h < steps; h++) {
|
|
486
|
+
logVariance = omega + beta * logVariance;
|
|
487
|
+
variance.push(Math.exp(logVariance));
|
|
488
|
+
}
|
|
489
|
+
return {
|
|
490
|
+
variance,
|
|
491
|
+
volatility: variance.map(v => Math.sqrt(v)),
|
|
492
|
+
annualized: variance.map(v => Math.sqrt(v * this.periodsPerYear) * 100),
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Get the return series
|
|
497
|
+
*/
|
|
498
|
+
getReturns() {
|
|
499
|
+
return [...this.returns];
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Get initial variance estimate
|
|
503
|
+
*/
|
|
504
|
+
getInitialVariance() {
|
|
505
|
+
return this.initialVariance;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Convenience function to calibrate EGARCH(1,1) from candles
|
|
510
|
+
*/
|
|
511
|
+
function calibrateEgarch(data, options = {}) {
|
|
512
|
+
const model = new Egarch(data, options);
|
|
513
|
+
return model.fit(options);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
export { EXPECTED_ABS_NORMAL, Egarch, Garch, calculateReturns, calculateReturnsFromPrices, calibrateEgarch, calibrateGarch, checkLeverageEffect, nelderMead, sampleVariance, sampleVarianceWithMean };
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "garch",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "GARCH and EGARCH volatility models for TypeScript",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./build/index.cjs",
|
|
7
|
+
"module": "./build/index.mjs",
|
|
8
|
+
"types": "./types.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./types.d.ts",
|
|
12
|
+
"import": "./build/index.mjs",
|
|
13
|
+
"require": "./build/index.cjs"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"build",
|
|
18
|
+
"types.d.ts",
|
|
19
|
+
"README.md"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "rollup -c",
|
|
23
|
+
"test": "vitest run",
|
|
24
|
+
"test:watch": "vitest",
|
|
25
|
+
"prepublishOnly": "npm run build"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"garch",
|
|
29
|
+
"egarch",
|
|
30
|
+
"volatility",
|
|
31
|
+
"finance",
|
|
32
|
+
"econometrics",
|
|
33
|
+
"time-series"
|
|
34
|
+
],
|
|
35
|
+
"author": "",
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@rollup/plugin-typescript": "^12.3.0",
|
|
39
|
+
"@types/node": "^20.10.0",
|
|
40
|
+
"rollup": "^4.57.1",
|
|
41
|
+
"rollup-plugin-dts": "^6.3.0",
|
|
42
|
+
"rollup-plugin-peer-deps-external": "^2.2.4",
|
|
43
|
+
"tslib": "^2.8.1",
|
|
44
|
+
"typescript": "^5.3.0",
|
|
45
|
+
"vitest": "^1.0.0"
|
|
46
|
+
}
|
|
47
|
+
}
|