Введение

На этом занятии мы углубимся в захватывающую область анализа временных рядов и его важность для принятия решений на основе данных. Наше внимание будет сосредоточено на области анализа цен на акции, где временные ряды имеют решающее значение для понимания рыночных тенденций и прогнозирования будущих движений акций. Мы также обсудим эффективные методы визуализации и прогнозирования данных.

Обзор анализа временных рядов

Временной ряд относится к набору точек данных, расположенных в хронологическом порядке. Анализ временных рядов включает использование статистических методов для моделирования и понимания основных закономерностей и структур, присутствующих в данных. Данные временных рядов широко используются в различных областях, включая финансы, экономику, прогнозирование погоды и здравоохранение, где их можно использовать для мониторинга показателей жизнедеятельности.

Основными компонентами временного ряда являются:

  • Тенденция: долгосрочное движение данных.
  • Сезонность: регулярные и предсказуемые колебания, которые повторяются в течение определенного периода времени.
  • Цикличность: колебания, которые не следуют фиксированному шаблону или периоду, но зависят от внешних факторов.
  • Нерегулярные/шумовые: случайные колебания, которые непредсказуемы и не связаны с какой-либо закономерностью.

Важность визуализации и прогнозирования временных рядов

Визуализация и прогнозирование являются жизненно важными аспектами анализа временных рядов. Они дают ценную информацию и позволяют нам принимать решения на основе данных.

Визуализация временных рядов помогает нам:

  • Понимание основных закономерностей, тенденций и сезонности данных.
  • Определите аномалии или выбросы в данных.
  • Эффективно сообщайте информацию заинтересованным сторонам.

Прогнозирование временных рядов позволяет нам:

  • Прогнозировать будущие значения на основе исторических данных.
  • Принимайте обоснованные решения, такие как инвестиционные стратегии или распределение ресурсов.
  • Оцените эффективность вмешательств или изменений в политике.

Вариант использования: анализ цен на акции

Анализ цен на акции — прекрасный пример того, как анализ временных рядов можно применять в реальном мире. В финансах цены на акции представляют собой ряд точек данных с течением времени, и понимание тенденций, сезонности и неравномерности может дать ценную информацию для инвесторов.

Одной из популярных акций, которая постоянно привлекала аудиторию и вызывала интерес, является Apple Inc. (AAPL). Движение цен на акции Apple часто привлекает значительное внимание как инвесторов, так и широкой публики. История цен на акции компании может дать интересные сведения о том, как рынок реагировал на различные события, такие как запуск продуктов, отчеты о прибылях и убытках и глобальные экономические тенденции.

Описание данных

Данные об акциях Apple (AAPL), доступные на Yahoo Finance, предлагают исторические и актуальные цены на акции Apple Inc. Ссылка: https://finance.yahoo.com/quote/AAPL/history/

Вот описание каждого столбца в наборе данных:

  • Дата: дата записи данных фондового рынка (в формате ГГГГ-ММ-ДД).
  • Открытие: цена открытия акций Apple в конкретный день.
  • Высокая: самая высокая цена, по которой акции Apple торговались в течение дня.
  • Низкая: самая низкая цена, по которой акции Apple торговались в течение дня.
  • Закрытие: цена закрытия акций Apple в конкретный день.
  • Adj Close: скорректированная цена закрытия, которая учитывает дивиденды, дробление акций и новые предложения акций, обеспечивая более точное отражение стоимости акций с течением времени.
  • Объем: количество акций Apple, проданных в течение дня.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
df = pd.read_csv('https://raw.githubusercontent.com/chakraborty-arnab/DataSphere/main/AAPL.csv')

Предварительная обработка данных

Преобразование даты в объект DateTime и построение всей истории цен на акции с течением времени

df['Date'] = pd.to_datetime(df.Date)
plt.figure(figsize=(20, 6))
plt.plot(df["Adj Close"])
plt.xlabel("Date")
plt.ylabel("Adj Closing Price ($)")
plt.title("Apple Stock Prices Over Time")
plt.show()

Ключевые моменты истории

Ежемесячная годовая волатильность – это финансовый показатель, используемый для измерения изменчивости или разброса доходности актива за период в один месяц, увеличенный до полного года.

  • Он обычно используется для количественной оценки уровня риска, связанного с инвестициями, при этом более высокая волатильность указывает на большую неопределенность и потенциальные колебания цен.
  • Этот показатель рассчитывается путем взятия стандартного отклонения ежемесячной доходности актива, а затем умножения его на квадратный корень из 12 (количество месяцев в году), чтобы перевести цифру в год.

