波動率
這是使用 GARCH 預測股價波動的正確方法嗎
我試圖預測未來某個時間(比如 90 天)的股票波動。GARCH 似乎是一個傳統上使用的模型。
我在下面使用 Python 的
arch
庫實現了這一點。我所做的一切都在評論中進行了解釋,執行程式碼唯一需要更改的是提供您自己的每日價格,而不是我從自己的 API 中檢索它們的位置。import utils import numpy as np import pandas as pd import arch import matplotlib.pyplot as plt ticker = 'AAPL' # Ticker to retrieve data for forecast_horizon = 90 # Number of days to forecast # Retrive prices from IEX API prices = utils.dw.get(filename=ticker, source='iex', iex_range='5y') df = prices[['date', 'close']] df['daily_returns'] = np.log(df['close']).diff() # Daily log returns df['monthly_std'] = df['daily_returns'].rolling(21).std() # Standard deviation across trading month df['annual_vol'] = df['monthly_std'] * np.sqrt(252) # Annualize monthly standard devation df = df.dropna().reset_index(drop=True) # Convert decimal returns to % returns = df['daily_returns'] * 100 # Fit GARCH model am = arch.arch_model(returns[:-forecast_horizon]) res = am.fit(disp='off') # Calculate fitted variance values from model parameters # Convert variance to standard deviation (volatility) # Revert previous multiplication by 100 fitted = 0.1 * np.sqrt( res.params['omega'] + res.params['alpha[1]'] * res.resid**2 + res.conditional_volatility**2 * res.params['beta[1]'] ) # Make forecast # Convert variance to standard deviation (volatility) # Revert previous multiplication by 100 forecast = 0.1 * np.sqrt(res.forecast(horizon=forecast_horizon).variance.values[-1]) # Store actual, fitted, and forecasted results vol = pd.DataFrame({ 'actual': df['annual_vol'], 'model': np.append(fitted, forecast) }) # Plot Actual vs Fitted/Forecasted plt.plot(vol['actual'][:-forecast_horizon], label='Train') plt.plot(vol['actual'][-forecast_horizon - 1:], label='Test') plt.plot(vol['model'][:-forecast_horizon], label='Fitted') plt.plot(vol['model'][-forecast_horizon - 1:], label='Forecast') plt.legend() plt.show()
對於 Apple,這會產生以下情節:
顯然,擬合值始終遠低於實際值,這也導致預測被嚴重低估(這是一個糟糕的例子,因為蘋果的波動性在這個測試期間異常高,但我嘗試的所有公司,模型總是低估擬合值)。
我做的一切是否正確,GARCH 模型不是很強大,或者波動率建模非常困難?還是我犯了一些錯誤?
我通過以下方式執行滾動預測解決了這個問題。我不確定 (1) 此滾動預測是否正確,以及 (2) 如何在未來 30 天執行滾動預測。
import numpy as np import pandas as pd import matplotlib.pyplot as plt from rpy2.robjects.packages import importr import rpy2.robjects as robjects from rpy2.robjects import numpy2ri ticker = 'AAPL' forecast_horizon = 30 prices = utils.dw.get(filename=ticker, source='iex', iex_range='5y') df = prices[['date', 'close']] df['daily_returns'] = np.log(df['close']).diff() # Daily log returns df['monthly_std'] = df['daily_returns'].rolling(21).std() # Standard deviation across trading month df['annual_vol'] = df['monthly_std'] * np.sqrt(252) # Convert monthly standard devation to annualized volatility df = df.dropna().reset_index(drop=True) # Initialize R GARCH model rugarch = importr('rugarch') garch_spec = rugarch.ugarchspec( mean_model=robjects.r('list(armaOrder = c(0,0))'), variance_model=robjects.r('list(garchOrder=c(1,1))'), distribution_model='std' ) # Used to convert training set to R list for model input numpy2ri.activate() # Train R GARCH model on returns as % garch_fitted = rugarch.ugarchfit( spec=garch_spec, data=df['daily_returns'].values * 100, out_sample=forecast_horizon ) numpy2ri.deactivate() # Model's fitted standard deviation values # Revert previous multiplication by 100 # Convert to annualized volatility fitted = 0.01 * np.sqrt(252) * np.array(garch_fitted.slots['fit'].rx2('sigma')).flatten() # Forecast using R GACRH model garch_forecast = rugarch.ugarchforecast( garch_fitted, n_ahead=1, n_roll=forecast_horizon - 1 ) # Model's forecasted standard deviation values # Revert previous multiplication by 100 # Convert to annualized volatility forecast = 0.01 * np.sqrt(252) * np.array(garch_forecast.slots['forecast'].rx2('sigmaFor')).flatten() volatility = pd.DataFrame({ 'actual': df['annual_vol'].values, 'model': np.append(fitted, forecast), }) plt.plot(volatility['actual'][:-forecast_horizon], label='Train') plt.plot(volatility['actual'][-forecast_horizon - 1:], label='Test') plt.plot(volatility['model'][:-forecast_horizon], label='Fitted') plt.plot(volatility['model'][-forecast_horizon - 1:], label='Forecasted') plt.legend() plt.show()
這產生了這個情節: