0
$\begingroup$

Please be easy on me since quant finance is not my strength.

I have the following Python code that models volatility under GARCH(1,1) for the S&P500:

import numpy as np
import pandas as pd
import yfinance as yf
import matplotlib.pyplot as plt
import scipy.optimize as spop

ticker = '^GSPC'
start = '2015-12-31'
end = '2021-06-25'

prices = yf.download(ticker, start, end)['Close']

returns = np.array(prices)[1:]/np.array(prices)[:-1] - 1

mean = np.average(returns)
var = np.std(returns)**2

def garch_mle(params):
    mu = params[0]
    omega = params[1]
    alpha = params[2]
    beta = params[3]

    long_run = (omega/(1 - alpha - beta))**(1/2)

    resid = returns - mu
    realised = abs(resid)
    conditional = np.zeros(len(returns))
    conditional[0] =  long_run
    for t in range(1,len(returns)):
        conditional[t] = (omega + alpha*resid[t-1]**2 + beta*conditional[t-1]**2)**(1/2)

    likelihood = 1/((2*np.pi)**(1/2)*conditional)*np.exp(-realised**2/(2*conditional**2))
    log_likelihood = np.sum(np.log(likelihood))
    return -log_likelihood

res = spop.minimize(garch_mle, [mean, var, 0, 0], method='Nelder-Mead')

params = res.x
mu = res.x[0]
omega = res.x[1]
alpha = res.x[2]
beta = res.x[3]
log_likelihood = -float(res.fun)

long_run = (omega/(1 - alpha - beta))**(1/2)
resid = returns - mu
realised = abs(resid)
conditional = np.zeros(len(returns))
conditional[0] =  long_run
for t in range(1,len(returns)):
    conditional[t] = (omega + alpha*resid[t-1]**2 + beta*conditional[t-1]**2)**(1/2)

And it produces these values, after optimization:

mu 0.000961
omega 4e-06
alpha 0.2604
beta 0.721
long-run volatility 0.0149
log-likelihood 4691.0025

