shubhayu-64 commited on
Commit
112f52b
·
1 Parent(s): 533a8f5

add: app file

Browse files
Files changed (4) hide show
  1. Models/datamodels.py +9 -0
  2. Models/stocknames.csv +13 -0
  3. app.py +913 -0
  4. requirements.txt +8 -0
Models/datamodels.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ from datetime import datetime
2
+ from typing import List, Optional
3
+ from uuid import uuid4
4
+
5
+ from pydantic import BaseModel, Field
6
+
7
+ class StockNameModel(BaseModel):
8
+ name: str = Field(..., min_length=3, max_length=50)
9
+ ticker: str = Field(..., min_length=3, max_length=50)
Models/stocknames.csv ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name,ticker
2
+ Google,GOOGL
3
+ Amazon,AMZN
4
+ Apple,AAPL
5
+ AMD,AMD
6
+ Reliance Industries,RELIANCE.NS
7
+ TCS,TCS.NS
8
+ HDFC Bank,HDFCBANK.NS
9
+ ICICI Bank,ICICIBANK.NS
10
+
11
+
12
+
13
+
app.py ADDED
@@ -0,0 +1,913 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List, Optional
2
+ import yfinance as yf
3
+ from datetime import date, timedelta
4
+ import streamlit as st
5
+ import pandas as pd
6
+ import numpy as np
7
+ from Models.datamodels import StockNameModel
8
+ import matplotlib as mpl
9
+ import matplotlib.pyplot as plt
10
+ import matplotlib.style as style
11
+ from matplotlib.dates import date2num, DateFormatter, WeekdayLocator,\
12
+ DayLocator, MONDAY
13
+ import seaborn as sns
14
+ import mplfinance as mpf
15
+ from mplfinance.original_flavor import candlestick_ohlc
16
+
17
+
18
+
19
+ class Stonks:
20
+ def __init__(self, stocks_filepath: str) -> None:
21
+ # Classwise global variables
22
+ self.stocks = None
23
+ self.selected_stock = None
24
+ self.selected_ticker = None
25
+ self.start_date = date.today() - timedelta(weeks=52)
26
+ self.end_date = date.today()
27
+ self.stock_df = None
28
+ self.stick = "day"
29
+ self.rsi_period = 14
30
+ self.mfi_period = 14
31
+ self.mfi_upper_band = 80
32
+ self.mfi_lower_band = 20
33
+ self.stochastic_oscillator_period = 14
34
+ self.stochastic_oscillator_upper_band = 80
35
+ self.stochastic_oscillator_lower_band = 20
36
+ self.bollinger_band_period = 20
37
+ self.on_balance_volumne_period = 20
38
+
39
+ # Init functions
40
+ self.stocksFilePath = stocks_filepath
41
+ self.get_stocks()
42
+
43
+ def get_stocks(self):
44
+ if self.stocks is None:
45
+ stocksNames = pd.read_csv(self.stocksFilePath)
46
+ self.stocks = [StockNameModel(name=row['name'], ticker=row['ticker']) for index, row in stocksNames.iterrows()]
47
+
48
+ def get_stock_data(self):
49
+ self.stock_df = yf.download(self.selected_ticker, self.start_date, self.end_date)
50
+
51
+ def pandas_candlestick_ohlc(self, dat, txt, stick = "day", otherseries = None) :
52
+ """
53
+ Japanese candlestick chart showing OHLC prices for a specified time period
54
+
55
+ :param dat: pandas dataframe object with datetime64 index, and float columns "Open", "High", "Low", and "Close"
56
+ :param stick: A string or number indicating the period of time covered by a single candlestick. Valid string inputs include "day", "week", "month", and "year", ("day" default), and any numeric input indicates the number of trading days included in a period
57
+ :param otherseries: An iterable that will be coerced into a list, containing the columns of dat that hold other series to be plotted as lines
58
+
59
+ :returns: a Japanese candlestick plot for stock data stored in dat, also plotting other series if passed.
60
+ """
61
+ mondays = WeekdayLocator(MONDAY) # major ticks on the mondays
62
+ alldays = DayLocator() # minor ticks on the days
63
+ dayFormatter = DateFormatter('%d') # e.g., 12
64
+
65
+ # Create a new DataFrame which includes OHLC data for each period specified by stick input
66
+ transdat = dat.loc[:,["Open", "High", "Low", "Close"]]
67
+ if (type(stick) == str):
68
+ if stick == "day":
69
+ plotdat = transdat
70
+ stick = 1 # Used for plotting
71
+ elif stick in ["week", "month", "year"]:
72
+ if stick == "week":
73
+ transdat["week"] = pd.to_datetime(transdat.index).map(lambda x: x.isocalendar()[1]) # Identify weeks
74
+ elif stick == "month":
75
+ transdat["month"] = pd.to_datetime(transdat.index).map(lambda x: x.month) # Identify months
76
+ transdat["year"] = pd.to_datetime(transdat.index).map(lambda x: x.isocalendar()[0]) # Identify years
77
+ grouped = transdat.groupby(list(set(["year",stick]))) # Group by year and other appropriate variable
78
+ plotdat = pd.DataFrame({"Open": [], "High": [], "Low": [], "Close": []}) # Create empty data frame containing what will be plotted
79
+ for name, group in grouped:
80
+ temporary_entry = pd.DataFrame({"Open": group.iloc[0,0],
81
+ "High": max(group.High),
82
+ "Low": min(group.Low),
83
+ "Close": group.iloc[-1,3]},
84
+ index = [group.index[0]],)
85
+ temporary_entry.name = group.index[0]
86
+ plotdat = pd.concat([plotdat, temporary_entry], ignore_index = False)
87
+ if stick == "week": stick = 5
88
+ elif stick == "month": stick = 30
89
+ elif stick == "year": stick = 365
90
+ elif (type(stick) == int and stick >= 1):
91
+ transdat["stick"] = [np.floor(i / stick) for i in range(len(transdat.index))]
92
+ grouped = transdat.groupby("stick")
93
+ plotdat = pd.DataFrame({"Open": [], "High": [], "Low": [], "Close": []}) # Create empty data frame containing what will be plotted
94
+ for name, group in grouped:
95
+ temporary_entry = pd.DataFrame({"Open": group.iloc[0,0],
96
+ "High": max(group.High),
97
+ "Low": min(group.Low),
98
+ "Close": group.iloc[-1,3]},
99
+ index = [group.index[0]],)
100
+ temporary_entry.name = group.index[0]
101
+ plotdat = pd.concat([plotdat, temporary_entry], ignore_index = False)
102
+
103
+ else:
104
+ raise ValueError('Valid inputs to argument "stick" include the strings "day", "week", "month", "year", or a positive integer')
105
+
106
+ # Set plot parameters, including the axis object ax used for plotting
107
+ fig, ax = plt.subplots()
108
+ fig.subplots_adjust(bottom=0.2)
109
+ """
110
+ if plotdat.index[-1] - plotdat.index[0] < pd.Timedelta('730 days'):
111
+ weekFormatter = DateFormatter('%b %d') # e.g., Jan 12
112
+ ax.xaxis.set_major_locator(mondays)
113
+ ax.xaxis.set_minor_locator(alldays)
114
+ """
115
+ if pd.Timedelta(f"{plotdat.index[-1] - plotdat.index[0]} days") < pd.Timedelta('730 days'):
116
+ weekFormatter = DateFormatter('%b %d') # e.g., Jan 12
117
+ ax.xaxis.set_major_locator(mondays)
118
+ ax.xaxis.set_minor_locator(alldays)
119
+ else:
120
+ weekFormatter = DateFormatter('%b %d, %Y')
121
+ ax.xaxis.set_major_formatter(weekFormatter)
122
+
123
+ ax.grid(True)
124
+
125
+ # Create the candelstick chart
126
+ candlestick_ohlc(ax, list(zip(list(date2num(plotdat.index.tolist())), plotdat["Open"].tolist(), plotdat["High"].tolist(),
127
+ plotdat["Low"].tolist(), plotdat["Close"].tolist())),
128
+ colorup = "green", colordown = "red", width = stick * .4)
129
+
130
+ # Plot other series (such as moving averages) as lines
131
+ if otherseries != None:
132
+ if type(otherseries) != list:
133
+ otherseries = [otherseries]
134
+ dat.loc[:,otherseries].plot(ax = ax, lw = 1.3, grid = True)
135
+
136
+ ax.xaxis_date()
137
+ ax.autoscale_view()
138
+
139
+ plt.setp(plt.gca().get_xticklabels(), rotation=45, horizontalalignment='right')
140
+ sns.set(rc={'figure.figsize':(20, 10)})
141
+ plt.style.use('ggplot')
142
+ plt.title(f"Candlestick chart of {txt}", color = 'black', fontsize = 20)
143
+ plt.xlabel('Date', color = 'black', fontsize = 15)
144
+ plt.ylabel('Stock Price (p)', color = 'black', fontsize = 15)
145
+
146
+ return fig
147
+
148
+ def sma(self, title_txt: str, label_txt: str, days: List[int]):
149
+ fig, ax = plt.subplots(figsize=(15,9))
150
+ for day in days:
151
+ self.stock_df['Adj Close'].loc[str(self.start_date):str(self.end_date)].rolling(window=day).mean().plot(ax=ax, label=f'{day} Day Avg')
152
+ self.stock_df['Adj Close'].loc[str(self.start_date):str(self.end_date)].plot(ax=ax, label=f"{label_txt}")
153
+ ax.set_title(f"{title_txt}", color = 'black', fontsize = 20)
154
+ ax.set_xlabel('Date', color = 'black', fontsize = 15)
155
+ ax.set_ylabel('Stock Price (p)', color = 'black', fontsize = 15)
156
+ ax.legend()
157
+ return fig
158
+
159
+ def ewma(self, title_txt: str, label_txt: str, days: List[int]):
160
+ fig, ax = plt.subplots(figsize=(15,9))
161
+ for day in days:
162
+ self.stock_df['Adj Close'].loc[str(self.start_date):str(self.end_date)].ewm(window=day).mean().plot(ax=ax, label=f'{day} Day Avg')
163
+ self.stock_df['Adj Close'].loc[str(self.start_date):str(self.end_date)].plot(ax=ax, label=f"{label_txt}")
164
+ ax.set_title(f"{title_txt}", color = 'black', fontsize = 20)
165
+ ax.set_xlabel('Date', color = 'black', fontsize = 15)
166
+ ax.set_ylabel('Stock Price (p)', color = 'black', fontsize = 15)
167
+ ax.legend()
168
+ return fig
169
+
170
+ def tripple_ewma(self, title_txt: str, label_txt: str, short_ema_span: int, middle_ema_span: int, long_ema_span: int):
171
+ fig, ax = plt.subplots(figsize=(15,9))
172
+
173
+ ax.plot(self.stock_df['Adj Close'].loc[str(self.start_date):str(self.end_date)], label=f"{label_txt}", color = 'blue')
174
+ ax.plot(self.stock_df.loc[str(self.start_date):str(self.end_date)]['Adj Close'].ewm(span=short_ema_span, adjust=False).mean(), label = 'Short/Fast EMA', color = 'red')
175
+ ax.plot(self.stock_df.loc[str(self.start_date):str(self.end_date)]['Adj Close'].ewm(span=middle_ema_span, adjust=False).mean(), label = 'Middle/Medium EMA', color = 'orange')
176
+ ax.plot(self.stock_df.loc[str(self.start_date):str(self.end_date)]['Adj Close'].ewm(span=long_ema_span, adjust=False).mean(), label = 'Long/Slow EMA', color = 'green')
177
+
178
+ ax.set_title(f"{title_txt}", color = 'black', fontsize = 20)
179
+ ax.set_xlabel('Date', color = 'black', fontsize = 15)
180
+ ax.set_ylabel('Stock Price (p)', color = 'black', fontsize = 15)
181
+ ax.legend()
182
+ return fig
183
+
184
+ def buy_sell_triple_ewma(self, data):
185
+ buy_list = []
186
+ sell_list = []
187
+ flag_long = False
188
+ flag_short = False
189
+
190
+ for i in range(0, len(data)):
191
+ if data['Middle'][i] < data['Long'][i] and data['Short'][i] < data['Middle'][i] and flag_long == False and flag_short == False:
192
+ buy_list.append(data['Adj Close'][i])
193
+ sell_list.append(np.nan)
194
+ flag_short = True
195
+ elif flag_short == True and data['Short'][i] > data['Middle'][i]:
196
+ sell_list.append(data['Adj Close'][i])
197
+ buy_list.append(np.nan)
198
+ flag_short = False
199
+ elif data['Middle'][i] > data['Long'][i] and data['Short'][i] > data['Middle'][i] and flag_long == False and flag_short == False:
200
+ buy_list.append(data['Adj Close'][i])
201
+ sell_list.append(np.nan)
202
+ flag_long = True
203
+ elif flag_long == True and data['Short'][i] < data['Middle'][i]:
204
+ sell_list.append(data['Adj Close'][i])
205
+ buy_list.append(np.nan)
206
+ flag_long = False
207
+ else:
208
+ buy_list.append(np.nan)
209
+ sell_list.append(np.nan)
210
+
211
+ return (buy_list, sell_list)
212
+
213
+ def buy_sell_ewma3_plot(self, data, label_txt: str, title_txt: str):
214
+ fig, ax = plt.subplots(figsize=(18, 10))
215
+
216
+ ax.plot(data['Adj Close'], label=f"{label_txt}", color = 'blue', alpha = 0.35)
217
+ ax.plot(data["Short"], label = 'Short/Fast EMA', color = 'red', alpha = 0.35)
218
+ ax.plot(data["Middle"], label = 'Middle/Medium EMA', color = 'orange', alpha = 0.35)
219
+ ax.plot(data["Long"], label = 'Long/Slow EMA', color = 'green', alpha = 0.35)
220
+ ax.scatter(data.index, data['Buy'], color = 'green', label = 'Buy Signal', marker = '^', alpha = 1)
221
+ ax.scatter(data.index, data['Sell'], color = 'red', label = 'Sell Signal', marker='v', alpha = 1)
222
+
223
+ ax.set_title(f"{title_txt}", color = 'black', fontsize = 20)
224
+ ax.set_xlabel('Date', color = 'black', fontsize = 15)
225
+ ax.set_ylabel('Stock Price (p)', color = 'black', fontsize = 15)
226
+ ax.legend()
227
+ return fig
228
+
229
+ def exponential_smoothing(self, series, alpha):
230
+ result = [series[0]] # first value is same as series
231
+ for n in range(1, len(series)):
232
+ result.append(alpha * series[n] + (1 - alpha) * result[n-1])
233
+ return result
234
+
235
+ def plot_exponential_smoothing(self, series, alphas, label_txt: str, title_txt: str):
236
+ fig, ax = plt.subplots(figsize=(17, 8))
237
+ for alpha in alphas:
238
+ ax.plot(self.exponential_smoothing(series, alpha), label=f"Alpha {alpha}")
239
+ ax.plot(series.values, "c", label = f"{label_txt}")
240
+ ax.set_xlabel('Days', color = 'black', fontsize = 15)
241
+ ax.set_ylabel('Stock Price (p)', color = 'black', fontsize = 15)
242
+ ax.legend(loc="best")
243
+ ax.axis('tight')
244
+ ax.set_title(f"{title_txt}", color = 'black', fontsize = 20)
245
+ ax.grid(True)
246
+ return fig
247
+
248
+ def double_exponential_smoothing(self, series, alpha, beta):
249
+ result = [series[0]]
250
+ for n in range(1, len(series)+1):
251
+ if n == 1:
252
+ level, trend = series[0], series[1] - series[0]
253
+ if n >= len(series): # forecasting
254
+ value = result[-1]
255
+ else:
256
+ value = series[n]
257
+ last_level, level = level, alpha * value + (1 - alpha) * (level + trend)
258
+ trend = beta * (level - last_level) + (1 - beta) * trend
259
+ result.append(level + trend)
260
+ return result
261
+
262
+ def plot_double_exponential_smoothing(self, series, alphas, betas, label_txt: str, title_txt: str):
263
+ fig, ax = plt.subplots(figsize=(17, 8))
264
+ for alpha in alphas:
265
+ for beta in betas:
266
+ ax.plot(self.double_exponential_smoothing(series, alpha, beta), label=f"Alpha {alpha}, beta {beta}")
267
+ ax.plot(series.values, label = f"{label_txt}")
268
+ ax.set_xlabel('Days', color = 'black', fontsize = 15)
269
+ ax.set_ylabel('Stock Price (p)', color = 'black', fontsize = 15)
270
+ ax.legend(loc="best")
271
+ ax.axis('tight')
272
+ ax.set_title(f"{title_txt}", color = 'black', fontsize = 20)
273
+ ax.grid(True)
274
+ return fig
275
+
276
+ def plot_macd_signal(self, macd, signal, macd_label_txt: str, sig_label_txt: str, title_txt: str):
277
+ fig, ax = plt.subplots(figsize=(15, 9))
278
+ ax.plot(macd, label = f"{macd_label_txt}", color= 'red')
279
+ ax.plot(signal, label = f"{sig_label_txt}", color= 'blue')
280
+ ax.set_title(f"{title_txt}", color = 'black', fontsize = 20)
281
+ ax.set_xlabel('Date', color = 'black', fontsize = 15)
282
+ ax.legend(loc='upper left')
283
+ return fig
284
+
285
+
286
+ def buy_sell_macd(self, signal):
287
+ Buy = []
288
+ Sell = []
289
+ flag = -1
290
+
291
+ for i in range(0, len(signal)):
292
+ if signal['MACD'][i] < signal['Signal Line'][i]:
293
+ Sell.append(np.nan)
294
+ if flag != 1:
295
+ Buy.append(signal['Adj Close'][i])
296
+ flag = 1
297
+ else:
298
+ Buy.append(np.nan)
299
+ elif signal['MACD'][i] > signal['Signal Line'][i]:
300
+ Buy.append(np.nan)
301
+ if flag != 0:
302
+ Sell.append(signal['Adj Close'][i])
303
+ flag = 0
304
+ else:
305
+ Sell.append(np.nan)
306
+ else:
307
+ Buy.append(np.nan)
308
+ Sell.append(np.nan)
309
+
310
+ return (Buy, Sell)
311
+
312
+ def buy_sell_macd_plot(self, data, title_txt: str):
313
+ fig, ax = plt.subplots(figsize=(20, 10))
314
+ ax.scatter(data.index, data['Buy_Signal_Price'], color='green', label='Buy', marker='^', alpha=1)
315
+ ax.scatter(data.index, data['Sell_Signal_Price'], color='red', label='Sell', marker='v', alpha=1)
316
+ ax.plot(data['Adj Close'], label='Adj Close Price', alpha = 0.35)
317
+ ax.set_title(f"{title_txt}", color = 'black', fontsize = 20)
318
+ ax.set_xlabel('Date', color = 'black', fontsize = 15)
319
+ ax.set_ylabel('Adj Close Price')
320
+ ax.legend(loc = 'upper left')
321
+ return fig
322
+
323
+ def plot_rsi(self, title_txt: str, rsi_data):
324
+ fig, ax = plt.subplots(figsize=(20, 10))
325
+ ax.set_title(f"{title_txt}", color = 'black', fontsize = 20)
326
+ ax.set_xlabel('Date', color = 'black', fontsize = 15)
327
+ ax.set_ylabel('RSI', color = 'black', fontsize = 15)
328
+ rsi_data.plot(ax=ax)
329
+ return fig
330
+
331
+
332
+ def plot_rsi_with_sma(self, data, title_txt: str):
333
+ fig, ax = plt.subplots(figsize=(20, 10))
334
+ ax.set_title(f"{title_txt}", color = 'black', fontsize = 20)
335
+ ax.plot(data['RSI'])
336
+ ax.set_xlabel('Date', color = 'black', fontsize = 15)
337
+ ax.axhline(0, linestyle='--', alpha = 0.5, color='gray')
338
+ ax.axhline(10, linestyle='--', alpha = 0.5, color='orange')
339
+ ax.axhline(20, linestyle='--', alpha = 0.5, color='green')
340
+ ax.axhline(30, linestyle='--', alpha = 0.5, color='red')
341
+ ax.axhline(70, linestyle='--', alpha = 0.5, color='red')
342
+ ax.axhline(80, linestyle='--', alpha = 0.5, color='green')
343
+ ax.axhline(90, linestyle='--', alpha = 0.5, color='orange')
344
+ ax.axhline(100, linestyle='--', alpha = 0.5, color='gray')
345
+ return fig
346
+
347
+ def plot_rsi_with_ewma(self, data, title_txt: str):
348
+ fig, ax = plt.subplots(figsize=(20, 10))
349
+ ax.set_title(f"{title_txt}", color = 'black', fontsize = 20)
350
+ ax.set_xlabel('Date', color = 'black', fontsize = 15)
351
+ ax.plot(data['RSI2'])
352
+ ax.axhline(0, linestyle='--', alpha = 0.5, color='gray')
353
+ ax.axhline(10, linestyle='--', alpha = 0.5, color='orange')
354
+ ax.axhline(20, linestyle='--', alpha = 0.5, color='green')
355
+ ax.axhline(30, linestyle='--', alpha = 0.5, color='red')
356
+ ax.axhline(70, linestyle='--', alpha = 0.5, color='red')
357
+ ax.axhline(80, linestyle='--', alpha = 0.5, color='green')
358
+ ax.axhline(90, linestyle='--', alpha = 0.5, color='orange')
359
+ ax.axhline(100, linestyle='--', alpha = 0.5, color='gray')
360
+ return fig
361
+
362
+ def plot_mfi(self, data, title_txt: str):
363
+ fig, ax = plt.subplots(figsize=(20, 10))
364
+ ax.plot(data['MFI'], label = 'MFI')
365
+ ax.axhline(10, linestyle = '--', color = 'orange')
366
+ ax.axhline(20, linestyle = '--', color = 'blue')
367
+ ax.axhline(80, linestyle = '--', color = 'blue')
368
+ ax.axhline(90, linestyle = '--', color = 'orange')
369
+ ax.set_title(f"{title_txt}", color = 'black', fontsize = 20)
370
+ ax.set_xlabel('Time periods', color = 'black', fontsize = 15)
371
+ ax.set_ylabel('MFI Values', color = 'black', fontsize = 15)
372
+ return fig
373
+
374
+ def mfi_buy_sell_plot(self, data, title_txt: str):
375
+ fig, ax = plt.subplots(figsize=(20, 10))
376
+ ax.plot(data['Close'], label = 'Close Price', alpha = 0.5)
377
+ ax.scatter(data.index, data['Buy'], color = 'green', label = 'Buy Signal', marker = '^', alpha = 1)
378
+ ax.scatter(data.index, data['Sell'], color = 'red', label = 'Sell Signal', marker = 'v', alpha = 1)
379
+ ax.set_title(f"{title_txt}", color = 'black', fontsize = 20)
380
+ ax.set_xlabel('Date', color = 'black', fontsize = 15)
381
+ ax.set_ylabel('Close Price', color = 'black', fontsize = 15)
382
+ ax.legend(loc='upper left')
383
+ return fig
384
+
385
+ def plot_stochastic_oscillator(self, data, title_txt: str):
386
+ fig, ax = plt.subplots(figsize=(20, 10))
387
+ data[['Strategy Returns','Market Returns']].cumsum().plot(ax=ax)
388
+ ax.set_title('Strategy returns versus AZN.L returns', color = 'black', fontsize = 20)
389
+ return fig
390
+
391
+ def plot_bollinger_bands(self, data, column_list, title_txt: str):
392
+ fig, ax = plt.subplots(figsize=(20, 10))
393
+ data[column_list].plot(ax=ax)
394
+ plt.style.use('ggplot')
395
+ ax.set_title(title_txt, color = 'black', fontsize = 20)
396
+ ax.set_ylabel('Close Price', color = 'black', fontsize = 15)
397
+ return fig
398
+
399
+ def plot_bollinger_bands_shaded(self, data, title_txt: str):
400
+ fig, ax = plt.subplots(figsize=(20,10))
401
+ x_axis = data.index
402
+ ax.fill_between(x_axis, data['Upper'], data['Lower'], color='grey')
403
+ ax.plot(data['Close'], color='gold', lw=3, label = 'Close Price') #lw = line width
404
+ ax.plot(data['SMA'], color='blue', lw=3, label = 'Simple Moving Average')
405
+ ax.set_title(title_txt, color = 'black', fontsize = 20)
406
+ ax.set_xlabel('Date', color = 'black', fontsize = 15)
407
+ ax.set_ylabel('Close Price', color = 'black', fontsize = 15)
408
+ plt.xticks(rotation = 45)
409
+ ax.legend()
410
+ return fig
411
+
412
+ def plot_bollinger_bands_shaded_with_signals(self, data, title_txt: str):
413
+ fig, ax = plt.subplots(figsize=(20,10))
414
+ x_axis = data.index
415
+ ax.fill_between(x_axis, data['Upper'], data['Lower'], color='grey')
416
+ ax.plot(data['Close'], color='gold', lw=3, label = 'Close Price', alpha = 0.5)
417
+ ax.plot(data['SMA'], color='blue', lw=3, label = 'Moving Average', alpha = 0.5)
418
+ ax.scatter(x_axis, data['Buy'], color='green', lw=3, label = 'Buy', marker = '^', alpha = 1)
419
+ ax.scatter(x_axis, data['Sell'], color='red', lw=3, label = 'Sell', marker = 'v', alpha = 1)
420
+ ax.set_title(title_txt, color = 'black', fontsize = 20)
421
+ ax.set_xlabel('Date', color = 'black', fontsize = 15)
422
+ ax.set_ylabel('Close Price', color = 'black', fontsize = 15)
423
+ plt.xticks(rotation = 45)
424
+ ax.legend()
425
+ return fig
426
+
427
+ def plot_obv_ema(self, data, title_txt: str):
428
+ fig, ax = plt.subplots(figsize=(17, 8))
429
+ plt.style.use('ggplot')
430
+ ax.plot(data['OBV'], label = 'OBV', color = 'orange')
431
+ ax.plot(data['OBV_EMA'], label = 'OBV_EMA', color = 'purple')
432
+ ax.set_title(title_txt, color = 'black', fontsize = 20)
433
+ ax.set_xlabel('Date', fontsize = 15)
434
+ ax.set_ylabel('Price', fontsize = 15)
435
+ ax.legend(loc = 'upper left')
436
+ return fig
437
+
438
+ def buy_sell_obv_plot(self, data, title_txt: str):
439
+ fig, ax = plt.subplots(figsize=(17, 8))
440
+ plt.style.use('ggplot')
441
+ ax.plot(data['Adj Close'], label = 'Adjusted Close', alpha = 0.5)
442
+ ax.scatter(data.index, data['Buy'], label = 'Buy Signal', marker = '^', alpha = 1, color = 'green')
443
+ ax.scatter(data.index, data['Sell'], label = 'Sell Signal', marker = 'v', alpha = 1, color = 'red')
444
+ ax.set_title(title_txt, color = 'black', fontsize = 20)
445
+ ax.set_xlabel('Date', fontsize = 15)
446
+ ax.set_ylabel('Price', fontsize = 15)
447
+ ax.legend(loc = 'upper left')
448
+ return fig
449
+
450
+
451
+ def ui_renderer(self):
452
+ st.title('Stonks 📈')
453
+ st.image('https://i.ytimg.com/vi/if-2M3K1tqk/maxresdefault.jpg')
454
+
455
+ #
456
+ # TODO: Update details with markdown and add more meta docs
457
+ #
458
+ st.write('Welcome to Stonks, a simple web app that allows you to analyze stocks.')
459
+ st.write("Final Year Project by: [Sayan Kumar Ghosh]([email protected]), [Vishal Choubey]([email protected]), [Jit Karan]([email protected]), [Soumili Saha]([email protected]), [Shubhayu Majumdar]([email protected])")
460
+
461
+ st.markdown("""---""")
462
+
463
+ # Sidebar Inputs
464
+ stock_names = [stock.name for stock in self.stocks]
465
+ self.selected_stock = st.sidebar.selectbox('Select a Stock:', stock_names)
466
+ self.selected_ticker = self.stocks[stock_names.index(self.selected_stock)].ticker
467
+ self.start_date = st.sidebar.date_input('Start date', date.today() - timedelta(weeks=52))
468
+ self.end_date = st.sidebar.date_input('End date', date.today())
469
+ self.stick = st.sidebar.selectbox('Stick', ["day", "week", "month", "year"])
470
+ st.sidebar.markdown("""---""")
471
+ self.rsi_period = st.sidebar.number_input('RSI Period', 14, 100, 14)
472
+ st.sidebar.markdown("""---""")
473
+ self.mfi_period = st.sidebar.number_input('MFI Period', 14, 100, 14)
474
+ self.mfi_upper_band = st.sidebar.number_input('MFI Upper Band', 50, 100, 80)
475
+ self.mfi_lower_band = st.sidebar.number_input('MFI Lower Band', 0, 50, 20)
476
+ st.sidebar.markdown("""---""")
477
+ self.stochastic_oscillator_period = st.sidebar.number_input('Stochastic Oscillator Period', 14, 100, 14)
478
+ self.stochastic_oscillator_upper_band = st.sidebar.number_input('Stochastic Oscillator Upper Band', 50, 100, 80)
479
+ self.stochastic_oscillator_lower_band = st.sidebar.number_input('Stochastic Oscillator Lower Band', 0, 50, 20)
480
+ st.sidebar.markdown("""---""")
481
+ self.bollinger_band_period = st.sidebar.number_input('Bollinger Band Period', 20, 100, 20)
482
+ st.sidebar.markdown("""---""")
483
+ self.on_balance_volumne_period = st.sidebar.number_input('On Balance Volumne Period', 20, 100, 20)
484
+
485
+
486
+ # Assertions for all inputs
487
+ if not self.start_date <= self.end_date:
488
+ st.error("Error: Start date must fall before end date.")
489
+ st.toast("Error: Start date must fall before end date.")
490
+ st.stop()
491
+
492
+ if not self.end_date <= date.today():
493
+ st.error("Error: End date must not be in the future.")
494
+ st.toast("Error: End date must not be in the future.")
495
+ st.stop()
496
+
497
+ st.subheader(f"Stonks Analysis on {self.selected_stock} from {self.start_date} to {self.end_date}")
498
+
499
+ self.get_stock_data()
500
+
501
+ if self.stock_df.empty:
502
+ st.error("Error: No data found for selected stock.")
503
+ st.stop()
504
+
505
+ st.dataframe(self.stock_df)
506
+
507
+ st.header("Visualising stock data")
508
+ st.markdown("""
509
+ **Japanese candlestick charts** are tools used in a particular trading style called price action to predict market movement through pattern recognition of continuations, breakouts and reversals.
510
+
511
+ Unlike a line chart, all of the price information can be viewed in one figure showing the high, low, open and close price of the day or chosen time frame. Price action traders observe patterns formed by green bullish candles where the stock is trending upwards over time, and red or black bearish candles where there is a downward trend.
512
+ """)
513
+
514
+ txt = f"{self.selected_stock} OHLC stock prices from {self.start_date} - {self.end_date}"
515
+ st.pyplot(self.pandas_candlestick_ohlc(self.stock_df, stick=self.stick, txt = txt))
516
+
517
+ st.header("Trend-following strategies")
518
+ st.write("Trend-following is about profiting from the prevailing trend through buying an asset when its price trend goes up, and selling when its trend goes down, expecting price movements to continue.")
519
+
520
+ st.subheader("Moving averages")
521
+ st.write("Moving averages smooth a series filtering out noise to help identify trends, one of the fundamental principles of technical analysis being that prices move in trends. Types of moving averages include simple, exponential, smoothed, linear-weighted, MACD, and as lagging indicators they follow the price action and are commonly referred to as trend-following indicators.")
522
+
523
+ st.subheader("Simple Moving Average (SMA)")
524
+ st.markdown("""
525
+ The simplest form of a moving average, known as a Simple Moving Average (SMA), is calculated by taking the arithmetic mean of a given set of values over a set time period. This model is probably the most naive approach to time series modelling and simply states that the next observation is the mean of all past observations and each value in the time period carries equal weight.
526
+
527
+ Modelling this an as average calculation problem we would try to predict the future stock market prices (for example, x<sub>t</sub>+1 ) as an average of the previously observed stock market prices within a fixed size window (for example, x<sub>t</sub>-n, ..., x<sub>t</sub>). This helps smooth out the price data by creating a constantly updated average price so that the impacts of random, short-term fluctuations on
528
+ the price of a stock over a specified time-frame are mitigated.
529
+ """, unsafe_allow_html = True)
530
+
531
+ st.pyplot(self.sma(title_txt=f"20-day Simple Moving Average for {self.selected_stock} stock", label_txt=f"{self.selected_stock}", days=[20]))
532
+ st.pyplot(self.sma(title_txt=f"20, 50, 100 and 200 day moving averages for {self.selected_stock} stock", label_txt=f"{self.selected_stock}", days=[20, 50, 100, 200]))
533
+
534
+ st.markdown("""
535
+ The chart shows that the 20-day moving average is the most sensitive to local changes, and the 200-day moving average the least. Here, the 200-day moving average indicates an overall bullish trend - the stock is trending upward over time. The 20- and 50-day moving averages are at times bearish and at other times bullish.
536
+
537
+ The major drawback of moving averages, however, is that because they are lagging, and smooth out prices, they tend to recognise reversals too late and are therefore not very helpful when used alone.
538
+ """)
539
+
540
+ st.markdown("""
541
+ ### Trading Strategy
542
+
543
+ The moving average crossover trading strategy will be to take two moving averages - 20-day (fast) and 200-day (slow) - and to go long (buy) when the fast MA goes above the slow MA and to go short (sell) when the fast MA goes below the slow MA.
544
+ """)
545
+
546
+
547
+ temp_df = self.stock_df.copy()
548
+ temp_df["20d"] = np.round(temp_df["Adj Close"].rolling(window = 20, center = False).mean(), 2)
549
+ temp_df["50d"] = np.round(temp_df["Adj Close"].rolling(window = 50, center = False).mean(), 2)
550
+ temp_df["200d"] = np.round(temp_df["Adj Close"].rolling(window = 200, center = False).mean(), 2)
551
+
552
+ st.pyplot(self.pandas_candlestick_ohlc(temp_df.loc[str(self.start_date):str(self.end_date),:], otherseries = ["20d", "50d", "200d"], txt = txt, stick=self.stick))
553
+
554
+ st.markdown("""
555
+ ### Exponential Moving Average
556
+
557
+ In a Simple Moving Average, each value in the time period carries
558
+ equal weight, and values outside of the time period are not included in the average. However, the Exponential Moving Average is a cumulative calculation where a different decreasing weight is assigned to each observation. Past values have a diminishing contribution to the average, while more recent values have a greater contribution. This method allows the moving average to be more responsive to changes in the data.
559
+ """)
560
+
561
+ st.pyplot(self.sma(title_txt=f"20-day Exponential Moving Average for {self.selected_stock} stock", label_txt=f"{self.selected_stock}", days=[20]))
562
+ st.pyplot(self.sma(title_txt=f"20, 50, 100 and 200-day Exponential Moving Averages for {self.selected_stock} stock", label_txt=f"{self.selected_stock}", days=[20, 50, 100, 200]))
563
+
564
+
565
+ st.markdown("""
566
+ ### Triple Moving Average Crossover Strategy
567
+
568
+ This strategy uses three moving moving averages - short/fast, middle/medium and long/slow - and has two buy and sell signals.
569
+
570
+ The first is to buy when the middle/medium moving average crosses above the long/slow moving average and the short/fast moving average crosses above the middle/medium moving average. If we use this buy signal the strategy is to sell if the short/fast moving average crosses below the middle/medium moving average.
571
+
572
+ The second is to buy when the middle/medium moving average crosses below the long/slow moving average and the short/fast moving average crosses below the middle/medium moving average. If we use this buy signal the strategy is to sell if the short/fast moving average crosses above the middle/medium moving average.
573
+ """)
574
+ st.pyplot(self.tripple_ewma(title_txt=f"Triple Exponential Moving Average for {self.selected_stock} stock", label_txt=f"{self.selected_stock}", short_ema_span=5, middle_ema_span=21, long_ema_span=63))
575
+
576
+
577
+ temp_df = self.stock_df.copy()
578
+ temp_df["Short"] = temp_df["Adj Close"].ewm(span=5, adjust=False).mean()
579
+ temp_df["Middle"] = temp_df["Adj Close"].ewm(span=21, adjust=False).mean()
580
+ temp_df["Long"] = temp_df["Adj Close"].ewm(span=63, adjust=False).mean()
581
+
582
+ temp_df["Buy"], temp_df["Sell"] = self.buy_sell_triple_ewma(temp_df)
583
+
584
+ st.pyplot(self.buy_sell_ewma3_plot(temp_df, label_txt=f"{self.selected_stock}", title_txt=f"Trading signals for {self.selected_stock} stock"))
585
+
586
+
587
+ st.markdown("""
588
+ ### Exponential Smoothing
589
+
590
+ Single Exponential Smoothing, also known as Simple Exponential Smoothing, is a time series forecasting method for univariate data without a trend or seasonality. It requires an alpha parameter, also called the smoothing factor or smoothing coefficient, to control the rate at which the influence of the observations at prior time steps decay exponentially.
591
+ """)
592
+ st.pyplot(self.plot_exponential_smoothing(self.stock_df["Adj Close"], [0.3, 0.05], label_txt=f"{self.selected_stock}", title_txt=f"Single Exponential Smoothing for {self.selected_stock} stock using 0.05 and 0.3 as alpha values"))
593
+
594
+ st.markdown("""
595
+ The smaller the smoothing factor (coefficient), the smoother the time series will be. As the smoothing factor approaches 0, we approach the moving average model so the smoothing factor of 0.05 produces a smoother time series than 0.3. This indicates slow learning (past observations have a large influence on forecasts). A value close to 1 indicates fast learning (that is, only the most recent values influence the forecasts).
596
+
597
+ **Double Exponential Smoothing (Holt’s Linear Trend Model)** is an extension being a recursive use of Exponential Smoothing twice where beta is the trend smoothing factor, and takes values between 0 and 1. It explicitly adds support for trends.
598
+ """)
599
+ st.pyplot(self.plot_double_exponential_smoothing(self.stock_df["Adj Close"], alphas=[0.9, 0.02], betas=[0.9, 0.02], label_txt=f"{self.selected_stock}", title_txt=f"Double Exponential Smoothing for {self.selected_stock} stock with different alpha and beta values"))
600
+
601
+ st.markdown("""
602
+ The third main type is Triple Exponential Smoothing (Holt Winters Method) which is an extension of Exponential Smoothing that explicitly adds support for seasonality, or periodic fluctuations.
603
+ """)
604
+
605
+ st.markdown("""
606
+ ### Moving Average Convergence Divergence (MACD)
607
+
608
+ The MACD is a trend-following momentum indicator turning two trend-following indicators, moving averages, into a momentum oscillator by subtracting the longer moving average from the shorter one.
609
+
610
+ It is useful although lacking one prediction element - because it is unbounded it is not particularly useful for identifying overbought and oversold levels. Traders can look for signal line crossovers, neutral/centreline crossovers (otherwise known as the 50 level) and divergences from the price action to generate signals.
611
+
612
+ The default parameters are 26 EMA of prices, 12 EMA of prices and a 9-moving average of the difference between the first two.
613
+ """)
614
+
615
+ short_ema = self.stock_df['Adj Close'].ewm(span=12, adjust=False).mean()
616
+ long_ema = self.stock_df['Adj Close'].ewm(span=26, adjust=False).mean()
617
+ macd = short_ema - long_ema
618
+ signal = macd.ewm(span=9, adjust=False).mean()
619
+
620
+ st.pyplot(self.plot_macd_signal(macd, signal, macd_label_txt=f"{self.selected_stock} MACD", sig_label_txt=f"Signal Line", title_txt=f"MACD and Signal Line for {self.selected_stock} stock"))
621
+
622
+
623
+ temp_df = self.stock_df.copy()
624
+ temp_df['MACD'] = macd
625
+ temp_df['Signal Line'] = signal
626
+ temp_df['Buy_Signal_Price'], temp_df['Sell_Signal_Price'] = self.buy_sell_macd(temp_df)
627
+
628
+ st.write("When the MACD line crosses above the signal line this indicates a good time to buy.")
629
+ st.pyplot(self.buy_sell_macd_plot(temp_df, title_txt=f"MACD Buy and Sell Signals for {self.selected_stock} stock"))
630
+
631
+
632
+ st.markdown("""
633
+ ## Momentum Strategies
634
+
635
+ In momentum algorithmic trading strategies stocks have momentum (i.e. upward or downward trends) that we can detect and exploit.
636
+
637
+ ### Relative Strength Index (RSI)
638
+
639
+ The RSI is a momentum indicator. A typical momentum strategy will buy stocks that have been showing an upward trend in hopes that the trend will continue, and make predictions based on whether the past recent values were going up or going down.
640
+
641
+ The RSI determines the level of overbought (70) and oversold (30) zones using a default lookback period of 14 i.e. it uses the last 14 values to calculate its values. The idea is to buy when the RSI touches the 30 barrier and sell when it touches the 70 barrier.
642
+ """)
643
+
644
+ def get_rsi():
645
+ temp_df = self.stock_df.copy()
646
+ delta = temp_df['Adj Close'].diff(1)
647
+ delta.dropna(inplace=True)
648
+ up, down = delta.clip(lower=0), -delta.clip(upper=0)
649
+
650
+ # Relative Strength Index
651
+ rs = up.rolling(window=self.rsi_period).mean() / down.abs().rolling(window=self.rsi_period).mean()
652
+
653
+ # Relative Strength Index with Expoential Weighted Moving Average
654
+ rs_ewma = up.ewm(span=self.rsi_period).mean() / down.abs().ewm(span=self.rsi_period).mean()
655
+
656
+ return 100 - (100 / (1 + rs)), 100 - (100 / (1 + rs_ewma))
657
+
658
+ # PLot RSI with SMA
659
+ temp_df = self.stock_df.copy()
660
+ temp_df['RSI'], temp_df['RSI2'] = get_rsi()
661
+
662
+ st.pyplot(self.plot_rsi(title_txt=f"RSI for {self.selected_stock} stock", rsi_data=temp_df["RSI"]))
663
+ st.pyplot(self.plot_rsi_with_sma(temp_df, title_txt=f"RSI with {self.rsi_period}-day SMA for {self.selected_stock} stock"))
664
+ st.pyplot(self.plot_rsi_with_ewma(temp_df, title_txt=f"RSI with {self.rsi_period}-day EWMA for {self.selected_stock} stock"))
665
+
666
+ st.markdown("""
667
+ ### Money Flow Index (MFI)
668
+
669
+ Money Flow Index (MFI) is a technical oscillator, and momentum indicator, that uses price and volume data for identifying overbought or oversold signals in an asset. It can also be used to spot divergences which warn of a trend change in price. The oscillator moves between 0 and 100 and a reading of above 80 implies overbought conditions, and below 20 implies oversold conditions.
670
+
671
+ It is related to the Relative Strength Index (RSI) but incorporates volume, whereas the RSI only considers price.
672
+ """)
673
+
674
+ def get_mfi():
675
+ temp_df = self.stock_df.copy()
676
+ typical_price = (temp_df['High'] + temp_df['Low'] + temp_df['Close']) / 3
677
+ money_flow = typical_price * temp_df['Volume']
678
+
679
+ # Get all positive and negative money flows
680
+ positive_flow = []
681
+ negative_flow = []
682
+
683
+ # Loop through typical price
684
+ for i in range(1, len(typical_price)):
685
+ if typical_price[i] > typical_price[i-1]:
686
+ positive_flow.append(money_flow[i-1])
687
+ negative_flow.append(0)
688
+ elif typical_price[i] < typical_price[i-1]:
689
+ negative_flow.append(money_flow[i-1])
690
+ positive_flow.append(0)
691
+ else:
692
+ positive_flow.append(0)
693
+ negative_flow.append(0)
694
+
695
+ positive_mf = []
696
+ negative_mf = []
697
+
698
+ for i in range(self.mfi_period-1, len(positive_flow)):
699
+ positive_mf.append(sum(positive_flow[i + 1 - self.mfi_period : i+1]))
700
+ for i in range(self.mfi_period-1, len(negative_flow)):
701
+ negative_mf.append(sum(negative_flow[i + 1 - self.mfi_period : i+1]))
702
+
703
+ mfi = 100 * (np.array(positive_mf) / (np.array(positive_mf) + np.array(negative_mf)))
704
+ mfi = np.append([np.nan]*self.mfi_period, mfi)
705
+
706
+ return mfi
707
+
708
+ temp_df = self.stock_df.copy()
709
+ temp_df['MFI'] = get_mfi()
710
+ st.pyplot(self.plot_mfi(temp_df, title_txt=f"MFI for {self.selected_stock} stock"))
711
+
712
+ def get_mfi_signal(data, high, low):
713
+ buy_signal = []
714
+ sell_signal = []
715
+
716
+ for i in range(len(data['MFI'])):
717
+ if data['MFI'][i] > high:
718
+ buy_signal.append(np.nan)
719
+ sell_signal.append(data['Close'][i])
720
+ elif data['MFI'][i] < low:
721
+ buy_signal.append(data['Close'][i])
722
+ sell_signal.append(np.nan)
723
+ else:
724
+ sell_signal.append(np.nan)
725
+ buy_signal.append(np.nan)
726
+
727
+ return (buy_signal, sell_signal)
728
+
729
+ temp_df["Buy"], temp_df["Sell"] = get_mfi_signal(temp_df, self.mfi_upper_band, self.mfi_lower_band)
730
+ st.pyplot(self.mfi_buy_sell_plot(temp_df, title_txt=f"MFI Buy and Sell Signals for {self.selected_stock} stock"))
731
+
732
+ st.markdown("""
733
+ ### Stochastic Oscillator
734
+
735
+ The stochastic oscillator is a momentum indicator comparing the closing price of a security to the range of its prices over a certain period of time and is one of the best-known momentum indicators along with RSI and MACD.
736
+
737
+ The intuition is that in a market trending upward, prices will close near the high, and in a market trending downward, prices close near the low.
738
+
739
+ The stochastic oscillator is plotted within a range of zero and 100. The default parameters are an overbought zone of 80, an oversold zone of 20 and well-used lookbacks period of 14 and 5 which can be used simultaneously. The oscillator has two lines, the %K and %D, where the former measures momentum and the latter measures the moving average of the former. The %D line is more important of the two indicators and tends to produce better trading signals which are created when the %K crosses through the %D.
740
+ """)
741
+
742
+ st.markdown("""
743
+ The stochastic oscillator is calculated using the following formula:
744
+ ```python
745
+ %K = 100(C – L)/(H – L)
746
+ ```
747
+
748
+ Where:
749
+
750
+ C -> the most recent closing price
751
+
752
+ L -> the low of the given previous trading sessions
753
+
754
+ H -> the highest price traded during the same given period
755
+
756
+ %K -> the current market rate for the currency pair
757
+
758
+ %D -> 3-period moving average of %K
759
+ """)
760
+
761
+
762
+ def get_stochastic_oscillator():
763
+ temp_df = self.stock_df.copy()
764
+
765
+ temp_df["L"] = temp_df['Low'].rolling(window=self.stochastic_oscillator_period).min()
766
+ temp_df["H"] = temp_df['High'].rolling(window=self.stochastic_oscillator_period).max()
767
+ temp_df['%K'] = 100*((temp_df['Close'] - temp_df['L']) / (temp_df['H'] - temp_df['L']) )
768
+ temp_df['%D'] = temp_df['%K'].rolling(window=3).mean()
769
+ temp_df['Sell Entry'] = ((temp_df['%K'] < temp_df['%D']) & (temp_df['%K'].shift(1) > temp_df['%D'].shift(1))) & (temp_df['%D'] > self.stochastic_oscillator_upper_band)
770
+ temp_df['Sell Exit'] = ((temp_df['%K'] > temp_df['%D']) & (temp_df['%K'].shift(1) < temp_df['%D'].shift(1)))
771
+
772
+ temp_df['Short'] = np.where(temp_df['Sell Entry'], -1, np.where(temp_df['Sell Exit'], 0, 0))
773
+ temp_df['Short'] = temp_df['Short'].fillna(method='pad')
774
+ temp_df['Buy Entry'] = ((temp_df['%K'] > temp_df['%D']) & (temp_df['%K'].shift(1) < temp_df['%D'].shift(1))) & (temp_df['%D'] < self.stochastic_oscillator_lower_band)
775
+ temp_df['Buy Exit'] = ((temp_df['%K'] < temp_df['%D']) & (temp_df['%K'].shift(1) > temp_df['%D'].shift(1)))
776
+
777
+ temp_df['Long'] = np.where(temp_df['Buy Entry'], 1, np.where(temp_df['Buy Exit'], 0, 0))
778
+ temp_df['Long'] = temp_df['Long'].fillna(method='pad')
779
+ temp_df['Long'][0] = 0
780
+
781
+ temp_df['Position'] = temp_df['Long'] + temp_df['Short']
782
+ temp_df['Market Returns'] = temp_df['Close'].pct_change()
783
+ temp_df['Strategy Returns'] = temp_df['Market Returns'] * temp_df['Position'].shift(1)
784
+
785
+ return temp_df
786
+
787
+ temp_df = get_stochastic_oscillator()
788
+ st.pyplot(self.plot_stochastic_oscillator(temp_df, title_txt=f"Stochastic Oscillator for {self.selected_stock} stock"))
789
+
790
+ st.markdown("""
791
+ ## Volatility trading strategies
792
+
793
+ Volatility trading involves predicting the stability of an asset’s value. Instead of trading on the price rising or falling, traders take a position on whether it will move in any direction. Volatility trading is a good option for investors who believe that the price of an asset will stay within a certain range.
794
+
795
+ ### Bollinger Bands
796
+
797
+ A Bollinger Band is a volatility indicator based on based on the correlation between the normal distribution and stock price and can be used to draw support and resistance curves. It is defined by a set of lines plotted two standard deviations (positively and negatively) away from a simple moving average (SMA) of the security's price, but can be adjusted to user preferences.
798
+
799
+ By default it calculates a 20-period SMA (the middle band), an upper band two standard deviations above the the moving average and a lower band two standard deviations below it.
800
+
801
+ If the price moves above the upper band this could indicate a good time to sell, and if it moves below the lower band it could be a good time to buy.
802
+
803
+ Whereas the RSI can only be used as a confirming factor inside a ranging market, not a trending market, by using Bollinger bands we can calculate the widening variable, or moving spread between the upper and the lower bands, that tells us if prices are about to trend and whether the RSI signals might not be that reliable.
804
+
805
+ Despite 90% of the price action happening between the bands, however, a breakout is not necessarily a trading signal as it provides no clue as to the direction and extent of future price movement.
806
+ """)
807
+
808
+
809
+ def get_bollinger_bands():
810
+ temp_df = self.stock_df.copy()
811
+ temp_df['SMA'] = temp_df['Close'].rolling(window=self.bollinger_band_period).mean()
812
+ temp_df['STD'] = temp_df['Close'].rolling(window=self.bollinger_band_period).std()
813
+ temp_df['Upper'] = temp_df['SMA'] + (temp_df['STD'] * 2)
814
+ temp_df['Lower'] = temp_df['SMA'] - (temp_df['STD'] * 2)
815
+ column_list = ['Close', 'SMA', 'Upper', 'Lower']
816
+
817
+ return temp_df, column_list
818
+
819
+
820
+ temp_df, column_list = get_bollinger_bands()
821
+ st.pyplot(self.plot_bollinger_bands(temp_df, column_list, title_txt=f"Bollinger Bands for {self.selected_stock} stock"))
822
+ st.pyplot(self.plot_bollinger_bands_shaded(temp_df, title_txt=f"Shaded Bollinger Bands region for {self.selected_stock} stock"))
823
+
824
+ def get_signal_bb(data):
825
+ buy_signal = []
826
+ sell_signal = []
827
+
828
+ for i in range(len(data['Close'])):
829
+ if data['Close'][i] > data['Upper'][i]:
830
+ buy_signal.append(np.nan)
831
+ sell_signal.append(data['Close'][i])
832
+ elif data['Close'][i] < data['Lower'][i]:
833
+ sell_signal.append(np.nan)
834
+ buy_signal.append(data['Close'][i])
835
+ else:
836
+ buy_signal.append(np.nan)
837
+ sell_signal.append(np.nan)
838
+ return (buy_signal, sell_signal)
839
+
840
+ temp_df['Buy'], temp_df['Sell'] = get_signal_bb(temp_df)
841
+ st.pyplot(self.plot_bollinger_bands_shaded_with_signals(temp_df, title_txt=f"Bollinger Bands with Buy and Sell Signals for {self.selected_stock} stock"))
842
+
843
+
844
+ st.markdown("""
845
+ ## Volume Trading Strategies
846
+
847
+ Volume trading is a measure of how much of a given financial asset has traded in a period of time. Volume traders look for instances of increased buying or selling orders. They also pay attention to current price trends and potential price movements. Generally, increased trading volume will lean heavily towards buy orders.
848
+
849
+ ### On Balance Volume (OBV)
850
+
851
+ OBV is a momentum-based indicator which measures volume flow to gauge the direction of the trend. Volume and price rise are directly proportional and OBV can be used as a confirmation tool with regards to price trends. A rising price is depicted by a rising OBV and a falling OBV stands for a falling price.
852
+
853
+ It is a cumulative total of the up and down volume. When the close is higher than the previous close, the volume is added to the running
854
+ total, and when the close is lower than the previous close, the volume is subtracted
855
+ from the running total.
856
+ """)
857
+
858
+ def get_obv():
859
+ OBV = []
860
+ OBV.append(0)
861
+
862
+ for i in range(1, len(temp_df['Adj Close'])):
863
+ if temp_df['Adj Close'][i] > temp_df['Adj Close'][i-1]:
864
+ OBV.append(OBV[-1] + temp_df.Volume[i])
865
+ elif temp_df['Adj Close'][i] < temp_df['Adj Close'][i-1]:
866
+ OBV.append(OBV[-1] - temp_df.Volume[i])
867
+ else:
868
+ OBV.append(OBV[-1])
869
+ return OBV
870
+
871
+ temp_df = self.stock_df.copy()
872
+ temp_df['OBV'] = get_obv()
873
+ temp_df['OBV_EMA'] = temp_df['OBV'].ewm(span=self.on_balance_volumne_period).mean()
874
+
875
+ st.pyplot(self.plot_obv_ema(temp_df, title_txt=f"On Balance Volume for {self.selected_stock} stock"))
876
+
877
+ def buy_sell_obv(signal, col1, col2):
878
+ sigPriceBuy = []
879
+ sigPriceSell = []
880
+ flag = -1
881
+
882
+ for i in range(0, len(signal)):
883
+ # If OBV > OBV_EMA then buy --> col1 => If OBV < OBV_EMA then sell => 'OBV_EMA'
884
+ if signal[col1][i] < signal[col2][i] and flag != 1:
885
+ sigPriceBuy.append(signal['Adj Close'][i])
886
+ sigPriceSell.append(np.nan)
887
+ flag = 1
888
+ # If OBV < OBV_EMA then sell
889
+ elif signal[col1][i] > signal[col2][i] and flag != 0:
890
+ sigPriceSell.append(signal['Adj Close'][i])
891
+ sigPriceBuy.append(np.nan)
892
+ flag = 0
893
+ else:
894
+ sigPriceSell.append(np.nan)
895
+ sigPriceBuy.append(np.nan)
896
+
897
+ return (sigPriceBuy, sigPriceSell)
898
+
899
+ temp_df['Buy'], temp_df['Sell'] = buy_sell_obv(temp_df, 'OBV', 'OBV_EMA')
900
+
901
+ st.pyplot(self.buy_sell_obv_plot(temp_df, title_txt=f"On Balance Volume Buy and Sell Signals for {self.selected_stock} stock"))
902
+
903
+ st.markdown("""---""")
904
+ st.markdown("""
905
+ ## Conclusion
906
+
907
+ It is almost certainly better to choose technical indicators that complement each other, not just those that move in unison and generate the same signals. The intuition here is that the more indicators you have that confirm each other, the better your chances are to profit. This can be done by combining strategies to form a system, and looking for multiple signals.
908
+ """)
909
+
910
+
911
+ if __name__ == '__main__':
912
+ stonks = Stonks(stocks_filepath="Models/stocknames.csv")
913
+ stonks.ui_renderer()
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ matplotlib
2
+ mplfinance
3
+ numpy
4
+ pandas
5
+ pydantic
6
+ seaborn
7
+ streamlit
8
+ yfinance