danielle2003 commited on
Commit
4f8e612
·
verified ·
1 Parent(s): 7a2fd6d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +793 -711
app.py CHANGED
@@ -1,397 +1,28 @@
1
  import streamlit as st
2
- import yfinance as yf
3
  import streamlit.components.v1 as components
4
-
5
- # Set the page layout
6
- st.set_page_config(layout="wide")
7
-
8
- import matplotlib.pyplot as plt
9
- import numpy as np
10
- import base64
11
  import pandas as pd
12
- import time
 
13
  from keras.models import load_model
14
  from sklearn.preprocessing import MinMaxScaler
 
 
15
 
 
 
16
 
17
- if "framework" not in st.session_state:
18
- st.session_state.framework = "gen"
19
- # Initialize state
20
- if "show_modal" not in st.session_state:
21
- st.session_state.show_modal = False
22
- if "show_overlay" not in st.session_state:
23
- st.session_state.show_overlay = False
24
- if "model" not in st.session_state:
25
- st.session_state.model = "best_bilstm_model.h5"
26
-
27
-
28
- # Loading model
29
- @st.cache_resource
30
- def load_lstm_model(path):
31
- return load_model(path)
32
-
33
-
34
- @st.cache_resource
35
- def load_data():
36
- data = yf.download("AMZN", period="4y", multi_level_index=False)
37
- data.reset_index(inplace=True)
38
- return data
39
-
40
-
41
- #################################################################################################
42
-
43
-
44
- def predict_future_prices(
45
- df: pd.DataFrame, n_future_days: int, model_path: str = st.session_state.model
46
- ) -> tuple[plt.Figure, pd.DataFrame]:
47
- # Ensure DataFrame is sorted and clean
48
- df = df.sort_values("Date").dropna(subset=["Close"])
49
- df = df.reset_index(drop=True)
50
-
51
- # Scale data
52
- scaler = MinMaxScaler()
53
- prices = df["Close"].values.reshape(-1, 1)
54
- scaled_prices = scaler.fit_transform(prices)
55
-
56
- # Load model and get timesteps
57
- model = load_lstm_model(model_path)
58
- n_steps = model.input_shape[1]
59
-
60
- # --- Calculate validation error (historical residuals) ---
61
- X_hist, y_hist = [], []
62
- for i in range(n_steps, len(scaled_prices)):
63
- X_hist.append(scaled_prices[i - n_steps : i])
64
- y_hist.append(scaled_prices[i])
65
- X_hist = np.array(X_hist)
66
- y_hist = np.array(y_hist)
67
-
68
- # Predict historical values
69
- hist_predictions = model.predict(X_hist, verbose=0)
70
-
71
- # Calculate residuals (error)
72
- hist_prices_rescaled = scaler.inverse_transform(y_hist.reshape(-1, 1)).flatten()
73
- hist_preds_rescaled = scaler.inverse_transform(
74
- hist_predictions.reshape(-1, 1)
75
- ).flatten()
76
- residuals = hist_prices_rescaled - hist_preds_rescaled
77
- error_std = np.std(residuals) # Key metric for confidence interval
78
-
79
- # --- Predict future values ---
80
- last_sequence = scaled_prices[-n_steps:]
81
- predicted = []
82
- current_sequence = last_sequence.copy()
83
-
84
- for _ in range(n_future_days):
85
- pred = model.predict(current_sequence.reshape(1, n_steps, 1), verbose=0)
86
- predicted.append(pred[0, 0])
87
- current_sequence = np.append(current_sequence[1:], [[pred[0, 0]]], axis=0)
88
-
89
- # Rescale predictions
90
- predicted_prices = scaler.inverse_transform(
91
- np.array(predicted).reshape(-1, 1)
92
- ).flatten()
93
- last_date = df["Date"].max()
94
- today = pd.Timestamp.today().normalize()
95
-
96
- if last_date >= today:
97
- start_date = last_date + pd.Timedelta(days=1)
98
- else:
99
- start_date = today # fallback to today if data is old
100
-
101
- future_dates = pd.date_range(start=start_date, periods=n_future_days)
102
-
103
- prediction_df = pd.DataFrame(
104
- {"Date": future_dates, "Predicted Price": predicted_prices}
105
- )
106
-
107
- # --- Plotting with confidence interval ---
108
- plt.rcParams["font.family"] = "Times New Roman "
109
-
110
- fig, ax = plt.subplots(figsize=(10, 6), facecolor="none")
111
- ax.patch.set_alpha(0)
112
- fig.patch.set_alpha(0)
113
-
114
- # Historical data
115
- ax.plot(df["Date"], df["Close"], label="Historical", color="cyan", linewidth=2)
116
-
117
- # Confidence interval (expanding uncertainty)
118
- days = np.arange(1, n_future_days + 1)
119
- expanding_std = error_std * np.sqrt(days)
120
- upper = predicted_prices + 1.96 * expanding_std # 95% CI
121
- lower = predicted_prices - 1.96 * expanding_std
122
-
123
- ax.fill_between(
124
- prediction_df["Date"],
125
- lower,
126
- upper,
127
- color="lightblue",
128
- alpha=0.3,
129
- label="95% Confidence Interval",
130
- )
131
-
132
- # Predicted points (magenta dots)
133
- ax.plot(
134
- prediction_df["Date"],
135
- prediction_df["Predicted Price"],
136
- label=f"Next {n_future_days-1} Days Forecast",
137
- color="magenta",
138
- linestyle="None",
139
- marker=".",
140
- markersize=5,
141
- )
142
-
143
- # ---- NEW: Trend line spanning historical + forecasted data ----
144
- # Combine historical and predicted dates/prices
145
- all_dates = np.concatenate([df["Date"].values, prediction_df["Date"].values])
146
- all_prices = np.concatenate(
147
- [df["Close"].values, prediction_df["Predicted Price"].values]
148
- )
149
-
150
- window_size = 30
151
- trend_line = pd.Series(all_prices).rolling(window=window_size, min_periods=1).mean()
152
-
153
- # Plotting the trend line (blue dashed)
154
- ax.plot(
155
- all_dates,
156
- trend_line,
157
- color="blue",
158
- linestyle="--",
159
- linewidth=1.5,
160
- label="Long-Term Trend",
161
- )
162
-
163
- # Style
164
- ax.set_title(
165
- f"📈 Stock Price Forecast ({st.session_state.model})",
166
- fontsize=14,
167
- fontweight="bold",
168
- )
169
- ax.set_xlabel("Date", fontsize=12)
170
- ax.set_ylabel("Price", fontsize=12)
171
- ax.legend(loc="upper left")
172
- ax.grid(True, linestyle="--", alpha=0.6)
173
-
174
- return fig, prediction_df
175
-
176
-
177
- #####################################################################################################
178
-
179
- # Function to load data
180
-
181
-
182
- # Load the data
183
- # data = load_data()
184
- # import matplotlib.pyplot as plt
185
- # Path to your logo image
186
- encoded_logo = "tensorflow.png"
187
- main_bg_ext = "png"
188
- main_bg = "Picture3.png"
189
-
190
-
191
- if st.session_state.framework == "lstm":
192
- bg_color = "#FF5733" # For example, a warm red/orange
193
- bg_color_iv = "orange" # For example, a warm red/orange
194
- text_h1 = "BI-DIRECTIONAL"
195
- text_i = "Long short term memory"
196
- model = "TENSORFLOW"
197
- st.session_state.model = "best_bilstm_model.h5"
198
- if st.session_state.framework == "gru":
199
- bg_color = "#FF5733" # For example, a warm red/orange
200
- bg_color_iv = "orange" # For example, a warm red/orange
201
- text_h1 = "GATED RECURRENT UNIT"
202
- text_i = "Recurrent Neural Network"
203
- model = "TENSORFLOW"
204
- st.session_state.model = "best_gru_model.h5"
205
- if st.session_state.framework == "gen":
206
- bg_color = "#FF5733" # For example, a warm red/orange
207
- bg_color_iv = "orange" # For example, a warm red/orange
208
- text_h1 = "Amazon Stock Predictor"
209
- text_i = "21 Days Ahead of the Market"
210
- model = "TENSORFLOW"
211
- st.markdown(
212
- f"""
213
  <style>
214
- /* Container for logo and text */
215
- /* Container for logo and text */
216
- .logo-text-container {{
217
- position: fixed;
218
- top: 20px; /* Adjust vertical position */
219
- left: 30px; /* Align with sidebar */
220
- display: flex;
221
- align-items: center;
222
- gap: 25px;
223
- width: 70%;
224
- z-index:1000;
225
- }}
226
-
227
- /* Logo styling */
228
- .logo-text-container img {{
229
- width: 50px; /* Adjust logo size */
230
- border-radius: 10px; /* Optional: round edges */
231
- margin-left:-5px;
232
- margin-top: -15px;
233
-
234
- }}
235
-
236
- /* Bold text styling */
237
- .logo-text-container h1 {{
238
- font-family: Nunito;
239
- color: #0175C2;
240
- font-size: 25px;
241
- font-weight: bold;
242
- margin-right :100px;
243
- padding:0px;
244
- top:0;
245
- margin-top: -12px;
246
- }}
247
- .logo-text-container i{{
248
- font-family: Nunito;
249
- color: orange;
250
- font-size: 15px;
251
- margin-right :10px;
252
- padding:0px;
253
- margin-left:-18.5%;
254
- margin-top:1%;
255
- }}
256
-
257
- /* Sidebar styling */
258
- section[data-testid="stSidebar"][aria-expanded="true"] {{
259
- margin-top: 100px !important; /* Space for the logo */
260
- border-radius: 0 60px 0px 60px !important; /* Top-left and bottom-right corners */
261
- width: 200px !important; /* Sidebar width */
262
- background: none; /* No background */
263
- color: white !important;
264
- }}
265
-
266
- header[data-testid="stHeader"] {{
267
- background: transparent !important;
268
- margin-right: 100px !important;
269
- margin-top: 1px !important;
270
- z-index: 1 !important;
271
-
272
- color: blue; /* White text */
273
- font-family: "Times New Roman " !important; /* Font */
274
- font-size: 18px !important; /* Font size */
275
- font-weight: bold !important; /* Bold text */
276
- padding: 10px 20px; /* Padding for buttons */
277
- border: none; /* Remove border */
278
- border-radius: 35px; /* Rounded corners */
279
- box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.2); /* Shadow effect */
280
- transition: all 0.3s ease-in-out; /* Smooth transition */
281
- display: flex;
282
- align-items: center;
283
- justify-content: center;
284
- margin: 10px 0;
285
- width:90%;
286
- left:5.5%;
287
- height:60px;
288
- margin-top:70px;
289
- backdrop-filter: blur(10px);
290
- border: 2px solid rgba(255, 255, 255, 0.4); /* Light border */
291
-
292
- }}
293
-
294
- div[data-testid="stDecoration"] {{
295
- background-image: none;
296
- }}
297
-
298
- div[data-testid="stApp"] {{
299
- background: url(data:image/{main_bg_ext};base64,{base64.b64encode(open(main_bg, "rb").read()).decode()});
300
- background-size: cover; /* Ensure the image covers the full page */
301
- background-position: center;
302
- background-repeat:no-repeat;
303
- height: 98vh;
304
- width: 99.3%;
305
- border-radius: 20px !important;
306
- margin-left: 5px;
307
- margin-right: 20px;
308
- margin-top: 10px;
309
- overflow: hidden;
310
- backdrop-filter: blur(10px); /* Glass effect */
311
- -webkit-backdrop-filter: blur(10px);
312
- border: 1px solid rgba(255, 255, 255, 0.2); /* Light border */
313
-
314
- }}
315
-
316
- div[data-testid="stSidebarNav"] {{
317
- display: none;
318
- }}
319
-
320
- div[data-testid="stSlider"] {{
321
- margin-top:35px;
322
- }}
323
- label[data-testid="stWidgetLabel"]{{
324
- margin-left:20px !important;
325
- }}
326
-
327
- div[data-baseweb="slider"] {{
328
- border-radius: 30px;
329
- padding-right:40px;
330
- z-index: 1;
331
- /* Glass effect background */
332
- backdrop-filter: blur(2px);
333
- -webkit-backdrop-filter: blur(12px);
334
- /* Shiny blue borders (left & right) */
335
- border-top: 2px solid rgba(255, 255, 155, 0.4); /* Light border */
336
- margin-left:13px;
337
- border-bottom: 2px solid rgba(255, 255, 155, 0.4); /* Light border */
338
-
339
-
340
- }}
341
- div[data-baseweb="slider"] > :nth-child(1)> div {{
342
- margin-left:20px !important;
343
- margin-top:10px;
344
- }}
345
- div[data-testid="stSliderTickBarMin"]{{
346
- background:none !important;
347
- margin-left:20px !important;
348
- font-size:12px;
349
- margin-bottom:5px;
350
- font-family: "Times New Roman " !important; /* Font */
351
- }}
352
- div[data-testid="stSliderTickBarMax"]{{
353
- background:none !important;
354
- font-size:12px;
355
- margin-bottom:5px;
356
-
357
- font-family: "Times New Roman " !important; /* Font */
358
- }}
359
- div[data-testid="stSliderThumbValue"]{{
360
- font-size:12px;
361
- font-family: "Times New Roman " !important; /* Font */
362
-
363
- }}
364
- div[data-testid="stProgress"]{{
365
- margin-right:25px;
366
- margin-top:-70px;
367
- height:10px !important;
368
-
369
- }}
370
- [class*="st-key-content-container-3"] {{
371
- margin-top: 80px; /* Adjust top margin */
372
- marging-left:50px !important;
373
- color:orange;
374
-
375
- }}
376
-
377
- /* Button row styling */
378
- .button-row {{
379
- display: flex;
380
- justify-content: flex-start;
381
- gap: 20px;
382
- margin-bottom: 20px;
383
- }}
384
-
385
-
386
-
387
- .custom-button:hover {{
388
- background-color: #0056b3;
389
- }}
390
- div.stButton > button p{{
391
- color: orange !important;
392
- font-weight:bold;
393
- }}
394
- div.stButton > button {{
395
  background: rgba(255, 255, 255, 0.2);
396
  color: orange !important; /* White text */
397
  font-family: "Times New Roman " !important; /* Font */
@@ -406,358 +37,809 @@ st.markdown(
406
  align-items: center;
407
  justify-content: center;
408
  margin: 10px 0;
409
- width:160px;
410
  height:50px;
411
  margin-top:5px;
412
-
413
- }}
414
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
415
  /* Hover effect */
416
- div.stButton > button:hover {{
417
  background: rgba(255, 255, 255, 0.2);
418
  box-shadow: 0px 6px 12px rgba(0, 0, 0, 0.4); /* Enhanced shadow on hover */
419
  transform: scale(1.05); /* Slightly enlarge button */
420
  transform: scale(1.1); /* Slight zoom on hover */
421
  box-shadow: 0px 4px 12px rgba(255, 255, 255, 0.4); /* Glow effect */
422
- }}
423
-
424
- div[data-testid="stMarkdownContainer"] p {{
425
- font-family: "Times New Roman" !important; /* Elegant font for title */
426
- color:black !important;
427
-
428
- }}
429
- .titles{{
430
- margin-top:-50px !important;
431
- margin-left:-40px;
432
- font-family: "Times New Roman" !important;
433
-
434
- }}
435
- .header {{
436
- display: flex;
437
- align-items: center;
438
- gap: 20px; /* Optional: adds space between image and text */
439
- }}
440
- .header img {{
441
- height: 120px; /* Adjust as needed */
442
- margin-top:-15px;
443
- }}
444
- /* Title styling */
445
- .header h1{{
446
- font-family: "Times New Roman" !important; /* Elegant font for title
447
- font-size: 2.7rem;
448
- font-weight: bold;
449
- margin-left: 5px;
450
- /* margin-top:-50px;*/
451
- margin-bottom:30px;
452
- padding: 0;
453
- color: black; /* Neutral color for text */
454
- }}
455
- .titles .content{{
456
- font-family: "Times New Roman" !important; /* Elegant font for title */
457
- font-size: 1.2rem;
458
- margin-left: 150px;
459
- margin-bottom:1px;
460
- padding: 0;
461
- color:black; /* Neutral color for text */
462
- }}
463
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
464
 
 
 
 
 
 
 
 
465
 
 
466
 
467
- </style>
468
-
469
- """,
470
- unsafe_allow_html=True,
471
- )
472
- # Overlay container
473
- st.markdown(
474
- f"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
475
  <style>
476
- .logo-text-containers {{
477
- position: absolute;
478
- top: 100px;
479
- right: 40px;
480
- background-color: rgba(255, 255, 255, 0.9);
481
- padding: 15px;
482
- border-radius: 12px;
483
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
484
- z-index: 10;
485
- width:80vw;
486
- height:620px;
487
- }}
488
- .logo-text-containers img {{
489
- height: 40px;
490
- right:0;
491
  }}
492
- .logo-text-containers h1 {{
493
- display: inline;
494
- font-size: 20px;
495
- vertical-align: middle;
496
  }}
497
- .logo-text-containers i {{
498
- display: block;
499
- margin-top: 5px;
500
- font-size: 14px;
501
- color: #333;
502
  }}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
503
 
504
- [class*="st-key-close-btn"] {{
505
- top: 5px;
506
- font-size: 20px;
507
- font-weight: bold !important;
508
- cursor: pointer;
509
- position:absolute;
510
- margin-left:1150px;
511
- color:red !important;
512
- z-index:1000;
513
- }}
514
- [class*="st-key-close-btn"]:hover {{
515
- color: #f00;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
516
  }}
517
- [class*="st-key-divider-col"] {{
518
- border-left: 3px solid rgba(255, 255, 155, 0.5); /* Light border */
519
- border-radius: 35px; /* Rounded corners */
520
- margin-top:-18px;
521
- margin-left:3px;
522
 
 
 
 
 
 
 
 
 
 
 
 
 
 
523
  }}
524
- [class*="st-key-col1"] {{
525
- border-right: 3px solid rgba(255, 255, 155, 0.5); /* Light border */
526
- border-radius: 35px; /* Rounded corners */
527
- margin-left:20px;
528
- margin-top:-18px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
529
 
 
 
530
  }}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
531
 
532
- [class*="st-key-logo-text-containers"] {{
533
- margin: 10px; /* Set a margin inside the container */
534
- max-width: 100%;
535
- overflow: hidden;
536
-
537
- position: absolute;
538
- top:-20px;
539
- left:10px;
540
- overflow: hidden;
541
- background-color: tansparent;
542
- padding: 15px;
543
- border-radius: 30px;
544
- padding-right:40px;
545
- z-index: 1;
546
- width:88vw;
547
- height:615px;
548
- /* Glass effect background */
549
- background: rgba(255, 255, 255, 0.15);
550
- backdrop-filter: blur(12px);
551
- -webkit-backdrop-filter: blur(12px);
552
- /* Shiny blue borders (left & right) */
553
- border-left: 3px solid rgba(255, 255, 255, 0.9); /* Light border */
554
- border-right: 3px solid rgba(255, 255, 255, 0.9); /* Light border */
555
 
 
 
 
556
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
557
  }}
558
-
559
- @media (max-width: 768px) {{
560
- .logo-text-container h1 {{
561
- font-size: 12px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
562
 
563
- }}
564
- .logo-text-container i {{
565
- font-size: 10px;
566
- ma
567
- }}
568
-
569
-
570
- .logo-text-container img {{
571
- width: 30px; /* Adjust logo size */
572
- border-radius: 10px; /* Optional: round edges */
573
- margin-left:15px;
574
- margin-top: -35px;
575
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
576
  }}
577
-
578
- }}
579
- </style>
580
- """,
581
- unsafe_allow_html=True,
582
- )
583
-
584
- if st.session_state.show_overlay:
585
-
586
- with st.container(key="logo-text-containers"):
587
- if st.button("✕", key="close-btn"):
588
- st.session_state.show_overlay = False
589
- st.session_state.framework = "gen"
590
- st.rerun()
591
- with st.spinner("Downloading and processing the Data..."):
592
- progress_bar = st.progress(0)
593
- for i in range(1, 11):
594
- time.sleep(0.6)
595
- progress_bar.progress(i * 10)
596
- with st.container(key="content"):
597
- days = st.slider(
598
- "Amazon Stock Insight: Predictive Analytics Over 21 Days",
599
- 1,
600
- 21,
601
- 7,
602
- key="days_slider",
603
- )
604
 
605
- col1, col2 = st.columns([2.5, 3])
606
- data = load_data()
607
- if data is not None and not data.empty:
608
- fig, future_data = predict_future_prices(
609
- data, days+1, st.session_state.model
610
- )
611
- with col1:
612
- with st.container(key="col1"):
613
- future_data["Date"] = future_data["Date"].dt.strftime("%Y-%m-%d")
614
- future_data = future_data[1:]
615
- styled_html = (
616
- future_data.style.set_table_attributes('class="glass-table"')
617
- .set_table_styles(
618
- [
619
- {
620
- "selector": "th",
621
- "props": [
622
- ("padding", "12px"),
623
- ("color", "#000"),
624
- (
625
- "background-color",
626
- "rgba(255, 255, 255, 0.15)",
627
- ),
628
- ],
629
- },
630
- {
631
- "selector": "td",
632
- "props": [
633
- ("padding", "10px"),
634
- ("color", "#000"),
635
- ("border-bottom", "1px solid rgba(0,0,0,0.1)"),
636
- ],
637
- },
638
- {
639
- "selector": "table",
640
- "props": [
641
- ("width", "100%"),
642
- ("border-collapse", "collapse"),
643
- ],
644
- },
645
- ]
646
- )
647
- .to_html()
648
- )
649
-
650
-
651
- # Glassmorphism CSS + vertical scroll + black text
652
- glass_css = """
653
- <style>
654
- /* Outer shell for glass effect & border radius */
655
- .outer-glass-wrapper {
656
- backdrop-filter: blur(10px);
657
- -webkit-backdrop-filter: blur(10px);
658
- background: rgba(255, 255, 255, 0.15);
659
- border-radius: 20px;
660
- box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.2);
661
- max-height: 600px;
662
- max-width: 800px;
663
- overflow: hidden;
664
- margin-right: 15px;
665
- margin-left:3px;
666
- font-family: "Times New Roman " !important; /* Font */
667
- font-size: 14px;
668
- border: 1px solid rgba(255, 255, 255, 0.2);
669
- margin-bottom:30px;
670
- }
671
-
672
- /* Inner scrolling container */
673
- .glass-container {
674
- max-height: 400px;
675
- overflow-y: auto;
676
- padding: 16px 24px 16px 16px; /* right padding gives room for scrollbar */
677
- }
678
-
679
- /* Scrollbar styles */
680
- .glass-container::-webkit-scrollbar {
681
- width: 4px;
682
- }
683
- .glass-container::-webkit-scrollbar-track {
684
- background: transparent;
685
- }
686
- .glass-container::-webkit-scrollbar-thumb {
687
- background-color: rgba(0, 0, 0, 0.3);
688
- border-radius: 10px;
689
- }
690
- .glass-container {
691
- scrollbar-width: thin;
692
- scrollbar-color: rgba(0, 0, 0, 0.3) transparent;
693
- }
694
-
695
- /* Table styling */
696
- .glass-table {
697
- width: 100%;
698
- }
699
- .glass-table th, .glass-table td {
700
- text-align: left;
701
- white-space: nowrap;
702
- color: #000;
703
- }
704
- </style>
705
- """
706
-
707
- st.markdown(glass_css, unsafe_allow_html=True)
708
- st.markdown(
709
- f""" <div class="outer-glass-wrapper">
710
- <div class="glass-container">
711
- {styled_html}</div> </div>
712
- """,
713
- unsafe_allow_html=True,
714
- )
715
-
716
- with col2:
717
- with st.container(key="divider-col"):
718
- st.pyplot(fig)
719
 
720
- else:
721
- st.error("No data loaded. Please check your internet connection.")
722
- # Show overlay if triggered
723
- st.markdown(
724
- f""" <div class="logo-text-container">
725
- <img src="data:image/png;base64,{base64.b64encode(open("tensorflow.png","rb").read()).decode()}" alt="Uploaded Image">
726
- <h1>{text_h1}<br></h1>
727
- <i>{ text_i}</i>
728
- </div>
729
-
730
- """,
731
- unsafe_allow_html=True,
732
- )
733
-
734
-
735
- st.markdown(
736
- f""" <div class="titles">
737
- <div class = "header">
738
- <img src="data:image/png;base64,{base64.b64encode(open("logo2.png","rb").read()).decode()}" alt="Uploaded Image">
739
- <h1></br>ACTIONS </br> TREND ANALYTICS</h1>
740
- </div>
741
- <div class="content">
742
- A deep learning-powered tool that analyzes Amazon's stock trends.<br>
743
- the models(BI-Direcional Lstm and GRU) predicts future market<br> actions based on past trends,
744
- providing a confidence score to <br> help users interpret the data more accurately and take timely actions.
745
- </div>
746
- </div>
747
- """,
748
- unsafe_allow_html=True,
749
- )
750
-
751
-
752
- with st.container(key="content-container-3"):
753
- col1, col2 = st.columns([1.5, 10.5])
754
- with col1:
755
- if st.button(" BIDIR-LSTM"):
756
- st.session_state.framework = "lstm"
757
- st.session_state.show_overlay = True
758
- st.rerun()
759
- with col2:
760
- if st.button("GRU"):
761
- st.session_state.framework = "gru"
762
- st.session_state.show_overlay = True
763
- st.rerun()
 
 
 
 
 
 
 
1
  import streamlit as st
 
2
  import streamlit.components.v1 as components
3
+ import yfinance as yf
 
 
 
 
 
 
4
  import pandas as pd
5
+ import numpy as np
6
+ from datetime import datetime, timedelta
7
  from keras.models import load_model
8
  from sklearn.preprocessing import MinMaxScaler
9
+ import time
10
+ import os
11
 
12
+ # --- Page Configuration ---
13
+ st.set_page_config(layout="wide")
14
 
15
+ # --- Custom CSS ---
16
+ st.markdown("""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  <style>
18
+ /* Hide Streamlit's default header, footer, and hamburger menu */
19
+ #MainMenu, header, footer { visibility: hidden; }
20
+
21
+ /* Remove padding from the main block container for a full-width feel */
22
+ .block-container {
23
+ padding: 0 !important;
24
+ }
25
+ div.stButton > button {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  background: rgba(255, 255, 255, 0.2);
27
  color: orange !important; /* White text */
28
  font-family: "Times New Roman " !important; /* Font */
 
37
  align-items: center;
38
  justify-content: center;
39
  margin: 10px 0;
40
+ width:190px;
41
  height:50px;
42
  margin-top:5px;
43
+ }
44
+ div[data-testid="stSelectbox"]
45
+ {
46
+ background-color: white !important;
47
+ position: relative;
48
+ border-bottom:1px solid #ccc;
49
+ border-radius:0px;
50
+
51
+
52
+
53
+ }
54
+ div[data-testid="stTextInput"]{
55
+
56
+ }
57
+ div[data-testid="stTextInput"] > div >div {
58
+ background-color: rgba(255, 158, 87, 0.12) !important;
59
+
60
+ }
61
+ div[data-testid="stTextInputRootElement"]{
62
+ border: 1px solid white !important;
63
+ }
64
  /* Hover effect */
