GARCH models in Numpyro

Question:

I was curious how best to implement GARCH models in numpyro. I tried reading https://num.pyro.ai/en/stable/tutorials/time_series_forecasting.html but found it generally unclear (the model notation and variable names are not easy to map, the model is overly complicated for a basic intro)

I wrote the following code to estimate a GARCH-M model, but the performance doesn’t seem great and I’m curious to see if others have suggestions.

The model is

enter image description here

but the question applies quite generally. I chose a GARCH-M model because it requires looping, you can’t infer the entire sequence of epsilon’s at once because you need to infer time-varying volatility.

# preliminaries
import numpyro
import numpyro.distributions    as dist
import jax.numpy as jnp
from numpyro.infer import MCMC, NUTS
from numpyro.contrib.control_flow import scan
import numpy                    as np

def my_model(y=None):

    μ = numpyro.sample("μ", dist.Normal(0, 4))
    ω = numpyro.sample("ω", dist.HalfCauchy(2))
    α = numpyro.sample("α", dist.Uniform())
    β = numpyro.sample("β", dist.Uniform())
    λ = numpyro.sample("λ", dist.Normal(0, 4))

    σ2_0 = ω / (1 - α - β)

    def gjr_var(state, new):
        # state is past variance and past shock
        σ2_t, r_t = state

        ɛ_t = (r_t - μ - λ*σ2_t**0.5)/σ2_t**0.5

        σ2_tp = ω + α * ɛ_t**2 + β * σ2_t 
        r_tp = numpyro.sample('r', dist.Normal(μ + λ*σ2_tp**0.5, σ2_tp**0.5), obs=new)

        return (σ2_tp, r_tp), r_tp

    _, r = scan(gjr_var, (σ2_0, y[0]), y[1:])

    return r
    
Asked By: Shffl

||

Answers:

I re-visited this after several months and came up with some tweaks that resolved the problem.

The main problem seems to be that if alpha + beta >= 1, then the volatility process is non-stationary. Worse yet, it leads to an negative unconditional variance due to the formula σ2_0 = ω / (1 - α - β)

I imposed stationary by drawing alpha and beta from a 3d Dirichlet distribution, and now the estimates match what is produced by the arch Python package

def my_model(y: np.ndarray):

    μ = numpyro.sample("μ", dist.ImproperUniform(dist.constraints.real, (), ()))
    ω = numpyro.sample("ω", dist.ImproperUniform(dist.constraints.positive, (), ()))
    β_vec = numpyro.sample("β_intermediate", dist.Dirichlet(jnp.ones(3)))
    α = numpyro.deterministic("α", β_vec[0])
    β = numpyro.deterministic("β", β_vec[1])

    λ = numpyro.sample("λ", dist.ImproperUniform(dist.constraints.real, (), ()))

    σ2_0 = ω / (1 - α - β)

    def gjr_var(state, new):
        # state is past variance and past shock
        σ2_t, r_t = state

        ɛ_t = r_t - μ - λ * σ2_t**0.5

        σ2_tp = numpyro.deterministic(
            "σ2", ω + α * ɛ_t**2 + β * σ2_t
        )

        r_tp = numpyro.sample(
            "r", dist.Normal(μ + λ * σ2_tp**0.5, σ2_tp**0.5), obs=new
        )

        ɛ_tp = numpyro.deterministic("ɛ", r_tp - μ - λ * σ2_tp**0.5)

        return (σ2_tp, r_tp), (r_tp, σ2_tp, ɛ_tp)

    scan(gjr_var, (σ2_0, y[0]), y[1:])
Answered By: Shffl
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.