Skip to content

Add CensoredGARCH: latent-variance correction for censored return series - #875

Open
uzair1509 wants to merge 8 commits into
bashtage:mainfrom
uzair1509:feature/censored-garch
Open

uzair1509 wants to merge 8 commits into
bashtage:mainfrom
uzair1509:feature/censored-garch

Conversation

@uzair1509

@uzair1509 uzair1509 commented Sep 28, 2026 •

Copy link
Copy Markdown

Implements a GARCH(p, q) volatility process for return series subject to a known, time-varying censoring rule (e.g. an exchange price-limit band, such as PSX's daily circuit breaker). When a return is censored (the true shock exceeded a known threshold but only the threshold is observed), using the raw truncated squared residual in the ordinary GARCH recursion understates the true conditional variance.

CensoredGARCH corrects this by replacing the squared residual at a censored observation with its conditional expectation given censoring, under the model's own conditional variance, using the truncated-normal second-moment identity:

E[eps_t^2 | |eps_t| >= c_t] = sigma_t^2 * (1 + a_t * phi(a_t) / (1 - Phi(a_t)))
a_t = c_t / sigma_t

This is the correction described in Morgan & Trevor (1999) and applied to exchange price-limit censoring in follow-up work (Wei 2002, Hsieh & Yang 2009). A companion open-source package (mtgarch on PyPI) applies this same estimator to Pakistan Stock Exchange data; this PR ports the estimator into arch as a proper VolatilityProcess subclass so it can be used directly with arch_model's full estimation, inference, and forecasting machinery instead of a standalone implementation.

Changes

  • New arch/univariate/censored_garch.py: CensoredGARCH class and its recursion (censored_garch_recursion), following the GARCH / FixedVariance classes as templates. Implements all required abstract methods (bounds, constraints, compute_variance, starting_values, parameter_names, simulate) plus one-step-ahead analytic forecasting.
  • Recursion acceleration: optional numba JIT path (~100x faster than pure Python), with a pure-Python fallback used automatically when numba is not installed, consistent with how arch treats numba elsewhere.
  • arch/univariate/__init__.py: export CensoredGARCH.
  • arch/tests/univariate/test_censored_garch.py: unit tests covering parameter/constraint setup, input validation, reduction to plain GARCH when nothing is censored, a direct check of the recursion against the truncated-normal identity, edge-case branches of the recursion, and an end-to-end ConstantMean + CensoredGARCH fit/forecast smoke test.
  • Docs: added to the volatility-processes autosummary and changelog.

Known limitations

  • Only horizon-1 analytic forecasting is supported; multi-step forecasts would be a natural follow-up.
  • No Cython backend (numba and pure Python only).

Implements a GARCH(p, q) volatility process for return series subject
to a known, time-varying censoring rule (e.g. an exchange price-limit
band, such as PSX's daily circuit breaker). When a return is censored
(the true shock exceeded a known threshold but only the threshold is
observed), using the raw truncated squared residual in the ordinary
GARCH recursion understates the true conditional variance.

CensoredGARCH corrects this by replacing the squared residual at a
censored observation with its conditional expectation given censoring,
under the model's own conditional variance, using the truncated-normal
second-moment identity:

    E[eps_t^2 | |eps_t| >= c_t] = sigma_t^2 * (1 + a_t * phi(a_t) / (1 - Phi(a_t)))
    a_t = c_t / sigma_t

This is the correction described in Morgan & Trevor (1999) and applied
to exchange price-limit censoring in follow-up work (Wei 2002, Hsieh &
Yang 2009). A companion open-source package (mtgarch on PyPI) applies
this same estimator to Pakistan Stock Exchange data; this PR ports the
estimator into `arch` as a proper VolatilityProcess subclass so it can
be used directly with arch_model's full estimation, inference, and
forecasting machinery instead of a standalone implementation.

- New `arch/univariate/censored_garch.py`: `CensoredGARCH` class and
  its pure-Python recursion (`censored_garch_recursion`), following the
  `GARCH`/`FixedVariance` classes as templates. Implements all required
  abstract methods (bounds, constraints, compute_variance,
  starting_values, parameter_names, simulate) plus one-step-ahead
  analytic forecasting.
- `arch/univariate/__init__.py`: export `CensoredGARCH`.
- `arch/tests/univariate/test_censored_garch.py`: unit tests covering
  parameter/constraint setup, input validation, the reduction to plain
  GARCH when nothing is censored, a direct check of the recursion
  against the truncated-normal identity, and an end-to-end
  ConstantMean + CensoredGARCH fit/forecast smoke test.
- Docs: added to the volatility-processes autosummary and changelog.

No accelerated (Cython/Numba) backend yet; only horizon-1 analytic
forecasting is supported. Both are noted as known limitations in the
class docstring and would be natural follow-ups.
@codecov

codecov Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.74490% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 98.98%. Comparing base (80d3688) to head (c51b360).

Files with missing lines Patch % Lines
arch/tests/univariate/test_censored_garch.py 99.57% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@           Coverage Diff            @@
##             main     #875    +/-   ##
========================================
  Coverage   98.97%   98.98%            
========================================
  Files         136      138     +2     
  Lines       30028    30420   +392     
  Branches     2327     2355    +28     
========================================
+ Hits        29719    30110   +391     
  Misses        255      255            
- Partials       54       55     +1     
Flag Coverage Δ
adder 98.92% <99.74%> (+0.01%) ⬆️
subtractor 98.92% <99.74%> (+0.01%) ⬆️

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.

uzair1509 and others added 5 commits September 28, 2026 14:54
Add tests for the length-mismatch error in compute_variance, p=0 / q=0
and higher-order starting values, and the simulate initial-value paths
(user-supplied, stationary default, unit-root fallback). Run the
deep-tail, zero-threshold and normal-helper tests against the pure-Python
sources too, so their lines are counted when numba JIT is active.
@uzair1509

Copy link
Copy Markdown
Author

Added tests for the uncovered branches in censored_garch.py. Coverage is now 100% (line and branch), with and without numba. Ready for review.

@uzair1509

Copy link
Copy Markdown
Author

Merged the latest main and fixed the conflict in volatility.rst. Also fixed the lint issues (flake8, ruff, black, isort, numpydoc) and added tests for the uncovered branches in censored_garch.py (now 100% coverage). Ready for review.

This branch has not been deployed

No deployments
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.

2 participants