楊育晟(Peter Yang)

嗨, 我叫育晟, 部落格文章主題包含了程式設計、財務金融及投資...等等,內容多是記錄一些學習的過程和心得,任何想法都歡迎留言一起討論。



Email: ycy.tai@gmail.com
LinkedIn: Peter Yang
Github: ycytai

利用ARIMA模型對股價進行預測(Forecasting Stock Price with ARIMA)

什麼是ARIMA模型?

今天將介紹量化的其中一項工具- 自迴歸移動平均模型(Autoregressive Integrated Moving Average Model, ARIMA)

$ARIMA(p,d,q)$為時間序列預測模型,能用於預測股價、利率等時間序列資料,是由三個模型組成,如下所示

Autoregressive (自回歸)

利用股票前幾期的報酬率作為自變數,用股票自身的歷史報酬率來預測未來的報酬率

$$ AR(p) \Rightarrow y_t= \alpha + \phi_{1}y_{t-1}+ \phi_{2}y_{t-2} + ... +\phi_{p}y_{t-p}+\epsilon_{t} $$

Integrated (差分整合)

將第(t)期股票報酬率減掉(t-1)期的股票報酬率,讓資料變為定態

$$ y_t=y_{t-1}+\epsilon_{i} $$

Moving Average (移動平均)

利用歷史的殘差作為自變數,用歷史的殘差資料協助預測股票未來的報酬率

$$ MA(q)\Rightarrow y_t=\alpha+\epsilon_t+\theta_{1}\epsilon_{t-1}+\theta_{2}\epsilon_{t-2}+...++\theta_{q}\epsilon_{t-q} $$

模型因為概念容易理解,屬於好上手、廣為人知的模型,使用模型分為以下步驟

1.資料是否為定態,如果非定態,就進行差分處理

2.處理完後,決定落後期數,有兩種方法

  • 透過訊息準則選擇落後期數,也就是  $ARIMA_{p,d,q}$ 中的 $p,q$
  • 利用ACF和PACF圖輔助

3.進行估計和執行預測

單根問題(unit root)

其中如果原本的資料為非定態,則可以透過d的差分項數來處理,也因此當差分項數為1時,及等同於一階差分過後的ARMA。

當一筆時間序列存在著單根時,會使得變異數發散,這時便無法估計條件期望值。

當存在單根時,假如期數不斷增加到無限大,變異數也將無限發散,然而變異數無限發散的情況下,標準差也將無限擴大,信賴區間隨之增加,這時將無法拒絕H0,使得古典的估計、檢定皆無效。

使用ARIMA預測台積電股價

在財務金融的世界裡,所有投資人都想要知道商品未來價格的走勢,因為掌握了價格的變動,就能把握住獲利的機會,有人是憑著經驗法則,透過以往的經歷來評斷未來會發生的事,也就是透過知識和經驗來進行預測,也有人是利用程式工具,用數據搭配量化的方式來實現這件事。

接著將用Python來動手實作ARIMA模型,並進行預測,使用的資料是台積電(2330)於2021/7/27到2022/7/27的股價資料,頻率為日頻率。

資料來源用yahoo finance

Taiwan Semiconductor Manufacturing Company Limited (2330.TW) Stock Historical Prices & Data - Yahoo Finance

讀取資料

import pandas as pd

df = pd.read_csv(
    '2330.TW.csv', 
    parse_dates=['Date'], 
    index_col='Date'
)
close_price = df[['Close']].copy()
close_price.tail()

ADF檢定-觀察是否定態

先來看看台積電股價的自相關圖

from statsmodels.graphics.tsaplots import plot_acf
import matplotlib.pyplot as plt

fig = plt.figure(figsize=(10,10))
ax1 = fig.add_subplot(311)
fig = plot_acf(close_price, ax=ax1, title='Autocorrelation')

可以看到自我相關係數相當的明顯,通常這樣的現象會存在序列相關,而序列相關的影響就如同前面所說的會使得變異數隨時間發散,造成古典估計失效。

我們也用ADF檢定來看看是否存在單根,虛無假說如下

$$ H_{0}:\phi_{1}=1 $$

from statsmodels.tsa.stattools import adfuller

result = adfuller(close_price)import pandas as pd
print('p-value: {}'.format(round(result[1],4)))

#  p-value: 0.7895

出來結果的P值為0.7895,大於顯著水準,不拒絕虛無假設,也因此我們知道資料為非定態,等等在幫ARIMA定階時要做差分(differencing)處理。

來觀察差分後的自我相關圖

close_price_diff = close_price.diff().dropna()

fig = plt.figure(figsize=(10,10))
ax2 = fig.add_subplot(312)
fig = plot_acf(close_price_diff, ax=ax2, title='Differencing')

差分過後的自相關看起來變得不顯著了,再用ADF檢定來看看差分過後的結果

result = adfuller(close_price_diff)
print('p-value: %f' % result[1])

# p-value: 0.000000

通過ADF檢定,解決了單根問題

決定落後期數

在替ARIMA模型定階時有兩個常用的方法,一個直接觀察ACF和PACF圖,另一個則是用訊息準則(Information Criterion),有三種常見的訊息準則

  1. 赤池資訊準則 (akaike information criterion,AIC )
  2. 赤池資訊準則 (akaike information criterion,AICc)
  3. 貝式資訊準則 (bayesian information criterion,BIC)

