Оригинал: Stock Prices Prediction Using Machine Learning and Deep Learning Techniques (with Python codes)
Введение
Предсказать, как будет развиваться фондовый рынок, - одна из самых сложных задач. В прогнозировании задействовано очень много факторов - физические факторы против физиологического, рационального и иррационального поведения и т. д. Все эти аспекты в совокупности делают цены на акции нестабильными, и их очень трудно предсказать с высокой степенью точности.
Можем ли мы использовать машинное обучение, чтобы изменить правила игры в этой области? Используя такие данные, как последние новости о компании, ее квартальные результаты по доходам и т. д., методы машинного обучения могут выявить закономерности и идеи, которых мы раньше не видели, и их можно использовать для получения точных прогнозов.
В этой статье мы будем работать с историческими данными о курсах акций публичной компании. Мы реализуем сочетание алгоритмов машинного обучения для прогнозирования будущей цены акций этой компании, начиная с простых алгоритмов, таких как усреднение и линейная регрессия, а затем перейдем к продвинутым методам, таким как Auto ARIMA и LSTM.
Основная идея этой статьи - продемонстрировать, как реализуются эти алгоритмы, поэтому я кратко опишу технику и предоставлю соответствующие ссылки, чтобы освежать в памяти концепции по мере необходимости.
Постановка проблемы
Мы скоро погрузимся в часть этой статьи, посвященную реализации, но сначала важно определить, что мы хотим сделать. В целом анализ фондового рынка делится на две части - фундаментальный анализ и технический анализ.
Фундаментальный анализ включает анализ будущей прибыльности компании на основе ее текущей деловой репутации и финансовых показателей.
Технический анализ, с другой стороны, включает чтение графиков и использование статистических данных для определения тенденций на фондовом рынке.
Как вы уже догадались, наше внимание будет сосредоточено на техническом анализе. Мы будем использовать набор данных от Quandl (вы можете найти исторические данные по различным акциям здесь), и для этого конкретного проекта я использовал данные для «Tata Global Beverages». Пора начинать!
Примечание. Вот набор данных, который я использовал в коде: скачать.
Сначала мы загрузим набор данных и определим целевую переменную для нашей проблемы:
#import packages import pandas as pd import numpy as np #to plot within notebook import matplotlib.pyplot as plt %matplotlib inline #setting figure size from matplotlib.pylab import rcParams rcParams['figure.figsize'] = 20,10 #for normalizing data from sklearn.preprocessing import MinMaxScaler scaler = MinMaxScaler(feature_range=(0, 1)) #read the file df = pd.read_csv('NSE-TATAGLOBAL(1).csv') #print the head df.head()
В наборе данных есть несколько переменных - date, open, high, low, last, close, total_trade_quantity и turnover.
Столбцы «Open» и «Close» представляют собой начальную и конечную цену, по которой акция торгуется в определенный день.
High, Low и Last представляют максимальную, минимальную и последнюю цену акции за день.
Общее количество сделок - это количество акций, купленных или проданных в день, а Turnover (Lacs) - это оборот конкретной компании на заданную дату.
Еще одна важная вещь, на которую следует обратить внимание, это то, что рынок закрыт по выходным и праздничным дням. Обратите внимание на приведенную выше таблицу, некоторые значения дат отсутствуют - 10.02.2018, 10.06.2018, 10.07.2018. Из этих дат 2-е - национальный праздник, а 6-е и 7-е - выходные.
Расчет прибыли или убытка обычно определяется ценой закрытия акции в течение дня, поэтому мы будем рассматривать цену закрытия в качестве целевой переменной. Давайте изобразим целевую переменную, чтобы понять, как она отражается в наших данных:
#setting index as date df['Date'] = pd.to_datetime(df.Date,format='%Y-%m-%d') df.index = df['Date'] #plot plt.figure(figsize=(16,8)) plt.plot(df['Close'], label='Close Price history')
В следующих разделах мы исследуем эти переменные и будем использовать различные методы для прогнозирования дневной цены закрытия акций.
Скользящая средняя
Вступление
«Среднее» - одна из самых распространенных вещей, которые мы используем в повседневной жизни. Например, вычисление средних оценок для определения общей успеваемости или определение средней температуры за последние несколько дней, чтобы получить представление о сегодняшней температуре - все это рутинные задачи, которые мы выполняем на регулярной основе. Так что это хорошая отправная точка для использования в нашем наборе данных для составления прогнозов.
Прогнозируемая цена закрытия для каждого дня будет средним из набора ранее наблюдаемых значений. Вместо использования простого среднего мы будем использовать метод скользящего среднего, который использует последний набор значений для каждого прогноза. Другими словами, для каждого последующего шага предсказанные значения учитываются при удалении самого старого наблюдаемого значения из набора. Вот простой рисунок, который поможет вам понять это с большей ясностью.
Мы реализуем этот метод в нашем наборе данных. Первый шаг - создать dataframe, который содержит только столбцы даты и цены закрытия, а затем разделить его на наборы для обучения и проверки, чтобы проверить наши прогнозы.
Реализация
# importing libraries
import pandas as pd
import numpy as np
# reading the data
df = pd.read_csv('NSE-TATAGLOBAL11.csv')
# looking at the first five rows of the data
print(df.head())
print('\n Shape of the data:')
print(df.shape)
# setting the index as date
df['Date'] = pd.to_datetime(df.Date,format='%Y-%m-%d')
df.index = df['Date']
#creating dataframe with date and the target variable
data = df.sort_index(ascending=True, axis=0)
new_data = pd.DataFrame(index=range(0,len(df)),columns=['Date', 'Close'])
for i in range(0,len(data)):
new_data['Date'][i] = data['Date'][i]
new_data['Close'][i] = data['Close'][i]
# NOTE: While splitting the data into train and validation set, we cannot use
# random splitting since that will destroy the time component. So here we have
# set the last year’s data into validation and the 4 years’ data before that into
# train set.
# splitting into train and validation
train = new_data[:987]
valid = new_data[987:]
# shapes of training set
print('\n Shape of training set:')
print(train.shape)
# shapes of validation set
print('\n Shape of validation set:')
print(valid.shape)
# In the next step, we will create predictions for the validation set and check
# the RMSE using the actual values.
# making predictions
preds = []
for i in range(0,valid.shape[0]):
a = train['Close'][len(train)-248+i:].sum() + sum(preds)
b = a/248
preds.append(b)
# checking the results (RMSE value)
rms=np.sqrt(np.mean(np.power((np.array(valid['Close'])-preds),2)))
print('\n RMSE value on validation set:')
print(rms)
Простая проверка RMSE не помогает нам понять, как работает модель. Давайте визуализируем ее, чтобы получить более интуитивное понимание. Итак, вот график прогнозируемых значений вместе с фактическими значениями:
#plot valid['Predictions'] = 0 valid['Predictions'] = preds plt.plot(train['Close']) plt.plot(valid[['Close', 'Predictions']])
Вывод
Значение RMSE близко к 105, но результаты не очень многообещающие (как вы можете понять из графика). Прогнозируемые значения находятся в том же диапазоне, что и наблюдаемые значения в обучающем наборе (сначала наблюдается тенденция к увеличению, а затем - к медленному снижению).
В следующем разделе мы рассмотрим два часто используемых метода машинного обучения - линейную регрессию и kNN - и посмотрим, как они работают с данными фондового рынка.
Линейная регрессия
Вступление
Самый простой алгоритм машинного обучения, который может быть реализован на этих данных, - это линейная регрессия. Модель линейной регрессии возвращает уравнение, определяющее взаимосвязь между независимыми переменными и зависимой переменной.
Уравнение линейной регрессии можно записать как:
Здесь x1, x2,… .xn представляют собой независимые переменные, а коэффициенты θ1, θ2,…. θn представляют собой веса. Вы можете обратиться к следующей статье, чтобы изучить линейную регрессию более подробно:
Для нашей постановки задачи у нас нет набора независимых переменных. Вместо этого у нас есть только даты. Давайте воспользуемся столбцом даты для извлечения таких признаков, как день, месяц, год, пн/пт и т. д., а затем применим модель линейной регрессии.
Реализация
Сначала мы отсортируем набор данных в порядке возрастания, а затем создадим отдельный набор данных, чтобы любой новый созданный признак не влиял на исходные данные.
#setting index as date values df['Date'] = pd.to_datetime(df.Date,format='%Y-%m-%d') df.index = df['Date'] #sorting data = df.sort_index(ascending=True, axis=0) #creating a separate dataset new_data = pd.DataFrame(index=range(0,len(df)),columns=['Date', 'Close']) for i in range(0,len(data)): new_data['Date'][i] = data['Date'][i] new_data['Close'][i] = data['Close'][i]
#create features from fastai.structured import add_datepart add_datepart(new_data, 'Date') new_data.drop('Elapsed', axis=1, inplace=True) #elapsed will be the time stamp
Этот код создает такие признаки, как:
‘Year’, ‘Month’, ‘Week’, ‘Day’, ‘Dayofweek’, ‘Dayofyear’, ‘Is_month_end’, ‘Is_month_start’, ‘Is_quarter_end’, ‘Is_quarter_start’, ‘Is_year_end’, and ‘Is_year_start’.
Примечание: я использовал add_datepart из библиотеки fastai. Если она у вас не установлена, вы можете просто использовать команду pip install fastai. В противном случае вы можете создать этот признак, используя простые циклы for в Python. Я привел пример ниже.
Помимо этого, мы можем добавить наш собственный набор признаков, которые, по нашему мнению, будут иметь отношение к прогнозам. Например, моя гипотеза состоит в том, что первый и последний дни недели потенциально могут повлиять на цену закрытия акций гораздо больше, чем другие дни. Итак, я создал функцию, которая определяет, является ли данный день понедельником/пятницей или вторником/средой/четвергом. Это можно сделать с помощью следующих строк кода:
new_data['mon_fri'] = 0 for i in range(0,len(new_data)): if (new_data['Dayofweek'][i] == 0 or new_data['Dayofweek'][i] == 4): new_data['mon_fri'][i] = 1 else: new_data['mon_fri'][i] = 0
Если день недели равен 0 или 4, значение столбца будет равно 1, иначе 0. Точно так же вы можете создать несколько других объектов. Если у вас есть идеи по признакам, которые могут быть полезны при прогнозировании курса акций, поделитесь ими в разделе комментариев.
Теперь мы разделим данные на наборы для обучения и проверки, чтобы проверить эффективность модели.
#split into train and validation train = new_data[:987] valid = new_data[987:] x_train = train.drop('Close', axis=1) y_train = train['Close'] x_valid = valid.drop('Close', axis=1) y_valid = valid['Close'] #implement linear regression from sklearn.linear_model import LinearRegression model = LinearRegression() model.fit(x_train,y_train)
Результаты
#make predictions and find the rmse preds = model.predict(x_valid) rms=np.sqrt(np.mean(np.power((np.array(y_valid)-np.array(preds)),2))) rms
121.16291596523156
Значение RMSE выше, чем у предыдущего метода, что ясно показывает, что линейная регрессия работает плохо. Давайте посмотрим на график и поймем, почему линейная регрессия не принесла успеха:
#plot valid['Predictions'] = 0 valid['Predictions'] = preds valid.index = new_data[987:].index train.index = new_data[:987].index plt.plot(train['Close']) plt.plot(valid[['Close', 'Predictions']])
Вывод
Линейная регрессия - это простой метод, который довольно легко интерпретировать, но у него есть несколько очевидных недостатков. Одна из проблем при использовании алгоритмов регрессии заключается в том, что модель не соответствует столбцу даты и месяца. Вместо того, чтобы принимать во внимание предыдущие значения с точки зрения прогноза, модель будет рассматривать значение с той же даты месяц назад или с той же даты/месяца год назад.
Как видно из графика выше, в январе 2016 г. и январе 2017 г. наблюдалось падение стоимости акций. Модель предсказала то же самое на январь 2018 года. Метод линейной регрессии может хорошо работать для таких проблем, как продажи в большом магазине, где независимые признаки полезны для определения целевой переменной.
k-Nearest Neighbours
Вступление
Еще один интересный алгоритм машинного обучения, который можно использовать здесь, это kNN (k ближайших соседей). На основе независимых переменных kNN находит сходство между новыми и старыми точками данных. Позвольте мне объяснить это на простом примере.
Возьмем рост и возраст для 11 человек. На основе заданных характеристик («Возраст» и «Рост») таблица может быть представлена в графическом формате, как показано ниже:
Чтобы определить вес для ID # 11, kNN учитывает вес ближайших соседей этого ID. Предполагается, что вес идентификатора № 11 будет средним весов его соседей. Если мы сейчас рассмотрим трех соседей (k = 3), вес для ID # 11 будет = (77 + 72 + 60) / 3 = 69,66 кг.
Реализация
#importing libraries from sklearn import neighbors from sklearn.model_selection import GridSearchCV from sklearn.preprocessing import MinMaxScaler scaler = MinMaxScaler(feature_range=(0, 1))
Используем те же обучающие и тестовые наборы из предыдущего раздела:
#scaling data x_train_scaled = scaler.fit_transform(x_train) x_train = pd.DataFrame(x_train_scaled) x_valid_scaled = scaler.fit_transform(x_valid) x_valid = pd.DataFrame(x_valid_scaled) #using gridsearch to find the best parameter params = {'n_neighbors':[2,3,4,5,6,7,8,9]} knn = neighbors.KNeighborsRegressor() model = GridSearchCV(knn, params, cv=5) #fit the model and make predictions model.fit(x_train,y_train) preds = model.predict(x_valid)
Результаты
#rmse rms=np.sqrt(np.mean(np.power((np.array(y_valid)-np.array(preds)),2))) rms
115.17086550026721
В значениях RMSE нет большой разницы, но график для прогнозируемых и фактических значений должен обеспечить более четкое понимание.
#plot valid['Predictions'] = 0 valid['Predictions'] = preds plt.plot(valid[['Close', 'Predictions']]) plt.plot(train['Close'])
Вывод
Значение RMSE почти аналогично модели линейной регрессии, и график показывает ту же картину. Как и линейная регрессия, kNN также предсказала падение в январе 2018 года, поскольку это было закономерностью в течение последних лет. Мы можем с уверенностью сказать, что алгоритмы регрессии не очень хорошо работают с этим набором данных.
Давайте продолжим и рассмотрим некоторые методы прогнозирования временных рядов, чтобы узнать, как они работают, когда сталкиваются с этой проблемой прогнозирования цен на акции.
Auto ARIMA
Вступление
ARIMA - очень популярный статистический метод прогнозирования временных рядов. Модели ARIMA учитывают прошлые значения для прогнозирования будущих значений. В ARIMA есть три важных параметра:
- p (прошлые значения, используемые для прогнозирования следующего значения);
- q (прошлые ошибки прогноза, используемые для прогнозирования будущих значений);
- d (порядок разности).
Настройка параметров для ARIMA занимает много времени. Поэтому мы будем использовать auto ARIMA, который автоматически выбирает лучшую комбинацию (p, q, d), обеспечивающую наименьшую ошибку.
Реализация
from pyramid.arima import auto_arima data = df.sort_index(ascending=True, axis=0) train = data[:987] valid = data[987:] training = train['Close'] validation = valid['Close'] model = auto_arima(training, start_p=1, start_q=1,max_p=3, max_q=3, m=12,start_P=0, seasonal=True,d=1, D=1, trace=True,error_action='ignore',suppress_warnings=True) model.fit(training) forecast = model.predict(n_periods=248) forecast = pd.DataFrame(forecast,index = valid.index,columns=['Prediction'])
Результаты
rms=np.sqrt(np.mean(np.power((np.array(valid['Close'])-np.array(forecast['Prediction'])),2))) rms
44.954584993246954
#plot plt.plot(train['Close']) plt.plot(valid['Close']) plt.plot(forecast['Prediction'])
Вывод
Как мы видели ранее, модель auto ARIMA использует прошлые данные для понимания закономерностей во временных рядах. Используя эти значения, модель зафиксировала растущую тенденцию в ряде. Хотя прогнозы с использованием этого метода намного лучше, чем прогнозы ранее реализованных моделей машинного обучения, эти прогнозы все еще не слишком близки к реальным значениям.
Как видно из графика, модель уловила тренд в ряде, но не фокусируется на сезонной части. В следующем разделе мы реализуем модель временных рядов, которая учитывает как тенденцию, так и сезонность ряда.
Prophet
Вступление
Существует ряд методов анализа временных рядов, которые могут быть реализованы в наборе данных прогнозирования цен на акции, но большинство из этих методов требуют предварительной обработки большого количества данных перед подгонкой модели. Prophet, разработанная и впервые использованная Facebook, представляет собой библиотеку прогнозирования временных рядов, которая не требует предварительной обработки данных и чрезвычайно проста в реализации. Входными данными для Prophet является dataframe с двумя столбцами: date и target (ds и y).
Prophet пытается уловить сезонность в прошлых данных и хорошо работает, когда набор данных большой. Вот интересная статья, которая объясняет Prophet простым и интуитивно понятным образом:
Реализация
#importing prophet from fbprophet import Prophet #creating dataframe new_data = pd.DataFrame(index=range(0,len(df)),columns=['Date', 'Close']) for i in range(0,len(data)): new_data['Date'][i] = data['Date'][i] new_data['Close'][i] = data['Close'][i] new_data['Date'] = pd.to_datetime(new_data.Date,format='%Y-%m-%d') new_data.index = new_data['Date'] #preparing data new_data.rename(columns={'Close': 'y', 'Date': 'ds'}, inplace=True) #train and validation train = new_data[:987] valid = new_data[987:] #fit the model model = Prophet() model.fit(train) #predictions close_prices = model.make_future_dataframe(periods=len(valid)) forecast = model.predict(close_prices)
Результаты
#rmse forecast_valid = forecast['yhat'][987:] rms=np.sqrt(np.mean(np.power((np.array(valid['y'])-np.array(forecast_valid)),2))) rms
57.494461930575149
#plot valid['Predictions'] = 0 valid['Predictions'] = forecast_valid.values plt.plot(train['y']) plt.plot(valid[['y', 'Predictions']])
Вывод
Prophet (как и большинство методов прогнозирования временных рядов) пытается уловить тренд и сезонность на основе прошлых данных. Эта модель обычно хорошо работает с наборами данных временных рядов, но в данном случае не соответствует своей репутации.
Как выясняется, цены на акции не имеют определенного тренда или сезонности. Они сильно зависят от того, что сейчас происходит на рынке. Следовательно, такие методы прогнозирования, как ARIMA, SARIMA и Prophet, не дадут хороших результатов для этой конкретной проблемы.
Давайте продолжим и попробуем другой продвинутый метод - Long Short Term Memory (LSTM).
Long Short Term Memory (LSTM)
LSTM широко используются для задач прогнозирования последовательностей и оказались чрезвычайно эффективными. Причина, по которой они работают так хорошо, заключается в том, что LSTM может хранить прошлую информацию, которая важна, и забывать информацию, которая не является важной. LSTM имеет три вентиля:
- Входной вентиль (input gate): входной вентиль добавляет информацию о состоянии ячейки.
- Вентиль забыть (forget gate): они удаляют информацию, которая больше не требуется модели.
- Выходной вентиль (output gate): Выходной вентиль в LSTM выбирает информацию, которая будет отображаться как выход.
Для более подробного понимания LSTM и его архитектуры вы можете прочитать следующую статью:
А пока давайте реализуем LSTM как черный ящик и проверим его эффективность на наших конкретных данных.
Реализация
#importing required libraries from sklearn.preprocessing import MinMaxScaler from keras.models import Sequential from keras.layers import Dense, Dropout, LSTM #creating dataframe data = df.sort_index(ascending=True, axis=0) new_data = pd.DataFrame(index=range(0,len(df)),columns=['Date', 'Close']) for i in range(0,len(data)): new_data['Date'][i] = data['Date'][i] new_data['Close'][i] = data['Close'][i] #setting index new_data.index = new_data.Date new_data.drop('Date', axis=1, inplace=True) #creating train and test sets dataset = new_data.values train = dataset[0:987,:] valid = dataset[987:,:] #converting dataset into x_train and y_train scaler = MinMaxScaler(feature_range=(0, 1)) scaled_data = scaler.fit_transform(dataset) x_train, y_train = [], [] for i in range(60,len(train)): x_train.append(scaled_data[i-60:i,0]) y_train.append(scaled_data[i,0]) x_train, y_train = np.array(x_train), np.array(y_train) x_train = np.reshape(x_train, (x_train.shape[0],x_train.shape[1],1)) # create and fit the LSTM network model = Sequential() model.add(LSTM(units=50, return_sequences=True, input_shape=(x_train.shape[1],1))) model.add(LSTM(units=50)) model.add(Dense(1)) model.compile(loss='mean_squared_error', optimizer='adam') model.fit(x_train, y_train, epochs=1, batch_size=1, verbose=2) #predicting 246 values, using past 60 from the train data inputs = new_data[len(new_data) - len(valid) - 60:].values inputs = inputs.reshape(-1,1) inputs = scaler.transform(inputs) X_test = [] for i in range(60,inputs.shape[0]): X_test.append(inputs[i-60:i,0]) X_test = np.array(X_test) X_test = np.reshape(X_test, (X_test.shape[0],X_test.shape[1],1)) closing_price = model.predict(X_test) closing_price = scaler.inverse_transform(closing_price)
Результаты
rms=np.sqrt(np.mean(np.power((valid-closing_price),2))) rms
11.772259608962642
#for plotting train = new_data[:987] valid = new_data[987:] valid['Predictions'] = closing_price plt.plot(train['Close']) plt.plot(valid[['Close','Predictions']])
Вывод
Вау! LSTM легко превзошел любой алгоритм, который мы видели до сих пор. Модель LSTM можно настроить на различные параметры, такие как изменение количества слоев LSTM, добавление значения выпадения или увеличение количества эпох. Но достаточно ли прогнозов LSTM, чтобы определить, будет ли цена акций увеличиваться или уменьшаться? Конечно нет!
Как я упоминал в начале статьи, на цену акций влияют новости о компании и другие факторы, такие как демонетизация или слияние/разделение компаний. Также существуют определенные нематериальные факторы, которые зачастую невозможно предсказать заранее.
Заключение
Как я понял во время написания этих статей, прогнозирование временных рядов - очень интригующая область для работы. В сообществе бытует мнение, что это сложная область, и хотя в этом есть доля правды, это не так уж и сложно, если вы освоите основные методы.
Мне интересно узнать, как LSTM работает с другим типом задач временных рядов, и рекомендую вам попробовать это самостоятельно. Если у вас есть какие-либо вопросы, не стесняйтесь связаться со мной в разделе комментариев ниже.
Добрый день! Не подскажите, как посмотреть возможные цены на будущее?
ОтветитьУдалить