65
+ div.stButton > button:hover {
66
  background: rgba(255, 255, 255, 0.2);
67
  box-shadow: 0px 6px 12px rgba(0, 0, 0, 0.4); /* Enhanced shadow on hover */
68
  transform: scale(1.05); /* Slightly enlarge button */
69
  transform: scale(1.1); /* Slight zoom on hover */
70
  box-shadow: 0px 4px 12px rgba(255, 255, 255, 0.4); /* Glow effect */
71
+ }
72
+ /* Style the sidebar to have a modern, dark look */
73
+ section[data-testid="stSidebar"] {
74
+ backdrop-filter: blur(10px);
75
+ background: rgba(255, 255, 255, 0.15);
76
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
77
+
78
+ [data-testid="stSidebar"] h2 {
79
+ color: #FFFFFF; /* White headers in the sidebar */
80
+ font-family:time new roman !important;
81
+
82
+ }
83
+ [data-testid="stSidebar"] .st-emotion-cache-1629p8f a {
84
+ color: #94A3B8; /* Lighter text color for links */
85
+ font-family:time new roman !important;
86
+ }
87
+ [data-testid="stImageContainer"]>img{
88
+ max-width:70% !important;
89
+ margin-top:-70px;
90
+ }
91
+ div[data-testid="stMarkdownContainer"] >p{
92
+ font-family:time new roman !important;
93
+
94
+ }
95
+
96
+ </style>
97
+ """, unsafe_allow_html=True)
98
+
99
+ # --- Python Backend Functions ---
100
+
101
+ @st.cache_resource(ttl=3600)
102
+ def load_pytorch_model(path, model_type='Bi-Directional LSTM', input_dim=1, hidden_dim=100, num_layers=2, output_dim=1, dropout_prob=0.2):
103
+ import torch.nn as nn
104
+ import torch
105
+
106
+ class GRUModel(nn.Module):
107
+ def __init__(self):
108
+ super(GRUModel, self).__init__()
109
+ self.gru = nn.GRU(input_dim, hidden_dim, num_layers, batch_first=True, dropout=dropout_prob)
110
+ self.fc = nn.Linear(hidden_dim, output_dim)
111
+
112
+ def forward(self, x):
113
+ h0 = torch.zeros(num_layers, x.size(0), hidden_dim).to(x.device)
114
+ out, _ = self.gru(x, h0)
115
+ return self.fc(out[:, -1, :])
116
+
117
+ class BiLSTMModel(nn.Module):
118
+ def __init__(self):
119
+ super(BiLSTMModel, self).__init__()
120
+ self.lstm = nn.LSTM(
121
+ input_size=1,
122
+ hidden_size=100,
123
+ num_layers=1, # <- match saved model
124
+ batch_first=True,
125
+ dropout=0.2,
126
+ bidirectional=True
127
+ )
128
+ self.fc = nn.Linear(200, 1) # 2 * hidden_size because of bidirectional
129
 
130
+ def forward(self, x):
131
+ h0 = torch.zeros(2 * 1, x.size(0), 100)
132
+ c0 = torch.zeros(2 * 1, x.size(0), 100)
133
+ out, _ = self.lstm(x, (h0, c0))
134
+ return self.fc(out[:, -1, :])
135
+ model_class = BiLSTMModel if model_type == 'Bi-Directional LSTM' else GRUModel
136
+ model = model_class()
137
 
138
+ checkpoint = torch.load(path, map_location=torch.device('cpu'))
139
 
140
+ # If full checkpoint was saved with keys like 'model_state_dict'
141
+ if 'model_state_dict' in checkpoint:
142
+ model.load_state_dict(checkpoint['model_state_dict'])
143
+ else:
144
+ model.load_state_dict(checkpoint) # Just raw state_dict
145
+
146
+ model.eval()
147
+ return model
148
+
149
+ @st.cache_data(ttl=900) # Cache data for 15 minutes
150
+ def get_stock_data(ticker):
151
+ """Fetches historical stock data from Yahoo Finance for the last 4 years."""
152
+ end_date = datetime.now()
153
+ start_date = end_date - timedelta(days=4 * 365)
154
+ print(f"Fetching data for ticker: {ticker} from {start_date.date()} to {end_date.date()}")
155
+ data = yf.download(ticker, period="4y", multi_level_index=False)
156
+ if data.empty:
157
+ print(f"No data found for ticker: {ticker}")
158
+ return None
159
+ data.reset_index(inplace=True)
160
+ print(f"Successfully fetched {len(data)} rows for {ticker}")
161
+ return data
162
+
163
+ def predict_with_model(data: pd.DataFrame, n_days: int, model_path: str, model_type: str) -> pd.DataFrame:
164
+ import torch
165
+
166
+ try:
167
+ model = load_pytorch_model(model_path, model_type=model_type)
168
+ except FileNotFoundError as e:
169
+ raise e
170
+ print("model:",model)
171
+ close_prices = data['Close'].values.reshape(-1, 1)
172
+ scaler = MinMaxScaler(feature_range=(0, 1))
173
+ scaled_prices = scaler.fit_transform(close_prices)
174
+
175
+ sequence_length = 90
176
+ if len(scaled_prices) < sequence_length:
177
+ raise ValueError(f"Not enough historical data ({len(scaled_prices)} points) to create a sequence of {sequence_length} for prediction.")
178
+
179
+ last_sequence = scaled_prices[-sequence_length:]
180
+ current_seq = torch.tensor(last_sequence.reshape(1, sequence_length, 1), dtype=torch.float32)
181
+
182
+ predictions_scaled = []
183
+ with torch.no_grad():
184
+ for _ in range(n_days):
185
+ pred = model(current_seq)
186
+ predictions_scaled.append(pred.item())
187
+ next_input = pred.view(1, 1, 1)
188
+ current_seq = torch.cat((current_seq[:, 1:, :], next_input), dim=1)
189
+
190
+ predictions = scaler.inverse_transform(np.array(predictions_scaled).reshape(-1, 1)).flatten()
191
+ print("predictions",predictions)
192
+ last_date = pd.to_datetime(data['Date'].iloc[-1])
193
+ future_dates = [last_date + timedelta(days=i) for i in range(1, n_days + 1)]
194
+
195
+ prediction_df = pd.DataFrame({'Date': future_dates, 'Predicted Price': predictions})
196
+
197
+ historical_returns = data['Close'].pct_change().dropna()
198
+ volatility = historical_returns.std() if not historical_returns.empty else 0.01
199
+
200
+ error_std_growth = volatility * np.sqrt(np.arange(1, n_days + 1))
201
+ prediction_df['Upper CI'] = predictions * (1 + 1.96 * error_std_growth)
202
+ prediction_df['Lower CI'] = predictions * (1 - 1.96 * error_std_growth)
203
+
204
+ return prediction_df
205
+
206
+
207
+ # --- Streamlit Session State Initialization ---
208
+ if 'run_button_clicked' not in st.session_state:
209
+ st.session_state.run_button_clicked = False
210
+ if 'loading' not in st.session_state:
211
+ st.session_state.loading = False
212
+ if 'data' not in st.session_state:
213
+ st.session_state.data = None
214
+ if 'predictions' not in st.session_state:
215
+ st.session_state.predictions = None
216
+ if 'error' not in st.session_state:
217
+ st.session_state.error = None
218
+
219
+ # --- Streamlit Sidebar Controls ---
220
+ with st.sidebar:
221
+ st.image("logo2.png", use_container_width=True)
222
+ st.markdown("Dashboard Controls")
223
+ ticker = st.text_input("Stock Ticker", st.session_state.get('last_ticker', "AMZN")).upper()
224
+ model_type = st.selectbox(
225
+ "Prediction Model",
226
+ ("Bi-Directional LSTM", "Gated Recurrent Unit (GRU)"),
227
+ key="model_choice",
228
+ help="Select the neural network architecture for prediction."
229
+ )
230
+
231
+ prediction_days = st.slider("Prediction Horizon (Days)", 7, 30, st.session_state.get('last_prediction_days', 7))
232
+
233
+ if st.button("Generate Dashboard", use_container_width=True):
234
+ st.session_state.run_button_clicked = True
235
+ st.session_state.loading = True
236
+ st.session_state.last_ticker = ticker
237
+ st.session_state.last_prediction_days = prediction_days
238
+ st.session_state.error = None
239
+ print("Generate Dashboard button clicked. Loading state set to True.")
240
+ st.rerun()
241
+ # Check if model or prediction days have changed
242
+ if (
243
+ ticker != st.session_state.get('last_ticker', '') or
244
+ model_type != st.session_state.get('last_model_type', '') or
245
+ prediction_days != st.session_state.get('last_prediction_days', 7)
246
+ ):
247
+ st.session_state.run_button_clicked = True
248
+ st.session_state.loading = True
249
+ st.session_state.last_ticker = ticker
250
+ st.session_state.last_model_type = model_type
251
+ st.session_state.last_prediction_days = prediction_days
252
+ st.rerun()
253
+ # --- Main Application Logic ---
254
+ if st.session_state.run_button_clicked:
255
+ print(f"Inside main logic block. Current loading state: {st.session_state.loading}")
256
+ try:
257
+ st.session_state.data = get_stock_data(ticker)
258
+
259
+ if st.session_state.data is None:
260
+ st.session_state.error = f"Could not fetch data for ticker '{ticker}'. It may be an invalid symbol or network issue."
261
+ else:
262
+ model_path = "best_bilstm_model.pth" if model_type == "Bi-Directional LSTM" else "best_gru_model.pth"
263
+ st.session_state.predictions = predict_with_model(st.session_state.data, prediction_days, model_path,model_type)
264
+ st.session_state.error = None
265
+
266
+ except FileNotFoundError as e:
267
+ st.session_state.error = str(e)
268
+ print(f"Caught FileNotFoundError: {e}")
269
+ except ValueError as e:
270
+ st.session_state.error = str(e)
271
+ print(f"Caught ValueError: {e}")
272
+ except Exception as e:
273
+ st.session_state.error = f"An unexpected error occurred: {str(e)}"
274
+ print(f"Caught general Exception: {e}")
275
+
276
+ st.session_state.loading = False
277
+ st.session_state.run_button_clicked = False
278
+ print(f"Processing complete. Loading state set to False. Error: {st.session_state.error}")
279
+ st.rerun()
280
+
281
+ # --- Data Preparation for Front-End ---
282
+ historical_data_json = 'null'
283
+ prediction_data_json = 'null'
284
+ is_loading_js = str(st.session_state.get('loading', False)).lower()
285
+ error_message_js = 'null'
286
+
287
+ if st.session_state.get('error'):
288
+ error_message_js = f"'{st.session_state.error}'" # Pass error to JS
289
+
290
+ if st.session_state.data is not None and st.session_state.get('error') is None:
291
+ historical_data_json = st.session_state.data.to_json(orient='split', date_format='iso')
292
+ prediction_data_json = st.session_state.predictions.to_json(orient='split', date_format='iso')
293
+
294
+ # --- HTML Front-End ---
295
+ html_code = f"""
296
+ <!DOCTYPE html>
297
+ <html lang="en">
298
+ <head>
299
+ <meta charset="UTF-8">
300
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
301
+ <title>Stock Intelligence Dashboard</title>
302
+ <script src="https://cdn.tailwindcss.com"></script>
303
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.js"></script>
304
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/chartjs-adapter-date-fns.bundle.min.js"></script>
305
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
306
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css">
307
  <style>
308
+ body {{ font-family: 'time new roman'; background-color: #f1f5f9;scrollbar-width: 2px !important; scrollbar-color: rgba(100, 100, 100, 0.4) transparent;}}
309
+ .metric-card, .info-card {{ background-color: #ffffff; border-radius: 1rem; box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); transition: all 0.3s ease-in-out; border: 1px solid #e2e8f0; }}
310
+ .metric-card:hover {{ transform: translateY(-5px); box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); }}
311
+ .positive {{ color: #10B981; }}
312
+ .negative {{ color: #EF4444; }}
313
+ .neutral {{ color: #64748b; }}
314
+ ::-webkit-scrollbar {{
315
+ width: 6px;
 
 
 
 
 
 
 
316
  }}
317
+
318
+ ::-webkit-scrollbar-thumb {{
319
+ background-color: rgba(100, 100, 100, 0.4);
320
+ border-radius: 3px;
321
  }}
322
+
323
+ ::-webkit-scrollbar-track {{
324
+ background: transparent;
 
 
325
  }}
326
+ #predictionTable table {{ width: 100%; border-collapse: collapse; }}
327
+ #predictionTable th, #predictionTable td {{ padding: 0.75rem 1rem; text-align: left; border-bottom: 1px solid #e2e8f0; }}
328
+ #predictionTable th {{ background-color: #f8fafc; font-weight: 600; font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.05em; color: #64748b; }}
329
+ #loading-overlay {{ position: fixed; inset: 0; background-color: rgba(255, 255, 255, 0.8); z-index: 100; display: flex; align-items: center; justify-content: center; backdrop-filter: blur(4px); transition: opacity 0.3s ease; }}
330
+ .spinner {{ width: 56px; height: 56px; border: 5px solid #3b82f6; border-bottom-color: transparent; border-radius: 50%; display: inline-block; box-sizing: border-box; animation: spin 1s linear infinite; }}
331
+ @keyframes spin {{ 0% {{ transform: rotate(0deg); }} 100% {{ transform: rotate(360deg); }} }}
332
+ .hidden {{ display: none !important; }}
333
+ .error-message {{ color: #EF4444; font-weight: 600; text-align: center; margin-top: 20px; padding: 15px; background-color: #fee2e2; border-radius: 0.5rem; border: 1px solid #ef4444; }}
334
+ </style>
335
+ </head>
336
+ <body class="antialiased text-slate-800">
337
+
338
+
339
+ <main id="content-wrapper">
340
+ <header class="bg-white/80 backdrop-blur-lg sticky top-0 z-50 border-b border-slate-200">
341
+ <div class="max-w-8xl mx-auto px-4 sm:px-6 lg:px-8">
342
+ <div class="flex items-center justify-between h-16">
343
+ <div class="flex items-center">
344
+ <i class="fas fa-chart-line text-2xl text-orange-400"></i>
345
+ <h1 id="dashboard-title" class="text-xl font-bold text-slate-900 ml-3">{ticker} Intelligence Dashboard</h1>
346
+ </div>
347
+ <div class="text-sm text-slate-500 flex items-center">
348
+ <div id="status-message" class="text-center text-sm text-slate-500 mt-4 hidden">Loading updated data...</div>
349
+ <i class="fas fa-rocket mr-2 text-orange-400"></i> Powered by a <span class="font-semibold text-yellow-600 ml-1">{model_type}</span>&nbsp model
350
+ </div>
351
+ </div>
352
+ </div>
353
+ </header>
354
+
355
+ <div class="p-4 sm:p-6 lg:p-8">
356
+ <div class="max-w-8xl mx-auto">
357
+ <div id="dashboard-error-message" class="hidden error-message"></div>
358
+ <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 mb-8" id="metrics-grid"></div>
359
+ <div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
360
+ <div class="lg:col-span-2 space-y-8">
361
+ <div class="info-card p-4 sm:p-6">
362
+ <canvas id="priceChart" style="height: 350px;"></canvas>
363
+ </div>
364
+ <div class="info-card p-4 sm:p-6">
365
+ <canvas id="volumeChart" style="height: 200px;"></canvas>
366
+ </div>
367
+ <div id="predictionDetailsContainer" class="info-card p-4 sm:p-6 hidden">
368
+ <h3 class="text-lg font-semibold mb-4 text-slate-800">AI Prediction Details</h3>
369
+ <div class="overflow-x-auto" id="predictionTable"></div>
370
+ </div>
371
+ </div>
372
+ <div class="lg:col-span-1 space-y-8">
373
+ <div class="info-card p-6">
374
+ <h3 class="text-lg font-semibold mb-4 text-slate-800 flex items-center"><i class="fas fa-robot mr-3 text-orange-400"></i> AI Prediction Summary</h3>
375
+ <div id="predictionResult" class="mt-4 text-center"></div>
376
+ </div>
377
+ <div class="info-card p-6">
378
+ <h3 class="text-lg font-semibold mb-4 text-slate-800">Technical Summary</h3>
379
+ <div class="space-y-3" id="tech-summary"></div>
380
+ </div>
381
+ </div>
382
+ </div>
383
+ </div>
384
+ </div>
385
+ </main>
386
+
387
+ <script>
388
+ document.addEventListener('DOMContentLoaded', function () {{
389
+ const {{
390
+ LineController,
391
+ LineElement,
392
+ PointElement,
393
+ LinearScale,
394
+ TimeScale,
395
+ Legend,
396
+ Tooltip,
397
+ BarController,
398
+ BarElement,
399
+ CategoryScale // Although you use TimeScale for X, CategoryScale might be needed for other internal reasons or for completeness for Bar charts
400
+ }} = Chart;
401
+
402
+ Chart.register(
403
+ LineController,
404
+ LineElement,
405
+ PointElement,
406
+ LinearScale,
407
+ TimeScale,
408
+ Legend,
409
+ Tooltip,
410
+ BarController,
411
+ BarElement,
412
+ CategoryScale
413
+ );
414
+ console.log("JS: Chart.js components registered.");
415
+
416
+ const historicalDataJson = {historical_data_json};
417
+ const predictionDataJson = {prediction_data_json};
418
+ const isLoading = {is_loading_js};
419
+ const errorMessage = {error_message_js}; // Now receiving Python error
420
 
421
+ console.log("JS: DOMContentLoaded. Initial isLoading:", isLoading, "Error:", errorMessage);
422
+
423
+ const loadingOverlay = document.getElementById('loading-overlay');
424
+ const contentWrapper = document.getElementById('content-wrapper');
425
+ const metricsGridEl = document.getElementById('metrics-grid');
426
+ const techSummaryEl = document.getElementById('tech-summary');
427
+ const predictionResultEl = document.getElementById('predictionResult');
428
+ const predictionDetailsContainerEl = document.getElementById('predictionDetailsContainer');
429
+ const predictionTableEl = document.getElementById('predictionTable');
430
+ const dashboardErrorMessageEl = document.getElementById('dashboard-error-message');
431
+
432
+ let priceChart;
433
+ let volumeChart;
434
+
435
+ function parseData(jsonData) {{
436
+ try {{
437
+ if (!jsonData || !jsonData.columns) return null;
438
+ return {{
439
+ dates: jsonData.data.map(row => new Date(row[jsonData.columns.indexOf('Date')])),
440
+ prices: jsonData.data.map(row => row[jsonData.columns.indexOf('Close')]),
441
+ volumes: jsonData.data.map(row => row[jsonData.columns.indexOf('Volume')]),
442
+ highs: jsonData.data.map(row => row[jsonData.columns.indexOf('High')]),
443
+ }};
444
+ }} catch (e) {{
445
+ console.error("JS: Error parsing historical data:", e);
446
+ return null;
447
+ }}
448
  }}
 
 
 
 
 
449
 
450
+ function parsePredictions(jsonData) {{
451
+ try {{
452
+ if (!jsonData || !jsonData.columns) return [];
453
+ return jsonData.data.map(row => ({{
454
+ x: new Date(row[jsonData.columns.indexOf('Date')]),
455
+ y: row[jsonData.columns.indexOf('Predicted Price')],
456
+ upperCI: row[jsonData.columns.indexOf('Upper CI')],
457
+ lowerCI: row[jsonData.columns.indexOf('Lower CI')]
458
+ }}));
459
+ }} catch (e) {{
460
+ console.error("JS: Error parsing prediction data:", e);
461
+ return [];
462
+ }}
463
  }}
464
+
465
+ function displayMetric(elementId, value, prefix = '', suffix = '', decimals = 0) {{
466
+ const el = document.getElementById(elementId);
467
+ if (el) {{
468
+ el.textContent = prefix + value.toLocaleString(undefined, {{ minimumFractionDigits: decimals, maximumFractionDigits: decimals }}) + suffix;
469
+ }}
470
+ }}
471
+
472
+ function updateMetrics(data) {{
473
+ if (!data || data.prices.length < 2) {{
474
+ metricsGridEl.innerHTML = `<div class="col-span-full text-center text-slate-500 p-4">Not enough historical data to display metrics.</div>`;
475
+ return;
476
+ }}
477
+ const currentPrice = data.prices[data.prices.length - 1];
478
+ const prevPrice = data.prices[data.prices.length - 2];
479
+ const change = currentPrice - prevPrice;
480
+ const changePct = (change / prevPrice) * 100;
481
+ const volume = data.volumes[data.volumes.length - 1];
482
+ const sharesOutstanding = 10.33 * 1e9; // Example value
483
+ const marketCap = currentPrice * sharesOutstanding;
484
+
485
+ const metrics = [
486
+ {{ id: 'price', title: 'Current Price', value: currentPrice, change: `${{change >= 0 ? '+' : ''}}${{change.toFixed(2)}} (${{changePct.toFixed(2)}}%)`, status: change >= 0 ? 'positive' : 'negative', icon: 'fa-dollar-sign', prefix: '$', decimals: 2 }},
487
+ {{ id: 'market-cap', title: 'Market Cap', value: marketCap, change: 'USD', status: 'neutral', icon: 'fa-building', prefix: '$', suffix: '', decimals: 2, isCurrency: true }},
488
+ {{ id: 'volume', title: 'Daily Volume', value: volume, change: 'Shares Traded', status: 'neutral', icon: 'fa-chart-bar', suffix: '', decimals: 0 }},
489
+ {{ id: '52-week-high', title: '52-Week High', value: Math.max(...data.highs.slice(-252)), change: 'Annual Peak', status: 'neutral', icon: 'fa-arrow-trend-up', prefix: '$', decimals: 2 }},
490
+ ];
491
+
492
+ metricsGridEl.innerHTML = metrics.map(metric => `<div class="metric-card p-5"><div class="flex items-center justify-between"><p class="text-sm font-medium text-slate-500">${{metric.title}}</p><div class="text-2xl text-slate-300"><i class="fas ${{metric.icon}}"></i></div></div><p class="text-3xl font-bold text-slate-900 mt-2" id="${{metric.id}}">0</p><p class="text-xs ${{metric.status}} mt-1 font-semibold">${{metric.change}}</p></div>`).join('');
493
+
494
+ metrics.forEach(metric => {{
495
+ let displayValue = metric.value;
496
+ let displaySuffix = metric.suffix;
497
+ let displayDecimals = metric.decimals;
498
+
499
+ if (metric.isCurrency) {{
500
+ if (metric.value >= 1e12) {{
501
+ displayValue = metric.value / 1e12;
502
+ displaySuffix = 'T';
503
+ displayDecimals = 2;
504
+ }} else if (metric.value >= 1e9) {{
505
+ displayValue = metric.value / 1e9;
506
+ displaySuffix = 'B';
507
+ displayDecimals = 2;
508
+ }} else if (metric.value >= 1e6) {{
509
+ displayValue = metric.value / 1e6;
510
+ displaySuffix = 'M';
511
+ displayDecimals = 2;
512
+ }}
513
+ }}
514
+
515
+ if (metric.id === 'volume') {{
516
+ if (metric.value >= 1e9) {{
517
+ displayValue = metric.value / 1e9;
518
+ displaySuffix = 'B';
519
+ displayDecimals = 2;
520
+ }} else if (metric.value >= 1e6) {{
521
+ displayValue = metric.value / 1e6;
522
+ displaySuffix = 'M';
523
+ displayDecimals = 2;
524
+ }} else if (metric.value >= 1e3) {{
525
+ displayValue = metric.value / 1e3;
526
+ displaySuffix = 'K';
527
+ displayDecimals = 2;
528
+ }}
529
+ }}
530
 
531
+ displayMetric(metric.id, displayValue, metric.prefix || '', displaySuffix, displayDecimals);
532
+ }});
533
  }}
534
+
535
+ function updateTechSummary(data) {{
536
+ if (!data || data.prices.length < 50) {{ // Need enough data for 50-day SMA
537
+ techSummaryEl.innerHTML = '<p class="text-sm text-slate-500">Not enough data for full technical analysis (min 50 days required).</p>';
538
+ return;
539
+ }}
540
+ const prices = data.prices;
541
+ const lastPrice = prices[prices.length - 1];
542
+
543
+ // Ensure slice has enough elements
544
+ const sma20 = prices.slice(-20).length >= 20 ? prices.slice(-20).reduce((a, b) => a + b, 0) / 20 : NaN;
545
+ const sma50 = prices.slice(-50).length >= 50 ? prices.slice(-50).reduce((a, b) => a + b, 0) / 50 : NaN;
546
+
547
+ let gains = [];
548
+ let losses = [];
549
+ for (let i = 1; i < prices.length; i++) {{
550
+ let diff = prices[i] - prices[i-1];
551
+ if (diff > 0) {{
552
+ gains.push(diff);
553
+ losses.push(0);
554
+ }} else {{
555
+ gains.push(0);
556
+ losses.push(Math.abs(diff));
557
+ }}
558
+ }}
559
 
560
+ let avgGain = 0;
561
+ let avgLoss = 0;
562
+ if (gains.length >= 14) {{
563
+ avgGain = gains.slice(-14).reduce((a, b) => a + b, 0) / 14;
564
+ avgLoss = losses.slice(-14).reduce((a, b) => a + b, 0) / 14;
565
+ }} else if (gains.length > 0) {{
566
+ avgGain = gains.reduce((a, b) => a + b, 0) / gains.length;
567
+ avgLoss = losses.reduce((a, b) => a + b, 0) / losses.length;
568
+ }}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
569
 
570
+ let rs = (avgLoss === 0 || isNaN(avgLoss)) ? (avgGain > 0 ? Infinity : 0) : avgGain / avgLoss;
571
+ let rsi = 100 - (100 / (1 + rs));
572
+ if (isNaN(rsi)) rsi = 0;
573
 
574
+ let rsiClass = 'neutral';
575
+ if (rsi > 70) rsiClass = 'negative';
576
+ else if (rsi < 30) rsiClass = 'positive';
577
+
578
+ const summary = [
579
+ {{ label: 'SMA (20 Day)', value: isNaN(sma20) ? 'N/A' : `$${{sma20.toFixed(2)}}`, status: lastPrice > sma20 ? 'positive' : (isNaN(sma20) ? 'neutral' : 'negative') }},
580
+ {{ label: 'SMA (50 Day)', value: isNaN(sma50) ? 'N/A' : `$${{sma50.toFixed(2)}}`, status: lastPrice > sma50 ? 'positive' : (isNaN(sma50) ? 'neutral' : 'negative') }},
581
+ {{ label: 'RSI (14 Day)', value: rsi.toFixed(1), status: rsiClass }}
582
+ ];
583
+ techSummaryEl.innerHTML = summary.map(item => `<div class="flex justify-between items-center text-sm"><span class="text-slate-600">${{item.label}}</span><span class="font-semibold ${{item.status}}">${{item.value}}</span></div>`).join('');
584
+ }}
585
+
586
+ function renderCharts(data, predictions) {{
587
+ // Render Price Chart
588
+ const priceCtx = document.getElementById('priceChart').getContext('2d');
589
+ if (priceChart) priceChart.destroy();
590
+
591
+ const priceDatasets = [
592
+ {{
593
+ label: 'Historical Price',
594
+ data: data.dates.map((d, i) => ({{x: d, y: data.prices[i]}})),
595
+ borderColor: '#3b82f6',
596
+ backgroundColor: 'rgba(59, 130, 246, 0.1)',
597
+ borderWidth: 2,
598
+ pointRadius: 0,
599
+ fill: true,
600
+ tension: 0.3
601
  }}
602
+ ];
603
+
604
+ if (predictions.length > 0) {{
605
+ priceDatasets.push({{
606
+ label: 'AI Prediction',
607
+ data: predictions,
608
+ borderColor: '#10b981',
609
+ borderWidth: 2,
610
+ pointRadius: 2,
611
+ borderDash: [5, 5],
612
+ fill: false,
613
+ tension: 0.3
614
+ }});
615
+
616
+ // Add confidence interval
617
+ const confidenceData = [
618
+ ...predictions.map(p => ({{x: p.x, y: p.lowerCI}})),
619
+ ...predictions.map(p => ({{x: p.x, y: p.upperCI}})).reverse()
620
+ ];
621
+
622
+ priceDatasets.push({{
623
+ label: '95% Confidence',
624
+ data: confidenceData,
625
+ fill: '1',
626
+ backgroundColor: 'rgba(234, 179, 8, 0.2)',
627
+ borderColor: 'transparent',
628
+ pointRadius: 0
629
+ }});
630
+ }}
631
+
632
+ priceChart = new Chart(priceCtx, {{
633
+ type: 'line', // Explicitly define type
634
+ data: {{ datasets: priceDatasets }},
635
+ options: {{
636
+ responsive: true,
637
+ maintainAspectRatio: false,
638
+ scales: {{
639
+ x: {{
640
+ type: 'time',
641
+ time: {{
642
+ unit: 'month',
643
+ tooltipFormat: 'MMM d, yyyy'
644
+ }},
645
+ grid: {{ display: false }}
646
+ }},
647
+ y: {{
648
+ title: {{ display: true, text: 'Price (USD)' }},
649
+ grid: {{ color: '#f1f5f9' }}
650
+ }}
651
+ }},
652
+ plugins: {{
653
+ legend: {{
654
+ display: true,
655
+ position: 'top',
656
+ align: 'end'
657
+ }},
658
+ tooltip: {{
659
+ mode: 'index',
660
+ intersect: false,
661
+ callbacks: {{
662
+ title: function(context) {{
663
+ return context[0].label;
664
+ }},
665
+ label: function(context) {{
666
+ let label = context.dataset.label || '';
667
+ if (label) label += ': ';
668
+ label += '$' + context.parsed.y.toFixed(2);
669
+ if (context.dataset.label === 'AI Prediction' && predictions.length > 0) {{
670
+ const predictionPoint = predictions.find(p => p.x.getTime() === context.parsed.x);
671
+ if (predictionPoint) {{
672
+ label += ` (CI: $${{predictionPoint.lowerCI.toFixed(2)}} - $${{predictionPoint.upperCI.toFixed(2)}})`;
673
+ }}
674
+ }}
675
+ return label;
676
+ }}
677
+ }}
678
+ }}
679
+ }}
680
+ }}
681
+ }});
682
+
683
+ // Render Volume Chart
684
+ const volumeCtx = document.getElementById('volumeChart').getContext('2d');
685
+ if (volumeChart) volumeChart.destroy();
686
+
687
+ volumeChart = new Chart(volumeCtx, {{
688
+ type: 'bar', // Explicitly define type
689
+ data: {{
690
+ datasets: [{{
691
+ label: 'Volume',
692
+ data: data.dates.map((d, i) => ({{x: d, y: data.volumes[i]}})),
693
+ backgroundColor: '#e2e8f0',
694
+ borderColor: '#cbd5e1',
695
+ borderWidth: 1
696
+ }}]
697
+ }},
698
+ options: {{
699
+ responsive: true,
700
+ maintainAspectRatio: false,
701
+ scales: {{
702
+ x: {{
703
+ type: 'time',
704
+ time: {{
705
+ unit: 'month'
706
+ }},
707
+ grid: {{ display: false }}
708
+ }},
709
+ y: {{
710
+ title: {{ display: true, text: 'Volume' }},
711
+ grid: {{ color: '#f1f5f9' }},
712
+ ticks: {{
713
+ callback: function(value) {{
714
+ if (value >= 1e9) return (value / 1e9).toFixed(0) + 'B';
715
+ if (value >= 1e6) return (value / 1e6).toFixed(0) + 'M';
716
+ if (value >= 1e3) return (value / 1e3).toFixed(0) + 'K';
717
+ return value;
718
+ }}
719
+ }}
720
+ }}
721
+ }},
722
+ plugins: {{
723
+ legend: {{
724
+ display: false
725
+ }},
726
+ tooltip: {{
727
+ callbacks: {{
728
+ label: function(context) {{
729
+ let label = context.dataset.label || '';
730
+ if (label) label += ': ';
731
+ let value = context.parsed.y;
732
+ if (value >= 1e9) label += (value / 1e9).toLocaleString(undefined, {{maximumFractionDigits: 1}}) + 'B';
733
+ else if (value >= 1e6) label += (value / 1e6).toLocaleString(undefined, {{maximumFractionDigits: 1}}) + 'M';
734
+ else if (value >= 1e3) label += (value / 1e3).toLocaleString(undefined, {{maximumFractionDigits: 1}}) + 'K';
735
+ else label += value.toLocaleString();
736
+ return label;
737
+ }}
738
+ }}
739
+ }}
740
+ }}
741
+ }}
742
+ }});
743
+ }}
744
 
745
+ function displayPredictions(data, predictions) {{
746
+ if (!data || predictions.length === 0) {{
747
+ predictionDetailsContainerEl.classList.add('hidden');
748
+ predictionResultEl.innerHTML = '<p class="text-sm text-slate-500">No predictions available or not enough data for prediction.</p>';
749
+ return;
750
+ }}
751
+ predictionDetailsContainerEl.classList.remove('hidden');
752
+ const lastHistoricalPrice = data.prices[data.prices.length - 1];
753
+ const finalPredictedPrice = predictions[predictions.length - 1].y;
 
 
 
754
 
755
+ const changeOverall = finalPredictedPrice - lastHistoricalPrice;
756
+ const changePctOverall = (changeOverall / lastHistoricalPrice) * 100;
757
+ const statusClass = changeOverall >= 0 ? 'positive' : 'negative';
758
+
759
+ predictionResultEl.innerHTML = `<p class="text-sm text-slate-500">Predicted price in ${{predictions.length}} days:</p><p class="text-3xl font-bold mt-1 ${{statusClass}}">$${{finalPredictedPrice.toFixed(2)}} <span class="text-base font-normal">(${{changeOverall >= 0 ? '+' : ''}}${{changeOverall.toFixed(2)}} / ${{changePctOverall.toFixed(2)}}%)</span></p>`;
760
+
761
+ const tableRows = predictions.map(p => `
762
+ <tr>
763
+ <td>${{new Date(p.x).toLocaleDateString()}}</td>
764
+ <td class="font-semibold">$${{p.y.toFixed(2)}}</td>
765
+ <td>$${{p.lowerCI.toFixed(2)}} - $${{p.upperCI.toFixed(2)}}</td>
766
+ </tr>
767
+ `).join('');
768
+ predictionTableEl.innerHTML = `
769
+ <table>
770
+ <thead>
771
+ <tr>
772
+ <th>Date</th>
773
+ <th>Predicted Price</th>
774
+ <th>95% Confidence Interval</th>
775
+ </tr>
776
+ </thead>
777
+ <tbody>${{tableRows}}</tbody>
778
+ </table>
779
+ `;
780
  }}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
781
 
782
+ function loadDashboard() {{
783
+ console.log("JS: loadDashboard() called. Current isLoading:", isLoading, "Error:", errorMessage);
784
+ const statusMessageEl = document.getElementById('status-message');
785
+
786
+ // Handle loading overlay visibility
787
+
788
+ if (isLoading === 'true') {{
789
+ statusMessageEl.classList.remove('hidden');
790
+ dashboardErrorMessageEl.classList.add('hidden'); // Hide any previous error
791
+ return; // Stop execution, let Streamlit re-run and call again when done
792
+ }} else {{
793
+ console.log("JS: in() called. Current isLoading:", isLoading, "Error:", errorMessage);
794
+ statusMessageEl.classList.add('hidden');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
795
 
796
+ }}
797
+
798
+ // Handle errors
799
+ if (errorMessage && errorMessage !== 'null') {{
800
+ dashboardErrorMessageEl.textContent = "Error: " + errorMessage;
801
+ dashboardErrorMessageEl.classList.remove('hidden');
802
+ // Clear existing charts if any, and other content
803
+ if (priceChart) priceChart.destroy();
804
+ if (volumeChart) volumeChart.destroy();
805
+ metricsGridEl.innerHTML = `<div class="col-span-full text-center text-slate-500 p-8 info-card">An error occurred. Please check the ticker or model.</div>`;
806
+ predictionDetailsContainerEl.classList.add('hidden');
807
+ predictionResultEl.innerHTML = '<p class="text-sm text-slate-500">No results due to error.</p>';
808
+ techSummaryEl.innerHTML = '<p class="text-sm text-slate-500">No technical summary due to error.</p>';
809
+ return;
810
+ }} else {{
811
+ dashboardErrorMessageEl.classList.add('hidden'); // Ensure error message is hidden if no error
812
+ }}
813
+
814
+ // If no error and not loading, proceed to render dashboard
815
+ const historicalData = parseData(historicalDataJson);
816
+ const predictionData = parsePredictions(predictionDataJson);
817
+
818
+ if (!historicalData) {{
819
+ metricsGridEl.innerHTML = `<div class="col-span-full text-center text-slate-500 p-8 info-card">Click "Generate Dashboard" in the sidebar to load data.</div>`;
820
+ predictionDetailsContainerEl.classList.add('hidden');
821
+ predictionResultEl.innerHTML = '<p class="text-sm text-slate-500">No data loaded yet.</p>';
822
+ techSummaryEl.innerHTML = '<p class="text-sm text-slate-500">No data for technical summary.</p>';
823
+ if (priceChart) priceChart.destroy();
824
+ if (volumeChart) volumeChart.destroy();
825
+ console.log("JS: No historical data available to render dashboard.");
826
+ return;
827
+ }}
828
+
829
+ updateMetrics(historicalData);
830
+ updateTechSummary(historicalData);
831
+ renderCharts(historicalData, predictionData); // Renamed to plural as it handles both
832
+ displayPredictions(historicalData, predictionData);
833
+ console.log("JS: Dashboard loaded successfully.");
834
+ }}
835
+
836
+ loadDashboard(); // Initial call when DOM is ready
837
+ }});
838
+ </script>
839
+ </body>
840
+ </html>
841
+ """
842
+
843
+ # --- Embed HTML Component in Streamlit ---
844
+ # No need for st.error here, as the JS will handle displaying the error in the HTML component
845
+ components.html(html_code, height=1200, scrolling=True)