先决条件

本教程假设您对 StatsForecast 有基本了解。如需最简单的示例,请访问快速入门

引言

广义自回归条件异方差 (GARCH) 模型用于处理随时间表现出非恒定波动性的时间序列。这里的波动性指的是条件标准差。GARCH(p,q) 模型由下式给出

其中 vtv_t 是独立同分布的,均值为零,方差为单位方差,且 σt\sigma_t 根据下式演变

上述方程中的系数必须满足以下条件

  1. w>0w>0, αi0\alpha_i \geq 0 对于所有 ii,且 βj0\beta_j \geq 0 对于所有 jj
  2. k=1max(p,q)αk+βk<1\sum_{k=1}^{max(p,q)} \alpha_k + \beta_k < 1。这里假设 αi=0\alpha_i=0 对于 i>pi>pβj=0\beta_j=0 对于 j>qj>q

GARCH 模型的一个特例是 ARCH 模型,其中 q=0q=0。这两种模型在金融领域常用于模拟股票价格、汇率、利率及其他金融工具的波动性。它们也用于风险管理,以估计金融资产价格大幅波动的概率。

学完本教程后,您将很好地了解如何在StatsForecast中实现 GARCH 或 ARCH 模型,以及如何使用它们分析和预测金融时间序列数据。

大纲

  1. 安装库
  2. 加载和探索数据
  3. 训练模型
  4. 执行时间序列交叉验证
  5. 评估结果
  6. 预测波动性

提示

您可以使用 Colab 交互式地运行此 Notebook

安装库

我们假设您已经安装了 StatsForecast。如果还没有,请查看此指南了解如何安装 StatsForecast 的说明

使用 pip install statsforecast 安装必要的包

pip install statsforecast -U

加载和探索数据

在本教程中,我们将使用标普500指数和几家上市公司的近5年价格数据。可以使用yfinance从 Yahoo! Finance 下载数据。要安装它,请使用 pip install yfinance

# pip install yfinance

我们还需要 pandas 来处理数据帧。

import yfinance as yf
import pandas as pd
tickers = ['SPY', 'MSFT', 'AAPL', 'GOOG', 'AMZN', 'TSLA', 'NVDA', 'META', 'NKE', 'NFLX'] 
df = yf.download(tickers, start = '2018-01-01', end = '2022-12-31', interval='1mo', progress=False) # use monthly prices
df.head()
价格调整收盘价成交量
股票代码AAPLAMZNGOOGMETAMSFTNFLXNKENVDASPYTSLAAAPLAMZNGOOGMETAMSFTNFLXNKENVDASPYTSLA
日期
2018-01-0139.38808472.54450258.353695186.32897988.027702270.29998863.3418626.078998252.56521623.620667263871760019274240005747680004956557005742584002383776001578122001145621600019855067001864072500
2018-02-0141.90290875.62249855.101181177.78472986.878807291.38000562.2369385.985018243.38188222.870667371157720027556800008476400005162516007256633001845858001603170001491552800029237220001637850000
2018-03-0139.63134472.36699751.463116159.31033384.959763295.35000661.6891335.731123235.76637317.742001285491080026080020009070660009962017007507548002634494001740667001411844000023235618002359027500
2018-04-0139.03610678.30650350.741886171.48368887.054207312.45999163.6917615.565567237.93400619.593332266461720025983920008343180007500727006681307002620060001589819001114400800019984665002854662000
2018-05-0144.14059881.48100354.116600191.20431592.006393351.60000666.8675086.240908243.71795718.982000248390520014323100006369880004011441005094179001420508001295663001197824000016063972002333671500

下载的数据包含不同的价格。我们将使用调整收盘价,这是考虑了股票分割或股息分配等任何公司行为后的收盘价。它也是用于检验历史回报的价格。

注意,yfinance 返回的数据帧具有MultiIndex(多重索引),因此我们需要同时选择调整后的价格和股票代码。