Здесь мы будем использовать метрику Ежемесячная годовая волатильность, чтобы увидеть влияние ключевых экономических событий на цену акций.

# Set interceptate column as the index
newdf=df.set_index('Date')

#To model returns we will use daily % change
daily = newdf['Adj Close'].pct_change()
daily.dropna(inplace = True)

#Resample returns per month and take STD as measure of volatility
monthly=daily.resample("M").std()*np.sqrt(12)

import matplotlib.patches as mpatches

#Visulize major market events show up in the volatility
plt.figure(figsize=(20, 6))
plt.plot(monthly)
plt.axvspan('1987','1988',color='grey',alpha=.5)
plt.axvspan('2000','2002',color='purple',alpha=.5)
plt.axvspan('2008','2009',color='r',alpha=.5)
plt.axvspan('2020','2021',color='g',alpha=.5)
plt.title("Monthly Annualized volatility")
l1=mpatches.Patch(color='grey',alpha=.5, label="Black Monday")
l2=mpatches.Patch(color='purple',alpha=.5, label="Dot-com bubble & Sep 11 attack ")
l3=mpatches.Patch(color='red',alpha=.5, label="2007-2008 Financial Crisis")
l4=mpatches.Patch(color='green',alpha=.5, label="2020 Stock Market Crash")
plt.legend(handles=[l1,l2,l3,l4])

Ежемесячная повторная выборка

Ежемесячная выборка ежедневных цен на акции может быть полезной для прогнозирования и анализа в определенных контекстах. Некоторые причины использования ежемесячных данных включают в себя:

  • Уменьшенный шум
  • Меньшая вычислительная сложность
  • Легкая интерпретация
  • Снижено влияние микрособытий
  • Сглаживание сезонности
  • Согласование с отчетными периодами
# Resample the data to the monthly level
monthly_mean = newdf['Adj Close'].resample('M').mean()
monthly_data = monthly_mean.to_frame()
##Monthly Stock Price 
fig = plt.figure(figsize=(20,6))
plt.plot(monthly_data['Adj Close'],label='Monthly Averages Apple Stock')
plt.legend(prop={'size': 12})
plt.show()

Наблюдение:

  • Мы можем наблюдать общий восходящий тренд с 2007 по 2021 год.
  • Однако мы можем наблюдать, что акции вступили в фазу консолидации в 2022 году.
monthly_data['Year'] = monthly_data.index.year
monthly_data['Month'] = monthly_data.index.strftime('%B')
monthly_data['Quarter'] = monthly_data.index.quarter
fig, ax = plt.subplots(figsize=(20,6))
palette = sns.color_palette("mako_r", 4)
a = sns.barplot(x="Year", y="Adj Close",hue = 'Month',data=monthly_data)
a.set_title("Stock Prices Year & Month Wise",fontsize=15)
plt.legend(loc='upper left')
plt.show()

quarter = monthly_data.groupby(["Year", "Quarter"])["Adj Close"].mean().unstack()
plt.figure(figsize=(14, 8))
sns.heatmap(quarter, cmap="coolwarm", annot=True, fmt=".2f", linewidths=.5)
plt.title("Apple Stock Prices: Quarterly Averages")
plt.show()

Наблюдение:

  • Мы можем заметить, что квартал 3 и 4 в целом имеют более высокие цены по сравнению с кварталом 1 и 2.
  • Основная причина этого заключается в том, что у Apple есть дата выпуска продукта в это время, Уолл-стрит в восторге от будущих продуктов и периода праздников.
  • Мы можем наблюдать снижение цены акций в 2022 году из-за проблем с производством и корректировки цен для всех крупных технологических компаний. Мы удалим данные за 2022 год из прогноза, поскольку они обусловлены внешними факторами.

Разложение временных рядов

from statsmodels.tsa.seasonal import seasonal_decompose as sd

plt.figure(figsize=(30,12))
decomposed_series = sd(monthly_data['Adj Close'])
decomposed_series.plot()
plt.show()

Выводы: -

  • Тренд: в целом восходящий тренд.
  • Сезонность: кажется, что есть сезонность. Как и ожидалось, AAPL сплотилась во время курортного сезона. Поскольку период праздников имеет хорошие продажи для Apple на протяжении многих лет.
##Drilling Down and Observing Seasonality
fig = plt.figure(figsize=(9,5))
decomposed_series.seasonal['2007':'2008'].plot()

Проверка стационарности временных рядов

Для простоты автоматизации мы будем использовать расширенный тест Дики-Фуллера (ADF).

