Spaces:
Runtime error
Runtime error
Upload functions.py
Browse files- functions.py +458 -0
functions.py
ADDED
@@ -0,0 +1,458 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from asyncio.constants import LOG_THRESHOLD_FOR_CONNLOST_WRITES
|
2 |
+
import yfinance as yf
|
3 |
+
import pandas as pd
|
4 |
+
import numpy as np
|
5 |
+
import plotly.graph_objs as go
|
6 |
+
from stocks import *
|
7 |
+
from transformers import AutoModelForSequenceClassification, pipeline, AutoTokenizer
|
8 |
+
import os
|
9 |
+
from random import random
|
10 |
+
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
|
11 |
+
import tensorflow as tf
|
12 |
+
import math
|
13 |
+
import datetime
|
14 |
+
import random
|
15 |
+
import time
|
16 |
+
#import kaleido
|
17 |
+
from sklearn.preprocessing import MinMaxScaler
|
18 |
+
import matplotlib.pyplot as plt
|
19 |
+
#import warnings
|
20 |
+
import tensorflow as tf
|
21 |
+
from tensorflow import keras
|
22 |
+
from keras.layers import Dropout, Activation
|
23 |
+
from keras import layers
|
24 |
+
from keras.callbacks import EarlyStopping
|
25 |
+
from sklearn.metrics import r2_score
|
26 |
+
import plotly.graph_objs as go
|
27 |
+
import plotly.io as pio
|
28 |
+
pio.templates
|
29 |
+
|
30 |
+
model = AutoModelForSequenceClassification.from_pretrained("fine_tuned_FinBERT", from_tf=False, config="config.json")
|
31 |
+
tokenizer = AutoTokenizer.from_pretrained("fine_tuned_FinBERT/tokenizer/")
|
32 |
+
|
33 |
+
class Models(object):
|
34 |
+
def __init__(self):
|
35 |
+
self.stock_data = Stock_Data()
|
36 |
+
|
37 |
+
def bollinger_bands_20d_2std(self, ticker):
|
38 |
+
'''
|
39 |
+
This method calculates the Bollinger Bands with a Rolling average of the last 20 days and 2 standard deviations. In a plot,
|
40 |
+
this would be represented as 3 lines: a rolling average, an upper bound (rolling average + 2 standard deviations) and a lower
|
41 |
+
bound (rolling average - 2 standard deviations). When the price of a stock is between the rolling average and lower bound, it is
|
42 |
+
considered as oversold, so it makes sense to buy, if it is between the roll. avg. and the upper bound, it is considered as
|
43 |
+
overbought, so it makes sense to sell, if it is equal to the roll.avg. it is neutral and if it is outside the bounds, it is
|
44 |
+
considered an Unusual Event. The function returns the outlook of the stock (either "Buy", or "Sell" or "Hold" or "Unusual Event")
|
45 |
+
'''
|
46 |
+
if self.stock_data.status_getter(ticker) != "Open":
|
47 |
+
return "Market Closed"
|
48 |
+
else:
|
49 |
+
data = self.stock_data.stock_data_getter(ticker)
|
50 |
+
low_high_closing_df = pd.DataFrame(data)
|
51 |
+
low_high_closing_df = data.iloc[:, 4:5] # Getting only the "Adj Close" column
|
52 |
+
low_high_closing_df = low_high_closing_df.tail(40) # Getting the last 40 days
|
53 |
+
|
54 |
+
low_high_closing_df["rolling_avg_20d"] = low_high_closing_df['Adj Close'].rolling(20, min_periods = 20).mean()
|
55 |
+
low_high_closing_df["sd"] = low_high_closing_df["Adj Close"].rolling(20, min_periods = 20).std()
|
56 |
+
low_high_closing_df = low_high_closing_df.tail(20) # Keeping the last 20 days only
|
57 |
+
|
58 |
+
recent_data = low_high_closing_df.iloc[-1, :].to_list() # Creating a Series object with the most recent data (last row only)
|
59 |
+
|
60 |
+
upper_bound = recent_data[1] + 2*recent_data[2] # Upper Bound
|
61 |
+
lower_bound = recent_data[1] - 2*recent_data[2] # Lower Bound
|
62 |
+
mean_20d = recent_data[1] # Rolling average of last 20 days
|
63 |
+
|
64 |
+
if self.stock_data.current_price_getter(ticker) is None:
|
65 |
+
return "Market Closed"
|
66 |
+
else:
|
67 |
+
message = ""
|
68 |
+
|
69 |
+
if self.stock_data.current_price_getter(ticker) < mean_20d and self.stock_data.current_price_getter(ticker) >= lower_bound:
|
70 |
+
message = "Buy"
|
71 |
+
elif self.stock_data.current_price_getter(ticker) > mean_20d and self.stock_data.current_price_getter(ticker) <= upper_bound:
|
72 |
+
message = "Sell"
|
73 |
+
elif self.stock_data.current_price_getter(ticker) == mean_20d:
|
74 |
+
message = "Hold"
|
75 |
+
elif self.stock_data.current_price_getter(ticker) <= lower_bound or self.stock_data.current_price_getter(ticker) >= upper_bound:
|
76 |
+
message = "Unusual Event"
|
77 |
+
return message
|
78 |
+
|
79 |
+
def bollinger_bands_10d_1point5std(self, ticker):
|
80 |
+
'''
|
81 |
+
This method calculates the Bollinger Bands with a Rolling average of the last 10 days and 1.5 standard deviations. In a plot,
|
82 |
+
this would be represented as 3 lines: a rolling average, an upper bound (rolling average + 1.5 standard deviations) and a lower
|
83 |
+
bound (rolling average - 1.5 standard deviations). When the price of a stock is between the rolling average and lower bound, it is
|
84 |
+
considered as oversold, so it makes sense to buy, if it is between the roll. avg. and the upper bound, it is considered as
|
85 |
+
overbought, so it makes sense to sell, if it is equal to the roll.avg. it is neutral and if it is outside the bounds, it is
|
86 |
+
considered an Unusual Event. The function returns the outlook of the stock (either "Buy", or "Sell" or "Hold" or "Unusual Event")
|
87 |
+
'''
|
88 |
+
if self.stock_data.status_getter(ticker) != "Open":
|
89 |
+
return "Market Closed"
|
90 |
+
else:
|
91 |
+
data = self.stock_data.stock_data_getter(ticker)
|
92 |
+
|
93 |
+
low_high_closing_df = pd.DataFrame(data)
|
94 |
+
low_high_closing_df = data.iloc[:, 4:5] # Getting only the "Adj Close" column
|
95 |
+
low_high_closing_df = low_high_closing_df.tail(20) # Getting the last 20 days
|
96 |
+
|
97 |
+
low_high_closing_df["rolling_avg_10d"] = low_high_closing_df['Adj Close'].rolling(10, min_periods = 10).mean()
|
98 |
+
low_high_closing_df["sd"] = low_high_closing_df["Adj Close"].rolling(10, min_periods = 10).std()
|
99 |
+
low_high_closing_df = low_high_closing_df.tail(10) # Keeping the last 10 days only
|
100 |
+
|
101 |
+
recent_data = low_high_closing_df.iloc[-1, :].to_list() # Creating a Series object with the most recent data (last row only)
|
102 |
+
|
103 |
+
upper_bound = recent_data[1] + 1.5*recent_data[2] # Upper Bound
|
104 |
+
lower_bound = recent_data[1] - 1.5*recent_data[2] # Lower Bound
|
105 |
+
mean_10d = recent_data[1] # Rolling average of last 10 days
|
106 |
+
|
107 |
+
if self.stock_data.current_price_getter(ticker) is None:
|
108 |
+
return "Market Closed"
|
109 |
+
else:
|
110 |
+
message = ""
|
111 |
+
|
112 |
+
if self.stock_data.current_price_getter(ticker) < mean_10d and self.stock_data.current_price_getter(ticker) >= lower_bound:
|
113 |
+
message = "Buy"
|
114 |
+
elif self.stock_data.current_price_getter(ticker) > mean_10d and self.stock_data.current_price_getter(ticker) <= upper_bound:
|
115 |
+
message = "Sell"
|
116 |
+
elif self.stock_data.current_price_getter(ticker) == mean_10d:
|
117 |
+
message = "Hold"
|
118 |
+
elif self.stock_data.current_price_getter(ticker) <= lower_bound or self.stock_data.current_price_getter(ticker) >= upper_bound:
|
119 |
+
message = "Unusual Event"
|
120 |
+
return message
|
121 |
+
|
122 |
+
def bollinger_bands_50d_3std(self, ticker):
|
123 |
+
'''
|
124 |
+
This method calculates the Bollinger Bands with a Rolling average of the last 50 days and 3 standard deviations. In a plot,
|
125 |
+
this would be represented as 3 lines: a rolling average, an upper bound (rolling average + 3 standard deviations) and a lower
|
126 |
+
bound (rolling average - 3 standard deviations). When the price of a stock is between the rolling average and lower bound, it is
|
127 |
+
considered as oversold, so it makes sense to buy, if it is between the roll. avg. and the upper bound, it is considered as
|
128 |
+
overbought, so it makes sense to sell, if it is equal to the roll.avg. it is neutral and if it is outside the bounds, it is
|
129 |
+
considered an Unusual Event. The function returns the outlook of the stock (either "Buy", or "Sell" or "Hold" or "Unusual Event")
|
130 |
+
'''
|
131 |
+
if self.stock_data.status_getter(ticker) != "Open":
|
132 |
+
return "Market Closed"
|
133 |
+
else:
|
134 |
+
data = self.stock_data.stock_data_getter(ticker)
|
135 |
+
|
136 |
+
low_high_closing_df = pd.DataFrame(data)
|
137 |
+
low_high_closing_df = data.iloc[:, 4:5] # Getting only the "Adj Close" column
|
138 |
+
low_high_closing_df = low_high_closing_df.tail(100) # Getting the last 100 days
|
139 |
+
|
140 |
+
low_high_closing_df["rolling_avg_50d"] = low_high_closing_df['Adj Close'].rolling(50, min_periods = 50).mean()
|
141 |
+
low_high_closing_df["sd"] = low_high_closing_df["Adj Close"].rolling(50, min_periods = 50).std()
|
142 |
+
low_high_closing_df = low_high_closing_df.tail(50) # Keeping the last 50 days only
|
143 |
+
|
144 |
+
recent_data = low_high_closing_df.iloc[-1, :].to_list() # Creating a Series object with the most recent data (last row only)
|
145 |
+
|
146 |
+
upper_bound = recent_data[1] + 3*recent_data[2] # Upper Bound
|
147 |
+
lower_bound = recent_data[1] - 3*recent_data[2] # Lower Bound
|
148 |
+
mean_50d = recent_data[1] # Rolling average of last 50 days
|
149 |
+
|
150 |
+
# Finding the outlook dependent on the current price
|
151 |
+
if self.stock_data.current_price_getter(ticker) is None:
|
152 |
+
return "Market Closed"
|
153 |
+
else:
|
154 |
+
message = ""
|
155 |
+
if self.stock_data.current_price_getter(ticker) < mean_50d and self.stock_data.current_price_getter(ticker) >= lower_bound:
|
156 |
+
message = "Buy"
|
157 |
+
elif self.stock_data.current_price_getter(ticker) > mean_50d and self.stock_data.current_price_getter(ticker) <= upper_bound:
|
158 |
+
message = "Sell"
|
159 |
+
elif self.stock_data.current_price_getter(ticker) == mean_50d:
|
160 |
+
message = "Hold"
|
161 |
+
elif self.stock_data.current_price_getter(ticker) <= lower_bound or self.stock_data.current_price_getter(ticker) >= upper_bound:
|
162 |
+
message = "Unusual Event"
|
163 |
+
return message
|
164 |
+
|
165 |
+
def MACD(self, ticker):
|
166 |
+
'''
|
167 |
+
This method calculates the MACD (Mean Average Convergence Divergence) for a stock. The decision of whether to buy or sell
|
168 |
+
a stock when using this method, depends on the difference of two "lines". The 1st one is called "MACD" and is equal to the
|
169 |
+
difference between the Exponential Moving Average of the adjusted closing price of the last 12 days, and the Moving Average
|
170 |
+
of the adjusted closing price of the last 26 days. The 2nd line is the 9 day moving average of the adj. closing price.
|
171 |
+
When MACD > 9 day M.A. it is considered that there is an uptrend, else, an downtrend.
|
172 |
+
At last, when MACD line crosses the 9 day M.A. from "above", a "Sell" signal is given,
|
173 |
+
while it crosses it from below, a "Buy" signal is given.
|
174 |
+
'''
|
175 |
+
if self.stock_data.status_getter(ticker) != "Open":
|
176 |
+
return "Market Closed"
|
177 |
+
else:
|
178 |
+
data = self.stock_data.stock_data_getter(ticker)
|
179 |
+
|
180 |
+
low_high_closing_df = pd.DataFrame(data)
|
181 |
+
low_high_closing_df = data.iloc[:, 4:5] # Getting only the "Adj Close" column
|
182 |
+
low_high_closing_df = low_high_closing_df.tail(52) # Getting the last 52 days
|
183 |
+
|
184 |
+
|
185 |
+
# Get the 12-day EMA of the closing price
|
186 |
+
low_high_closing_df['EMA_12d'] = low_high_closing_df['Adj Close'].ewm(span=12, adjust=False, min_periods=12).mean()
|
187 |
+
# Get the 26-day MA of the closing price
|
188 |
+
low_high_closing_df['MA_26d'] = low_high_closing_df['Adj Close'].ewm(span=26, adjust=False, min_periods=26).mean()
|
189 |
+
# Subtract the 26-day EMA from the 12-Day EMA to get the MACD
|
190 |
+
low_high_closing_df['MACD'] = low_high_closing_df['EMA_12d'] - low_high_closing_df['MA_26d']
|
191 |
+
# Making the signal line
|
192 |
+
low_high_closing_df['MA_9d'] = low_high_closing_df['MACD'].ewm(span=9, adjust=False, min_periods=9).mean()
|
193 |
+
|
194 |
+
low_high_closing_df['Diff'] = low_high_closing_df['MACD'] - low_high_closing_df['MA_9d']
|
195 |
+
|
196 |
+
Diff = low_high_closing_df['Diff'].astype(float)
|
197 |
+
|
198 |
+
if self.stock_data.current_price_getter(ticker) is None:
|
199 |
+
return "Market Closed"
|
200 |
+
else:
|
201 |
+
message = ""
|
202 |
+
|
203 |
+
if Diff.iloc[-1] < 0:
|
204 |
+
if Diff.iloc[-2] >= 0:
|
205 |
+
message = "Downtrend and sell signal"
|
206 |
+
else:
|
207 |
+
message = "Downtrend and no signal"
|
208 |
+
else:
|
209 |
+
if Diff.iloc[-2] <= 0:
|
210 |
+
message = "Uptrend and buy signal"
|
211 |
+
else:
|
212 |
+
message = "Uptrend and no signal"
|
213 |
+
return message
|
214 |
+
|
215 |
+
def finbert_headlines_sentiment(self, ticker):
|
216 |
+
'''
|
217 |
+
This method uses a the "weights" and the "tokenizer" of a fine-tuned Fin-BERT model, which is a BERT model that
|
218 |
+
was furtherly trained on financial data. The "article_parser()" method scraps www.marketwatch.com and returns the
|
219 |
+
last 17 headers of the chosen stock's articles. The, the FinBERT model classifies each one of them as either "Positive"
|
220 |
+
or "Negative" or "Neutral", and a score is assigned to each header (+100, -100, and 0) correspondingly. At last, a
|
221 |
+
rolling average of window size = 5 is used to "smooth" the sentiment line of the "plotly" plot that is returned.
|
222 |
+
'''
|
223 |
+
|
224 |
+
articles_df = self.stock_data.article_parser(ticker)
|
225 |
+
articles_list = articles_df["headline"].tolist()
|
226 |
+
|
227 |
+
clf = pipeline("text-classification", model=model, tokenizer=tokenizer)
|
228 |
+
outputs_list = clf(articles_list)
|
229 |
+
|
230 |
+
sentiments = []
|
231 |
+
|
232 |
+
for item in outputs_list:
|
233 |
+
sentiments.append(item["label"])
|
234 |
+
|
235 |
+
sentiments_df = pd.DataFrame(sentiments)
|
236 |
+
sentiments_df.rename(columns = {0:'sentiment'}, inplace = True)
|
237 |
+
|
238 |
+
sentiments_df["sentiment"] = sentiments_df["sentiment"].apply(lambda x: 100 if x == "positive" else -100 if x=="negative" else 0)
|
239 |
+
sentiments_df["roll_avg"] = round(sentiments_df["sentiment"].rolling(5, min_periods = 1).mean(), 2)
|
240 |
+
sentiments_df = sentiments_df.tail(12).reset_index()
|
241 |
+
|
242 |
+
pd.options.plotting.backend = "plotly"
|
243 |
+
|
244 |
+
fig = sentiments_df["roll_avg"].plot(title="Sentiment Analysis of the last 12 www.marketwatch.com articles about " + ticker,
|
245 |
+
|
246 |
+
template="plotly_dark",
|
247 |
+
labels=dict(index="12 most recent article headlines", value="sentiment score (rolling avg. of window size 5)"))
|
248 |
+
fig.update_traces(line=dict(color="#3D9140", width=3))
|
249 |
+
fig.update_layout(yaxis_range=[-100,100])
|
250 |
+
fig.update_layout(xaxis_range=[0,12])
|
251 |
+
fig.update_layout(showlegend=False)
|
252 |
+
fig.add_hline(y=0, line_width=1.5, line_color="black")
|
253 |
+
|
254 |
+
current_sentiment = sentiments_df["roll_avg"].tail(1).values[0]
|
255 |
+
|
256 |
+
return {'fig': fig, 'current_sentiment': current_sentiment}
|
257 |
+
|
258 |
+
def LSTM_7_days_price_predictor(self, ticker):
|
259 |
+
'''
|
260 |
+
This method predicts the price of a chosen stock for the next 7 days as of today, by using the daily adjusted closing
|
261 |
+
prices for the last 2 years. At first, a 60-day window of historical prices (i-60) is created as our feature data (x_train)
|
262 |
+
and the following 60-days window as label data (y_train). For every stock available, we have manually defined different
|
263 |
+
parameters so that they fit as good as it gets to the model. Finally we combute the R2 metric and make the predictions. At
|
264 |
+
last, we proceed with the predictions. The model looks back in our data (60 days back) and predicta for the following 7 days.
|
265 |
+
'''
|
266 |
+
|
267 |
+
stock_data = self.stock_data.LSTM_stock_data_getter(ticker)
|
268 |
+
stock_data=pd.DataFrame(data=stock_data).drop(['Open','High','Low','Close', 'Volume'],axis=1).reset_index()
|
269 |
+
stock_data['Date'] = pd.to_datetime(stock_data['Date'])
|
270 |
+
stock_data=stock_data.dropna()
|
271 |
+
|
272 |
+
# Data Preprocessing
|
273 |
+
random.seed(1997)
|
274 |
+
close_prices = stock_data['Adj Close']
|
275 |
+
values = close_prices.values
|
276 |
+
training_data_len = math.ceil(len(values)* 0.8)
|
277 |
+
|
278 |
+
scaler = MinMaxScaler(feature_range=(0,1))
|
279 |
+
scaled_data = scaler.fit_transform(values.reshape(-1,1))
|
280 |
+
train_data = scaled_data[0: training_data_len, :]
|
281 |
+
|
282 |
+
x_train = []
|
283 |
+
y_train = []
|
284 |
+
|
285 |
+
for i in range(60, len(train_data)):
|
286 |
+
x_train.append(train_data[i-60:i, 0])
|
287 |
+
y_train.append(train_data[i, 0])
|
288 |
+
|
289 |
+
x_train, y_train = np.array(x_train), np.array(y_train)
|
290 |
+
x_train = np.reshape(x_train, (x_train.shape[0], x_train.shape[1], 1))
|
291 |
+
|
292 |
+
# Preparation of test set
|
293 |
+
test_data = scaled_data[training_data_len-60: , : ]
|
294 |
+
x_test = []
|
295 |
+
y_test = values[training_data_len:]
|
296 |
+
|
297 |
+
for i in range(60, len(test_data)):
|
298 |
+
x_test.append(test_data[i-60:i, 0])
|
299 |
+
|
300 |
+
x_test = np.array(x_test)
|
301 |
+
x_test = np.reshape(x_test, (x_test.shape[0], x_test.shape[1], 1))
|
302 |
+
|
303 |
+
##### Setting Up LSTM Network Architecture and the Training of the LSTM Model
|
304 |
+
def LSTM_trainer(seed, DROPOUT, LSTM_units,patience,batch_size, epochs):
|
305 |
+
|
306 |
+
tf.random.set_seed(seed)
|
307 |
+
DROPOUT = DROPOUT
|
308 |
+
global model_lstm
|
309 |
+
model_lstm = keras.Sequential()
|
310 |
+
model_lstm.add(layers.LSTM(LSTM_units, return_sequences=True, input_shape=(x_train.shape[1], 1)))
|
311 |
+
model_lstm.add(Dropout(rate=DROPOUT))
|
312 |
+
model_lstm.add(layers.LSTM(LSTM_units, return_sequences=False))
|
313 |
+
model_lstm.add(Dropout(rate=DROPOUT))
|
314 |
+
model_lstm.add(layers.Dense(25))
|
315 |
+
model_lstm.add(Dropout(rate=DROPOUT))
|
316 |
+
model_lstm.add(layers.Dense(1))
|
317 |
+
model_lstm.add(Activation('linear'))
|
318 |
+
|
319 |
+
print('\n')
|
320 |
+
print("Compiling the LSTM Model for the " + str(ticker) + " stock....\n")
|
321 |
+
t0 = time.time()
|
322 |
+
model_lstm.compile(optimizer='adam', loss='mean_squared_error',metrics=['mae'])
|
323 |
+
callback=EarlyStopping(monitor='val_loss',
|
324 |
+
min_delta=0,
|
325 |
+
patience=patience,
|
326 |
+
verbose=1, mode='auto')
|
327 |
+
model_lstm.fit(x_train,
|
328 |
+
y_train,
|
329 |
+
batch_size= batch_size,
|
330 |
+
epochs=epochs,
|
331 |
+
validation_split=0.1,# ...holding out 10% of the data for validation
|
332 |
+
shuffle=True,verbose=0,callbacks=[callback])
|
333 |
+
t1 = time.time()
|
334 |
+
global ex_time
|
335 |
+
ex_time = round(t1-t0, 2)
|
336 |
+
print("Compiling took :",ex_time,"seconds")
|
337 |
+
|
338 |
+
predictions = model_lstm.predict(x_test)
|
339 |
+
predictions = scaler.inverse_transform(predictions)
|
340 |
+
#rmse = np.sqrt(np.mean(((predictions - y_test) ** 2)))
|
341 |
+
global r_squared_score
|
342 |
+
r_squared_score = round(r2_score(y_test, predictions),2)
|
343 |
+
#print('Rmse Score: ', round(rmse),2)
|
344 |
+
print('R2 Score: ', r_squared_score)
|
345 |
+
|
346 |
+
if ticker == 'AAPL':
|
347 |
+
LSTM_trainer(1, 0.2, 100,2, 20, 30)
|
348 |
+
elif ticker == 'NVDA':
|
349 |
+
LSTM_trainer(2, 0.2, 100,2, 30, 50)
|
350 |
+
elif ticker == 'PYPL':
|
351 |
+
LSTM_trainer(6, 0.2, 100,10,25, 30)
|
352 |
+
elif ticker == 'MSFT':
|
353 |
+
LSTM_trainer(4, 0.1, 80, 2,20, 40)
|
354 |
+
elif ticker == 'TSLA':
|
355 |
+
LSTM_trainer(5, 0.1, 120, 4,20, 25)
|
356 |
+
elif ticker == 'AMZN':
|
357 |
+
LSTM_trainer(6, 0.1, 120,2, 20, 25)
|
358 |
+
elif ticker == 'SPOT':
|
359 |
+
LSTM_trainer(9, 0.2, 200,5, 20, 40)
|
360 |
+
elif ticker == 'TWTR' :
|
361 |
+
LSTM_trainer(15, 0.2, 100,4,20, 40)
|
362 |
+
elif ticker == 'UBER':
|
363 |
+
LSTM_trainer(15, 0.2, 100,7,20, 40)
|
364 |
+
elif ticker == 'ABNB':
|
365 |
+
LSTM_trainer(15, 0.2, 120,8,20, 40)
|
366 |
+
elif ticker == 'GOOG':
|
367 |
+
LSTM_trainer(15, 0.2, 100,3,20, 25)
|
368 |
+
|
369 |
+
# Unseen Predictions for the next 7 days
|
370 |
+
close_data = scaled_data
|
371 |
+
look_back = 60
|
372 |
+
|
373 |
+
def predict(num_prediction, model):
|
374 |
+
prediction_list = close_data[-look_back:]
|
375 |
+
|
376 |
+
for _ in range(num_prediction):
|
377 |
+
x = prediction_list[-look_back:]
|
378 |
+
x = x.reshape((1, look_back, 1))
|
379 |
+
|
380 |
+
out = model.predict(x)[0][0]
|
381 |
+
prediction_list = np.append(prediction_list, out)
|
382 |
+
prediction_list = prediction_list[look_back-1:]
|
383 |
+
|
384 |
+
return prediction_list
|
385 |
+
|
386 |
+
def predict_dates(num_prediction):
|
387 |
+
last_date = stock_data['Date'].values[-1]
|
388 |
+
prediction_dates = pd.date_range(last_date, periods=num_prediction+1).tolist()
|
389 |
+
return prediction_dates
|
390 |
+
|
391 |
+
num_prediction = 7
|
392 |
+
|
393 |
+
forecast = predict(num_prediction, model_lstm)
|
394 |
+
forecast_dates = predict_dates(num_prediction)
|
395 |
+
|
396 |
+
plt.figure(figsize=(25,10))
|
397 |
+
forecast = forecast.reshape(-1, 1)
|
398 |
+
forecast_inverse = scaler.inverse_transform(forecast)
|
399 |
+
|
400 |
+
# Ploting the Actual Prices and the Predictions of them for the next 7 days
|
401 |
+
base = stock_data['Date'].iloc[[-1]] # Here we create our base date (the last existing date with actual prices)
|
402 |
+
testdata = pd.DataFrame(forecast_inverse)# Here we create a data frame that contains the prediction prices and an empty column for their dates
|
403 |
+
testdata['Date'] = ""
|
404 |
+
testdata.columns = ["Adj Close","Date"]
|
405 |
+
testdata = testdata.iloc[1:,:]
|
406 |
+
testdata["Label"] = "" # Let's add a column "Label" that would show if the respective price is a prediction or not
|
407 |
+
testdata["Label"] = "Prediction"
|
408 |
+
testdata = testdata[["Date", "Adj Close", "Label"]]
|
409 |
+
|
410 |
+
date_list = [base + datetime.timedelta(days=x+1) for x in range(testdata.shape[0]+1)]
|
411 |
+
date_list = pd.DataFrame(date_list)
|
412 |
+
date_list.columns = ["Date"]
|
413 |
+
date_list.reset_index(inplace = True)
|
414 |
+
date_list.drop(["index"], axis = 1, inplace = True)
|
415 |
+
date_list.index = date_list.index + 1
|
416 |
+
testdata.Date = date_list
|
417 |
+
|
418 |
+
stock_data["Label"] = ""
|
419 |
+
stock_data["Label"] = "Actual price"
|
420 |
+
finaldf = pd.concat([stock_data,testdata], axis=0) # Here we concatenate the "testdata" and the original data frame "df" into a final one
|
421 |
+
finaldf.reset_index(inplace = True)
|
422 |
+
finaldf.drop(["index"], axis = 1, inplace = True)
|
423 |
+
finaldf['Date'] = pd.to_datetime(finaldf['Date'])
|
424 |
+
|
425 |
+
plt.rcParams["figure.figsize"] = (25,10)
|
426 |
+
#We create two different data frames, one that contains the actual prices and one that has only the predictions
|
427 |
+
finaldfPredictions = finaldf.iloc[-8:]
|
428 |
+
finaldfActuals = finaldf.iloc[:-7]
|
429 |
+
|
430 |
+
plot_1 = go.Scatter(
|
431 |
+
x = finaldfActuals['Date'],
|
432 |
+
y = finaldfActuals['Adj Close'],
|
433 |
+
mode = 'lines',
|
434 |
+
name = 'Historical Data (2 years)',
|
435 |
+
line=dict(width=1,color='#3D9140'))
|
436 |
+
plot_2 = go.Scatter(
|
437 |
+
x = finaldfPredictions['Date'],
|
438 |
+
y = finaldfPredictions['Adj Close'],
|
439 |
+
mode = 'lines',
|
440 |
+
name = '7-day Prediction',
|
441 |
+
line=dict(width=1,color="#EE3B3B"))
|
442 |
+
|
443 |
+
layout = go.Layout(
|
444 |
+
title = 'Next 7 days stock price prediction of ' + str(ticker),
|
445 |
+
xaxis = {'title' : "Date"},
|
446 |
+
yaxis = {'title' : "Price ($)"}
|
447 |
+
)
|
448 |
+
fig = go.Figure(data=[plot_1, plot_2], layout=layout)
|
449 |
+
fig.update_layout(template='plotly_dark',autosize=True)
|
450 |
+
fig.update_layout(legend=dict(
|
451 |
+
orientation="h",
|
452 |
+
yanchor="bottom",
|
453 |
+
y=1.02,
|
454 |
+
xanchor="right",
|
455 |
+
x=1
|
456 |
+
))
|
457 |
+
|
458 |
+
return fig
|