通常在小樣本的時候,會使用AICc大樣本時使用AIC或BIC,BIC更為強烈,訊息準則越小越好

這邊我們將利用AIC訊息準則來決定階數

from statsmodels.tsa.arima.model import ARIMA
import warnings
warnings.filterwarnings("ignore")

result = {}
for p in range(1, 5):
    for q in range(1, 3):

        model = ARIMA(close_price, order=(p, 1, q))
        results = model.fit()
        result[(p, 1, q)] = results.aic

"""
{(1, 1, 1): 1751.909713144756,
 (1, 1, 2): 1753.1329180870923,
 (2, 1, 1): 1752.7594697101404,
 (2, 1, 2): 1753.4031255615982,
 (3, 1, 1): 1754.5451767758948,
 (3, 1, 2): 1755.4006590416088,
 (4, 1, 1): 1754.7657804720363,
 (4, 1, 2): 1745.6467479071243}
"""

可以觀察到在 p=4 及 q=2 時的AIC為最小,故選擇(4, 1, 2)作為AR和MA的階數

接著我們來看看配適模型後的統計結果

model = ARIMA(close_price, order=(4, 1, 2))
results = model.fit(disp=0)
print(results.summary())

PACF, ACF

接著我們看看PACF,ACF圖,他們分別稱作偏自我相關自我相關,剛剛提到定階的另一種方法就是觀察這兩張圖

from statsmodels.graphics.tsaplots import plot_acf
from statsmodels.graphics.tsaplots import plot_pacf

fig = plt.figure(figsize=(10, 10))
ax3 = fig.add_subplot(312)
fig = plot_pacf(close_price.diff().dropna(), ax=ax3, title='Partial AutoCorrelation Function')

fig = plt.figure(figsize=(10, 10))
ax4 = fig.add_subplot(312)
fig = plot_acf(close_price.diff().dropna(), ax=ax4, title='AutoCorrelation Function')

估計和執行預測

接著開始進行預測,我們把資料分為兩個部份,訓練和測試用,用0.8和0.2的比例分配,而當我們預測完一天後,將會把當天的真實結果加進訓練資料,再進行隔天的預測。

也就是每天都預測隔一天的結果,而當天結果出來後,我們取來作為訓練資料,接著再預測隔天

from sklearn.metrics import mean_squared_error

# cut into train and test
price = list(close_price.Close)
cut = int(len(price) * 0.8)
train, test = price[0:cut], price[cut:]

preds = []
lower_bound = []
upper_bound = []
for i in range(len(test)):

    model = ARIMA(train, order=(4, 1, 2))
    model_fit = model.fit(disp=0)

    result = model_fit.get_forecast().summary_frame(alpha=0.05)
    pred = result['mean'].values[0]
    upper, lower = result['mean_ci_upper'].values[0], result['mean_ci_lower'].values[0]

    preds.append(pred)
    lower_bound.append(lower)
    upper_bound.append(upper)

    train.append(test[i])

mse = mean_squared_error(test, preds)
print(f'Mean Squared Error : {round(mse, 4)}')
# Mean Squared Error : 109.7636

預測完後,再計算一下實際和預測資料的誤差,我們用均方誤(MSE)來做比較,MSE越小表示預測表現越好,反之亦然

視覺化

最後,再來將資料視覺化

test_periods = close_price.index[cut:len(price)]

df = pd.DataFrame(index=test_periods)
df.index = pd.to_datetime(df.index)
df['real'] = test
df['pred'] = preds
df['lb'] = lower_bound
df['ub'] = upper_bound

fig = plt.figure(figsize = (12,5))
ax = fig.add_subplot()
 

fig.suptitle(
    'TSMC Stock Price Prediction & Reality', 
    fontsize=22, 
    fontweight='bold'
)

start = test_periods[0].date()
end = test_periods[-1].date()
ax.set_title(f'{start} - {end}', fontsize=16,)
fig.subplots_adjust(top=0.85)
 
ax.tick_params(labelsize=12)
ax.plot(df.real, color='red', label='Real', marker='o',
         markerfacecolor='red',markersize=6)
ax.plot(df.pred, color='#121466', label='Pred', marker='o',
         markerfacecolor='#121466',markersize=6)
ax.legend()

ax.plot(df.lb, color='#121466', ls="--", alpha = 1)
ax.plot(df.ub, color='#121466', ls="--", alpha = 1)

ax.fill_between(df.index, 
                df.lb,
                df.ub,
                color = '#121466',
                alpha=.2,)

ax.spines['right'].set_visible(False)
ax.spines['top'].set_visible(False)
plt.savefig('TSMC_prediction.png',dpi=300)

紅色是實際數據,藍色是預測數據,淺藍色的區塊則是預測的信賴區間,可以看到預測和實際的股價走勢很雷同,那是因為我們每一天都會加入前一天的最新(正確)資料,而前一天的資料對預測的影響最為明顯,所以預測走勢的變化通常會和一天的價格變化雷同。

今天使用了ARIMA模型來進行股價預測,可以利用上面的程式碼和介紹,一起體面模型工具預測的樂趣。但還是不免俗的加些警語,預測僅供投資參考,不能當作唯一依據

Tags:
# python
# investing
# econometric
# model
# arima
# stock
# forecast