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()