df = df.loc[:, (['Adj Close'], tickers)]
df.columns = df.columns.droplevel() # drop MultiIndex
df = df.reset_index()
df.head()
股票代码日期SPYMSFTAAPLGOOGAMZNTSLANVDAMETANKENFLX
02018-01-01252.56521688.02770239.38808458.35369572.54450223.6206676.078998186.32897963.341862270.299988
12018-02-01243.38188286.87880741.90290855.10118175.62249822.8706675.985018177.78472962.236938291.380005
22018-03-01235.76637384.95976339.63134451.46311672.36699717.7420015.731123159.31033361.689133295.350006
32018-04-01237.93400687.05420739.03610650.74188678.30650319.5933325.565567171.48368863.691761312.459991
42018-05-01243.71795792.00639344.14059854.11660081.48100318.9820006.240908191.20431566.867508351.600006

StatsForecast 的输入是一个长格式的数据帧,包含三列:unique_iddsy

  • unique_id:(字符串、整数或类别)序列的唯一标识符。
  • ds:(日期戳或整数)格式为 YYYY-MM-DD 或 YYYY-MM-DD HH:MM:SS 的日期戳,或表示时间的整数索引。
  • y:(数值)我们希望预测的度量值。

因此,我们需要重塑数据。我们将通过创建一个名为 price 的新数据帧来完成此操作。

prices = df.melt(id_vars = 'Date')
prices = prices.rename(columns={'Date': 'ds', 'Ticker': 'unique_id', 'value': 'y'})
prices = prices[['unique_id', 'ds', 'y']]
prices
unique_iddsy
0SPY2018-01-01252.565216
1SPY2018-02-01243.381882
2SPY2018-03-01235.766373
3SPY2018-04-01237.934006
4SPY2018-05-01243.717957
595NFLX2022-08-01223.559998
596NFLX2022-09-01235.440002
597NFLX2022-10-01291.880005
598NFLX2022-11-01305.529999
599NFLX2022-12-01294.880005

我们可以使用 StatsForecast 类的 plot 方法绘制此序列。

from statsforecast import StatsForecast
StatsForecast.plot(prices)

利用价格数据,我们可以计算标普500指数和上市公司的对数回报率。这是我们感兴趣的变量,因为它可能与 GARCH 框架配合良好。对数回报率由下式给出

returnt=log(pricetpricet1)return_t = log \big( \frac{price_t}{price_{t-1}} \big)

我们将在 price 数据帧上计算回报率,然后创建一个符合 StatsForecast 格式的回报率数据帧。为此,我们需要 numpy

import numpy as np 
prices['rt'] = prices['y'].div(prices.groupby('unique_id')['y'].shift(1))
prices['rt'] = np.log(prices['rt'])

returns = prices[['unique_id', 'ds', 'rt']]
returns = returns.rename(columns={'rt':'y'})
returns
unique_iddsy
0SPY2018-01-01NaN
1SPY2018-02-01-0.037038
2SPY2018-03-01-0.031790
3SPY2018-04-010.009152
4SPY2018-05-010.024018
595NFLX2022-08-01-0.005976
596NFLX2022-09-010.051776
597NFLX2022-10-010.214887
598NFLX2022-11-010.045705
599NFLX2022-12-01-0.035479

警告

如果数据的量级非常小(例如 <1e5<1e-5),scipy.optimize.minimize 可能无法成功终止。在这种情况下,请重新缩放数据,然后生成 GARCH 或 ARCH 模型。

StatsForecast.plot(returns)

从这张图可以看出,回报率似乎适合 GARCH 框架,因为大的冲击往往会伴随着其他的大的冲击。这并不意味着每次大的冲击后我们都应该期待另一次;仅仅意味着大方差的概率大于小方差的概率。

训练模型