Нулевая гипотеза: временной ряд нестационарен.

Альтернативная гипотеза: временной ряд является стационарным

Временной ряд является стационарным, если у нас есть постоянное среднее значение, постоянная дисперсия и отсутствие тренда и сезонности.

from statsmodels.tsa.stattools import adfuller

def ad_fuller_func(X):
  result_ad_fuller = adfuller(X)
  print('ADF Statistic: %f' % result_ad_fuller[0])
  print('p-value: %f' %result_ad_fuller[1])
  print('Critical Values:')
  for key, value in result_ad_fuller[4].items():
   print('\t%s: %.3f' % (key, value))
 
  if result_ad_fuller[0] < result_ad_fuller[4]['5%']:
    print('Reject Null Hypothesis- Time Series is Stationary')
  else:
    print('Failed to Reject Null Hypothesis- Time Series is Non-Stationary')

ad_fuller_func(monthly_data['Adj Close'])
ADF Statistic: 2.422043
p-value: 0.999020
Critical Values:
 1%: -3.478
 5%: -2.882
 10%: -2.578
Failed to Reject Null Hypothesis- Time Series is Non-StationaryObservation

Временной ряд не является стационарным, как это наблюдалось ранее также с помощью декомпозиции (присутствуют тренд и сезонность).

Автокорреляционная функция (ACF)

  • ACF измеряет линейную зависимость между точками данных во временном ряду с разным временным лагом. Он вычисляет коэффициент корреляции между переменной и ее запаздывающей версией для различных временных задержек. Высокое значение автокорреляции указывает на то, что наблюдения тесно связаны с их предыдущими наблюдениями.
  • ACF используется для определения соответствующего порядка компонента скользящего среднего (MA) в модели временного ряда.
from statsmodels.graphics.tsaplots import plot_acf

fig,(ax1,ax2) = plt.subplots(2,figsize=(12,9))
acf = plot_acf(monthly_data['Adj Close'],lags=90,ax=ax1)
ax1.set_title('AutoCorrelation Long Term')
acf = plot_acf(monthly_data['Adj Close'],lags=30,ax=ax2)
ax2.set_title('AutoCorrelation Short Term')
ax1.set_ylabel('Correlation')
ax1.set_xlabel('Lags')
ax2.set_ylabel('Correlation')
ax2.set_xlabel('Lags')

Функция частичной автокорреляции (PACF)

  • PACF измеряет прямую линейную зависимость между точками данных во временном ряду с определенной временной задержкой после устранения влияния любых корреляций при более коротких задержках. Другими словами, он вычисляет корреляцию между переменной и ее запаздывающей версией, при этом контролируя влияние всех промежуточных точек данных.
  • PACF используется для определения соответствующего порядка авторегрессионного (AR) компонента в модели временных рядов.
from statsmodels.graphics.tsaplots import plot_pacf

fig,(ax1,ax2) = plt.subplots(2,figsize=(12,9))
pacf = plot_pacf(monthly_data['Adj Close'],lags=60,ax=ax1)
ax1.set_title('Partial AutoCorrelation Long Term')
pacf = plot_pacf(monthly_data['Adj Close'],lags=30,ax=ax2)
ax2.set_title('Partial AutoCorrelation Short Term')
ax1.set_ylabel('Correlation')
ax1.set_xlabel('Lags')
ax2.set_ylabel('Correlation')
ax2.set_xlabel('Lags')
plt.tight_layout(pad=1)

Преобразование, чтобы сделать серию стационарной

Мы будем использовать дифференциацию первого порядка, чтобы исключить тренд ряда.

##Differencing By 1
monthly_diff = monthly_data['Adj Close'] - monthly_data['Adj Close'].shift(1)
monthly_diff[1:].plot(c='grey')
monthly_diff[1:].rolling(20).mean().plot(label='Rolling Mean',c='orange')
monthly_diff[1:].rolling(20).std().plot(label='Rolling STD',c='yellow')
plt.legend(prop={'size': 12})

Наблюдение

Ряд выглядит стационарным, так как имеет постоянное среднее значение и дисперсию.

##Checking if Time Series is Stationary by Running ADF Test
ad_fuller_func(monthly_diff[1:])
fig,(ax1,ax2) = plt.subplots(2,figsize=(10,6))
acf = plot_acf(monthly_diff[1:],lags=30,ax=ax1)
pacf = plot_pacf(monthly_diff[1:],lags=30,ax=ax2)
ax1.set_title('Autocorrelation For Differenced(1)')
ax1.set_ylabel('Correlation')
ax1.set_xlabel('Lags')
ax2.set_title('Partial Autocorrelation For Differenced(1)')
ax2.set_ylabel('Correlation')
ax2.set_xlabel('Lags')
plt.tight_layout(pad=1)

