GJR-GARCH models with exogenous regressors in the variance equation
Project description
gjr-garch-x
GJR-GARCH models with exogenous regressors in the variance equation.
A pure Python implementation of Glosten-Jagannathan-Runkle (1993) GARCH models that properly supports exogenous variables in the conditional variance equation—a feature missing from standard econometrics packages.
Why This Package?
Standard GARCH packages (including Python's arch) don't natively support exogenous regressors in the variance equation. This matters for:
- Event studies: Testing whether specific events (regulatory announcements, infrastructure failures) affect volatility
- Sentiment analysis: Including sentiment indicators as volatility drivers
- Regime-dependent volatility: Adding dummy variables for different market conditions
This package implements the full GJR-GARCH-X specification:
σ²_t = ω + α·ε²_{t-1} + γ·ε²_{t-1}·I(ε_{t-1}<0) + β·σ²_{t-1} + Σδⱼ·x_{j,t}
Where x_{j,t} are your exogenous variables with coefficients δⱼ estimated via maximum likelihood.
Installation
pip install gjr-garch-x
Quick Start
import pandas as pd
from gjr_garch_x import estimate_gjr_garch_x
# Your returns data (log returns × 100)
returns = pd.Series(...)
# Exogenous variables for variance equation
exog_vars = pd.DataFrame({
'D_event': event_dummy, # Event indicator
'sentiment': sentiment_score, # Continuous variable
}, index=returns.index)
# Estimate model
results = estimate_gjr_garch_x(returns, exog_vars)
# Results
print(f"Converged: {results.converged}")
print(f"AIC: {results.aic:.2f}")
print(f"Event effect: {results.event_effects['D_event']:.4f}")
print(f"Leverage effect (γ): {results.leverage_effect:.4f}")
# Full summary
print(results.summary())
Features
- Student-t innovations: Captures fat tails in financial returns
- GJR-GARCH leverage effect: Asymmetric response to positive/negative shocks
- Robust standard errors: Bollerslev-Wooldridge (1992) QMLE sandwich covariance by
default (
cov_type="robust"), with the classical inverse-Hessian estimator available viacov_type="hessian" - Stationarity constraints: Enforced during optimization
- Configurable coefficient caps:
alpha_max/beta_max(relaxed defaults so the bounds do not silently bind on high-volatility daily series such as crypto) - Pandas integration: Works directly with Series/DataFrame objects, with array-like coercion and informative alignment errors
- No dependencies on
arch: Standalone implementation - Type hints: Full type annotations for IDE support
Model Specification
Variance Equation (GJR-GARCH-X)
σ²_t = ω + α·ε²_{t-1} + γ·ε²_{t-1}·I(ε_{t-1}<0) + β·σ²_{t-1} + Σδⱼ·x_{j,t}
Parameters:
ω(omega): Intercept, baseline variance levelα(alpha): ARCH effect, response to recent squared shocksγ(gamma): Leverage effect, additional response to negative shocksβ(beta): GARCH effect, persistence of conditional varianceδⱼ: Coefficients on exogenous variablesν(nu): Degrees of freedom for Student-t distribution
Leverage Effect Interpretation:
- Positive shocks: volatility impact =
α - Negative shocks: volatility impact =
α + γ - If
γ > 0: bad news increases volatility more than good news
Stationarity Condition
α + β + |γ|/2 < 1
Enforced automatically during estimation.
Coefficient bounds
The individual ARCH/GARCH bounds are exposed as keyword arguments and default to loose values so that they do not silently bind:
| Argument | Default | Note |
|---|---|---|
alpha_max |
0.99 |
Upper bound on α (was a hard-coded 0.30 before v0.2.0) |
beta_max |
0.999 |
Upper bound on β (was a hard-coded 0.95 before v0.2.0) |
The economically meaningful restriction is the stationarity constraint
α + β + |γ|/2 < 1, which is always enforced regardless of these caps. Tighten the caps
explicitly (e.g. alpha_max=0.30) if you want the historical behaviour.
Standard Errors and Inference
Coefficients are estimated by quasi-maximum likelihood (QMLE) with Student-t innovations.
Two covariance estimators are available via cov_type:
-
cov_type="robust"(default) — the Bollerslev-Wooldridge (1992) QMLE sandwich covarianceV = H⁻¹ · OPG · H⁻¹where
His the observed information (Hessian of the negative log-likelihood) andOPG = Σ_t sₜ sₜᵀis the outer product of the per-observation score contributions. These standard errors remain valid when the Student-t likelihood is misspecified (the usual QMLE robustness), which is the relevant case for heavy-tailed asset returns. -
cov_type="hessian"— the classical inverse observed-information covarianceH⁻¹, valid only under correct specification.
The Hessian is symmetrised and checked for positive definiteness; if it is not positive
definite (a near-degenerate fit), the covariance falls back to the Moore-Penrose
pseudo-inverse and a RuntimeWarning is emitted rather than returning silent NaNs.
robust = estimate_gjr_garch_x(returns, exog_vars) # BW sandwich (default)
classical = estimate_gjr_garch_x(returns, exog_vars, cov_type="hessian")
print(robust.cov_type, classical.cov_type) # 'robust' 'hessian'
API Reference
estimate_gjr_garch_x(returns, exog_vars=None, method='SLSQP', max_iter=1000, verbose=False, cov_type='robust', alpha_max=0.99, beta_max=0.999)
Main estimation function.
Parameters:
returns: returns series (recommend log returns × 100). Accepts apd.Series(index preserved), a single-column DataFrame, or any 1-D array-like; NaNs are droppedexog_vars:pd.DataFrame(index must cover the returns index) or a bare array matching the number of returns observations; exogenous variables for the variance equationmethod: Optimization method ('SLSQP','L-BFGS-B','trust-constr')max_iter: Maximum optimizer iterationsverbose: Print estimation progresscov_type:'robust'(Bollerslev-Wooldridge sandwich, default) or'hessian'alpha_max,beta_max: Upper bounds onαandβ(see Coefficient bounds)
Returns: GJRGARCHXResults object
GJRGARCHXResults
Results container with attributes:
converged:bool— Did optimization converge?params:Dict[str, float]— All parameter estimatesstd_errors:Dict[str, float]— Standard errors (percov_type)pvalues:Dict[str, float]— Two-sided p-valuescov_type:str— Covariance estimator used ('robust'or'hessian')log_likelihood:floataic,bic:float— Information criteriavolatility:pd.Series— Conditional standard deviation σ_tresiduals:pd.Series— Demeaned residuals ε_texog_effects:Dict[str, float]— All exogenous variable coefficientsevent_effects:Dict[str, float]— Event-type exogenous coefficientssentiment_effects:Dict[str, float]— Sentiment-type coefficientsleverage_effect:float— γ parameteriterations:int— Optimizer iterationsn_obs:int— Number of observations
Example: Cryptocurrency Event Study
import pandas as pd
from gjr_garch_x import estimate_gjr_garch_x
# Load BTC returns
btc = pd.read_csv('btc_returns.csv', index_col='date', parse_dates=True)
returns = btc['log_return'] * 100 # Convert to percentage
# Create event dummies
exog = pd.DataFrame(index=returns.index)
exog['D_infrastructure'] = 0
exog['D_regulatory'] = 0
# Mark infrastructure events (e.g., exchange hacks)
infra_dates = ['2022-11-11', '2022-05-09'] # FTX, Terra
for date in infra_dates:
# 7-day event window
mask = (exog.index >= pd.Timestamp(date) - pd.Timedelta(days=3)) & \
(exog.index <= pd.Timestamp(date) + pd.Timedelta(days=3))
exog.loc[mask, 'D_infrastructure'] = 1
# Mark regulatory events
reg_dates = ['2024-01-10', '2021-09-24'] # ETF approval, China ban
for date in reg_dates:
mask = (exog.index >= pd.Timestamp(date) - pd.Timedelta(days=3)) & \
(exog.index <= pd.Timestamp(date) + pd.Timedelta(days=3))
exog.loc[mask, 'D_regulatory'] = 1
# Estimate
results = estimate_gjr_garch_x(returns, exog, verbose=True)
# Compare effects
print(f"Infrastructure effect: {results.event_effects['D_infrastructure']:.4f}")
print(f"Regulatory effect: {results.event_effects['D_regulatory']:.4f}")
print(f"Ratio: {results.event_effects['D_infrastructure'] / results.event_effects['D_regulatory']:.2f}x")
Backwards Compatibility
For users migrating from TARCH naming conventions, aliases are provided:
from gjr_garch_x import estimate_tarch_x, TARCHXResults, TARCHXEstimator
These are identical to the GJR-prefixed versions.
Citation
If you use this package in academic work, please cite:
@software{farzulla2025gjrgarchx,
author = {Farzulla, Murad},
title = {gjr-garch-x: GJR-GARCH Models with Exogenous Variance Regressors},
year = {2025},
publisher = {Zenodo},
doi = {10.5281/zenodo.17988193},
url = {https://github.com/studiofarzulla/gjr-garch-x}
}
For the research paper that motivated this implementation:
@techreport{farzulla2025infrastructure,
author = {Farzulla, Murad},
title = {Market Reaction Asymmetry: Infrastructure Disruption Dominance
Over Regulatory Uncertainty in Cryptocurrency Markets},
year = {2025},
type = {Working Paper},
doi = {10.2139/ssrn.5788082}
}
References
- Glosten, L. R., Jagannathan, R., & Runkle, D. E. (1993). On the relation between the expected value and the volatility of the nominal excess return on stocks. Journal of Finance, 48(5), 1779-1801.
- Engle, R. F., & Ng, V. K. (1993). Measuring and testing the impact of news on volatility. Journal of Finance, 48(5), 1749-1778.
- Bollerslev, T., & Wooldridge, J. M. (1992). Quasi-maximum likelihood estimation and inference in dynamic models with time-varying covariances. Econometric Reviews, 11(2), 143-172.
License
MIT License. See LICENSE for details.
Contributing
Contributions welcome. Please open an issue first to discuss proposed changes.
Author
Murad Farzulla MSc Finance Analytics, King's College London ORCID: 0009-0002-7164-8704
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file gjr_garch_x-0.2.0.tar.gz.
File metadata
- Download URL: gjr_garch_x-0.2.0.tar.gz
- Upload date:
- Size: 21.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cd9c25adbfb0a9446a90c5ed4bdecb031b6ce5aa2923d6859ffc99b052af5d95
|
|
| MD5 |
9b62ee093a8fb5f79e4a8f17f4b5a97d
|
|
| BLAKE2b-256 |
ba61e9987fadb7297ea14f6e858134e0d9aaeb4cd499058db1ff6f6b901fbaf7
|
File details
Details for the file gjr_garch_x-0.2.0-py3-none-any.whl.
File metadata
- Download URL: gjr_garch_x-0.2.0-py3-none-any.whl
- Upload date:
- Size: 16.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
935154b8b58eff7ac3a94f99f93e4cb8e6df97a8284d45bb799f66888662cac4
|
|
| MD5 |
d8281d02aff3662727df9f9bb8344141
|
|
| BLAKE2b-256 |
b50d1ab4b263d47906769d63981840d1bd8f1522ad934cc1255c4c3aa1a70e23
|