garch 1.2.4 → 2.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.
Files changed (6) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +191 -26
  3. package/build/index.cjs +2039 -275
  4. package/build/index.mjs +2023 -276
  5. package/package.json +53 -53
  6. package/types.d.ts +341 -14
package/package.json CHANGED
@@ -1,53 +1,53 @@
1
- {
2
- "name": "garch",
3
- "version": "1.2.4",
4
- "description": "GARCH and EGARCH volatility models for TypeScript",
5
- "funding": {
6
- "type": "individual",
7
- "url": "http://paypal.me/tripolskypetr"
8
- },
9
- "homepage": "https://backtest-kit.github.io/documents/article_07_ai_news_trading_signals.html",
10
- "type": "module",
11
- "main": "./build/index.cjs",
12
- "module": "./build/index.mjs",
13
- "types": "./types.d.ts",
14
- "exports": {
15
- ".": {
16
- "types": "./types.d.ts",
17
- "import": "./build/index.mjs",
18
- "require": "./build/index.cjs"
19
- }
20
- },
21
- "files": [
22
- "build",
23
- "types.d.ts",
24
- "README.md"
25
- ],
26
- "scripts": {
27
- "build": "rollup -c",
28
- "test": "vitest run",
29
- "test:watch": "vitest",
30
- "prepublishOnly": "npm run build"
31
- },
32
- "keywords": [
33
- "garch",
34
- "egarch",
35
- "volatility",
36
- "finance",
37
- "econometrics",
38
- "time-series"
39
- ],
40
- "author": "",
41
- "license": "MIT",
42
- "devDependencies": {
43
- "@rollup/plugin-typescript": "^12.3.0",
44
- "@types/node": "^20.10.0",
45
- "@vitest/coverage-v8": "^1.6.1",
46
- "rollup": "^4.57.1",
47
- "rollup-plugin-dts": "^6.3.0",
48
- "rollup-plugin-peer-deps-external": "^2.2.4",
49
- "tslib": "^2.8.1",
50
- "typescript": "^5.3.0",
51
- "vitest": "^1.0.0"
52
- }
53
- }
1
+ {
2
+ "name": "garch",
3
+ "version": "2.0.0",
4
+ "description": "GARCH and EGARCH volatility models for TypeScript",
5
+ "funding": {
6
+ "type": "individual",
7
+ "url": "http://paypal.me/tripolskypetr"
8
+ },
9
+ "homepage": "https://backtest-kit.github.io/documents/article_07_ai_news_trading_signals.html",
10
+ "type": "module",
11
+ "main": "./build/index.cjs",
12
+ "module": "./build/index.mjs",
13
+ "types": "./types.d.ts",
14
+ "exports": {
15
+ ".": {
16
+ "types": "./types.d.ts",
17
+ "import": "./build/index.mjs",
18
+ "require": "./build/index.cjs"
19
+ }
20
+ },
21
+ "files": [
22
+ "build",
23
+ "types.d.ts",
24
+ "README.md"
25
+ ],
26
+ "scripts": {
27
+ "build": "rollup -c",
28
+ "test": "vitest run",
29
+ "test:watch": "vitest",
30
+ "prepublishOnly": "npm run build"
31
+ },
32
+ "keywords": [
33
+ "garch",
34
+ "egarch",
35
+ "volatility",
36
+ "finance",
37
+ "econometrics",
38
+ "time-series"
39
+ ],
40
+ "author": "",
41
+ "license": "MIT",
42
+ "devDependencies": {
43
+ "@rollup/plugin-typescript": "^12.3.0",
44
+ "@types/node": "^20.10.0",
45
+ "@vitest/coverage-v8": "^1.6.1",
46
+ "rollup": "^4.57.1",
47
+ "rollup-plugin-dts": "^6.3.0",
48
+ "rollup-plugin-peer-deps-external": "^2.2.4",
49
+ "tslib": "^2.8.1",
50
+ "typescript": "^5.3.0",
51
+ "vitest": "^1.0.0"
52
+ }
53
+ }
package/types.d.ts CHANGED
@@ -68,6 +68,25 @@ interface HarRvParams {
68
68
  annualizedVol: number;
69
69
  r2: number;
70
70
  df: number;
71
+ /** true when the regression runs on ln RV (betas live in log space). */
72
+ logSpec?: boolean;
73
+ /** Residual variance of the log-RV regression (lognormal bias correction). */
74
+ residualLogVar?: number;
75
+ }
76
+ interface RealizedGarchParams {
77
+ omega: number;
78
+ beta: number;
79
+ gamma: number;
80
+ /** Measurement-equation intercept: ln RV_t = ξ + ln σ²_t + τ₁z + τ₂(z²−1) + u. */
81
+ xi: number;
82
+ tau1: number;
83
+ tau2: number;
84
+ /** Std of the measurement noise u — how much the model trusts RV. */
85
+ sigmaU: number;
86
+ persistence: number;
87
+ unconditionalVariance: number;
88
+ annualizedVol: number;
89
+ df: number;
71
90
  }
