Skip to content

ENH: Add the NGARCH volatility process - #966

Merged
bashtage merged 4 commits into
mainfrom
a91-f636-ngarch
Oct 29, 2026
Merged

bashtage merged 4 commits into
mainfrom
a91-f636-ngarch

Conversation

@bashtage

@bashtage bashtage commented Oct 18, 2026 •

Copy link
Copy Markdown
Owner

Target 9.1: merge after v9.0.0 is tagged. Closes #636.

Adds arch.univariate.NGARCH(p=1, o=1, q=1) (o <= p): the nonlinear asymmetric GARCH of
Engle and Ng (1993), which rugarch calls NAGARCH.

sigma2_t = omega + sum_i alpha_i (e_{t-i} - theta_i sigma_{t-i})^2 + sum_j beta_j sigma2_{t-j}

Sign convention: theta > 0 is the leverage effect. It was settled from the NBER working
paper (w3681), whose estimated model reads (e - 0.2683 sqrt(h))^2, and it matches rugarch's
eta21. The class Notes explain the naming clash with Higgins and Bera (1992).

What is included:

  • A compiled ngarch_recursion with a numba/Python twin and a .pyi stub.
  • NGARCHUpdater, so ARCHInMean works with NGARCH, including pickling.
  • arch_model(vol="ngarch", o=1). Without a shift the model is a GARCH, so
    arch_model(vol="ngarch") with o=0 (the default o of every vol) returns a GARCH
    process and issues a ValueWarning that says to use vol="garch" or set o > 0.
    NGARCH(o=0) issues the same kind of warning.
  • The analytic forecasts are exact at every horizon, f_h = omega + phi f_{h-1} with
    phi = sum alpha (1 + theta^2) + sum beta, for any standardized distribution, because
    E[(z - theta)^2] = 1 + theta^2.
  • Simulation and bootstrap forecasts, simulate, and the crash guards.
  • Constraint: the linear sum(alpha) + sum(beta) < 1 plus non-negativity, as for APARCH.
    It does not imply the stationarity condition phi < 1, so fit issues a ValueWarning
    when the estimated persistence phi is 1 or more (controlled by show_warning). This
    uses a new private hook, VolatilityProcess._fit_warning, which returns None for the
    other processes.

Validation: rugarch 1.5-6 on R 4.6.1, fGARCH/NAGARCH.

  • Variances to 4e-15 relative and log-likelihoods to 6e-14, for normal and Student-t,
    with matched initial values. rugarch's fGARCH filter adds an undocumented 1e-6 inside
    (z - eta)^2. That was confirmed on three parameter sets and is accounted for.
  • 10-step forecasts to 1e-6.
  • Fits on 4,000 observations: estimates within 0.008.
  • For an ARCH order of 2, rugarch's fGARCH disagrees with its own sGARCH, so p >= 2 is
    validated against an independent NumPy recursion and simulation instead.

Tests:

  • test_ngarch.py (175): the recursion against NumPy for 7 orders; Cython against numba
    against Python; theta = 0 equals GARCH; the updaters against the recursion.
  • Analytic forecasts against simulated means within 4 standard errors (normal and
    skew-t), horizon 1 from every origin, and parameter recovery for (1,1,1), (2,1,1) and
    (2,2,1).
  • The starting values have a grid persistence computed with the shift on the first o
    of the p lags.
  • Both warnings: raised where expected, pointing at the caller's line, and not raised for
    a model with a shift or a stationary fit; show_warning=False silences the fit warning.
  • arch_model, fix and ARCHInMean integration.
  • NGARCH is added to the existing validation, crash-guard, updater, string-option,
    integer-input, forecast, append and hedgehog-plot lists.
  • Every line the PR adds to volatility.py, recursions_python.py, mean.py and
    base.py is covered, with no partial branches.
  • Fast suite 27,511 passed (19 skipped: numba not installed, and o < p
    parametrizations) with no warnings; the slow univariate tests, 777, passed with no
    warnings. numpydoc lint, flake8, ruff, black and isort are clean, and mypy reports no
    new errors.

For review:

  • The ValueWarning for NGARCH(o=0) can be dropped if direct use of the class should
    stay silent; arch_model would still return a GARCH with a warning.
  • Use the exact nonlinear constraint only if users report persistence of 1 or more.

Thanks to @itsNH98 for the request.

🤖 Generated with Claude Code

Add NGARCH(p, o, q), the nonlinear asymmetric GARCH of Engle and Ng (1993), which
rugarch calls NAGARCH,

    sigma2[t] = omega + sum_i alpha[i] (e[t-i] - theta[i] sigma[t-i])**2
                + sum_j beta[j] sigma2[t-j]

with o <= p, as in APARCH. A positive theta is the leverage effect, the sign
convention of rugarch (Engle and Ng write +gamma sigma and expect gamma < 0).

