Photo by Isaac Smith on Unsplash
Table of Contents
首先,本文會執行模型建置的過程,讓讀者了解Python套件的應用,但是不會進行上篇文章中提及的任何檢定,以避免篇幅冗長;接著,計算預測報酬以及價格;最後將以視覺化方式比對真實歷史價格,來檢討ARMA-GARCH模型的預測效果。
Note:本文是利用”ARMA”模型對報酬率建模,而非上篇文章中的”ARIMA”,其差異處僅在ARIMA能夠處理非定態數據。上篇使用ARIMA是想盡量讓讀者了解時間序列的原理,而本文使用ARMA是想讓讀者多了解一種方法。
本文使用 MacOS 並以 Jupyter Notebook作為編輯器
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
import seaborn as sns
sns.set()
import tejapi
tejapi.ApiConfig.api_key = 'Your Key'
tejapi.ApiConfig.ignoretz = True
Step 1. 資料撈取
data = tejapi.get('TWN/APRCD', # 公司交易資料-收盤價
coid= '0050', # 台灣50
mdate={'gte': '2003-01-01', 'lte':'2021-12-31'},
opts={'columns': ['mdate', 'close_d', 'roi']},
chinese_column_name=True,
paginate=True)
data['年月日'] = pd.to_datetime(data['年月日'])
data = data.set_index('年月日')
data = data.rename(columns = {'收盤價(元)':'收盤價', '報酬率%':'日報酬率(%)'})
Step 2. 資料分割
train_date = data.index.get_level_values('年月日') <= '2020-12-31'
train_data = data[train_date].drop(columns = ['收盤價'])
test_data = data[~train_date]
# 保留test_data收盤價,用來比對模型預測值
Step 3. ARMA參數選擇
本文會利用statsmodels進行參數篩選,與上篇利用pmdarima不同。
import statsmodels.api as sm
# AIC、BIC準則
sm.tsa.stattools.arma_order_select_ic(train_data, ic=["aic", "bic"])
可以看到BIC準則的參數挑選項目為(0,0),這是源於BIC準則會對多解釋變數進行較嚴格的篩選,因此,本文選擇(p,q)=(2,2),進行模型建置。
Step 4. ARMA模型建置
from statsmodels.tsa.arima_model import ARMA
model = ARMA(train_data, order = (2, 2))
arma = model.fit()
print(arma.summary())
Step 5. GARCH模型建置
# 取得ARMA模型的殘差項目
arma_resid = list(arma.resid)
from arch import arch_model
mdl_garch = arch_model(arma_resid, vol = 'GARCH', p = 1, q = 1)
garch = mdl_garch.fit()
print(garch.summary())
在預測的部分,本文會用ARMA模型估計平均,並應用GARCH模型預測波動區間。
Step 1. ARMA預測平均報酬
# len(train_data) = 4333, len(data) = 4577 forecast_mu = arma.predict(start = 4333, end = 4576) # 預測函式的end包含當期,所以需進行4577-1=4576。
從上圖發現經過一段時間後,預測平均報酬會逐漸成為趨近於0的常數,僅在預測初期有較明顯的波動。
Step 2. GARCH預測波動度
garch_forecast = []
for i in range(len(test_data)):
train = arma_resid[:-(len(test_data)-i)]
model = arch_model(train, vol = 'GARCH', p = 1, q = 1)
garch_fit = model.fit()
prediction = garch_fit.forecast(horizon=1)
garch_forecast.append(np.sqrt(prediction.variance.values[-1:][0]))
本文此處運用滾動式的方法預測各期的波動度,所以上述程式碼是將GARCH模型包在迴圈當中,再回傳儲存值。接下來,先將上述各預測值存入test_data,並計算上下區間,供後續計算。
test_data['ARMA預測報酬(%)'] = list(forecast_mu)
test_data['GARCH預測波動度'] = (garch_forecast)
test_data['預測區間上限'] = test_data['ARMA預測報酬(%)'] + test_data['GARCH預測波動度']
test_data['預測區間下限'] = test_data['ARMA預測報酬(%)'] - test_data['GARCH預測波動度']
根據上圖,大部分的實際報酬率是包含在區間當中的;然而,無法有效預測單一日期波動幅度較大之報酬率。
Step 3. 預測價格
# 本文已經把train_data中的價格刪除,所以需重新計算2020-12-30的收盤價
first_price = test_data['收盤價'][0] / (1+test_data['日報酬率(%)'][0]*0.01)
# 計算第一期預測
test_data['ARMA預測價格'] = first_price * (1 + test_data['ARMA預測報酬(%)']*0.01)
test_data['預測價格區間上限'] = first_price * (1 + test_data['預測區間上限']*0.01)
test_data['預測價格區間下限'] = first_price * (1 + test_data['預測區間下限']*0.01)
# 計算剩餘預測區間
for i in range(1, len(test_data)):
test_data['ARMA預測價格'][i] = test_data['預測價格'][i-1] * (1 + test_data['ARMA預測報酬(%)'][i]*0.01)
test_data['預測價格區間上限'][i] = test_data['預測價格區間上限'][i-1] * (1 + test_data['預測區間上限'][i]*0.01)
test_data['預測價格區間下限'][i] = test_data['預測價格區間下限'][i-1] * (1 + test_data['預測區間下限'][i]*0.01)
# 計算區間均價
test_data['預測平均價格'] = (test_data['預測價格區間上限'] + test_data['預測價格區間下限']) / 2
上圖顯示,在預測初期時,預測區間較窄,並且兩項價格預測值也與實際價格相去不遠;然而,隨著預測時間越往後,預測區間擴大、區間均價偏移,導致無法準確判斷模型的成效。所以本文接下來會展示時間軸為兩個月的預測值。
new_date = test_data.index.get_level_values('年月日') <= '2021-03-01'
new_test = test_data[new_date]
在兩個月的區間中,讀者應該可以更清楚地發現,實際價格與預測值間的差異。首先,是兩項預測價格與實際價格的關係,可以發現區間均價更貼近實際價格;而在區間預測方面,隨著預測區間擴大以及實際價格回落,實際價格的走勢才涵蓋在預測波動當中。
藉由最後的結果,讀者應該可以明白此次ARMA-GARCH模型對0050並沒有很好的預測效果,儘管在模型配適上有不錯的成果。先不論隨時間推移而區間擴大,導致失去判斷標準這項問題,畢竟時間軸越遠,本來就應該進行更保守的估計;單就預測初期的結果,便可以發現實際價格超出模型預測的波動區間,也就代表模型在初期預測便沒有足夠的可信度,這可能是源於本文並無考量到的外生變量或是季節性因素而造成的。所以,若是讀者對這類型的議題有興趣,請持續關注此平台的文章;另外,歡迎選購 TEJ E Shop中的方案,就能夠輕鬆地對有興趣的標的,實作走勢預測。
電子報訂閱