我们首先需要从 statsforecast.models 中导入GARCHARCH模型,然后通过实例化一个新的 StatsForecast 对象来拟合它们。请注意,我们将使用不同的 ppqq 值。在下一节中,我们将使用交叉验证来确定哪个模型产生最准确的结果。我们还将导入Naive模型,因为我们将它用作基准。

from statsforecast.models import (
    GARCH, 
    ARCH, 
    Naive
)

models = [ARCH(1), 
          ARCH(2), 
          GARCH(1,1),
          GARCH(1,2),
          GARCH(2,2),
          GARCH(2,1),
          Naive()
]

要实例化一个新的 StatsForecast 对象,我们需要以下参数

  • df:包含训练数据的数据帧。
  • models:上一步中定义的模型列表。
  • freq:一个字符串,指示数据频率。这里我们将使用 **MS**,对应于月初。您可以在此处查看 panda 可用频率的列表。
  • n_jobs:一个整数,指示并行处理中使用的作业数。使用 -1 选择所有核心。
sf = StatsForecast(
    models = models, 
    freq = 'MS',
    n_jobs = -1
)

执行时间序列交叉验证

时间序列交叉验证是一种评估模型在过去表现的方法。它通过在历史数据上定义一个滑动窗口并预测其后续时期来工作。这里我们将使用 StatsForercast 的 cross-validation 方法来确定标普500指数和所选公司最准确的模型。

此方法接受以下参数

  • df:包含训练数据的数据帧。
  • h(整数):表示未来 h 步将要预测的时长。
  • step_size(整数):每个窗口之间的步长,表示您希望多久运行一次预测过程。
  • n_windows(整数):用于交叉验证的窗口数量,表示您希望评估的过去预测过程的数量。

对于本例,我们将使用 4 个窗口,每个窗口为 3 个月,即一年中的所有季度。

cv_df = sf.cross_validation(
    df = returns,
    h = 3,
    step_size = 3,
    n_windows = 4
  )

cv_df 对象是一个包含以下列的数据帧

  • unique_id:序列标识符。
  • ds:日期戳或时间索引
  • cutoffn_windows 的最后一个日期戳或时间索引。
  • y:真实值
  • "model":包含模型名称和拟合值的列。
cv_df.rename(columns = {'y' : 'actual'}, inplace = True)
cv_df.head()
unique_idds截止日期实际值ARCH(1)ARCH(2)GARCH(1,1)GARCH(1,2)GARCH(2,2)GARCH(2,1)Naive
0AAPL2022-01-012021-12-01-0.0158370.1424210.1440160.1429540.1416820.1416820.1440150.073061
1AAPL2022-02-012021-12-01-0.056856-0.056893-0.057158-0.056388-0.058786-0.058785-0.0571580.073061
2AAPL2022-03-012021-12-010.057156-0.045901-0.046479-0.047513-0.045711-0.045711-0.0464780.073061
3AAPL2022-04-012022-03-01-0.1021780.1386500.1402220.2281380.1361180.1361320.1402110.057156
4AAPL2022-05-012022-03-01-0.057505-0.056007-0.056268-0.087833-0.057078-0.057085-0.0562650.057156
StatsForecast.plot(returns, cv_df.drop(['cutoff', 'actual'], axis=1))

您可以在此处找到关于交叉验证的教程。

评估结果

为了计算预测的准确性,我们将使用平均绝对误差 (mae),它是绝对误差的总和除以预测数量。

from utilsforecast.losses import mae

需要为每个窗口计算 MAE,然后对所有窗口的结果取平均。为此,我们将创建以下函数。