The recursion, ngarch_recursion, and NGARCHUpdater are implemented in Cython and in
Python with numba, so that ARCHInMean works with the process. The pre-sample shock
term is alpha (1 + theta**2) times the backcast. Analytic forecasts are exact at
every horizon, since E[(z - theta)**2] = 1 + theta**2 for any standardized
distribution. Estimation uses the linear constraint sum(alpha) + sum(beta) < 1, as
APARCH does, and not the stationarity condition sum(alpha (1 + theta**2)) +
sum(beta) < 1, which is not linear. The process is available as vol="ngarch" in
arch_model.

Tests compare the recursion with an independent NumPy implementation and with the
variances, log-likelihoods and forecasts of rugarch 1.5-6, the Cython and Python
implementations with each other, the analytic forecasts with an independent forecast
recursion and with simulated paths, and the estimates with the parameters of
simulated data for normal, Student's t and skew t errors.

xref #636
Add NGARCH to the API reference, to the list of volatility processes in the
theoretical background with the reference of Engle and Ng (1993), and a section to
the volatility process page that states the model, the sign convention of theta, the
difference between the persistence and the constraint that is used in estimation, and
the exact analytic forecasts.

xref #636
self.q: int = _check_count("q", q, 0)
if self.o > self.p:
_fail(ValueError, "o", f"an integer <= p ({self.p})", _describe(o))
self._num_params = 1 + self.p + self.o + self.q
Comment thread arch/univariate/volatility.py Fixed
@codecov

codecov Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.99%. Comparing base (d2c90b4) to head (21e2562).
⚠️ Report is 49 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #966      +/-   ##
==========================================
+ Coverage   98.95%   98.99%   +0.04%     
==========================================
  Files         136      139       +3     
  Lines       30043    31317    +1274     
  Branches     2331     2418      +87     
==========================================
+ Hits        29729    31003    +1274     
  Misses        256      256              
  Partials       58       58              
Flag Coverage Δ
adder 98.93% <99.83%> (+0.03%) ⬆️
subtractor 98.93% <99.83%> (+0.03%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

…more

- arch_model(vol="ngarch") with o=0 returns a GARCH process and issues a
  ValueWarning, and NGARCH(o=0) issues one too, since a model without a
  shift is a GARCH.
- ARCHModel.fit issues a ValueWarning when the volatility process reports a
  problem with the estimates, through the new private hook
  VolatilityProcess._fit_warning. NGARCH reports a persistence of 1 or more,
  which the linear estimation constraint does not exclude. show_warning
  controls the warning.
- Remove the unreachable branch in NGARCH.starting_values.
- Test the persistence of the starting values, horizon-1 forecasts from
  every origin and both warnings, and add NGARCH to the shared forecast,
  append and hedgehog-plot tests.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
if self.o > self.p:
_fail(ValueError, "o", f"an integer <= p ({self.p})", _describe(o))
self._num_params = 1 + self.p + self.o + self.q
self._name = "NGARCH"
NGARCH(2, 1, 2) is weakly identified on the S&P 500 returns with an AR or
HAR mean: alpha[1] and a theta[1] of about 4.4 lie on a ridge, SLSQP stops
after about 88 iterations, and a relative change of 4e-16 in the data
moves the conditional volatility by 4e-4 for a log-likelihood change of
5e-6. test_alt_means compares two fits, so it failed on macOS Intel.
NGARCH(1, 1, 1) converges in about 16 iterations and moves by at most 4e-7.
test_fix_after_append, which does not compare two fits, keeps
NGARCH(2, 1, 2).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@bashtage
bashtage merged commit c5f7414 into main Oct 29, 2026
25 of 26 checks passed
jngod2011 pushed a commit to jngod2011/arch that referenced this pull request Oct 31, 2026
- Highlights and new features: NGARCH (bashtage#966), the VaR backtests (bashtage#965), the
  local block bootstrap (bashtage#967) and bandwidth_method (bashtage#964, and
  phillips_ouliaris here).
- Changes that alter results: the automatic bandwidth entry of bashtage#983 now
  also describes the bandwidth of the kernels with bounded support, the
  fractional bandwidths and the Tukey-Parzen constant of bashtage#964, its S&P 500
  bandwidths are those of the bandwidth attribute (30.96 and 34.50, where it
  said the 31.96 and 35.50 of opt_bandwidth), and it links phillips_ouliaris
  in arch.unitroot.cointegration. The ADF-Z tables of bashtage#972, bashtage#973 and bashtage#980.
- Backward-incompatible changes: strings in object data.
- Bug fixes: bashtage#954, bashtage#970, bashtage#974, bashtage#975, bashtage#977, bashtage#978, bashtage#982, and the constant
  data of the Newey-West bandwidth and mackinnonp at -inf and inf.
- Maintenance: the CI changes and the version file.
- Contributors: galafis, jackiectl2, mkzung and wavebyrd.
@bashtage
bashtage deleted the a91-f636-ngarch branch November 3, 2026 15:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ENH: Add NGARCH specification to set of volatility processes

2 participants