pythontime-seriesnumpyro

GARCH models in Numpyro


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
    

Solution

  • 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:])