garch 1.0.3 → 1.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 +401 -56
- package/build/index.cjs +1166 -60
- package/build/index.mjs +1154 -61
- package/package.json +1 -1
- package/types.d.ts +295 -11
package/README.md
CHANGED
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
<p align="center">
|
|
6
|
-
<strong>Missing
|
|
7
|
-
GARCH
|
|
6
|
+
<strong>Missing volatility forecast for NodeJS</strong><br>
|
|
7
|
+
Realized GARCH, Realized EGARCH, Realized GJR-GARCH, HAR-RV and NoVaS<br>
|
|
8
|
+
models for TypeScript. Zero dependencies.
|
|
8
9
|
</p>
|
|
9
10
|
|
|
10
11
|
## Installation
|
|
@@ -15,9 +16,11 @@ npm install garch
|
|
|
15
16
|
|
|
16
17
|
## API
|
|
17
18
|
|
|
18
|
-
### `predict(candles, interval, currentPrice?)`
|
|
19
|
+
### `predict(candles, interval, currentPrice?, confidence?)`
|
|
19
20
|
|
|
20
|
-
Forecast expected price range for the next candle (t+1). Auto-selects
|
|
21
|
+
Forecast expected price range for the next candle (t+1). Auto-selects the best model (GARCH, EGARCH, GJR-GARCH, HAR-RV or NoVaS) by QLIKE forecast-error comparison.
|
|
22
|
+
|
|
23
|
+
Uses **log-normal price bands**: `P·exp(±z·σ)`, where `z = probit(confidence)`. This correctly maps log-return volatility back to price space — the corridor is asymmetric (upside > downside in absolute terms) and `lowerPrice` can never go negative.
|
|
21
24
|
|
|
22
25
|
```typescript
|
|
23
26
|
import { predict } from 'garch';
|
|
@@ -25,21 +28,50 @@ import type { Candle } from 'garch';
|
|
|
25
28
|
|
|
26
29
|
const candles: Candle[] = await fetchCandles('BTCUSDT', '4h', 200);
|
|
27
30
|
|
|
31
|
+
// Default: ±1σ band (~68% coverage)
|
|
28
32
|
const result = predict(candles, '4h');
|
|
29
33
|
// {
|
|
30
34
|
// currentPrice: 97500,
|
|
31
|
-
// sigma: 0.012, // 1.2%
|
|
32
|
-
// move:
|
|
33
|
-
// upperPrice:
|
|
34
|
-
// lowerPrice:
|
|
35
|
+
// sigma: 0.012, // 1.2% per-period volatility
|
|
36
|
+
// move: 1177, // upward expected move (upper - current)
|
|
37
|
+
// upperPrice: 98677, // P·exp(+σ) — ceiling
|
|
38
|
+
// lowerPrice: 96337, // P·exp(-σ) — floor
|
|
35
39
|
// modelType: 'egarch',
|
|
36
40
|
// reliable: true
|
|
37
41
|
// }
|
|
38
42
|
|
|
39
|
-
//
|
|
43
|
+
// 95% VaR band (z ≈ 1.96)
|
|
44
|
+
const var95 = predict(candles, '4h', undefined, 0.95);
|
|
45
|
+
|
|
46
|
+
// Custom reference price (e.g. VWAP)
|
|
40
47
|
const result = predict(candles, '4h', vwap);
|
|
41
48
|
```
|
|
42
49
|
|
|
50
|
+
**Confidence → z mapping:**
|
|
51
|
+
|
|
52
|
+
| `confidence` | z-score | Meaning |
|
|
53
|
+
|-------------|---------|---------|
|
|
54
|
+
| 0.6827 (default) | 1.00 | ±1σ, ~68% of moves captured |
|
|
55
|
+
| 0.90 | 1.645 | Moderate VaR |
|
|
56
|
+
| 0.95 | 1.96 | 95% VaR (standard) |
|
|
57
|
+
| 0.99 | 2.576 | Conservative VaR |
|
|
58
|
+
|
|
59
|
+
Any value in (0, 1) is valid — the table above lists common choices, but `probit` computes z for arbitrary confidence.
|
|
60
|
+
|
|
61
|
+
Higher confidence = wider corridor. `sigma` stays the same (it's the model's volatility estimate), only the z-multiplier changes. Example with sigma=1.2% and P=$97,500:
|
|
62
|
+
|
|
63
|
+
| `confidence` | z | upperPrice | lowerPrice | Corridor width |
|
|
64
|
+
|-------------|---|-----------|-----------|----------------|
|
|
65
|
+
| 0.6827 | 1.00 | $98,677 | $96,337 | $2,340 |
|
|
66
|
+
| 0.95 | 1.96 | $99,808 | $95,222 | $4,586 |
|
|
67
|
+
| 0.99 | 2.58 | $100,545 | $94,520 | $6,025 |
|
|
68
|
+
|
|
69
|
+
**When to use which:**
|
|
70
|
+
|
|
71
|
+
- **±1σ (default)** — typical expected move for the next candle. Good for scalping SL/TP targets and assessing whether a move is "normal" or significant
|
|
72
|
+
- **95% VaR** — worst reasonable scenario. Good for risk management, position sizing, and stop-losses that shouldn't be triggered by noise
|
|
73
|
+
- **99% VaR** — extreme tail risk. Good for stress testing and margin calculations
|
|
74
|
+
|
|
43
75
|
**Parameters:**
|
|
44
76
|
|
|
45
77
|
| Parameter | Type | Default | Description |
|
|
@@ -47,6 +79,7 @@ const result = predict(candles, '4h', vwap);
|
|
|
47
79
|
| `candles` | `Candle[]` | required | OHLCV candle data |
|
|
48
80
|
| `interval` | `CandleInterval` | required | Candle timeframe |
|
|
49
81
|
| `currentPrice` | `number` | last close | Reference price to center the corridor |
|
|
82
|
+
| `confidence` | `number` | `0.6827` | Two-sided probability in (0,1). Controls band width via `z = probit(confidence)` |
|
|
50
83
|
|
|
51
84
|
**Returns:** `PredictionResult`
|
|
52
85
|
|
|
@@ -54,19 +87,19 @@ const result = predict(candles, '4h', vwap);
|
|
|
54
87
|
interface PredictionResult {
|
|
55
88
|
currentPrice: number; // Reference price
|
|
56
89
|
sigma: number; // One-period volatility (decimal, e.g. 0.012 = 1.2%)
|
|
57
|
-
move: number; //
|
|
58
|
-
upperPrice: number; //
|
|
59
|
-
lowerPrice: number; //
|
|
60
|
-
modelType: 'garch' | 'egarch'; // Auto-selected model
|
|
90
|
+
move: number; // Upward price move = upperPrice - currentPrice
|
|
91
|
+
upperPrice: number; // P · exp(+z·σ)
|
|
92
|
+
lowerPrice: number; // P · exp(-z·σ)
|
|
93
|
+
modelType: 'garch' | 'egarch' | 'gjr-garch' | 'har-rv' | 'novas'; // Auto-selected model
|
|
61
94
|
reliable: boolean; // Quality flag (convergence + persistence + Ljung-Box)
|
|
62
95
|
}
|
|
63
96
|
```
|
|
64
97
|
|
|
65
98
|
---
|
|
66
99
|
|
|
67
|
-
### `predictRange(candles, interval, steps, currentPrice?)`
|
|
100
|
+
### `predictRange(candles, interval, steps, currentPrice?, confidence?)`
|
|
68
101
|
|
|
69
|
-
Forecast cumulative expected price range over multiple candles. Cumulative sigma = sqrt(sigma_1^2 + sigma_2^2 + ... + sigma_n^2). Use for swing trades where you hold across multiple periods.
|
|
102
|
+
Forecast cumulative expected price range over multiple candles. Cumulative sigma = sqrt(sigma_1^2 + sigma_2^2 + ... + sigma_n^2). Uses the same log-normal bands as `predict`. Use for swing trades where you hold across multiple periods.
|
|
70
103
|
|
|
71
104
|
```typescript
|
|
72
105
|
import { predictRange } from 'garch';
|
|
@@ -75,12 +108,15 @@ const range = predictRange(candles, '4h', 5);
|
|
|
75
108
|
// {
|
|
76
109
|
// currentPrice: 97500,
|
|
77
110
|
// sigma: 0.027, // cumulative ~2.7% over 5 candles
|
|
78
|
-
// move:
|
|
79
|
-
// upperPrice:
|
|
80
|
-
// lowerPrice:
|
|
111
|
+
// move: 2669, // upward expected move
|
|
112
|
+
// upperPrice: 100169, // P·exp(+z·σ)
|
|
113
|
+
// lowerPrice: 94901, // P·exp(-z·σ)
|
|
81
114
|
// modelType: 'egarch',
|
|
82
115
|
// reliable: true
|
|
83
116
|
// }
|
|
117
|
+
|
|
118
|
+
// 95% VaR over 5 candles
|
|
119
|
+
const var95 = predictRange(candles, '4h', 5, undefined, 0.95);
|
|
84
120
|
```
|
|
85
121
|
|
|
86
122
|
**Parameters:**
|
|
@@ -91,20 +127,25 @@ const range = predictRange(candles, '4h', 5);
|
|
|
91
127
|
| `interval` | `CandleInterval` | required | Candle timeframe |
|
|
92
128
|
| `steps` | `number` | required | Number of candles to forecast over |
|
|
93
129
|
| `currentPrice` | `number` | last close | Reference price |
|
|
130
|
+
| `confidence` | `number` | `0.6827` | Two-sided probability in (0,1) |
|
|
94
131
|
|
|
95
132
|
**Returns:** `PredictionResult` (same structure as `predict`)
|
|
96
133
|
|
|
97
134
|
---
|
|
98
135
|
|
|
99
|
-
### `backtest(candles, interval, requiredPercent?)`
|
|
136
|
+
### `backtest(candles, interval, confidence?, requiredPercent?)`
|
|
100
137
|
|
|
101
|
-
Walk-forward validation of `predict`. Uses 75% of candles for fitting, 25% for testing. Checks if the model's
|
|
138
|
+
Walk-forward validation of `predict`. Uses 75% of candles for fitting, 25% for testing. Checks if the model's price corridor captures actual price moves at the required hit rate.
|
|
139
|
+
|
|
140
|
+
`confidence` and `requiredPercent` are independent: `confidence` controls the **band width** (via `probit`), `requiredPercent` controls the **pass/fail threshold**.
|
|
102
141
|
|
|
103
142
|
```typescript
|
|
104
143
|
import { backtest } from 'garch';
|
|
105
144
|
|
|
106
|
-
backtest(candles, '4h');
|
|
107
|
-
backtest(candles, '4h',
|
|
145
|
+
backtest(candles, '4h'); // ±1σ band, hit rate >= 68%
|
|
146
|
+
backtest(candles, '4h', 0.95); // 95% VaR band, hit rate >= 68%
|
|
147
|
+
backtest(candles, '4h', 0.95, 90); // 95% VaR band, hit rate >= 90%
|
|
148
|
+
backtest(candles, '4h', undefined, 50); // ±1σ band, hit rate >= 50%
|
|
108
149
|
```
|
|
109
150
|
|
|
110
151
|
**Parameters:**
|
|
@@ -113,7 +154,8 @@ backtest(candles, '4h', 50); // true -- hit rate >= 50% (custom)
|
|
|
113
154
|
|-----------|------|---------|-------------|
|
|
114
155
|
| `candles` | `Candle[]` | required | OHLCV candle data |
|
|
115
156
|
| `interval` | `CandleInterval` | required | Candle timeframe |
|
|
116
|
-
| `
|
|
157
|
+
| `confidence` | `number` | `0.6827` | Two-sided probability in (0,1) for the prediction band |
|
|
158
|
+
| `requiredPercent` | `number` | `68` | Minimum hit rate (0–100) to pass |
|
|
117
159
|
|
|
118
160
|
**Returns:** `boolean`
|
|
119
161
|
|
|
@@ -121,18 +163,20 @@ backtest(candles, '4h', 50); // true -- hit rate >= 50% (custom)
|
|
|
121
163
|
|
|
122
164
|
## Supported Intervals
|
|
123
165
|
|
|
124
|
-
| Interval | Min Candles | Periods/Year | Coverage |
|
|
125
|
-
|
|
126
|
-
| `1m` | 500 | 525,600 | ~8-16 hours |
|
|
127
|
-
| `3m` | 500 | 175,200 | ~25 hours |
|
|
128
|
-
| `5m` | 500 | 105,120 | ~1.7 days |
|
|
129
|
-
| `15m` | 300 | 35,040 | ~3 days |
|
|
130
|
-
| `30m` | 200 | 17,520 | ~4 days |
|
|
131
|
-
| `1h` | 200 | 8,760 | ~8 days |
|
|
132
|
-
| `2h` | 200 | 4,380 | ~17 days |
|
|
133
|
-
| `4h` | 200 | 2,190 | ~33 days |
|
|
134
|
-
| `6h` | 150 | 1,460 | ~37 days |
|
|
135
|
-
| `8h` | 150 | 1,095 | ~50 days |
|
|
166
|
+
| Interval | Min Candles | Recommended | Periods/Year | Coverage |
|
|
167
|
+
|----------|-------------|-------------|--------------|----------|
|
|
168
|
+
| `1m` | 500 | 1,500 | 525,600 | ~8-16 hours |
|
|
169
|
+
| `3m` | 500 | 1,500 | 175,200 | ~25 hours |
|
|
170
|
+
| `5m` | 500 | 1,500 | 105,120 | ~1.7 days |
|
|
171
|
+
| `15m` | 300 | 1,000 | 35,040 | ~3 days |
|
|
172
|
+
| `30m` | 200 | 1,000 | 17,520 | ~4 days |
|
|
173
|
+
| `1h` | 200 | 500 | 8,760 | ~8 days |
|
|
174
|
+
| `2h` | 200 | 500 | 4,380 | ~17 days |
|
|
175
|
+
| `4h` | 200 | 500 | 2,190 | ~33 days |
|
|
176
|
+
| `6h` | 150 | 300 | 1,460 | ~37 days |
|
|
177
|
+
| `8h` | 150 | 300 | 1,095 | ~50 days |
|
|
178
|
+
|
|
179
|
+
For intervals below 1h, per-candle Parkinson RV is noisier — more data helps OLS and QLIKE model selection. A `console.warn` is emitted when candle count is below the recommended value. Always check `reliable: true` in the output.
|
|
136
180
|
|
|
137
181
|
## Timeframes
|
|
138
182
|
|
|
@@ -157,7 +201,16 @@ Lower timeframes contain more microstructure noise — use larger datasets to co
|
|
|
157
201
|
|
|
158
202
|
### GARCH(1,1)
|
|
159
203
|
|
|
160
|
-
Conditional variance model (Bollerslev, 1986):
|
|
204
|
+
Conditional variance model (Bollerslev, 1986). Input type determines the innovation term automatically:
|
|
205
|
+
|
|
206
|
+
**Candle[] input** — Realized GARCH (Hansen & Huang, 2016). Uses Parkinson (1980) per-candle realized variance proxy (~5× more efficient than squared returns):
|
|
207
|
+
|
|
208
|
+
```
|
|
209
|
+
sigma_t^2 = omega + alpha * RV_{t-1} + beta * sigma_{t-1}^2
|
|
210
|
+
RV_t = (1 / (4·ln2)) · ln(H/L)^2 (Parkinson estimator)
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
**number[] input** — Classical GARCH. Uses squared returns:
|
|
161
214
|
|
|
162
215
|
```
|
|
163
216
|
sigma_t^2 = omega + alpha * epsilon_{t-1}^2 + beta * sigma_{t-1}^2
|
|
@@ -169,13 +222,16 @@ sigma_t^2 = omega + alpha * epsilon_{t-1}^2 + beta * sigma_{t-1}^2
|
|
|
169
222
|
- Stationarity constraint: **alpha + beta < 1**
|
|
170
223
|
- Unconditional variance: **E[sigma^2] = omega / (1 - alpha - beta)**
|
|
171
224
|
|
|
172
|
-
Parameter estimation via **
|
|
225
|
+
Parameter estimation via **Student-t MLE** (maximum likelihood) with multi-start Nelder-Mead optimization. The Student-t distribution captures fat tails observed in financial returns:
|
|
173
226
|
|
|
174
227
|
```
|
|
175
|
-
LL =
|
|
228
|
+
LL = n·[lnΓ((df+1)/2) - lnΓ(df/2) - 0.5·ln(π·(df-2))]
|
|
229
|
+
- 0.5 · sum[ ln(sigma_t^2) + (df+1)·ln(1 + r_t^2 / ((df-2)·sigma_t^2)) ]
|
|
176
230
|
```
|
|
177
231
|
|
|
178
|
-
|
|
232
|
+
- **df** > 2 — degrees of freedom (estimated jointly with omega, alpha, beta via multi-start Nelder-Mead)
|
|
233
|
+
|
|
234
|
+
Multi-step forecast converges to unconditional variance (E[RV] = sigma^2, so recursion is identical):
|
|
179
235
|
|
|
180
236
|
```
|
|
181
237
|
sigma_{t+h}^2 = omega + (alpha + beta) * sigma_{t+h-1}^2
|
|
@@ -183,26 +239,191 @@ sigma_{t+h}^2 = omega + (alpha + beta) * sigma_{t+h-1}^2
|
|
|
183
239
|
|
|
184
240
|
### EGARCH(1,1)
|
|
185
241
|
|
|
186
|
-
Exponential GARCH (Nelson, 1991). Models log-variance, capturing asymmetric volatility:
|
|
242
|
+
Exponential GARCH (Nelson, 1991). Models log-variance, capturing asymmetric volatility. Input type determines the magnitude term automatically:
|
|
243
|
+
|
|
244
|
+
**Candle[] input** — Realized EGARCH. Magnitude uses Parkinson RV, leverage keeps directional return:
|
|
187
245
|
|
|
188
246
|
```
|
|
189
|
-
ln(sigma_t^2) = omega + alpha * (
|
|
247
|
+
ln(sigma_t^2) = omega + alpha * (sqrt(RV_{t-1} / sigma_{t-1}^2) - E[|Z|]) + gamma * z_{t-1} + beta * ln(sigma_{t-1}^2)
|
|
190
248
|
```
|
|
191
249
|
|
|
192
|
-
|
|
250
|
+
**number[] input** — Classical EGARCH. Magnitude uses |z|:
|
|
193
251
|
|
|
252
|
+
```
|
|
253
|
+
ln(sigma_t^2) = omega + alpha * (|z_{t-1}| - E[|Z|]) + gamma * z_{t-1} + beta * ln(sigma_{t-1}^2)
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Where **z_t = epsilon_t / sigma_t** is the standardized residual and **E[|Z|]** is the expected absolute value of a standardized Student-t(df) variable:
|
|
257
|
+
|
|
258
|
+
```
|
|
259
|
+
E[|Z|] = sqrt((df-2)/pi) · Γ((df-1)/2) / Γ(df/2)
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
- **sqrt(RV/sigma^2)** is a more efficient estimate of |z| from OHLC data
|
|
194
263
|
- **gamma** < 0 — leverage effect (negative returns increase vol more than positive)
|
|
195
264
|
- No positivity constraints needed (log-variance is always real)
|
|
196
265
|
- Stationarity: **|beta| < 1**
|
|
197
266
|
- Unconditional variance: **E[sigma^2] ~ exp(omega / (1 - beta))**
|
|
267
|
+
- **df** > 2 — degrees of freedom (estimated jointly via multi-start Nelder-Mead)
|
|
268
|
+
|
|
269
|
+
### GJR-GARCH(1,1)
|
|
270
|
+
|
|
271
|
+
Threshold GARCH (Glosten, Jagannathan & Runkle, 1993). Captures leverage effect in variance space (unlike EGARCH which uses log-variance). Input type determines the innovation term automatically:
|
|
272
|
+
|
|
273
|
+
**Candle[] input** — Realized GJR-GARCH. Uses Parkinson RV as innovation, return sign as leverage indicator:
|
|
274
|
+
|
|
275
|
+
```
|
|
276
|
+
sigma_t^2 = omega + alpha * RV_{t-1} + gamma * RV_{t-1} * I(r_{t-1}<0) + beta * sigma_{t-1}^2
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
**number[] input** — Classical GJR-GARCH. Uses squared returns:
|
|
280
|
+
|
|
281
|
+
```
|
|
282
|
+
sigma_t^2 = omega + alpha * epsilon_{t-1}^2 + gamma * epsilon_{t-1}^2 * I(r_{t-1}<0) + beta * sigma_{t-1}^2
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Where **I(r<0) = 1** when return is negative, **0** otherwise.
|
|
286
|
+
|
|
287
|
+
- **gamma** >= 0 — leverage coefficient (bad news amplifies variance by extra gamma)
|
|
288
|
+
- **omega** > 0, **alpha** >= 0, **beta** >= 0
|
|
289
|
+
- Stationarity: **alpha + gamma/2 + beta < 1** (half of innovations are negative on average)
|
|
290
|
+
- Unconditional variance: **E[sigma^2] = omega / (1 - alpha - gamma/2 - beta)**
|
|
291
|
+
- **df** > 2 — degrees of freedom (estimated jointly via multi-start Nelder-Mead)
|
|
292
|
+
|
|
293
|
+
Multi-step forecast uses effective persistence = alpha + gamma/2 + beta:
|
|
294
|
+
|
|
295
|
+
```
|
|
296
|
+
sigma_{t+h}^2 = omega + (alpha + gamma/2 + beta) * sigma_{t+h-1}^2
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### HAR-RV
|
|
300
|
+
|
|
301
|
+
Heterogeneous Autoregressive model of Realized Variance (Corsi, 2009). Captures multi-scale volatility clustering via three overlapping horizons:
|
|
302
|
+
|
|
303
|
+
```
|
|
304
|
+
RV_{t+1} = beta_0 + beta_1 * RV_short + beta_2 * RV_medium + beta_3 * RV_long + epsilon
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
Where **RV_t** is the per-candle realized variance proxy:
|
|
308
|
+
|
|
309
|
+
- **OHLC input** — Parkinson (1980): **RV_t = (1 / (4·ln2)) · ln(H/L)^2** (~5× more efficient than close-to-close)
|
|
310
|
+
- **Prices-only input** — squared return: **RV_t = r_t^2** (fallback when no OHLC available)
|
|
311
|
+
|
|
312
|
+
Classic HAR-RV uses sum of intraday squared returns, but that requires tick/minute data. Parkinson uses the high-low range of each candle as a variance proxy — no intraday data needed.
|
|
313
|
+
|
|
314
|
+
- **RV_short** = mean(RV_t) — last 1 period (default)
|
|
315
|
+
- **RV_medium** = mean(RV_{t-4} ... RV_t) — last 5 periods
|
|
316
|
+
- **RV_long** = mean(RV_{t-21} ... RV_t) — last 22 periods
|
|
317
|
+
|
|
318
|
+
Parameter estimation via **OLS** (closed-form, always converges):
|
|
319
|
+
|
|
320
|
+
```
|
|
321
|
+
beta = (X'X)^{-1} X'y
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
After OLS, **df** (degrees of freedom) is profiled via grid search over the Student-t log-likelihood with the OLS-fitted variance series.
|
|
325
|
+
|
|
326
|
+
- Persistence: **beta_1 + beta_2 + beta_3 < 1** for stationarity
|
|
327
|
+
- Unconditional variance: **E[RV] = beta_0 / (1 - beta_1 - beta_2 - beta_3)**
|
|
328
|
+
- **R^2** measures explained variance in the regression
|
|
329
|
+
|
|
330
|
+
Multi-step forecast via iterative substitution: each predicted RV feeds back into the rolling components for subsequent steps.
|
|
331
|
+
|
|
332
|
+
### NoVaS
|
|
333
|
+
|
|
334
|
+
Normalizing and Variance-Stabilizing transformation (Politis, 2003). Model-free approach using the ARCH frame. Input type determines the innovation term automatically:
|
|
335
|
+
|
|
336
|
+
**Candle[] input** — Realized NoVaS. Uses Parkinson (1980) per-candle RV as innovation (~5× more efficient than squared returns):
|
|
337
|
+
|
|
338
|
+
```
|
|
339
|
+
sigma_t^2 = a_0 + a_1 * RV_{t-1} + a_2 * RV_{t-2} + ... + a_p * RV_{t-p}
|
|
340
|
+
RV_t = (1 / (4·ln2)) · ln(H/L)^2 (Parkinson estimator)
|
|
341
|
+
W_t = X_t / sigma_t
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
**number[] input** — Classical NoVaS. Uses squared returns:
|
|
345
|
+
|
|
346
|
+
```
|
|
347
|
+
sigma_t^2 = a_0 + a_1 * X_{t-1}^2 + a_2 * X_{t-2}^2 + ... + a_p * X_{t-p}^2
|
|
348
|
+
W_t = X_t / sigma_t
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
Parameters **a_0, ..., a_p** are chosen to minimize the non-normality of the transformed series {W_t}:
|
|
352
|
+
|
|
353
|
+
```
|
|
354
|
+
D^2 = S^2 + (K - 3)^2
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
where **S** = skewness and **K** = kurtosis of {W_t}. For perfect normality D^2 = 0.
|
|
358
|
+
|
|
359
|
+
- **a_0** > 0 — baseline variance
|
|
360
|
+
- **a_j** >= 0 — weight on j-th lagged innovation (RV or squared return)
|
|
361
|
+
- Stationarity: **sum(a_1 ... a_p) < 1**
|
|
362
|
+
- Unconditional variance: **E[sigma^2] = a_0 / (1 - sum(a_1 ... a_p))**
|
|
363
|
+
- Default lags: **p = 10** (configurable)
|
|
364
|
+
- Parkinson RV is less noisy than r², so Candle[] typically achieves lower D^2
|
|
365
|
+
|
|
366
|
+
Key difference from GARCH: parameters are found via **normality criterion** (D^2 minimization), not MLE. No distributional assumptions on the return series — truly model-free. Uses multi-start Nelder-Mead for optimization (6 restarts to escape local minima in the 11-dimensional D^2 landscape). After fitting, **df** is profiled via grid search over the Student-t log-likelihood (same as HAR-RV).
|
|
367
|
+
|
|
368
|
+
After D^2 optimization, weights are **rescaled via OLS** on the realized variance series to minimize forecast error. This keeps NoVaS model-free (D^2 selects lag structure) while making it QLIKE-competitive with HAR-RV.
|
|
369
|
+
|
|
370
|
+
**Two-stage calibration:**
|
|
371
|
+
|
|
372
|
+
- **Stage 1** — D^2 minimization: discovers lag structure via normality criterion (model-free). Produces `weights` (a_0, ..., a_p).
|
|
373
|
+
- **Stage 2** — OLS rescaling: regresses RV_{t+1} on sigma_t^2(D^2) to produce forecast-optimal weights. Produces `forecastWeights` = [beta_0, beta_1].
|
|
374
|
+
|
|
375
|
+
```
|
|
376
|
+
forecast_sigma_t^2 = beta_0 + beta_1 * sigma_t^2(D^2)
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
D^2 acts as a data-driven smoother over RV lags — more flexible than HAR-RV's fixed rolling means (1, 5, 22). OLS rescaling adjusts for bias with only 2 parameters, keeping the model robust on small samples with noisy per-candle RV.
|
|
380
|
+
|
|
381
|
+
Multi-step forecast: replace future innovations with sigma^2 (since E[RV] = E[X^2] = sigma^2), then rescale:
|
|
382
|
+
|
|
383
|
+
```
|
|
384
|
+
d2_{t+h} = a_0 + sum_j a_j * E[innovation_{t+h-j}]
|
|
385
|
+
sigma_{t+h}^2 = beta_0 + beta_1 * d2_{t+h}
|
|
386
|
+
```
|
|
198
387
|
|
|
199
388
|
### Model Auto-Selection
|
|
200
389
|
|
|
201
|
-
`predict` and `predictRange`
|
|
390
|
+
`predict` and `predictRange` fit all five models and pick the winner by **QLIKE** (Patton, 2011) — a neutral forecast-error metric that judges how well each model's variance series predicts realized variance:
|
|
391
|
+
|
|
392
|
+
```
|
|
393
|
+
QLIKE = (1/n) · sum[ RV_t / sigma_t^2 - ln(RV_t / sigma_t^2) - 1 ]
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
Lower QLIKE = better forecast. Unlike AIC (which favors MLE-calibrated models), QLIKE is neutral to calibration method — OLS, D^2-minimization, and MLE all compete on equal footing.
|
|
397
|
+
|
|
398
|
+
1. **GARCH-family pipeline**: fit GARCH, EGARCH, GJR-GARCH — pick best by AIC (fair since all three optimize Student-t LL)
|
|
399
|
+
2. **HAR-RV pipeline**: fit HAR-RV via OLS. Skip if persistence >= 1 or R^2 < 0
|
|
400
|
+
3. **NoVaS pipeline**: fit NoVaS via D^2 minimization + OLS rescaling. Skip if persistence >= 1
|
|
401
|
+
4. Compute Parkinson RV from candles. Score each pipeline's variance series by QLIKE. Lowest QLIKE wins
|
|
402
|
+
|
|
403
|
+
```
|
|
404
|
+
fitModel(candles)
|
|
405
|
+
|-- fitGarchFamily() --> min_AIC(GARCH, EGARCH, GJR-GARCH) --> QLIKE_1
|
|
406
|
+
|-- fitHarRv() --> HAR-RV (OLS) --> QLIKE_2
|
|
407
|
+
|-- fitNoVaS() --> NoVaS (D²) --> QLIKE_3
|
|
408
|
+
|-- rv = perCandleParkinson(candles)
|
|
409
|
+
\-- return min(QLIKE_1, QLIKE_2, QLIKE_3)
|
|
410
|
+
```
|
|
202
411
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
412
|
+
When given `Candle[]`, all five OHLC-aware models (GARCH, EGARCH, GJR-GARCH, HAR-RV, NoVaS) use Parkinson per-candle RV instead of squared returns, extracting ~5× more information from the same data.
|
|
413
|
+
|
|
414
|
+
### When Each Model Wins
|
|
415
|
+
|
|
416
|
+
The library fits all five models on every call and picks the best by QLIKE. Each model uses its own calibration method (MLE, OLS, or D^2) but they all compete on the same forecast-error metric. Here's what patterns in data favor each:
|
|
417
|
+
|
|
418
|
+
- **GARCH** — Volatility spikes after any big move (up or down equally), then gradually fades back to normal. No difference between bullish and bearish shocks. Classic symmetric mean-reverting vol clustering.
|
|
419
|
+
|
|
420
|
+
- **EGARCH** — Drops hit harder than pumps. A -5% candle causes way more volatility than a +5% candle. Strong "fear > greed" asymmetry. Works through a log-variance model so the asymmetry coefficient `gamma * z` directly amplifies negative shocks. Typical for stocks and BTC during risk-off periods.
|
|
421
|
+
|
|
422
|
+
- **GJR-GARCH** — Same idea as EGARCH (red candles increase vol more than green ones) but the effect is milder and simpler: when the return is negative, a bonus term `gamma * epsilon^2` is added to variance. When positive — nothing extra. A binary switch rather than a continuous asymmetry. Common in altcoins and less panic-prone markets.
|
|
423
|
+
|
|
424
|
+
- **HAR-RV** — Volatility has memory at multiple horizons. The model takes a single timeframe of candles and internally builds three scales: last candle's RV (short, lag 1), rolling average over 5 candles (medium), and rolling average over 22 candles (long). These three components are combined via OLS regression. Works well when different types of participants (scalpers, swing traders, institutions) all influence the same market at different speeds. If your asset has visible "rhythm" across day/week/month — HAR-RV will likely beat GARCH family (sideways, daily patterns).
|
|
425
|
+
|
|
426
|
+
- **NoVaS** — Volatility drifts or cycles without clear shock-and-decay patterns. Slow trend changes, regime shifts, compression/expansion phases, or "breathing" patterns that don't fit any parametric formula. Model-free: finds weights `a_0...a_p` that make the normalized series as close to Gaussian as possible (minimizes D^2 = skewness^2 + (kurtosis - 3)^2). No assumptions about the distribution shape.
|
|
206
427
|
|
|
207
428
|
### Variance Estimators
|
|
208
429
|
|
|
@@ -222,6 +443,87 @@ sigma^2_GK = (1/n) * sum[ 0.5 * ln(H/L)^2 - (2*ln2 - 1) * ln(C/O)^2 ]
|
|
|
222
443
|
|
|
223
444
|
~5x more efficient than close-to-close variance.
|
|
224
445
|
|
|
446
|
+
### Log-Normal Price Bands
|
|
447
|
+
|
|
448
|
+
GARCH models volatility of **log-returns**, not absolute price changes. The correct mapping from log-return volatility back to price space uses the exponential:
|
|
449
|
+
|
|
450
|
+
```
|
|
451
|
+
upperPrice = P · exp(+z · sigma)
|
|
452
|
+
lowerPrice = P · exp(-z · sigma)
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
where `z = probit(confidence)` is the z-score corresponding to the desired two-sided confidence level. This produces **asymmetric** bands (upside > downside in absolute terms) and guarantees `lowerPrice > 0`.
|
|
456
|
+
|
|
457
|
+
The previous linear approximation `P · (1 ± sigma)` is a first-order Taylor expansion of `exp(±sigma)`. The difference grows with sigma:
|
|
458
|
+
|
|
459
|
+
| sigma | Linear `1 + sigma` | Exact `exp(sigma)` | Error |
|
|
460
|
+
|-------|--------------------|--------------------|-------|
|
|
461
|
+
| 0.02 | 1.0200 | 1.0202 | ~0.01% |
|
|
462
|
+
| 0.10 | 1.1000 | 1.1052 | ~0.5% |
|
|
463
|
+
| 0.30 | 1.3000 | 1.3499 | ~3.8% |
|
|
464
|
+
|
|
465
|
+
### Probit (Inverse Normal CDF)
|
|
466
|
+
|
|
467
|
+
`probit(confidence)` computes the inverse of the standard normal CDF (Phi^{-1}). It converts a two-sided probability to a z-score:
|
|
468
|
+
|
|
469
|
+
```
|
|
470
|
+
confidence = P(-z < Z < z), Z ~ N(0,1)
|
|
471
|
+
z = probit(confidence)
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
There is no closed-form solution for Phi^{-1} — it is a transcendental equation. The implementation uses **Acklam's rational approximation** (Peter J. Acklam, 2002) with max relative error < 1.15 x 10^{-9}.
|
|
475
|
+
|
|
476
|
+
**Step 1** — convert two-sided confidence to upper-tail probability:
|
|
477
|
+
|
|
478
|
+
```
|
|
479
|
+
p = (1 + confidence) / 2
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
**Step 2** — piecewise rational approximation over three regions:
|
|
483
|
+
|
|
484
|
+
**Central region** (0.02425 <= p <= 0.97575) — covers ~95% of inputs:
|
|
485
|
+
|
|
486
|
+
```
|
|
487
|
+
q = p - 0.5
|
|
488
|
+
r = q^2
|
|
489
|
+
z = (a1·r^5 + a2·r^4 + a3·r^3 + a4·r^2 + a5·r + a6) · q
|
|
490
|
+
/ (b1·r^5 + b2·r^4 + b3·r^3 + b4·r^2 + b5·r + 1)
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
**Tails** (p < 0.02425 or p > 0.97575):
|
|
494
|
+
|
|
495
|
+
```
|
|
496
|
+
q = sqrt(-2·ln(p)) // left tail
|
|
497
|
+
z = (c1·q^5 + ... + c6) / (d1·q^4 + ... + 1)
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
For the right tail: `q = sqrt(-2·ln(1-p))`, result is negated. The 16 coefficients (a1–a6, b1–b5, c1–c6, d1–d4) are minimax-optimal rational approximation constants.
|
|
501
|
+
|
|
502
|
+
| `confidence` | `p` | `z` | Meaning |
|
|
503
|
+
|-------------|-----|-----|---------|
|
|
504
|
+
| 0.6827 | 0.8413 | 1.000 | ±1 sigma (default) |
|
|
505
|
+
| 0.90 | 0.9500 | 1.645 | Moderate VaR |
|
|
506
|
+
| 0.95 | 0.9750 | 1.960 | 95% VaR |
|
|
507
|
+
| 0.99 | 0.9950 | 2.576 | Conservative VaR |
|
|
508
|
+
|
|
509
|
+
### Student-t Log-Likelihood
|
|
510
|
+
|
|
511
|
+
All five models use a **Student-t** distribution for log-likelihood computation. Financial return series exhibit fat tails (excess kurtosis), and the Student-t captures this with an additional **degrees of freedom (df)** parameter:
|
|
512
|
+
|
|
513
|
+
- **df ~ 3–10**: Heavy tails (common in crypto/equity returns)
|
|
514
|
+
- **df ~ 20–50**: Mild excess kurtosis
|
|
515
|
+
- **df → ∞**: Thin tails
|
|
516
|
+
|
|
517
|
+
For GARCH, EGARCH, and GJR-GARCH, **df** is optimized jointly with the other parameters via Nelder-Mead. For HAR-RV and NoVaS, **df** is profiled via a two-pass grid search (coarse 2.5–50, then fine ±1 around the best) after the main optimization.
|
|
518
|
+
|
|
519
|
+
Helper functions exported from the library:
|
|
520
|
+
|
|
521
|
+
- `logGamma(x)` — Lanczos approximation (g=7, n=9), ~15-digit accuracy
|
|
522
|
+
- `studentTNegLL(returns, varianceSeries, df)` — Full negative log-likelihood
|
|
523
|
+
- `expectedAbsStudentT(df)` — E[|Z|] for standardized t(df), used in EGARCH centering
|
|
524
|
+
- `profileStudentTDf(returns, varianceSeries)` — Grid search for optimal df
|
|
525
|
+
- `qlike(varianceSeries, rv)` — QLIKE loss (Patton, 2011) for volatility forecast evaluation
|
|
526
|
+
|
|
225
527
|
### Reliability Check
|
|
226
528
|
|
|
227
529
|
The `reliable` flag in `PredictionResult` is `true` when all three conditions hold:
|
|
@@ -232,25 +534,68 @@ The `reliable` flag in `PredictionResult` is `true` when all three conditions ho
|
|
|
232
534
|
|
|
233
535
|
### Optimization
|
|
234
536
|
|
|
235
|
-
|
|
537
|
+
GARCH/EGARCH/GJR-GARCH parameters (including df) are estimated via **multi-start Nelder-Mead** simplex method (derivative-free). Each model runs Nelder-Mead from multiple deterministic starting points (golden-ratio quasi-random perturbation) and keeps the best result — this escapes local minima that single-start NM would get stuck in, especially important for higher-dimensional problems like NoVaS (11 parameters).
|
|
538
|
+
|
|
539
|
+
| Model | Parameters | Restarts | Total NM runs |
|
|
540
|
+
|-------|-----------|----------|---------------|
|
|
541
|
+
| GARCH | 4 (omega, alpha, beta, df) | 3 | 4 |
|
|
542
|
+
| EGARCH | 5 (omega, alpha, gamma, beta, df) | 4 | 5 |
|
|
543
|
+
| GJR-GARCH | 5 (omega, alpha, gamma, beta, df) | 4 | 5 |
|
|
544
|
+
| NoVaS | p+1 (a_0...a_p, default p=10) | 6 | 7 |
|
|
545
|
+
| HAR-RV | 4 (beta_0...beta_3) | — | OLS (closed-form) |
|
|
546
|
+
|
|
547
|
+
Default per run: 1000 iterations (2000 for NoVaS D^2), tolerance 1e-8. Initial simplex uses 20% perturbation from x0 for broader exploration. HAR-RV uses **OLS** (exact solution in one step) + df profiling.
|
|
548
|
+
|
|
549
|
+
Within the GARCH family, model comparison uses **AIC** (2k - 2LL) — fair since all three optimize the same Student-t LL objective. Across model families (GARCH vs HAR-RV vs NoVaS), comparison uses **QLIKE** (Patton, 2011):
|
|
550
|
+
|
|
551
|
+
```
|
|
552
|
+
QLIKE = (1/n) · sum[ RV_t / sigma_t^2 - ln(RV_t / sigma_t^2) - 1 ]
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
where RV_t is Parkinson per-candle realized variance and sigma_t^2 is the model's fitted conditional variance. QLIKE is the standard loss function for volatility forecast evaluation — it is neutral to calibration method (MLE, OLS, D^2 all compete fairly).
|
|
236
556
|
|
|
237
557
|
## Tests
|
|
238
558
|
|
|
239
|
-
**
|
|
559
|
+
**923 tests** across **22 test files**. All passing.
|
|
240
560
|
|
|
241
561
|
| Category | Files | Tests | What's covered |
|
|
242
562
|
|----------|-------|-------|----------------|
|
|
243
|
-
| Mathematical formulas | `math.test.ts` | 45 | GARCH/EGARCH variance recursion, log-likelihood, forecast formulas, AIC/BIC, Yang-Zhang, Garman-Klass, Ljung-Box, chi-squared |
|
|
563
|
+
| Mathematical formulas | `math.test.ts` | 45 | GARCH/EGARCH variance recursion, log-likelihood, forecast formulas, AIC/BIC, QLIKE, Yang-Zhang, Garman-Klass, Ljung-Box, chi-squared |
|
|
564
|
+
| Math coverage | `math-coverage.test.ts` | 79 | Parkinson formula verification, rv↔returns alignment, H=L fallback, Parkinson-based forecast, candle validation, reliable flag cascade, backtest validity, numerical precision, cross-model consistency, Realized GARCH/EGARCH/GJR-GARCH Candle[] vs number[], perCandleParkinson shared function |
|
|
244
565
|
| Full pipeline coverage | `plan-coverage.test.ts` | 73 | End-to-end: fit, forecast, predict, predictRange, backtest, model selection |
|
|
245
566
|
| GARCH unit | `garch.test.ts` | 10 | Parameter estimation, variance series, forecast convergence, candle vs price input |
|
|
246
|
-
| EGARCH unit | `egarch.test.ts` | 11 | Leverage detection, asymmetric volatility, model comparison
|
|
247
|
-
|
|
|
248
|
-
|
|
|
249
|
-
|
|
|
250
|
-
|
|
|
567
|
+
| EGARCH unit | `egarch.test.ts` | 11 | Leverage detection, asymmetric volatility, model comparison |
|
|
568
|
+
| GJR-GARCH unit | `gjr-garch.test.ts` | 86 | Variance recursion (r² and Parkinson), indicator function I(r<0), forecast formula (one-step + multi-step), constraint barriers, computed fields, AIC/BIC numParams=5, estimation properties (perturbation, determinism), numerical stability, degenerate params, Realized path (Candle[] vs number[], flat candles, bad OHLC), options forwarding, immutability, instance isolation, cross-model consistency, scale invariance, property-based fuzz, predict/predictRange/backtest integration |
|
|
569
|
+
| HAR-RV unit | `har.test.ts` | 138 | OLS regression, R^2, Parkinson RV proxy, forecast convergence, multi-step iterative substitution, rolling RV components, edge cases, fuzz, integration with predict, OLS orthogonality, TSS=RSS+ESS, normal equations, regression snapshots, mutation safety |
|
|
570
|
+
| NoVaS unit | `novas.test.ts` | 109 | D^2 minimization, normality improvement, variance series, forecast convergence, edge cases, fuzz, integration with predict, determinism, scale invariance |
|
|
571
|
+
| Optimizer | `optimizer.test.ts`, `optimizer-shrink.test.ts` | 20 | Nelder-Mead on Rosenbrock/quadratic/parabolic, convergence, shrinking, multi-start (Rastrigin escape, high-dimensional, equivalence) |
|
|
572
|
+
| Statistical properties | `properties.test.ts` | 15 | Parameter recovery from synthetic data, local LL maximum, unconditional variance, GJR-GARCH forecast convergence and model selection |
|
|
573
|
+
| Regression | `regression.test.ts` | 11 | Parameter recovery, deterministic outputs, cross-model consistency for GARCH/EGARCH/GJR-GARCH |
|
|
574
|
+
| Stability | `stability.test.ts` | 12 | Long-term forecast behavior, variance convergence, GJR-GARCH near-constant and outlier handling |
|
|
251
575
|
| Robustness | `robustness.test.ts` | 53 | Extreme moves, stress scenarios |
|
|
252
|
-
|
|
|
253
|
-
|
|
|
576
|
+
| Realized models | `realized-garch.test.ts` | 83 | Candle[] vs number[] for GARCH/EGARCH/GJR-GARCH/NoVaS, Parkinson RV edge cases, flat candles, extreme H/L, scale invariance, all-identical OHLC, minimum-length boundary, D² comparison, predict fallback, **ground-truth volatility recovery** (see below) |
|
|
577
|
+
| Edge cases | `edge-cases.test.ts`, `coverage-gaps*.test.ts` | 165 | Insufficient data, near-unit-root, zero returns, constant prices, negative prices, overflow/underflow, trending data, 10K+ data points, GJR-GARCH immutability and instance isolation, EGARCH df≤2 fallback, logGamma/expectedAbsStudentT/qlike edge cases |
|
|
578
|
+
| Miscellaneous | `misc.test.ts` | 13 | Integration scenarios, different intervals, immutability |
|
|
579
|
+
|
|
580
|
+
### Ground-Truth Volatility Test
|
|
581
|
+
|
|
582
|
+
The test suite includes an end-to-end integration test that verifies `predict` recovers known volatility from synthetic data. This is the strongest correctness guarantee — it proves the entire pipeline (data generation, model fitting, auto-selection, forecasting) produces numerically accurate results.
|
|
583
|
+
|
|
584
|
+
**How it works:**
|
|
585
|
+
|
|
586
|
+
1. Generate 500 OHLC candles with **known constant per-period volatility** sigma_true (returns are iid N(0, sigma_true^2), high/low simulated via Brownian bridge noise)
|
|
587
|
+
2. Run `predict()` on these candles — auto-selects the best model, fits parameters, produces a 1-step forecast
|
|
588
|
+
3. Verify predicted sigma matches sigma_true
|
|
589
|
+
|
|
590
|
+
**What is verified:**
|
|
591
|
+
|
|
592
|
+
| Test | Assertion |
|
|
593
|
+
|------|-----------|
|
|
594
|
+
| sigma_true = 0.2%, 1%, 3% | Relative error < 50% for each |
|
|
595
|
+
| Monotonicity | sigma_true_1 < sigma_true_2 < sigma_true_3 implies predicted_1 < predicted_2 < predicted_3 |
|
|
596
|
+
| Median accuracy (20 seeds) | Median relative error < 30% across 20 independent runs (sigma_true = 1%) |
|
|
597
|
+
| +-1 sigma hit rate (Monte Carlo) | Out-of-sample +-1 sigma corridor captures 45–90% of actual next moves across 30 trials (theoretical: 68.27%) |
|
|
598
|
+
| Proportionality | 2x sigma_true produces ~2x predicted sigma (ratio between 1.2x and 3.5x) |
|
|
254
599
|
|
|
255
600
|
```bash
|
|
256
601
|
npm test # run all tests
|