How do I continue modeling volatility, under GARCH(1,1), using the parameters from above (aka I wouldn't have access to the initial raw data anymore) and for the following new daily closes on the S&P500?

new_data = {
    '2021-06-25': 4280.700195,
    '2021-06-28': 4290.609863,
    '2021-06-29': 4291.799805,
    '2021-06-30': 4297.500000,
    '2021-07-01': 4319.939941,
    '2021-07-02': 4352.339844
}

Thanks!

$\endgroup$
2
  • $\begingroup$ If you simply want to use the current estimated parameters, then you calculate the returns from the new daily close prices and append them to the return vector, recover the residuals by subtracting the mean (aka. your resid) and then run the last part of the code that uses the estimated parameters, resid and the former conditional to give you a new GARCH(1,1) volatility estimate. $\endgroup$
    – Pleb
    Commented Jul 24 at 8:54
  • $\begingroup$ Could you drop the code for that as an official answer please? @Pleb I'd award it the points of the question. You can even copy/paste parts of my code if you want, to make it easier. It's just that I want to get it right, since it's for a sensitive thing, and, like I mentioned before, quant is not my forte so I'm afraid of miscalculating it with a stupid mistake. $\endgroup$
    – dNyrM
    Commented Jul 24 at 12:38

1 Answer 1

1
$\begingroup$

You can remove the estimation section from your code and extend the end of your data to include out-of-sample data. As written below, I have used the previous residual and volatility estimate for initialization. This implies that your code reduces to:

import numpy as np
import pandas as pd
import yfinance as yf
import matplotlib.pyplot as plt
import scipy.optimize as spop

ticker = '^GSPC'
start = '2021-06-25'
end = '2021-07-03' #Changing the end date will give you the new data that you desire. 

prices = yf.download(ticker, start, end)['Close']
returns = np.array(prices)[1:]/np.array(prices)[:-1] - 1

#The estimated parameters from previous:
mu = 0.000961
omega = 4e-06
alpha = 0.2604
beta = 0.721
long_run = 0.0149

#The last part of your code can be re-run with the added prices

old_conditional = 0.008016 #estimates from 2021-06-24
old_resid = 0.00485025 #estimates from 2021-06-24

#initiate resid and conditional
resid = np.zeros(len(returns) + 1)
resid[0] = old_resid

conditional = np.zeros(len(returns) + 1)
conditional[0] = old_conditional

resid[1:] = returns - mu
realised = abs(resid)
for t in range(1,len(returns) + 1):
    conditional[t] = (omega + alpha*resid[t-1]**2 + beta*conditional[t-1]**2)**(1/2)
     
#Created a dataframe for results.     
vol = pd.DataFrame(columns=["sigma"], index=prices.index)
vol["sigma"] = conditional

With corresponding results:

>>> vol.tail(7)
Date        sigma   
2021-06-24  0.008016    
2021-06-25  0.007514    
2021-06-28  0.006795    
2021-06-29  0.006145    
2021-06-30  0.005599    
2021-07-01  0.005161    
2021-07-02  0.005285    

Please consider this:

  • Under no re-estimation, the out-of-sample GARCH estimates eventually converges to the unconditional volatility (aka. around 0.0149). When $\alpha + \beta \approx 1$ the process converges at a slower rate. Hence, a re-estimation is needed for better volatility forecasts.

  • Using fixed GARCH parameters out-of-sample, makes the estimates insensitive to new underlying characteristics in the price process. This implies that during periods of increased volatility or structural breaks, the fixed estimates will perform poorly in forecasting the altered volatility process. To address this issue, econometricians opt either for a rolling- or expanding-window forecast, allowing the GARCH parameters to be re-estimated with each new consecutive price point. The "estimation" part, along with the concluding section of your code, can be rewritten to accommodate either of these forecasting methodologies.

  • Other than that, there is also a package in Python called arch, that contains various GARCH models. I have written a small and convenient answer here on how to produce forecasts using the package.

If this is truly for a sensitive thing, I would recommend you to consult with your peers on how to improve the volatility forecasts. For now, the above code do as you intend.

$\endgroup$
9
  • $\begingroup$ Planning on doing a rolling window recalibration of parameters in N amount of time (perhaps monthly). Taking that into account, gotta ask something: you added the additional price values to the initial raw data and calculated the volatility using that and the predetermined GARCH parameters. What I'm looking, at this moment, is using only the predetermined GARCH parameters + the additional price values to calculate the conditional volatility for those days only, so any data that falls outside those price values wouldn't be available/used. Is this possible? Would your code need to be changed? $\endgroup$
    – dNyrM
    Commented Jul 24 at 13:50
  • $\begingroup$ AFAIK: You want to use the already estimated GARCH parameters on new data, correct? In order for the GARCH process to run (eg. getting your estimate at time 2021-06-25 and after), you need the previous value of the volatility estimate and the residual in order to start the GARCH process out of sample. Hence, you need respectively the residual and the volatility estimate of 2021-06-24. If these are stored somewhere, you can use these. Otherwise, the process do not know at what "vol level" to begin at. $\endgroup$
    – Pleb
    Commented Jul 24 at 14:17
  • $\begingroup$ If you have these, I will redo the code to accommodate. Otherwise, I think you are out of luck since you cannot properly initialize the filter equation (last part of your code). $\endgroup$
    – Pleb
    Commented Jul 24 at 14:27
  • $\begingroup$ Yeah, I'd have them stored somewhere. So the residual is resid and the vol estimate isconditional[t] where t is 2021-06-24? If so, could you fix your code to represent this? You could call it old_resid and such to express the placeholder state of the variable, since the old data would be unknown in this context. I'd award the points once it's done. $\endgroup$
    – dNyrM
    Commented Jul 24 at 14:30
  • $\begingroup$ Yeah, I'd have them. Re-accomodate and we're done here. Thanks a lot for your help. $\endgroup$
    – dNyrM
    Commented Jul 24 at 14:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.