Spaces:
Build error
Build error
File size: 58,323 Bytes
c133ff5 112f52b c133ff5 112f52b c133ff5 112f52b c133ff5 112f52b 8580ce6 112f52b 8580ce6 112f52b 8580ce6 112f52b c133ff5 112f52b c133ff5 112f52b c133ff5 112f52b 8580ce6 112f52b c133ff5 c7b4f60 112f52b c133ff5 112f52b c133ff5 112f52b c133ff5 112f52b 8580ce6 c133ff5 8580ce6 112f52b c133ff5 112f52b c133ff5 112f52b c133ff5 112f52b c133ff5 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 |
import logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(message)s', filename='logger.log')
from typing import List
from preds import Predictions
import yfinance as yf
from datetime import date, timedelta
import streamlit as st
import pandas as pd
import numpy as np
from Models.datamodels import StockNameModel
import matplotlib.pyplot as plt
from matplotlib.dates import date2num, DateFormatter, WeekdayLocator,\
DayLocator, MONDAY
import seaborn as sns
import mplfinance as mpf
from mplfinance.original_flavor import candlestick_ohlc
from lstm import __lstm__
class Stonks:
def __init__(self, stocks_filepath: str) -> None:
logging.info("Initializing Stonks class")
# Classwise global variables
self.stocks = None
self.selected_stock = None
self.selected_ticker = None
self.start_date = date.today() - timedelta(weeks=52)
self.end_date = date.today()
self.stock_df = None
self.stick = "day"
self.rsi_period = 14
self.mfi_period = 14
self.mfi_upper_band = 80
self.mfi_lower_band = 20
self.stochastic_oscillator_period = 14
self.stochastic_oscillator_upper_band = 80
self.stochastic_oscillator_lower_band = 20
self.roc_period = 9
self.bollinger_band_period = 20
self.on_balance_volumne_period = 20
# Init functions
self.stocksFilePath = stocks_filepath
self.get_stocks()
def get_stocks(self):
if self.stocks is None:
stocksNames = pd.read_csv(self.stocksFilePath)
self.stocks = [StockNameModel(name=row['name'], ticker=row['ticker']) for index, row in stocksNames.iterrows()]
def get_stock_data(self):
self.stock_df = yf.download(self.selected_ticker, self.start_date, self.end_date)
def pandas_candlestick_ohlc(self, dat, txt, stick = "day", otherseries = None) :
"""
Japanese candlestick chart showing OHLC prices for a specified time period
:param dat: pandas dataframe object with datetime64 index, and float columns "Open", "High", "Low", and "Close"
: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
: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
:returns: a Japanese candlestick plot for stock data stored in dat, also plotting other series if passed.
"""
mondays = WeekdayLocator(MONDAY) # major ticks on the mondays
alldays = DayLocator() # minor ticks on the days
dayFormatter = DateFormatter('%d') # e.g., 12
# Create a new DataFrame which includes OHLC data for each period specified by stick input
transdat = dat.loc[:,["Open", "High", "Low", "Close"]]
if (type(stick) == str):
if stick == "day":
plotdat = transdat
stick = 1 # Used for plotting
elif stick in ["week", "month", "year"]:
if stick == "week":
transdat["week"] = pd.to_datetime(transdat.index).map(lambda x: x.isocalendar()[1]) # Identify weeks
elif stick == "month":
transdat["month"] = pd.to_datetime(transdat.index).map(lambda x: x.month) # Identify months
transdat["year"] = pd.to_datetime(transdat.index).map(lambda x: x.isocalendar()[0]) # Identify years
grouped = transdat.groupby(list(set(["year",stick]))) # Group by year and other appropriate variable
plotdat = pd.DataFrame({"Open": [], "High": [], "Low": [], "Close": []}) # Create empty data frame containing what will be plotted
for name, group in grouped:
temporary_entry = pd.DataFrame({"Open": group.iloc[0,0],
"High": max(group.High),
"Low": min(group.Low),
"Close": group.iloc[-1,3]},
index = [group.index[0]],)
temporary_entry.name = group.index[0]
plotdat = pd.concat([plotdat, temporary_entry], ignore_index = False)
if stick == "week": stick = 5
elif stick == "month": stick = 30
elif stick == "year": stick = 365
elif (type(stick) == int and stick >= 1):
transdat["stick"] = [np.floor(i / stick) for i in range(len(transdat.index))]
grouped = transdat.groupby("stick")
plotdat = pd.DataFrame({"Open": [], "High": [], "Low": [], "Close": []}) # Create empty data frame containing what will be plotted
for name, group in grouped:
temporary_entry = pd.DataFrame({"Open": group.iloc[0,0],
"High": max(group.High),
"Low": min(group.Low),
"Close": group.iloc[-1,3]},
index = [group.index[0]],)
temporary_entry.name = group.index[0]
plotdat = pd.concat([plotdat, temporary_entry], ignore_index = False)
else:
raise ValueError('Valid inputs to argument "stick" include the strings "day", "week", "month", "year", or a positive integer')
# Set plot parameters, including the axis object ax used for plotting
fig, ax = plt.subplots()
fig.subplots_adjust(bottom=0.2)
"""
if plotdat.index[-1] - plotdat.index[0] < pd.Timedelta('730 days'):
weekFormatter = DateFormatter('%b %d') # e.g., Jan 12
ax.xaxis.set_major_locator(mondays)
ax.xaxis.set_minor_locator(alldays)
"""
if pd.Timedelta(f"{plotdat.index[-1] - plotdat.index[0]} days") < pd.Timedelta('730 days'):
weekFormatter = DateFormatter('%b %d') # e.g., Jan 12
ax.xaxis.set_major_locator(mondays)
ax.xaxis.set_minor_locator(alldays)
else:
weekFormatter = DateFormatter('%b %d, %Y')
ax.xaxis.set_major_formatter(weekFormatter)
ax.grid(True)
# Create the candelstick chart
candlestick_ohlc(ax, list(zip(list(date2num(plotdat.index.tolist())), plotdat["Open"].tolist(), plotdat["High"].tolist(),
plotdat["Low"].tolist(), plotdat["Close"].tolist())),
colorup = "green", colordown = "red", width = stick * .4)
# Plot other series (such as moving averages) as lines
if otherseries != None:
if type(otherseries) != list:
otherseries = [otherseries]
dat.loc[:,otherseries].plot(ax = ax, lw = 1.3, grid = True)
ax.xaxis_date()
ax.autoscale_view()
plt.setp(plt.gca().get_xticklabels(), rotation=45, horizontalalignment='right')
sns.set(rc={'figure.figsize':(20, 10)})
plt.style.use('ggplot')
plt.title(f"Candlestick chart of {txt}", color = 'black', fontsize = 20)
plt.xlabel('Date', color = 'black', fontsize = 15)
plt.ylabel('Stock Price (p)', color = 'black', fontsize = 15)
return fig
def sma(self, title_txt: str, label_txt: str, days: List[int]):
fig, ax = plt.subplots(figsize=(15,9))
for day in days:
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')
self.stock_df['Adj Close'].loc[str(self.start_date):str(self.end_date)].plot(ax=ax, label=f"{label_txt}")
ax.set_title(f"{title_txt}", color = 'black', fontsize = 20)
ax.set_xlabel('Date', color = 'black', fontsize = 15)
ax.set_ylabel('Stock Price (p)', color = 'black', fontsize = 15)
ax.legend()
return fig
def ewma(self, title_txt: str, label_txt: str, days: List[int]):
fig, ax = plt.subplots(figsize=(15,9))
for day in days:
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')
self.stock_df['Adj Close'].loc[str(self.start_date):str(self.end_date)].plot(ax=ax, label=f"{label_txt}")
ax.set_title(f"{title_txt}", color = 'black', fontsize = 20)
ax.set_xlabel('Date', color = 'black', fontsize = 15)
ax.set_ylabel('Stock Price (p)', color = 'black', fontsize = 15)
ax.legend()
return fig
def tripple_ewma(self, title_txt: str, label_txt: str, short_ema_span: int, middle_ema_span: int, long_ema_span: int):
fig, ax = plt.subplots(figsize=(15,9))
ax.plot(self.stock_df['Adj Close'].loc[str(self.start_date):str(self.end_date)], label=f"{label_txt}", color = 'blue')
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')
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')
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')
ax.set_title(f"{title_txt}", color = 'black', fontsize = 20)
ax.set_xlabel('Date', color = 'black', fontsize = 15)
ax.set_ylabel('Stock Price (p)', color = 'black', fontsize = 15)
ax.legend()
return fig
def buy_sell_triple_ewma(self, data):
buy_list = []
sell_list = []
flag_long = False
flag_short = False
for i in range(0, len(data)):
if data['Middle'][i] < data['Long'][i] and data['Short'][i] < data['Middle'][i] and flag_long == False and flag_short == False:
buy_list.append(data['Adj Close'][i])
sell_list.append(np.nan)
flag_short = True
elif flag_short == True and data['Short'][i] > data['Middle'][i]:
sell_list.append(data['Adj Close'][i])
buy_list.append(np.nan)
flag_short = False
elif data['Middle'][i] > data['Long'][i] and data['Short'][i] > data['Middle'][i] and flag_long == False and flag_short == False:
buy_list.append(data['Adj Close'][i])
sell_list.append(np.nan)
flag_long = True
elif flag_long == True and data['Short'][i] < data['Middle'][i]:
sell_list.append(data['Adj Close'][i])
buy_list.append(np.nan)
flag_long = False
else:
buy_list.append(np.nan)
sell_list.append(np.nan)
return (buy_list, sell_list)
def buy_sell_ewma3_plot(self, data, label_txt: str, title_txt: str):
fig, ax = plt.subplots(figsize=(18, 10))
ax.plot(data['Adj Close'], label=f"{label_txt}", color = 'blue', alpha = 0.35)
ax.plot(data["Short"], label = 'Short/Fast EMA', color = 'red', alpha = 0.35)
ax.plot(data["Middle"], label = 'Middle/Medium EMA', color = 'orange', alpha = 0.35)
ax.plot(data["Long"], label = 'Long/Slow EMA', color = 'green', alpha = 0.35)
ax.scatter(data.index, data['Buy'], color = 'green', label = 'Buy Signal', marker = '^', alpha = 1)
ax.scatter(data.index, data['Sell'], color = 'red', label = 'Sell Signal', marker='v', alpha = 1)
ax.set_title(f"{title_txt}", color = 'black', fontsize = 20)
ax.set_xlabel('Date', color = 'black', fontsize = 15)
ax.set_ylabel('Stock Price (p)', color = 'black', fontsize = 15)
ax.legend()
return fig
def exponential_smoothing(self, series, alpha):
result = [series[0]] # first value is same as series
for n in range(1, len(series)):
result.append(alpha * series[n] + (1 - alpha) * result[n-1])
return result
def plot_exponential_smoothing(self, series, alphas, label_txt: str, title_txt: str):
fig, ax = plt.subplots(figsize=(17, 8))
for alpha in alphas:
ax.plot(self.exponential_smoothing(series, alpha), label=f"Alpha {alpha}")
ax.plot(series.values, "c", label = f"{label_txt}")
ax.set_xlabel('Days', color = 'black', fontsize = 15)
ax.set_ylabel('Stock Price (p)', color = 'black', fontsize = 15)
ax.legend(loc="best")
ax.axis('tight')
ax.set_title(f"{title_txt}", color = 'black', fontsize = 20)
ax.grid(True)
return fig
def double_exponential_smoothing(self, series, alpha, beta):
result = [series[0]]
for n in range(1, len(series)+1):
if n == 1:
level, trend = series[0], series[1] - series[0]
if n >= len(series): # forecasting
value = result[-1]
else:
value = series[n]
last_level, level = level, alpha * value + (1 - alpha) * (level + trend)
trend = beta * (level - last_level) + (1 - beta) * trend
result.append(level + trend)
return result
def plot_double_exponential_smoothing(self, series, alphas, betas, label_txt: str, title_txt: str):
fig, ax = plt.subplots(figsize=(17, 8))
for alpha in alphas:
for beta in betas:
ax.plot(self.double_exponential_smoothing(series, alpha, beta), label=f"Alpha {alpha}, beta {beta}")
ax.plot(series.values, label = f"{label_txt}")
ax.set_xlabel('Days', color = 'black', fontsize = 15)
ax.set_ylabel('Stock Price (p)', color = 'black', fontsize = 15)
ax.legend(loc="best")
ax.axis('tight')
ax.set_title(f"{title_txt}", color = 'black', fontsize = 20)
ax.grid(True)
return fig
def plot_macd_signal(self, macd, signal, macd_label_txt: str, sig_label_txt: str, title_txt: str):
fig, ax = plt.subplots(figsize=(15, 9))
ax.plot(macd, label = f"{macd_label_txt}", color= 'red')
ax.plot(signal, label = f"{sig_label_txt}", color= 'blue')
ax.set_title(f"{title_txt}", color = 'black', fontsize = 20)
ax.set_xlabel('Date', color = 'black', fontsize = 15)
ax.legend(loc='upper left')
return fig
def buy_sell_macd(self, signal):
Buy = []
Sell = []
flag = -1
for i in range(0, len(signal)):
if signal['MACD'][i] < signal['Signal Line'][i]:
Sell.append(np.nan)
if flag != 1:
Buy.append(signal['Adj Close'][i])
flag = 1
else:
Buy.append(np.nan)
elif signal['MACD'][i] > signal['Signal Line'][i]:
Buy.append(np.nan)
if flag != 0:
Sell.append(signal['Adj Close'][i])
flag = 0
else:
Sell.append(np.nan)
else:
Buy.append(np.nan)
Sell.append(np.nan)
return (Buy, Sell)
def buy_sell_macd_plot(self, data, title_txt: str):
fig, ax = plt.subplots(figsize=(20, 10))
ax.scatter(data.index, data['Buy_Signal_Price'], color='green', label='Buy', marker='^', alpha=1)
ax.scatter(data.index, data['Sell_Signal_Price'], color='red', label='Sell', marker='v', alpha=1)
ax.plot(data['Adj Close'], label='Adj Close Price', alpha = 0.35)
ax.set_title(f"{title_txt}", color = 'black', fontsize = 20)
ax.set_xlabel('Date', color = 'black', fontsize = 15)
ax.set_ylabel('Adj Close Price')
ax.legend(loc = 'upper left')
return fig
def plot_rsi(self, title_txt: str, rsi_data):
fig, ax = plt.subplots(figsize=(20, 10))
ax.set_title(f"{title_txt}", color = 'black', fontsize = 20)
ax.set_xlabel('Date', color = 'black', fontsize = 15)
ax.set_ylabel('RSI', color = 'black', fontsize = 15)
rsi_data.plot(ax=ax)
return fig
def plot_rsi_with_sma(self, data, title_txt: str):
fig, ax = plt.subplots(figsize=(20, 10))
ax.set_title(f"{title_txt}", color = 'black', fontsize = 20)
ax.plot(data['RSI'])
ax.set_xlabel('Date', color = 'black', fontsize = 15)
ax.axhline(0, linestyle='--', alpha = 0.5, color='gray')
ax.axhline(10, linestyle='--', alpha = 0.5, color='orange')
ax.axhline(20, linestyle='--', alpha = 0.5, color='green')
ax.axhline(30, linestyle='--', alpha = 0.5, color='red')
ax.axhline(70, linestyle='--', alpha = 0.5, color='red')
ax.axhline(80, linestyle='--', alpha = 0.5, color='green')
ax.axhline(90, linestyle='--', alpha = 0.5, color='orange')
ax.axhline(100, linestyle='--', alpha = 0.5, color='gray')
return fig
def plot_rsi_with_ewma(self, data, title_txt: str):
fig, ax = plt.subplots(figsize=(20, 10))
ax.set_title(f"{title_txt}", color = 'black', fontsize = 20)
ax.set_xlabel('Date', color = 'black', fontsize = 15)
ax.plot(data['RSI2'])
ax.axhline(0, linestyle='--', alpha = 0.5, color='gray')
ax.axhline(10, linestyle='--', alpha = 0.5, color='orange')
ax.axhline(20, linestyle='--', alpha = 0.5, color='green')
ax.axhline(30, linestyle='--', alpha = 0.5, color='red')
ax.axhline(70, linestyle='--', alpha = 0.5, color='red')
ax.axhline(80, linestyle='--', alpha = 0.5, color='green')
ax.axhline(90, linestyle='--', alpha = 0.5, color='orange')
ax.axhline(100, linestyle='--', alpha = 0.5, color='gray')
return fig
def plot_mfi(self, data, title_txt: str):
fig, ax = plt.subplots(figsize=(20, 10))
ax.plot(data['MFI'], label = 'MFI')
ax.axhline(10, linestyle = '--', color = 'orange')
ax.axhline(20, linestyle = '--', color = 'blue')
ax.axhline(80, linestyle = '--', color = 'blue')
ax.axhline(90, linestyle = '--', color = 'orange')
ax.set_title(f"{title_txt}", color = 'black', fontsize = 20)
ax.set_xlabel('Time periods', color = 'black', fontsize = 15)
ax.set_ylabel('MFI Values', color = 'black', fontsize = 15)
return fig
def mfi_buy_sell_plot(self, data, title_txt: str):
fig, ax = plt.subplots(figsize=(20, 10))
ax.plot(data['Close'], label = 'Close Price', alpha = 0.5)
ax.scatter(data.index, data['Buy'], color = 'green', label = 'Buy Signal', marker = '^', alpha = 1)
ax.scatter(data.index, data['Sell'], color = 'red', label = 'Sell Signal', marker = 'v', alpha = 1)
ax.set_title(f"{title_txt}", color = 'black', fontsize = 20)
ax.set_xlabel('Date', color = 'black', fontsize = 15)
ax.set_ylabel('Close Price', color = 'black', fontsize = 15)
ax.legend(loc='upper left')
return fig
def plot_stochastic_oscillator(self, data, title_txt: str):
fig, ax = plt.subplots(figsize=(20, 10))
data[['Strategy Returns','Market Returns']].cumsum().plot(ax=ax)
ax.set_title(title_txt, color = 'black', fontsize = 20)
return fig
def plot_bollinger_bands(self, data, column_list, title_txt: str):
fig, ax = plt.subplots(figsize=(20, 10))
data[column_list].plot(ax=ax)
plt.style.use('ggplot')
ax.set_title(title_txt, color = 'black', fontsize = 20)
ax.set_ylabel('Close Price', color = 'black', fontsize = 15)
return fig
def plot_bollinger_bands_shaded(self, data, title_txt: str):
fig, ax = plt.subplots(figsize=(20,10))
x_axis = data.index
ax.fill_between(x_axis, data['Upper'], data['Lower'], color='grey')
ax.plot(data['Close'], color='gold', lw=3, label = 'Close Price') #lw = line width
ax.plot(data['SMA'], color='blue', lw=3, label = 'Simple Moving Average')
ax.set_title(title_txt, color = 'black', fontsize = 20)
ax.set_xlabel('Date', color = 'black', fontsize = 15)
ax.set_ylabel('Close Price', color = 'black', fontsize = 15)
plt.xticks(rotation = 45)
ax.legend()
return fig
def plot_bollinger_bands_shaded_with_signals(self, data, title_txt: str):
fig, ax = plt.subplots(figsize=(20,10))
x_axis = data.index
ax.fill_between(x_axis, data['Upper'], data['Lower'], color='grey')
ax.plot(data['Close'], color='gold', lw=3, label = 'Close Price', alpha = 0.5)
ax.plot(data['SMA'], color='blue', lw=3, label = 'Moving Average', alpha = 0.5)
ax.scatter(x_axis, data['Buy'], color='green', lw=3, label = 'Buy', marker = '^', alpha = 1)
ax.scatter(x_axis, data['Sell'], color='red', lw=3, label = 'Sell', marker = 'v', alpha = 1)
ax.set_title(title_txt, color = 'black', fontsize = 20)
ax.set_xlabel('Date', color = 'black', fontsize = 15)
ax.set_ylabel('Close Price', color = 'black', fontsize = 15)
plt.xticks(rotation = 45)
ax.legend()
return fig
def plot_lstm_timefm_prediction(self, data, lstm_prediction):
fig, ax = plt.subplots(figsize=(17, 8))
sns.set_style('whitegrid')
ax.plot(data['Adj Close'], label='Actual Close', color='tab:blue', alpha=0.8)
ax.plot(data['pred_timesfm'], label='TimeFM Prediction', color='tab:red', alpha=0.8)
ax.plot(lstm_prediction, label="LSTM Prediction", color='tab:purple', alpha=0.8)
ax.set_title("LSTM and TimeFM Predictions vs Actual Close", fontsize=16)
ax.set_xlabel("Date", fontsize=14)
ax.set_ylabel("Price", fontsize=14)
ax.legend(fontsize=12)
ax.grid(True, linestyle='--', alpha=0.5)
plt.xticks(rotation=45)
plt.tight_layout()
return fig
def plot_obv_ema(self, data, title_txt: str):
fig, ax = plt.subplots(figsize=(17, 8))
plt.style.use('ggplot')
ax.plot(data['OBV'], label = 'OBV', color = 'orange')
ax.plot(data['OBV_EMA'], label = 'OBV_EMA', color = 'purple')
ax.set_title(title_txt, color = 'black', fontsize = 20)
ax.set_xlabel('Date', fontsize = 15)
ax.set_ylabel('Price', fontsize = 15)
ax.legend(loc = 'upper left')
return fig
def buy_sell_obv_plot(self, data, title_txt: str):
fig, ax = plt.subplots(figsize=(17, 8))
plt.style.use('ggplot')
ax.plot(data['Adj Close'], label = 'Adjusted Close', alpha = 0.5)
ax.scatter(data.index, data['Buy'], label = 'Buy Signal', marker = '^', alpha = 1, color = 'green')
ax.scatter(data.index, data['Sell'], label = 'Sell Signal', marker = 'v', alpha = 1, color = 'red')
ax.set_title(title_txt, color = 'black', fontsize = 20)
ax.set_xlabel('Date', fontsize = 15)
ax.set_ylabel('Price', fontsize = 15)
ax.legend(loc = 'upper left')
return fig
def ui_renderer(self):
st.title('Stonks 📈')
st.image('https://i.ytimg.com/vi/if-2M3K1tqk/maxresdefault.jpg')
#
# TODO: Update details with markdown and add more meta docs
#
st.write('Welcome to Stonks, a simple web app that allows you to analyze stocks.')
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])")
st.markdown("""---""")
# Sidebar Inputs
stock_names = [stock.name for stock in self.stocks]
self.selected_stock = st.sidebar.selectbox('Select a Stock:', stock_names)
self.selected_ticker = self.stocks[stock_names.index(self.selected_stock)].ticker
self.start_date = st.sidebar.date_input('Start date', date.today() - timedelta(weeks=52))
self.end_date = st.sidebar.date_input('End date', date.today())
self.stick = st.sidebar.selectbox('Stick', ["day", "week", "month", "year"])
st.sidebar.markdown("""---""")
self.rsi_period = st.sidebar.number_input('RSI Period', 14, 100, 14)
st.sidebar.markdown("""---""")
self.mfi_period = st.sidebar.number_input('MFI Period', 14, 100, 14)
self.mfi_upper_band = st.sidebar.number_input('MFI Upper Band', 50, 100, 80)
self.mfi_lower_band = st.sidebar.number_input('MFI Lower Band', 0, 50, 20)
st.sidebar.markdown("""---""")
self.stochastic_oscillator_period = st.sidebar.number_input('Stochastic Oscillator Period', 14, 100, 14)
self.stochastic_oscillator_upper_band = st.sidebar.number_input('Stochastic Oscillator Upper Band', 50, 100, 80)
self.stochastic_oscillator_lower_band = st.sidebar.number_input('Stochastic Oscillator Lower Band', 0, 50, 20)
st.sidebar.markdown("""---""")
self.roc_period = st.sidebar.number_input('ROC Period', 9, 100, 9)
st.sidebar.markdown("""---""")
self.bollinger_band_period = st.sidebar.number_input('Bollinger Band Period', 20, 100, 20)
st.sidebar.markdown("""---""")
self.on_balance_volumne_period = st.sidebar.number_input('On Balance Volumne Period', 20, 100, 20)
# Assertions for all inputs
if not self.start_date <= self.end_date:
st.error("Error: Start date must fall before end date.")
st.toast("Error: Start date must fall before end date.")
st.stop()
if not self.end_date <= date.today():
st.error("Error: End date must not be in the future.")
st.toast("Error: End date must not be in the future.")
st.stop()
st.subheader(f"Stonks Analysis on {self.selected_stock} from {self.start_date} to {self.end_date}")
self.get_stock_data()
if self.stock_df.empty:
st.error("Error: No data found for selected stock.")
st.stop()
# # Download Stock data after fetched
# st.sidebar.markdown("""---""")
# # st.sidebar.button("Download Data", self.stock_df.to_csv(f"{self.selected_stock}_data.csv", index=False, header=True, encoding='utf-8-sig'))
# st.sidebar.download_button(label="Download Data", data=self.stock_df.to_csv(index=False, header=True, encoding='utf-8-sig'), file_name=f"{self.selected_stock}_data.csv", mime='text/csv')
st.dataframe(self.stock_df)
st.header("Visualising stock data")
st.markdown("""
**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.
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.
""")
txt = f"{self.selected_stock} OHLC stock prices from {self.start_date} - {self.end_date}"
st.pyplot(self.pandas_candlestick_ohlc(self.stock_df, stick=self.stick, txt = txt))
st.header("Trend-following strategies")
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.")
st.subheader("Moving averages")
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.")
st.subheader("Simple Moving Average (SMA)")
st.markdown("""
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.
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
the price of a stock over a specified time-frame are mitigated.
""", unsafe_allow_html = True)
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]))
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]))
st.markdown("""
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.
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.
""")
st.markdown("""
### Trading Strategy
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.
""")
temp_df = self.stock_df.copy()
temp_df["20d"] = np.round(temp_df["Adj Close"].rolling(window = 20, center = False).mean(), 2)
temp_df["50d"] = np.round(temp_df["Adj Close"].rolling(window = 50, center = False).mean(), 2)
temp_df["200d"] = np.round(temp_df["Adj Close"].rolling(window = 200, center = False).mean(), 2)
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))
st.markdown("""
### Exponential Moving Average
In a Simple Moving Average, each value in the time period carries
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.
""")
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]))
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]))
st.markdown("""
### Triple Moving Average Crossover Strategy
This strategy uses three moving moving averages - short/fast, middle/medium and long/slow - and has two buy and sell signals.
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.
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.
""")
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))
temp_df = self.stock_df.copy()
temp_df["Short"] = temp_df["Adj Close"].ewm(span=5, adjust=False).mean()
temp_df["Middle"] = temp_df["Adj Close"].ewm(span=21, adjust=False).mean()
temp_df["Long"] = temp_df["Adj Close"].ewm(span=63, adjust=False).mean()
temp_df["Buy"], temp_df["Sell"] = self.buy_sell_triple_ewma(temp_df)
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"))
st.markdown("""
### Exponential Smoothing
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.
""")
# 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"))
st.markdown("""
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).
**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.
""")
# 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"))
st.markdown("""
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.
""")
st.markdown("""
### Moving Average Convergence Divergence (MACD)
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.
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.
The default parameters are 26 EMA of prices, 12 EMA of prices and a 9-moving average of the difference between the first two.
""")
short_ema = self.stock_df['Adj Close'].ewm(span=12, adjust=False).mean()
long_ema = self.stock_df['Adj Close'].ewm(span=26, adjust=False).mean()
macd = short_ema - long_ema
signal = macd.ewm(span=9, adjust=False).mean()
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"))
temp_df = self.stock_df.copy()
temp_df['MACD'] = macd
temp_df['Signal Line'] = signal
temp_df['Buy_Signal_Price'], temp_df['Sell_Signal_Price'] = self.buy_sell_macd(temp_df)
st.write("When the MACD line crosses above the signal line this indicates a good time to buy.")
st.pyplot(self.buy_sell_macd_plot(temp_df, title_txt=f"MACD Buy and Sell Signals for {self.selected_stock} stock"))
st.markdown("""
## Momentum Strategies
In momentum algorithmic trading strategies stocks have momentum (i.e. upward or downward trends) that we can detect and exploit.
### Relative Strength Index (RSI)
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.
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.
""")
def get_rsi():
temp_df = self.stock_df.copy()
delta = temp_df['Adj Close'].diff(1)
delta.dropna(inplace=True)
up, down = delta.clip(lower=0), -delta.clip(upper=0)
# Relative Strength Index
rs = up.rolling(window=self.rsi_period).mean() / down.abs().rolling(window=self.rsi_period).mean()
# Relative Strength Index with Expoential Weighted Moving Average
rs_ewma = up.ewm(span=self.rsi_period).mean() / down.abs().ewm(span=self.rsi_period).mean()
return 100 - (100 / (1 + rs)), 100 - (100 / (1 + rs_ewma))
# PLot RSI with SMA
temp_df = self.stock_df.copy()
temp_df['RSI'], temp_df['RSI2'] = get_rsi()
st.pyplot(self.plot_rsi(title_txt=f"RSI for {self.selected_stock} stock", rsi_data=temp_df["RSI"]))
st.pyplot(self.plot_rsi_with_sma(temp_df, title_txt=f"RSI with {self.rsi_period}-day SMA for {self.selected_stock} stock"))
st.pyplot(self.plot_rsi_with_ewma(temp_df, title_txt=f"RSI with {self.rsi_period}-day EWMA for {self.selected_stock} stock"))
st.markdown("""
### Money Flow Index (MFI)
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.
It is related to the Relative Strength Index (RSI) but incorporates volume, whereas the RSI only considers price.
""")
def get_mfi():
temp_df = self.stock_df.copy()
typical_price = (temp_df['High'] + temp_df['Low'] + temp_df['Close']) / 3
money_flow = typical_price * temp_df['Volume']
# Get all positive and negative money flows
positive_flow = []
negative_flow = []
# Loop through typical price
for i in range(1, len(typical_price)):
if typical_price[i] > typical_price[i-1]:
positive_flow.append(money_flow[i-1])
negative_flow.append(0)
elif typical_price[i] < typical_price[i-1]:
negative_flow.append(money_flow[i-1])
positive_flow.append(0)
else:
positive_flow.append(0)
negative_flow.append(0)
positive_mf = []
negative_mf = []
for i in range(self.mfi_period-1, len(positive_flow)):
positive_mf.append(sum(positive_flow[i + 1 - self.mfi_period : i+1]))
for i in range(self.mfi_period-1, len(negative_flow)):
negative_mf.append(sum(negative_flow[i + 1 - self.mfi_period : i+1]))
mfi = 100 * (np.array(positive_mf) / (np.array(positive_mf) + np.array(negative_mf)))
mfi = np.append([np.nan]*self.mfi_period, mfi)
return mfi
temp_df = self.stock_df.copy()
temp_df['MFI'] = get_mfi()
st.pyplot(self.plot_mfi(temp_df, title_txt=f"MFI for {self.selected_stock} stock"))
def get_mfi_signal(data, high, low):
buy_signal = []
sell_signal = []
for i in range(len(data['MFI'])):
if data['MFI'][i] > high:
buy_signal.append(np.nan)
sell_signal.append(data['Close'][i])
elif data['MFI'][i] < low:
buy_signal.append(data['Close'][i])
sell_signal.append(np.nan)
else:
sell_signal.append(np.nan)
buy_signal.append(np.nan)
return (buy_signal, sell_signal)
temp_df["Buy"], temp_df["Sell"] = get_mfi_signal(temp_df, self.mfi_upper_band, self.mfi_lower_band)
st.pyplot(self.mfi_buy_sell_plot(temp_df, title_txt=f"MFI Buy and Sell Signals for {self.selected_stock} stock"))
st.markdown("""
### Stochastic Oscillator
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.
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.
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.
""")
st.markdown("""
The stochastic oscillator is calculated using the following formula:
```python
%K = 100(C – L)/(H – L)
```
Where:
C -> the most recent closing price
L -> the low of the given previous trading sessions
H -> the highest price traded during the same given period
%K -> the current market rate for the currency pair
%D -> 3-period moving average of %K
""")
def get_stochastic_oscillator():
temp_df = self.stock_df.copy()
temp_df["L"] = temp_df['Low'].rolling(window=self.stochastic_oscillator_period).min()
temp_df["H"] = temp_df['High'].rolling(window=self.stochastic_oscillator_period).max()
temp_df['%K'] = 100*((temp_df['Close'] - temp_df['L']) / (temp_df['H'] - temp_df['L']) )
temp_df['%D'] = temp_df['%K'].rolling(window=3).mean()
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)
temp_df['Sell Exit'] = ((temp_df['%K'] > temp_df['%D']) & (temp_df['%K'].shift(1) < temp_df['%D'].shift(1)))
temp_df['Short'] = np.where(temp_df['Sell Entry'], -1, np.where(temp_df['Sell Exit'], 0, 0))
temp_df['Short'] = temp_df['Short'].fillna(method='pad')
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)
temp_df['Buy Exit'] = ((temp_df['%K'] < temp_df['%D']) & (temp_df['%K'].shift(1) > temp_df['%D'].shift(1)))
temp_df['Long'] = np.where(temp_df['Buy Entry'], 1, np.where(temp_df['Buy Exit'], 0, 0))
temp_df['Long'] = temp_df['Long'].fillna(method='pad')
temp_df['Long'][0] = 0
temp_df['Position'] = temp_df['Long'] + temp_df['Short']
temp_df['Market Returns'] = temp_df['Close'].pct_change()
temp_df['Strategy Returns'] = temp_df['Market Returns'] * temp_df['Position'].shift(1)
return temp_df
temp_df = get_stochastic_oscillator()
st.pyplot(self.plot_stochastic_oscillator(temp_df, title_txt=f"Stochastic Oscillator for {self.selected_stock} stock"))
st.markdown("""
### Rate of Change (ROC)
The ROC indicator is a pure momentum oscillator. The ROC calculation compares the current price with the price "n" periods ago e.g. when we compute the ROC of the daily price with a 9-day lag, we are simply looking at how much, in percentage, the price has gone up (or down) compared to 9 days ago. Like other momentum indicators, ROC has overbought and oversold zones that may be adjusted according to market conditions.
""")
def get_roc():
temp_df = self.stock_df.copy()
temp_df['ROC'] = ((temp_df['Adj Close'] - temp_df['Adj Close'].shift(self.roc_period)) / temp_df['Adj Close'].shift(self.roc_period) -1 ) * 100
return temp_df
temp_df = get_roc()
st.set_option('deprecation.showPyplotGlobalUse', False)
temp_df.index = pd.to_datetime(temp_df.index)
st.pyplot(mpf.plot(temp_df, type='candle', style='yahoo', figsize=(15,8), title=f"{self.selected_stock} Daily Price", volume=True))
st.markdown("""
## Volatility trading strategies
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.
### Bollinger Bands
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.
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.
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.
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.
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.
""")
def get_bollinger_bands():
temp_df = self.stock_df.copy()
temp_df['SMA'] = temp_df['Close'].rolling(window=self.bollinger_band_period).mean()
temp_df['STD'] = temp_df['Close'].rolling(window=self.bollinger_band_period).std()
temp_df['Upper'] = temp_df['SMA'] + (temp_df['STD'] * 2)
temp_df['Lower'] = temp_df['SMA'] - (temp_df['STD'] * 2)
column_list = ['Close', 'SMA', 'Upper', 'Lower']
return temp_df, column_list
temp_df, column_list = get_bollinger_bands()
st.pyplot(self.plot_bollinger_bands(temp_df, column_list, title_txt=f"Bollinger Bands for {self.selected_stock} stock"))
st.pyplot(self.plot_bollinger_bands_shaded(temp_df, title_txt=f"Shaded Bollinger Bands region for {self.selected_stock} stock"))
def get_signal_bb(data):
buy_signal = []
sell_signal = []
for i in range(len(data['Close'])):
if data['Close'][i] > data['Upper'][i]:
buy_signal.append(np.nan)
sell_signal.append(data['Close'][i])
elif data['Close'][i] < data['Lower'][i]:
sell_signal.append(np.nan)
buy_signal.append(data['Close'][i])
else:
buy_signal.append(np.nan)
sell_signal.append(np.nan)
return (buy_signal, sell_signal)
temp_df['Buy'], temp_df['Sell'] = get_signal_bb(temp_df)
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"))
st.markdown("""
## Volume Trading Strategies
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.
### On Balance Volume (OBV)
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.
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
total, and when the close is lower than the previous close, the volume is subtracted
from the running total.
""")
def get_obv():
OBV = []
OBV.append(0)
for i in range(1, len(temp_df['Adj Close'])):
if temp_df['Adj Close'][i] > temp_df['Adj Close'][i-1]:
OBV.append(OBV[-1] + temp_df.Volume[i])
elif temp_df['Adj Close'][i] < temp_df['Adj Close'][i-1]:
OBV.append(OBV[-1] - temp_df.Volume[i])
else:
OBV.append(OBV[-1])
return OBV
temp_df = self.stock_df.copy()
temp_df['OBV'] = get_obv()
temp_df['OBV_EMA'] = temp_df['OBV'].ewm(span=self.on_balance_volumne_period).mean()
st.pyplot(self.plot_obv_ema(temp_df, title_txt=f"On Balance Volume for {self.selected_stock} stock"))
def buy_sell_obv(signal, col1, col2):
sigPriceBuy = []
sigPriceSell = []
flag = -1
for i in range(0, len(signal)):
# If OBV > OBV_EMA then buy --> col1 => If OBV < OBV_EMA then sell => 'OBV_EMA'
if signal[col1][i] < signal[col2][i] and flag != 1:
sigPriceBuy.append(signal['Adj Close'][i])
sigPriceSell.append(np.nan)
flag = 1
# If OBV < OBV_EMA then sell
elif signal[col1][i] > signal[col2][i] and flag != 0:
sigPriceSell.append(signal['Adj Close'][i])
sigPriceBuy.append(np.nan)
flag = 0
else:
sigPriceSell.append(np.nan)
sigPriceBuy.append(np.nan)
return (sigPriceBuy, sigPriceSell)
temp_df['Buy'], temp_df['Sell'] = buy_sell_obv(temp_df, 'OBV', 'OBV_EMA')
st.pyplot(self.buy_sell_obv_plot(temp_df, title_txt=f"On Balance Volume Buy and Sell Signals for {self.selected_stock} stock"))
st.markdown("""---""")
# Predictions start here
st.header("Predictions")
st.markdown("""
We used TimesFM (200M parameters) and LSTM (66K parameters) for stock price prediction, achieving strong alignment with actual data. TimesFM's zero-shot performance on diverse datasets approached state-of-the-art supervised models. Discrepancies noted are expected to reduce with increased parameter size and further training.
""")
st.subheader("TimesFM (Time Series Foundation Model)")
st.markdown("""
TimesFM (200M parameters) uses long output patches to reduce error accumulation, enabling accurate long-horizon forecasts. Trained on sequences with varying prediction horizons, it excels in zero-shot predictions across diverse datasets, effectively predicting stock price movements.
""")
# ------------------------------------- Times FM ---------------------------------
temp_df = self.stock_df.copy()
temp_df.reset_index(inplace=True)
temp_df.rename(columns={'index': 'Date'}, inplace=True)
if 'pred' not in st.session_state:
st.session_state.pred = Predictions()
if "pred" in st.session_state:
st.session_state.pred.data_preprocess(
data = temp_df,
target_colm="Adj Close",
date_colm="Date",
)
stock_preds = st.session_state.pred.predict()
temp_df["pred_timesfm"] = stock_preds
fig, ax = plt.subplots(figsize=(20, 10))
temp_df[["Adj Close", "pred_timesfm"]].plot(ax=ax)
ax.set_title(f"{self.selected_stock} Price vs TimesFM Predictions", fontsize=18)
ax.set_xlabel("Date", fontsize=14)
ax.set_ylabel("Price", fontsize=14)
ax.legend(["Actual Price", "Predicted Price"], loc="upper left", fontsize=12)
ax.grid(True, linestyle='--', alpha=0.7)
st.pyplot(fig)
# -------------------------------- LSTM ------------------------------------------
st.subheader("LSTM (Long Short Term Memory)")
st.markdown("""
LSTM, equipped with 66,000 parameters, effectively captures long-term dependencies in stock market data. Its recurrent architecture enables accurate prediction of stock price movements, making it a valuable tool for financial forecasting.
""")
lstm_predictions = __lstm__(temp_df)
fig, ax = plt.subplots(figsize=(20, 10))
temp_df[["Adj Close"]].plot(ax=ax)
ax.plot(lstm_predictions, label = "LSTM", alpha = 0.5)
ax.set_title(f"{self.selected_stock} Price vs LSTM Predictions", fontsize=18)
ax.set_xlabel("Date", fontsize=14)
ax.set_ylabel("Price", fontsize=14)
ax.legend(["Actual Price", "Predicted Price"], loc="upper left", fontsize=12)
ax.grid(True, linestyle='--', alpha=0.7)
st.pyplot(fig)
# -------------------------------- TimesFM + LSTM --------------------------------
st.subheader("Comparison: TimesFM vs. LSTM for Stock Price Prediction")
st.markdown("""
While TimesFM utilizes transformer-based architecture with 200M parameters and focuses on capturing complex temporal dependencies for accurate long-horizon forecasts, LSTM, with 66,000 parameters, leverages its recurrent structure to capture long-term dependencies in stock market data, offering effective prediction of price movements.
""")
st.pyplot(self.plot_lstm_timefm_prediction(data = temp_df, lstm_prediction = lstm_predictions))
# --------------------------------------------------------------------------------
st.markdown("""---""")
st.markdown("""
## Conclusion
In conclusion, the success of stock market analysis relies on combining complementary technical indicators rather than solely relying on uniform signals. This diversification increases the chance of profitable outcomes by forming a robust system. Our comparison between TimesFM and LSTM emphasizes the importance of selecting models based on specific analytical needs. While TimesFM captures complex temporal dependencies effectively, LSTM excels in capturing long-term patterns. By integrating these insights, investors can make more informed decisions and navigate financial markets with greater confidence. """)
if __name__ == "__main__":
stonks = Stonks(stocks_filepath="Models/stocknames.csv")
stonks.ui_renderer() |