Сезонный АРИМА

Сезонный ARIMA (авторегрессионное интегрированное скользящее среднее) — это метод прогнозирования временных рядов, который расширяет базовую модель ARIMA для учета сезонности. Сезонный ARIMA обозначается как ARIMA(p, d, q)(P, D, Q)s

modelling_series = monthly_data['Adj Close']
from sklearn.model_selection import train_test_split as split
train,test = split(modelling_series,train_size=0.6,shuffle=False)

Гиперпараметры

САРИМА (p,d,q) (P,D,Q)m

Есть три элемента тренда, которые требуют настройки.

p: Trend autoregression order.
d: Trend difference order.
q: Trend moving average order.

Есть четыре сезонных элемента, которые не являются частью ARIMA и должны быть настроены; они есть:

P: Seasonal autoregressive order.
D: Seasonal difference order.
Q: Seasonal moving average order.
m: The number of time steps for a single seasonal period.
import itertools
p = d = q = range(0, 3)
pdq = list(itertools.product(p, d, q))
seasonal_pdq = [(x[0], x[1], x[2], 12) for x in list(itertools.product(p, d, q))]

list_param = []
list_param_seasonal=[]
list_results_aic=[]

from statsmodels.tsa.statespace.sarimax import SARIMAX
for param in pdq:
    for param_seasonal in seasonal_pdq:
        try:
            model = SARIMAX(train,
                                            order=param,
                                            seasonal_order=param_seasonal,
                                            enforce_stationarity=False,
                                            enforce_invertibility=False)

            results = model.fit()

            print('ARIMA{}x{}12 - AIC:{}'.format(param, param_seasonal, results.aic))
            
            list_param.append(param)
            list_param_seasonal.append(param_seasonal)
            list_results_aic.append(results.aic)
        except:
            continue

Наблюдение

Самый низкий AIC, мы приходим к порядку сезонности (2,2,0)12, а несезонный компонент равен (1,1,1), как было получено ранее с помощью коррелограмм.

Сезонная арима используется, поскольку у нас присутствует компонент сезонности. В осенний период (июль-ноябрь) акции, кажется, растут на новостях о запуске продукта и выпуске продукта в этом цикле года.

Обратное тестирование данных обучения и тестирования

  • Поскольку мы не можем использовать перекрестную проверку в наших наборах данных на основе временных рядов, так как это может смешивать наборы данных во время разных сверток.
  • Это не относится к данным временных рядов, где временная размерность наблюдений означает, что мы не можем случайным образом разделить их на группы. Мы можем использовать метод обратного тестирования для временных рядов.
  • При тестировании на исторических данных мы можем создать несколько разбиений обучающих тестов, учитывая временной порядок наших данных во время разбиений. Например, если у меня есть набор данных с января по декабрь
## Using TimeSeriesSplit from sklearn library
time_series_splits = TimeSeriesSplit(n_splits=4)
X = modelling_series.values
plt.figure(1)
fig = plt.figure(figsize=(24, 10))

index = 1
for train_index, test_index in time_series_splits.split(X):
 train = X[train_index]
 test = X[test_index]
 print('Observations: %d' % (len(train) + len(test)))
 print('Training Observations: %d' % (len(train)))
 print('Testing Observations: %d' % (len(test)))
    
 plt.subplot(360 + index)
 plt.plot(train)
 plt.plot([None for i in train] + [x for x in test])
    # pyplot.title(''.format())
 index += 1
plt.show()

import statsmodels.api as sm
def backtest_model(train,test):
    model = sm.tsa.SARIMAX(train,order=(1,1,1),seasonal_order=(2,2,0,12))
    results=model.fit()

    forecasts_train = results.predict(start=0,end=len(train))
    forecasts_test = results.predict(start=len(train),end=len(train)+len(test))


    fig,(ax1,ax2) = plt.subplots(2,figsize=(14,6))

    train = pd.DataFrame(train)
    test = pd.DataFrame(test)

    forecasts_train = pd.DataFrame(forecasts_train)
    forecasts_test = pd.DataFrame(forecasts_test)

    forecasts_train.plot(label='Forecasts',ax=ax1,title='SARIMA Forecasting -Train Data')
    train.plot(label='Actual',ax=ax1)
    ax1.set_ylabel('Stock Price')
    ax1.set_xlabel('Time')

    forecasts_test.plot(label='Forecasts',ax=ax2,title='SARIMA Forecasting -Test Data')
    test.plot(label='Actual',ax=ax2)
    ax2.set_ylabel('Stock Price')
    ax2.set_xlabel('Time')

    ax1.legend()
    ax2.legend()
    plt.tight_layout(pad=2)