72
91
  interface NoVaSParams {
73
92
  weights: number[];
@@ -83,6 +102,7 @@ interface NoVaSParams {
83
102
  interface OptimizerResult {
84
103
  x: number[];
85
104
  fx: number;
105
+ /** Iterations of the winning Nelder-Mead run (not summed across multi-start restarts). */
86
106
  iterations: number;
87
107
  converged: boolean;
88
108
  }
@@ -115,6 +135,8 @@ declare class Garch {
115
135
  fit(options?: {
116
136
  maxIter?: number;
117
137
  tol?: number;
138
+ forgetting?: number;
139
+ warmStart?: GarchParams;
118
140
  }): CalibrationResult<GarchParams>;
119
141
  /**
120
142
  * Calculate conditional variance series given parameters
@@ -168,11 +190,24 @@ declare class Egarch {
168
190
  fit(options?: {
169
191
  maxIter?: number;
170
192
  tol?: number;
193
+ forgetting?: number;
194
+ warmStart?: EgarchParams;
171
195
  }): CalibrationResult<EgarchParams>;
172
196
  /**
173
197
  * Calculate conditional variance series given parameters
174
198
  */
175
199
  getVarianceSeries(params: EgarchParams): number[];
200
+ /**
201
+ * Mean drift of the magnitude term under the fitted dynamics.
202
+ *
203
+ * With RV magnitude, E[√(RV/σ²)] ≠ E[|z|]: the in-sample recursion
204
+ * carries a mean offset α·m̄ per step that ω absorbed during fitting.
205
+ * A multi-step forecast that drops the α term entirely would therefore
206
+ * converge to a level systematically below the fitted dynamics.
207
+ * Returns m̄ = mean(magnitude − E|z|) over the sample (0 for prices-only
208
+ * input, where magnitude = |z| and the offset is sampling noise).
209
+ */
210
+ magnitudeDrift(params: EgarchParams): number;
176
211
  /**
177
212
  * Forecast variance forward
178
213
  *
@@ -200,6 +235,13 @@ interface HarRvOptions {
200
235
  shortLag?: number;
201
236
  mediumLag?: number;
202
237
  longLag?: number;
238
+ /**
239
+ * Regress ln RV instead of RV levels (log-HAR, Corsi). Positivity comes
240
+ * for free (no 1e-20 clamps on negative predictions), residuals are far
241
+ * closer to homoskedastic, and single RV prints stop dominating the OLS.
242
+ * Predictions are bias-corrected: E[RV] = exp(ŷ + σ²_ε/2).
243
+ */
244
+ logSpec?: boolean;
203
245
  }
204
246
  /**
205
247
  * HAR-RV model (Corsi, 2009)
@@ -223,6 +265,8 @@ declare class HarRv {
223
265
  private shortLag;
224
266
  private mediumLag;
225
267
  private longLag;
268
+ private logSpec;
269
+ private lnRv;
226
270
  constructor(data: Candle[] | number[], options?: HarRvOptions);
227
271
  /**
228
272
  * Calibrate HAR-RV via OLS.
@@ -230,6 +274,7 @@ declare class HarRv {
230
274
  fit(): CalibrationResult<HarRvParams>;
231
275
  /**
232
276
  * Internal: compute variance series from beta vector.
277
+ * For the log spec the prediction is E[RV] = exp(ŷ + σ²_ε/2).
233
278
  */
234
279
  private getVarianceSeriesInternal;
235
280
  /**
@@ -240,7 +285,8 @@ declare class HarRv {
240
285
  * Forecast variance forward.
241
286
  *
242
287
  * Uses iterative substitution: each forecast step feeds back
243
- * into the rolling RV components for subsequent steps.
288
+ * into the rolling RV components for subsequent steps (point forecasts
289
+ * of ln RV for the log spec, bias-corrected on output).
244
290
  */
245
291
  forecast(params: HarRvParams, steps?: number): VolatilityForecast;
246
292
  /**
@@ -270,7 +316,7 @@ interface GjrGarchOptions {
270
316
  * where:
271
317
  * - ω (omega) > 0: constant term
272
318
  * - α (alpha) ≥ 0: symmetric shock response
273
- * - γ (gamma) ≥ 0: asymmetric leverage coefficient
319
+ * - γ (gamma) ≥ −α: asymmetric leverage coefficient (negative = inverted leverage)
274
320
  * - β (beta) ≥ 0: persistence
275
321
  * - I(r<0) = 1 when return is negative, 0 otherwise
276
322
  * - Stationarity: α + γ/2 + β < 1
@@ -290,6 +336,8 @@ declare class GjrGarch {
290
336
  fit(options?: {
291
337
  maxIter?: number;
292
338
  tol?: number;
339
+ forgetting?: number;
340
+ warmStart?: GjrGarchParams;
293
341
  }): CalibrationResult<GjrGarchParams>;
294
342
  /**
295
343
  * Calculate conditional variance series given parameters
@@ -313,6 +361,63 @@ declare class GjrGarch {
313
361
  */
314
362
  declare function calibrateGjrGarch(data: Candle[] | number[], options?: GjrGarchOptions): CalibrationResult<GjrGarchParams>;
315
363
 
364
+ interface RealizedGarchOptions {
365
+ periodsPerYear?: number;
366
+ maxIter?: number;
367
+ tol?: number;
368
+ }
369
+ /**
370
+ * Realized GARCH(1,1) (Hansen, Huang & Shek, 2012), log-linear, φ = 1.
371
+ *
372
+ * r_t = σ_t·z_t, z_t ~ standardized t(df)
373
+ * ln σ²_t = ω + β·ln σ²_{t−1} + γ·ln RV_{t−1}
374
+ * ln RV_t = ξ + ln σ²_t + τ₁·z_t + τ₂·(z²_t − 1) + u_t, u_t ~ N(0, σ²_u)
375
+ *
376
+ * Unlike the RV-in-place-of-ε² hybrids, the measurement equation estimates
377
+ * the bias (ξ) and noise (σ_u) of the realized measure inside the joint
378
+ * likelihood: RV information is weighted by how trustworthy it actually is,
379
+ * and leverage enters through τ₁. Stationarity: β + γ < 1 (with φ = 1).
380
+ */
381
+ declare class RealizedGarch {
382
+ private returns;
383
+ private lnRv;
384
+ private periodsPerYear;
385
+ private initialVariance;
386
+ constructor(data: Candle[] | number[], options?: RealizedGarchOptions);
387
+ /**
388
+ * Calibrate by joint MLE over returns and the realized measure.
389
+ */
390
+ fit(options?: {
391
+ maxIter?: number;
392
+ tol?: number;
393
+ forgetting?: number;
394
+ warmStart?: RealizedGarchParams;
395
+ }): CalibrationResult<RealizedGarchParams>;
396
+ /**
397
+ * Conditional variance series (data scale). σ²_t is driven by RV_{t−1}
398
+ * through the log recursion — no look-ahead.
399
+ */
400
+ getVarianceSeries(params: RealizedGarchParams): number[];
401
+ /**
402
+ * Forecast variance forward. One step uses the actual last RV; further
403
+ * steps substitute E[ln RV_t] = ξ + ln σ²_t, giving the reduced recursion
404
+ * ln σ²_{t+h} = (ω + γξ) + (β + γ)·ln σ²_{t+h−1}.
405
+ */
406
+ forecast(params: RealizedGarchParams, steps?: number): VolatilityForecast;
407
+ /**
408
+ * Get the return series.
409
+ */
410
+ getReturns(): number[];
411
+ /**
412
+ * Get initial variance estimate.
413
+ */
414
+ getInitialVariance(): number;
415
+ }
416
+ /**
417
+ * Convenience function to calibrate Realized GARCH from candles or prices.
418
+ */
419
+ declare function calibrateRealizedGarch(data: Candle[] | number[], options?: RealizedGarchOptions): CalibrationResult<RealizedGarchParams>;
420
+
316
421
  interface NoVaSOptions {
317
422
  periodsPerYear?: number;
318
423
  lags?: number;
@@ -352,6 +457,7 @@ declare class NoVaS {
352
457
  fit(options?: {
353
458
  maxIter?: number;
354
459
  tol?: number;
460
+ warmWeights?: number[];
355
461
  }): CalibrationResult<NoVaSParams>;
356
462
  /**
357
463
  * Internal: compute variance series from D² weight vector.
@@ -384,6 +490,15 @@ declare class NoVaS {
384
490
  */
385
491
  declare function calibrateNoVaS(data: Candle[] | number[], options?: NoVaSOptions): CalibrationResult<NoVaSParams>;
386
492
 
493
+ /**
494
+ * Validate OHLC integrity. Garbage candles (NaN, non-positive prices,
495
+ * high < low) otherwise propagate silently as NaN through every estimator.
496
+ */
497
+ declare function validateCandles(candles: Candle[]): void;
498
+ /**
499
+ * Linear-interpolation quantile of a pre-sorted (ascending) sample.
500
+ */
501
+ declare function empiricalQuantile(sortedAsc: number[], p: number): number;
387
502
  /**
388
503
  * Calculate log returns from candles
389
504
  */
@@ -472,6 +587,28 @@ declare function studentTNegLL(returns: number[], varianceSeries: number[], df:
472
587
  * Converges to √(2/π) as df → ∞ (Gaussian limit).
473
588
  */
474
589
  declare function expectedAbsStudentT(df: number): number;
590
+ /**
591
+ * Regularized incomplete beta function I_x(a, b).
592
+ */
593
+ declare function incompleteBeta(x: number, a: number, b: number): number;
594
+ /**
595
+ * CDF of the (raw, unstandardized) Student-t distribution with df degrees
596
+ * of freedom: P(T ≤ t).
597
+ */
598
+ declare function studentTCdf(t: number, df: number): number;
599
+ /**
600
+ * Two-sided quantile of the STANDARDIZED Student-t distribution
601
+ * (unit variance). The t-analog of probit(): returns z such that
602
+ * P(|Z| ≤ z) = confidence when Z ~ t(df) scaled to variance 1.
603
+ *
604
+ * This is what price corridors must use when the model was fitted with
605
+ * Student-t innovations: with fat tails (small df) the Gaussian probit
606
+ * makes 68% bands too wide and 99% bands dangerously narrow.
607
+ *
608
+ * Falls back to probit() for df > 1000 (where the difference from the
609
+ * Gaussian quantile is < 0.3% even at 99%) or df ≤ 2 (variance undefined).
610
+ */
611
+ declare function studentTProbit(confidence: number, df: number): number;
475
612
  /**
476
613
  * 1D grid search for optimal df that minimizes Student-t neg-LL.
477
614
  * Used by HAR-RV and NoVaS where df is profiled after main optimization.
@@ -497,6 +634,14 @@ declare function qlike(varianceSeries: number[], rv: number[]): number;
497
634
  declare function probit(confidence: number): number;
498
635
 
499
636
  type CandleInterval = '1m' | '3m' | '5m' | '15m' | '30m' | '1h' | '2h' | '4h' | '6h' | '8h';
637
+ type WarningCode = 'LOW_SAMPLE' | 'NOT_CONVERGED' | 'HIGH_PERSISTENCE' | 'DEGENERATE_VARIANCE' | 'RESIDUAL_AUTOCORRELATION' | 'DATA_GAPS' | 'INTERVAL_MISMATCH';
638
+ interface PredictionWarning {
639
+ code: WarningCode;
640
+ /** Plain-language explanation with a suggested action. */
641
+ message: string;
642
+ /** true when this warning alone makes the forecast unreliable. */
643
+ critical: boolean;
644
+ }
500
645
  interface PredictionResult {
501
646
  /** Reference price used to compute the corridor (last close or the value passed as `currentPrice`). */
502
647
  currentPrice: number;
@@ -510,28 +655,178 @@ interface PredictionResult {
510
655
  upperPrice: number;
511
656
  /** Lower price band: `currentPrice · exp(-z·σ)`. Always positive. */
512
657
  lowerPrice: number;
513
- /** Volatility model auto-selected by QLIKE. */
514
- modelType: 'garch' | 'egarch' | 'gjr-garch' | 'har-rv' | 'novas';
515
- /** `true` when the model converged, persistence < 0.999, and Ljung-Box p-value ≥ 0.05. */
658
+ /** Top-weight member of the volatility model combination (selected by out-of-sample QLIKE). */
659
+ modelType: 'garch' | 'egarch' | 'gjr-garch' | 'realized-garch' | 'har-rv' | 'novas';
660
+ /** Student-t degrees of freedom profiled on scale-corrected residuals. */
661
+ df: number;
662
+ /**
663
+ * Average corridor multiplier, (zScoreUp + zScoreDown) / 2 — kept for
664
+ * backward compatibility. The bands themselves are asymmetric; use
665
+ * zScoreUp/zScoreDown to reconstruct them exactly.
666
+ */
667
+ zScore: number;
668
+ /** Upper-tail multiplier: `upperPrice = currentPrice · exp(+zScoreUp · sigma)`. */
669
+ zScoreUp: number;
670
+ /** Lower-tail multiplier: `lowerPrice = currentPrice · exp(−zScoreDown · sigma)`. */
671
+ zScoreDown: number;
672
+ /** `true` when no critical warning fired (model converged, persistence < 0.999, Ljung-Box p ≥ 0.05, non-degenerate variance). */
516
673
  reliable: boolean;
674
+ /**
675
+ * Everything the pipeline noticed, in plain language: why `reliable` is
676
+ * false (critical warnings) plus non-critical data quality notes.
677
+ */
678
+ warnings: PredictionWarning[];
679
+ /** Combination weights per model family (out-of-sample QLIKE softmax), summing to 1. */
680
+ modelWeights: Partial<Record<PredictionResult['modelType'], number>>;
681
+ /** `true` when a significant diurnal volatility profile was detected and removed before fitting. */
682
+ seasonalityDetected: boolean;
683
+ }
684
+ interface PredictOptions {
685
+ /** Reference price for the corridor; defaults to the last close. */
686
+ currentPrice?: number | null;
687
+ /** Two-sided probability in (0,1). Default ≈0.6827 (±1σ). */
688
+ confidence?: number;
689
+ }
690
+ interface DataIssue {
691
+ code: 'TOO_FEW_CANDLES' | 'INVALID_OHLC' | 'UNSORTED' | 'DUPLICATE_TIMESTAMPS' | 'LOW_SAMPLE' | 'DATA_GAPS' | 'INTERVAL_MISMATCH' | 'FLAT_CANDLES';
692
+ message: string;
693
+ severity: 'error' | 'warning';
694
+ }
695
+ interface DataReport {
696
+ /** false when any error-severity issue is present (predict would throw). */
697
+ ok: boolean;
698
+ issues: DataIssue[];
699
+ recommendedCandles: number;
700
+ }
701
+ /**
702
+ * Pre-flight data check with plain-language, actionable messages: run it on
703
+ * a new data source before wiring it into predict. Errors are conditions
704
+ * predict would throw on; warnings degrade quality but do not block.
705
+ */
706
+ declare function checkData(candles: Candle[], interval: CandleInterval): DataReport;
707
+ interface Seasonality {
708
+ /** Variance factor per intraday bucket, sample-weighted mean 1. */
709
+ factors: number[];
710
+ /** Bucket of return index t (i.e. candle t+1); also valid for future t. */
711
+ bucketOfReturn: (t: number) => number;
517
712
  }
713
+ /**
714
+ * Diurnal variance profile from per-candle Parkinson RV.
715
+ *
716
+ * Intraday markets have a strong time-of-day volatility pattern (sessions,
717
+ * funding, weekends). A GARCH-family fit smears it into average persistence,
718
+ * so corridors are systematically too narrow in active hours and too wide in
719
+ * quiet ones. Factors are estimated per intraday bucket (≤24 per day, bars
720
+ * grouped for sub-hour intervals), circularly smoothed, shrunk toward 1 by
721
+ * bucket support, and gated by a χ² significance test against the RV
722
+ * sampling noise (inflated for volatility clustering) — pure-GARCH data
723
+ * without seasonality returns null and the pipeline is unchanged.
724
+ *
725
+ * Timestamps (ms or s), when present on every candle, anchor buckets to
726
+ * real time of day and survive gaps; otherwise buckets are positional and
727
+ * assume contiguous bars.
728
+ */
729
+ declare function computeSeasonality(candles: Candle[], interval: CandleInterval): Seasonality | null;
730
+ /**
731
+ * Rescale each candle's log-moves by 1/√f(bucket) so the deseasonalized
732
+ * series has a flat diurnal profile. Gaps (open vs prev close) are scaled
733
+ * with the same factor; OHLC ordering is preserved (monotone log map).
734
+ */
735
+ declare function deseasonalizeCandles(candles: Candle[], season: Seasonality): Candle[];
736
+ /**
737
+ * Cached calibration state threaded between rolling refits: previous
738
+ * optima become warm starts with a reduced multi-start budget, and the
739
+ * HAR spec search collapses to the previously selected configuration.
740
+ */
741
+ interface WarmState {
742
+ garch?: GarchParams;
743
+ garchForget?: GarchParams;
744
+ egarch?: EgarchParams;
745
+ gjr?: GjrGarchParams;
746
+ rgarch?: RealizedGarchParams;
747
+ harLags?: [number, number, number];
748
+ harLog?: boolean;
749
+ novasWeights?: number[];
750
+ }
751
+ /**
752
+ * Stateful predictor for rolling use (bots, backtests): each subsequent
753
+ * predict/predictRange warm-starts every optimizer from the previous
754
+ * window's optimum with a reduced multi-start budget — same math, a
755
+ * fraction of the cost. State is per-instrument: do not share one
756
+ * predictor across symbols.
757
+ */
758
+ declare function createPredictor(interval: CandleInterval): {
759
+ predict: (candles: Candle[], currentPriceOrOptions?: number | null | PredictOptions, confidence?: number) => PredictionResult;
760
+ predictRange: (candles: Candle[], steps: number, currentPriceOrOptions?: number | null | PredictOptions, confidence?: number) => PredictionResult;
761
+ };
518
762
  /**
519
763
  * Forecast expected price range for t+1 (next candle).
520
764
  *
521
- * Auto-selects the best volatility model via QLIKE.
522
- * Uses log-normal price bands: P·exp(±z·σ), where z = probit(confidence).
765
+ * Combines all volatility models weighted by out-of-sample QLIKE,
766
+ * deseasonalizes the diurnal variance profile when one is present,
767
+ * rescales the variance to the return scale (Var(r/σ) = 1), and builds
768
+ * bands P·exp(±z·σ) where z is calibrated on the data itself: the
769
+ * empirical |z| quantile of the standardized residuals blended with the
770
+ * fitted Student-t quantile as the tail runs out of observations (see
771
+ * corridorZ). Empirical coverage tracks the requested confidence without
772
+ * assuming a distributional shape.
523
773
  * @param confidence — two-sided probability in (0,1). Default ≈0.6827 (±1σ).
524
- * Common values: 0.90 → z=1.645, 0.95 → z=1.96, 0.99 → z=2.576.
774
+ * Common values: 0.90, 0.95, 0.99.
525
775
  */
526
- declare function predict(candles: Candle[], interval: CandleInterval, currentPrice?: number | null, confidence?: number): PredictionResult;
776
+ declare function predict(candles: Candle[], interval: CandleInterval, currentPriceOrOptions?: number | null | PredictOptions, confidence?: number): PredictionResult;
527
777
  /**
528
778
  * Forecast expected price range over multiple candles.
529
779
  *
530
- * Cumulative σ = √(σ₁² + σ₂² + ... + σₙ²) — total expected move over N periods.
531
- * Uses log-normal price bands: P·exp(±z·σ), where z = probit(confidence).
780
+ * Cumulative σ = √(σ₁² + σ₂² + ... + σₙ²) — total expected move over N
781
+ * periods, with each step's variance carrying its own seasonal factor.
782
+ * Uses log-normal price bands P·exp(±z·σ) where z is calibrated at the
783
+ * requested horizon: the empirical quantile of |h-step standardized sums|
784
+ * from the sample itself, blended with the model-implied quantile simulated
785
+ * through the fitted recursions (volatility feedback and fat-tail decay
786
+ * included).
532
787
  * @param confidence — two-sided probability in (0,1). Default ≈0.6827 (±1σ).
788
+ * @param steps — horizon in candles, 1 ≤ steps ≤ candles.length.
789
+ */
790
+ declare function predictRange(candles: Candle[], interval: CandleInterval, steps: number, currentPriceOrOptions?: number | null | PredictOptions, confidence?: number): PredictionResult;
791
+ interface BacktestStats {
792
+ /** Number of test candles whose close landed inside the predicted corridor. */
793
+ hits: number;
794
+ /** Number of walk-forward predictions made. */
795
+ total: number;
796
+ /** Empirical coverage in percent (0–100). Compare against `confidence · 100`. */
797
+ hitRate: number;
798
+ /** Statistical judgment of the coverage against the nominal confidence (Kupiec POF test). */
799
+ verdict: 'well-calibrated' | 'too-narrow' | 'too-wide' | 'inconclusive';
800
+ /** Kupiec test p-value: probability of a coverage gap this large under correct calibration. */
801
+ pValue: number;
802
+ /** Plain-language interpretation of the verdict with the numbers filled in. */
803
+ message: string;
804
+ }
805
+ /**
806
+ * Kupiec (1995) proportion-of-failures test: is the observed hit rate
807
+ * statistically consistent with the nominal confidence, given how many
808
+ * walk-forward points there are? A raw hitRate of 63% vs nominal 68% means
809
+ * nothing without n — this answers "failure or noise" directly.
810
+ */
811
+ declare function kupiecTest(hits: number, total: number, confidence: number): Pick<BacktestStats, 'verdict' | 'pValue' | 'message'>;
812
+ /**
813
+ * Walk-forward calibration statistics for predict.
814
+ *
815
+ * Refits the model on a rolling window (75% of candles, min MIN_CANDLES)
816
+ * and checks whether the next close lands inside the predicted corridor.
817
+ * A well-calibrated tool has hitRate ≈ confidence·100.
818
+ *
819
+ * Every refit costs a full multi-model calibration, so by default the test
820
+ * points are subsampled to at most ~100 refits (stride grows with the test
821
+ * span). Pass `stride: 1` to evaluate every candle when runtime is not a
822
+ * concern, or any positive stride to control the trade-off yourself.
823
+ * Throws if not enough candles for the given interval.
824
+ * @param confidence — two-sided probability in (0,1) for the prediction band.
825
+ * Default ≈0.6827 (±1σ).
533
826
  */
534
- declare function predictRange(candles: Candle[], interval: CandleInterval, steps: number, currentPrice?: number | null, confidence?: number): PredictionResult;
827
+ declare function backtestStats(candles: Candle[], interval: CandleInterval, confidence?: number, options?: {
828
+ stride?: number;
829
+ }): BacktestStats;
535
830
  /**
536
831
  * Walk-forward backtest of predict.
537
832
  *
@@ -544,6 +839,30 @@ declare function predictRange(candles: Candle[], interval: CandleInterval, steps
544
839
  */
545
840
  declare function backtest(candles: Candle[], interval: CandleInterval, confidence?: number, requiredPercent?: number): boolean;
546
841
 
842
+ /**
843
+ * Typed error hierarchy so bot code can branch on error class instead of
844
+ * parsing message strings.
845
+ *
846
+ * try { predict(candles, '1h') }
847
+ * catch (e) {
848
+ * if (e instanceof NotEnoughDataError) await fetchMoreCandles();
849
+ * else if (e instanceof BadDataError) alertDataPipeline(e.message);
850
+ * else throw e;
851
+ * }
852
+ */
853
+ declare class GarchError extends Error {
854
+ constructor(message: string);
855
+ }
856
+ /** The sample is too short for the requested interval/model. Fetch more candles. */
857
+ declare class NotEnoughDataError extends GarchError {
858
+ }
859
+ /** The candles themselves are broken: invalid OHLC, unsorted or duplicated timestamps. Fix the data pipeline. */
860
+ declare class BadDataError extends GarchError {
861
+ }
862
+ /** A call argument is out of range or of the wrong shape (interval, confidence, steps, currentPrice). Fix the call site. */
863
+ declare class InvalidArgumentError extends GarchError {
864
+ }
865
+
547
866
  declare function nelderMead(fn: (x: number[]) => number, x0: number[], options?: {
548
867
  maxIter?: number;
549
868
  tol?: number;
@@ -556,7 +875,15 @@ declare function nelderMeadMultiStart(fn: (x: number[]) => number, x0: number[],
556
875
  maxIter?: number;
557
876
  tol?: number;
558
877
  restarts?: number;
878
+ /**
879
+ * Additional explicit starting points, each run through a full NM pass.
880
+ * The perturbation restarts below scale x0 multiplicatively, so they
881
+ * preserve its shape — basins whose shape differs from x0 (e.g. far-lag
882
+ * weight structures) are unreachable from x0 alone and must be seeded
883
+ * explicitly.
884
+ */
885
+ extraStarts?: number[][];
559
886
  }): OptimizerResult;
560
887
 
561
- export { EXPECTED_ABS_NORMAL, Egarch, Garch, GjrGarch, HarRv, NoVaS, backtest, calculateReturns, calculateReturnsFromPrices, calibrateEgarch, calibrateGarch, calibrateGjrGarch, calibrateHarRv, calibrateNoVaS, checkLeverageEffect, expectedAbsStudentT, garmanKlassVariance, ljungBox, logGamma, nelderMead, nelderMeadMultiStart, perCandleParkinson, predict, predictRange, probit, profileStudentTDf, qlike, sampleVariance, sampleVarianceWithMean, studentTNegLL, yangZhangVariance };
562
- export type { CalibrationResult, Candle, CandleInterval, EgarchOptions, EgarchParams, GarchOptions, GarchParams, GjrGarchOptions, GjrGarchParams, HarRvOptions, HarRvParams, LeverageStats, NoVaSOptions, NoVaSParams, OptimizerResult, PredictionResult, VolatilityForecast };
888
+ export { BadDataError, EXPECTED_ABS_NORMAL, Egarch, Garch, GarchError, GjrGarch, HarRv, InvalidArgumentError, NoVaS, NotEnoughDataError, RealizedGarch, backtest, backtestStats, calculateReturns, calculateReturnsFromPrices, calibrateEgarch, calibrateGarch, calibrateGjrGarch, calibrateHarRv, calibrateNoVaS, calibrateRealizedGarch, checkData, checkLeverageEffect, computeSeasonality, createPredictor, deseasonalizeCandles, empiricalQuantile, expectedAbsStudentT, garmanKlassVariance, incompleteBeta, kupiecTest, ljungBox, logGamma, nelderMead, nelderMeadMultiStart, perCandleParkinson, predict, predictRange, probit, profileStudentTDf, qlike, sampleVariance, sampleVarianceWithMean, studentTCdf, studentTNegLL, studentTProbit, validateCandles, yangZhangVariance };
889
+ export type { BacktestStats, CalibrationResult, Candle, CandleInterval, DataIssue, DataReport, EgarchOptions, EgarchParams, GarchOptions, GarchParams, GjrGarchOptions, GjrGarchParams, HarRvOptions, HarRvParams, LeverageStats, NoVaSOptions, NoVaSParams, OptimizerResult, PredictOptions, PredictionResult, PredictionWarning, RealizedGarchOptions, RealizedGarchParams, Seasonality, VolatilityForecast, WarmState, WarningCode };