CoolFace
Apppublic

IoannisTr/Tech_Stocks_Trading_Assistant

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