models = cv_df.columns.drop(['unique_id', 'ds', 'cutoff', 'actual'])
mae_cv = mae(cv_df, models=models, target_col='actual').set_index('unique_id')
mae_cv
ARCH(1)ARCH(2)GARCH(1,1)GARCH(1,2)GARCH(2,2)GARCH(2,1)Naive
unique_id
AAPL0.0717730.0689270.0801820.0753210.0691870.0688170.110426
AMZN0.1273900.1136130.1188590.1199300.1099100.1099100.115189
GOOG0.0938490.0937530.1096620.1015830.0946480.1033890.083233
META0.1983340.1988930.1996150.1997110.1997120.1988920.185346
MSFT0.0823730.0750550.0722410.0727650.0730060.0820660.086951
NFLX0.1593860.1595280.1996230.2324770.2300750.2307700.167421
NKE0.1083370.0989180.1033660.1102780.1071790.1027080.160404
NVDA0.1894610.2078710.1989990.1961700.2119320.2119400.215289
SPY0.0585110.0585830.0587010.0624920.0570530.0681920.089012
TSLA0.1920030.1926180.1902250.1923540.1916200.1914230.218857
mae_cv.idxmin(axis=1)
unique_id
AAPL    GARCH(2,1)
AMZN    GARCH(2,2)
GOOG         Naive
META         Naive
MSFT    GARCH(1,1)
NFLX       ARCH(1)
NKE        ARCH(2)
NVDA       ARCH(1)
SPY     GARCH(2,2)
TSLA    GARCH(1,1)
dtype: object

因此,描述苹果公司股票对数回报率最准确的模型是 GARCH(2, 1),对于亚马逊公司的股票是 GARCH(2,2),依此类推。

预测波动性

我们现在可以生成下一季度的预测。为此,我们将使用 forecast 方法,该方法需要以下参数

  • h:(整数)预测范围。
  • level:(浮点数列表)预测区间的置信水平
  • fitted:(布尔值 = False)返回样本内预测。
levels = [80, 95] # confidence levels for the prediction intervals 

forecasts = sf.forecast(df=returns, h=3, level=levels)
forecasts.head()
unique_iddsARCH(1)ARCH(1)-下限-95ARCH(1)-下限-80ARCH(1)-上限-80ARCH(1)-上限-95ARCH(2)ARCH(2)-下限-95ARCH(2)-下限-80GARCH(2,1)GARCH(2,1)-下限-95GARCH(2,1)-下限-80GARCH(2,1)-上限-80GARCH(2,1)-上限-95NaiveNaive-下限-80Naive-下限-95Naive-上限-80Naive-上限-95
0AAPL2023-01-010.1504570.1336410.1394620.1614530.1672730.1501580.1334090.1392060.1476020.1314180.1370200.1581840.163786-0.128762-0.284462-0.3668850.0269390.109362
1AAPL2023-02-01-0.056943-0.073924-0.068046-0.045839-0.039961-0.057207-0.074346-0.068414-0.059512-0.078060-0.071640-0.047384-0.040964-0.128762-0.348956-0.4655200.0914330.207997
2AAPL2023-03-01-0.048391-0.064843-0.059148-0.037633-0.031939-0.049282-0.066345-0.060439-0.054539-0.075438-0.068204-0.040875-0.033641-0.128762-0.398443-0.5412040.1409200.283681
3AMZN2023-01-010.1521470.1349520.1409040.1633910.1693430.1486580.1322420.1379240.1485990.1321960.1378730.1593240.165001-0.139141-0.315716-0.4091900.0374350.130909
4AMZN2023-02-01-0.057301-0.074497-0.068545-0.046058-0.040106-0.061187-0.080794-0.074007-0.069303-0.094457-0.085750-0.052856-0.044150-0.139141-0.388856-0.5210480.1105750.242767

根据上一节的结果,我们可以为标普500指数和所选公司选择最佳模型。部分图表如下所示。请注意,我们在 plot 方法中使用了一些附加参数

  • level:(整数列表)预测区间的置信水平(此参数已定义)。
  • unique_ids:(字符串、整数或类别列表)要绘制的 ID。
  • models:(字符串列表)。要绘制的模型。在此示例中,是交叉验证选择的模型。
StatsForecast.plot(returns, forecasts, max_insample_length=20)

参考文献