Используя ретроспективное тестирование, мы можем проверить нашу модель на нескольких сплит-тестах. Красный — это тренировочный набор, а синий — тестовый.

Давайте прогнозировать сейчас

Мы установим несезонный порядок = (1,1,1) и сезонный порядок как (2,2,0,12) и используем модель SARIMA для прогнозирования цены акций.

model = sm.tsa.SARIMAX(modelling_series,order=(1,1,1),seasonal_order=(2,2,0,12))
results=model.fit()
forecasts_train = results.predict(start='2007-01-31',end='2016-09-30')
forecasts_test = results.predict(start='2016-10-31',end='2019-12-31')

sd='2007-01-31'
ed='2016-09-30'
sd2='2016-10-31'
ed2='2019-12-31'

fig,(ax1,ax2) = plt.subplots(2,figsize=(18,10))

forecasts_train.plot(label='Forecasts',ax=ax1,title='SARIMA Forecasting -Train Data')
modelling_series.loc[sd:ed].plot(label='Actual',ax=ax1)
ax1.set_ylabel('Stock Price')

forecasts_test.plot(label='Forecasts',ax=ax2,title='SARIMA Forecasting -Test Data')
modelling_series.loc[sd2:ed2].plot(label='Actual',ax=ax2)
ax2.set_ylabel('Stock Price')

ax1.legend()
ax2.legend()
plt.tight_layout(pad=2)

Оценим прогнозы

from sklearn.metrics import mean_squared_error, r2_score, mean_absolute_error, median_absolute_error, mean_squared_log_error
##Function to Calculate Result Metrics
def result_metrics(test_series,forecast_series,model_name):
  print('Result Metrics for {}'.format(model_name))
  print('R2 Score : ',round(r2_score(test_series,forecast_series),3))
  print('Mean Squared Error : ',round(mean_squared_error(test_series,forecast_series),3))
  print('Mean Absolute Error : ',round(mean_absolute_error(test_series,forecast_series),3))

print(result_metrics(modelling_series[sd:ed],forecasts_train,'SARIMA-Train Data'))
print('----')
print(result_metrics(modelling_series[sd2:ed2],forecasts_test,'SARIMA-Test Data'))
Result Metrics for SARIMA-Train Data
R2 Score :  0.964
Mean Squared Error :  2.426
Mean Absolute Error :  1.149
None
----
Result Metrics for SARIMA-Test Data
R2 Score :  0.828
Mean Squared Error :  15.602
Mean Absolute Error :  3.068
NoneConclusion
print(results.summary())
SARIMAX Results                                      
===========================================================================================
Dep. Variable:                           Adj Close   No. Observations:                  156
Model:             SARIMAX(1, 1, 1)x(2, 2, [], 12)   Log Likelihood                -317.125
Date:                             Mon, 10 Apr 2023   AIC                            644.250
Time:                                     03:56:32   BIC                            658.626
Sample:                                 01-31-2007   HQIC                           650.092
                                      - 12-31-2019                                         
Covariance Type:                               opg                                         
==============================================================================
                 coef    std err          z      P>|z|      [0.025      0.975]
------------------------------------------------------------------------------
ar.L1          0.1173      0.307      0.383      0.702      -0.484       0.718
ma.L1          0.1468      0.313      0.469      0.639      -0.466       0.760
ar.S.L12      -1.2045      0.062    -19.322      0.000      -1.327      -1.082
ar.S.L24      -0.5728      0.079     -7.285      0.000      -0.727      -0.419
sigma2         6.3563      0.602     10.567      0.000       5.177       7.535
===================================================================================
Ljung-Box (L1) (Q):                   0.00   Jarque-Bera (JB):                34.68
Prob(Q):                              0.99   Prob(JB):                         0.00
Heteroskedasticity (H):              24.47   Skew:                            -0.23
Prob(H) (two-sided):                  0.00   Kurtosis:                         5.48
===================================================================================

Warnings:
[1] Covariance matrix calculated using the outer product of gradients (complex-step).

Заключение

Мы подогнали модель SARIMA, учитывающую тренд, сезонность. Модель, кажется, хорошо сидит. Добавляя внешние регрессоры, мы сможем еще больше улучшить модель.