Введение
На этом занятии мы углубимся в захватывающую область анализа временных рядов и его важность для принятия решений на основе данных. Наше внимание будет сосредоточено на области анализа цен на акции, где временные ряды имеют решающее значение для понимания рыночных тенденций и прогнозирования будущих движений акций. Мы также обсудим эффективные методы визуализации и прогнозирования данных.
Обзор анализа временных рядов
Временной ряд относится к набору точек данных, расположенных в хронологическом порядке. Анализ временных рядов включает использование статистических методов для моделирования и понимания основных закономерностей и структур, присутствующих в данных. Данные временных рядов широко используются в различных областях, включая финансы, экономику, прогнозирование погоды и здравоохранение, где их можно использовать для мониторинга показателей жизнедеятельности.
Основными компонентами временного ряда являются:
- Тенденция: долгосрочное движение данных.
- Сезонность: регулярные и предсказуемые колебания, которые повторяются в течение определенного периода времени.
- Цикличность: колебания, которые не следуют фиксированному шаблону или периоду, но зависят от внешних факторов.
- Нерегулярные/шумовые: случайные колебания, которые непредсказуемы и не связаны с какой-либо закономерностью.
Важность визуализации и прогнозирования временных рядов
Визуализация и прогнозирование являются жизненно важными аспектами анализа временных рядов. Они дают ценную информацию и позволяют нам принимать решения на основе данных.
Визуализация временных рядов помогает нам:
- Понимание основных закономерностей, тенденций и сезонности данных.
- Определите аномалии или выбросы в данных.
- Эффективно сообщайте информацию заинтересованным сторонам.
Прогнозирование временных рядов позволяет нам:
- Прогнозировать будущие значения на основе исторических данных.
- Принимайте обоснованные решения, такие как инвестиционные стратегии или распределение ресурсов.
- Оцените эффективность вмешательств или изменений в политике.
Вариант использования: анализ цен на акции
Анализ цен на акции — прекрасный пример того, как анализ временных рядов можно применять в реальном мире. В финансах цены на акции представляют собой ряд точек данных с течением времени, и понимание тенденций, сезонности и неравномерности может дать ценную информацию для инвесторов.
Одной из популярных акций, которая постоянно привлекала аудиторию и вызывала интерес, является 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, учитывающую тренд, сезонность. Модель, кажется, хорошо сидит. Добавляя внешние регрессоры, мы сможем еще больше улучшить модель.