NSruman commited on
Commit
bf6578d
Β·
verified Β·
1 Parent(s): be807c0

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +103 -84
app.py CHANGED
@@ -1,4 +1,4 @@
1
- # app.py β€” DataSynthis_ML_JobTask (ARIMA + optional LSTM, simple UI)
2
 
3
  import os, json, math, pickle, traceback
4
  from datetime import datetime, timedelta
@@ -16,10 +16,15 @@ from sklearn.metrics import mean_squared_error, mean_absolute_error, mean_absolu
16
  # -------------------------
17
  # Artifact Paths
18
  # -------------------------
19
- ARIMA_ORDER_JSON = "arima_order.json" # preferred way to get (p,d,q)
20
- ARIMA_PICKLE = "arima_model.pkl" # optional local archive (not used for inference)
21
- LSTM_KERAS = "lstm_model.keras" # optional: pre-trained LSTM (Keras 3)
22
- LSTM_INFO = "lstm_info.json" # optional: {"lookback": int, "train_min": float, "train_max": float}
 
 
 
 
 
23
 
24
  # -------------------------
25
  # Globals (lazy-load LSTM)
@@ -47,19 +52,14 @@ def fetch_data(ticker: str, start: str, end: str) -> pd.DataFrame:
47
 
48
  def load_arima_order(default=(2, 1, 2)):
49
  """
50
- Prefer reading (p,d,q) from arima_order.json; fall back to pickle; else default.
51
  """
52
- # JSON first (version-proof)
53
  if os.path.isfile(ARIMA_ORDER_JSON):
54
- try:
55
- with open(ARIMA_ORDER_JSON, "r") as f:
56
- obj = json.load(f)
57
- order = tuple(int(x) for x in obj.get("order", list(default)))
58
- return order
59
- except Exception as e:
60
- print(f"[WARN] Failed to read {ARIMA_ORDER_JSON}: {e}")
61
 
62
- # Fallback: try to extract order from pickle (not used for inference otherwise)
63
  if os.path.isfile(ARIMA_PICKLE):
64
  try:
65
  with open(ARIMA_PICKLE, "rb") as f:
@@ -69,7 +69,6 @@ def load_arima_order(default=(2, 1, 2)):
69
  except Exception as e:
70
  print(f"[WARN] Failed to read order from {ARIMA_PICKLE}: {e}")
71
 
72
- # Final fallback
73
  return default
74
 
75
  # -------------------------
@@ -78,7 +77,7 @@ def load_arima_order(default=(2, 1, 2)):
78
  def arima_roll_single_fit(train_s: pd.Series, test_s: pd.Series, order=(2, 1, 2)):
79
  """
80
  Fair evaluation: fit once on TRAIN, then roll 1-step ahead through TEST.
81
- Uses ARIMAResults.append() to update state; refits if append not available.
82
  """
83
  res = ARIMA(train_s, order=order,
84
  enforce_stationarity=False, enforce_invertibility=False).fit()
@@ -88,10 +87,8 @@ def arima_roll_single_fit(train_s: pd.Series, test_s: pd.Series, order=(2, 1, 2)
88
  y_true = test_s.iloc[i]
89
  trues.append(y_true)
90
  try:
91
- # Fast state update
92
  res = res.append(test_s.iloc[i:i+1])
93
  except Exception:
94
- # Rare fallback: refit to train + observed test slice
95
  res = ARIMA(pd.concat([train_s, test_s.iloc[:i+1]]), order=order,
96
  enforce_stationarity=False, enforce_invertibility=False).fit()
97
  return np.array(trues), np.array(preds)
@@ -108,23 +105,57 @@ def arima_future_forecast(all_close: pd.Series, order, steps: int) -> np.ndarray
108
  # -------------------------
109
  # LSTM helpers (Keras .keras + simple min-max scaling)
110
  # -------------------------
111
- def _maybe_load_lstm() -> bool:
 
 
 
 
 
 
 
112
  """
113
- Load LSTM and metadata once, if present. Returns True if available.
114
  """
115
  global _LSTM_MODEL, _LSTM_META
 
116
  if _LSTM_MODEL is not None and _LSTM_META is not None:
117
  return True
118
- if not (os.path.isfile(LSTM_KERAS) and os.path.isfile(LSTM_INFO)):
 
 
 
 
 
 
 
 
 
 
 
 
 
119
  return False
 
120
  try:
121
  import tensorflow as tf # import lazily
122
  _LSTM_MODEL = tf.keras.models.load_model(LSTM_KERAS, compile=False)
123
- with open(LSTM_INFO, "r") as f:
124
  _LSTM_META = json.load(f)
125
- assert all(k in _LSTM_META for k in ("lookback", "train_min", "train_max"))
 
 
 
 
 
 
 
 
 
126
  return True
 
127
  except Exception as e:
 
 
128
  print(f"[WARN] LSTM load failed: {e}")
129
  _LSTM_MODEL, _LSTM_META = None, None
130
  return False
@@ -151,8 +182,8 @@ def lstm_roll_eval_and_forecast(close_train: pd.Series, close_test: pd.Series, h
151
  (1) fair 1-step-ahead evaluation across TEST
152
  (2) recursive forecast for 'horizon' steps
153
  """
154
- if not _maybe_load_lstm():
155
- return None, None, None
156
 
157
  import tensorflow as tf # assured by requirements
158
  model = _LSTM_MODEL
@@ -164,6 +195,12 @@ def lstm_roll_eval_and_forecast(close_train: pd.Series, close_test: pd.Series, h
164
  train_vals = close_train.values.astype("float32")
165
  test_vals = close_test.values.astype("float32")
166
 
 
 
 
 
 
 
167
  train_s = _scale(train_vals, xmin, xmax)
168
  test_s = _scale(test_vals, xmin, xmax)
169
 
@@ -173,7 +210,10 @@ def lstm_roll_eval_and_forecast(close_train: pd.Series, close_test: pd.Series, h
173
 
174
  test_start_idx = len(train_s) - L
175
  if test_start_idx < 0:
176
- return None, None, None # not enough history for the lookback
 
 
 
177
 
178
  X_test = X_all[test_start_idx : test_start_idx + len(test_s)]
179
  y_test = y_all[test_start_idx : test_start_idx + len(test_s)]
@@ -193,8 +233,7 @@ def lstm_roll_eval_and_forecast(close_train: pd.Series, close_test: pd.Series, h
193
  x = window.reshape(1, L, 1)
194
  yhat_s = model.predict(x, verbose=0).reshape(-1)[0]
195
  fut.append(_unscale(yhat_s, xmin, xmax))
196
- # roll the window (note: keep in scaled space)
197
- window = np.concatenate([window[1:], [yhat_s]], axis=0)
198
 
199
  return y_true, y_pred, np.asarray(fut)
200
 
@@ -220,7 +259,7 @@ def make_chart(hist_df: pd.DataFrame,
220
  h['Date'] = pd.to_datetime(h['Date'])
221
  h_last = h.iloc[-252:] if len(h) > 252 else h
222
 
223
- fig, ax = plt.subplots(figsize=(9, 4))
224
  ax.plot(h_last['Date'], h_last['Close ($)'], label="Historical Close", linewidth=2)
225
 
226
  if arima_fut is not None and not arima_fut.empty:
@@ -250,29 +289,29 @@ def do_forecast(ticker_choice: str, years_back: int, horizon_days: int):
250
  Returns:
251
  - Historical table
252
  - Future ARIMA forecast table
253
- - KPI markdown (ARIMA + LSTM if available)
254
- - Chart (ARIMA + optional LSTM)
255
- - Comparison table (ARIMA vs LSTM) if LSTM present
256
  """
257
  try:
 
 
 
258
  ticker = (ticker_choice or "").strip().upper()
259
  end = datetime.utcnow().date()
260
  start = end - timedelta(days=int(years_back * 365.25))
261
 
262
  df = fetch_data(ticker, start.isoformat(), end.isoformat())
263
  if len(df) < 80:
264
- msg = "Not enough data to evaluate. Increase 'How much history?'."
265
- empty = pd.DataFrame()
266
- return empty, empty, msg, None, pd.DataFrame()
267
 
268
- split_idx = int(len(df) * 0.8)
269
  order = load_arima_order(default=(2, 1, 2))
 
270
 
271
  # ---------- ARIMA ----------
272
  train_s, test_s = df['Close'][:split_idx], df['Close'][split_idx:]
273
  y_true_a, y_pred_a = arima_roll_single_fit(train_s, test_s, order=order)
274
  ar_tbl = metrics_df(y_true_a, y_pred_a)
275
-
276
  fut_ar = arima_future_forecast(df['Close'], order, horizon_days)
277
 
278
  # Tables for UI (ARIMA)
@@ -284,40 +323,30 @@ def do_forecast(ticker_choice: str, years_back: int, horizon_days: int):
284
  hist_out = format_prices_df(hist, "date", "close", "Close ($)")
285
  fut_ar_out = format_prices_df(fut_ar_df, "date", "forecast", "Forecast ($)")
286
 
287
- # ---------- LSTM (optional) ----------
288
- lstm_available = _maybe_load_lstm()
289
- fut_lstm_out = None
290
- comp_tbl = None
291
- kpi_extra = ""
292
-
293
- if lstm_available:
294
- lstm_true, lstm_pred, fut_lstm = lstm_roll_eval_and_forecast(train_s, test_s, horizon_days)
295
- if lstm_true is not None:
296
- lstm_tbl = metrics_df(lstm_true, lstm_pred)
297
- fut_lstm_df = pd.DataFrame({"date": pd.Index(future_dates).astype(str),
298
- "forecast": fut_lstm.reshape(-1,)})
299
- fut_lstm_out = format_prices_df(fut_lstm_df, "date", "forecast", "Forecast ($)")
300
-
301
- comp_tbl = pd.DataFrame({
302
- "Model": ["ARIMA", "LSTM"],
303
- "RMSE": [ar_tbl["RMSE"][0], lstm_tbl["RMSE"][0]],
304
- "MAE": [ar_tbl["MAE"][0], lstm_tbl["MAE"][0]],
305
- "MAPE (%)": [ar_tbl["MAPE (%)"][0], lstm_tbl["MAPE (%)"][0]],
306
- }).round(3)
307
-
308
- kpi_extra = (f"\n**LSTM** | Test RMSE β‰ˆ {lstm_tbl['RMSE'][0]:.2f}, "
309
- f"MAE β‰ˆ {lstm_tbl['MAE'][0]:.2f}, "
310
- f"MAPE β‰ˆ {lstm_tbl['MAPE (%)'][0]:.2f}%")
311
-
312
- if comp_tbl is None:
313
- comp_tbl = pd.DataFrame(columns=["Model", "RMSE", "MAE", "MAPE (%)"])
314
-
315
- # KPI text (always show ARIMA; add LSTM if available)
316
  kpi = (
317
  f"**ARIMA {order}** | Test RMSE β‰ˆ {ar_tbl['RMSE'][0]:.2f}, "
318
  f"MAE β‰ˆ {ar_tbl['MAE'][0]:.2f}, MAPE β‰ˆ {ar_tbl['MAPE (%)'][0]:.2f}% "
319
- f"| **Ticker:** {ticker} | **Forecast days:** {horizon_days}"
320
- ) + kpi_extra
 
 
321
 
322
  # Chart overlay
323
  fig = make_chart(hist_out, fut_ar_out, fut_lstm_out)
@@ -328,6 +357,7 @@ def do_forecast(ticker_choice: str, years_back: int, horizon_days: int):
328
  traceback.print_exc()
329
  err = f"❌ {str(e)}"
330
  empty = pd.DataFrame()
 
331
  return empty, empty, err, None, pd.DataFrame()
332
 
333
  # -------------------------
@@ -338,9 +368,9 @@ POPULAR = ["AAPL", "MSFT", "GOOGL", "AMZN", "TSLA", "NVDA", "META"]
338
  with gr.Blocks(title="DataSynthis_ML_JobTask", theme=gr.themes.Soft()) as demo:
339
  gr.Markdown(
340
  """
341
- # DataSynthis_ML_JobTask β€” Stock Forecasts (Simple & Clear)
342
- **What it does:** Downloads recent daily prices, evaluates an ARIMA model fairly,
343
- predicts the next trading days, and (if provided) compares with a pre-trained LSTM.
344
  **How to use:** Pick a stock, choose how many years of history and how far ahead to forecast, then press **Run Forecast**.
345
  """
346
  )
@@ -366,7 +396,7 @@ with gr.Blocks(title="DataSynthis_ML_JobTask", theme=gr.themes.Soft()) as demo:
366
  run_btn = gr.Button("Run Forecast", variant="primary")
367
 
368
  gr.Markdown("### Results")
369
- kpi = gr.Markdown(label="Model summary (lower is better)", value="")
370
  chart = gr.Plot(label="Chart (last year + forecast)")
371
  with gr.Accordion("Tables (click to expand)", open=False):
372
  hist_df = gr.Dataframe(label="Historical Prices", wrap=True, interactive=False)
@@ -379,15 +409,4 @@ with gr.Blocks(title="DataSynthis_ML_JobTask", theme=gr.themes.Soft()) as demo:
379
  outputs=[hist_df, fut_df, kpi, chart, comp_df]
380
  )
381
 
382
- with gr.Tab("Help & Glossary"):
383
- gr.Markdown(
384
- """
385
- ### Notes
386
- - **ARIMA (p,d,q)** is read from `arima_order.json` and fit on your *selected history* for future forecasts.
387
- - **Fair evaluation:** For both ARIMA and (if present) LSTM, we train on the first 80% of your selected history,
388
- then predict **one day ahead** across the remaining 20% (no peeking), and report RMSE/MAE/MAPE.
389
- - **LSTM (optional):** If `lstm_model.keras` and `lstm_info.json` are present, we score and overlay its forecast.
390
- """
391
- )
392
-
393
  demo.launch()
 
1
+ # app.py β€” DataSynthis_ML_JobTask (ARIMA + LSTM REQUIRED, simple UI)
2
 
3
  import os, json, math, pickle, traceback
4
  from datetime import datetime, timedelta
 
16
  # -------------------------
17
  # Artifact Paths
18
  # -------------------------
19
+ ARIMA_ORDER_JSON = "arima_order.json" # required for ARIMA order (p,d,q)
20
+ ARIMA_PICKLE = "arima_model.pkl" # optional (not used for inference)
21
+
22
+ # LSTM artifacts (LSTM is REQUIRED in this build)
23
+ LSTM_KERAS = "lstm_model.keras" # required
24
+ LSTM_INFO_PRIMARY = "lstm_info.json" # primary metadata location
25
+ LSTM_INFO_ALT = "lstm_model/info.json" # alternate metadata location
26
+
27
+ REQUIRE_LSTM = True # <- make LSTM mandatory
28
 
29
  # -------------------------
30
  # Globals (lazy-load LSTM)
 
52
 
53
  def load_arima_order(default=(2, 1, 2)):
54
  """
55
+ Read (p,d,q) from arima_order.json; fallback to pickle; else default.
56
  """
 
57
  if os.path.isfile(ARIMA_ORDER_JSON):
58
+ with open(ARIMA_ORDER_JSON, "r") as f:
59
+ obj = json.load(f)
60
+ order = tuple(int(x) for x in obj.get("order", list(default)))
61
+ return order
 
 
 
62
 
 
63
  if os.path.isfile(ARIMA_PICKLE):
64
  try:
65
  with open(ARIMA_PICKLE, "rb") as f:
 
69
  except Exception as e:
70
  print(f"[WARN] Failed to read order from {ARIMA_PICKLE}: {e}")
71
 
 
72
  return default
73
 
74
  # -------------------------
 
77
  def arima_roll_single_fit(train_s: pd.Series, test_s: pd.Series, order=(2, 1, 2)):
78
  """
79
  Fair evaluation: fit once on TRAIN, then roll 1-step ahead through TEST.
80
+ Uses ARIMAResults.append() to update state; refits if append is unavailable.
81
  """
82
  res = ARIMA(train_s, order=order,
83
  enforce_stationarity=False, enforce_invertibility=False).fit()
 
87
  y_true = test_s.iloc[i]
88
  trues.append(y_true)
89
  try:
 
90
  res = res.append(test_s.iloc[i:i+1])
91
  except Exception:
 
92
  res = ARIMA(pd.concat([train_s, test_s.iloc[:i+1]]), order=order,
93
  enforce_stationarity=False, enforce_invertibility=False).fit()
94
  return np.array(trues), np.array(preds)
 
105
  # -------------------------
106
  # LSTM helpers (Keras .keras + simple min-max scaling)
107
  # -------------------------
108
+ def _find_lstm_info_path():
109
+ if os.path.isfile(LSTM_INFO_PRIMARY):
110
+ return LSTM_INFO_PRIMARY
111
+ if os.path.isfile(LSTM_INFO_ALT):
112
+ return LSTM_INFO_ALT
113
+ return None
114
+
115
+ def _maybe_load_lstm(required: bool = False) -> bool:
116
  """
117
+ Load LSTM and metadata once. If `required=True`, raise a RuntimeError when missing.
118
  """
119
  global _LSTM_MODEL, _LSTM_META
120
+
121
  if _LSTM_MODEL is not None and _LSTM_META is not None:
122
  return True
123
+
124
+ info_path = _find_lstm_info_path()
125
+ missing_parts = []
126
+ if not os.path.isfile(LSTM_KERAS):
127
+ missing_parts.append(LSTM_KERAS)
128
+ if info_path is None:
129
+ missing_parts.append("lstm_info.json (or lstm_model/info.json)")
130
+
131
+ if missing_parts:
132
+ msg = ("LSTM artifacts missing: " + ", ".join(missing_parts) +
133
+ ". Please add them to the repo. (LSTM is required.)")
134
+ if required:
135
+ raise RuntimeError(msg)
136
+ print("[WARN]", msg)
137
  return False
138
+
139
  try:
140
  import tensorflow as tf # import lazily
141
  _LSTM_MODEL = tf.keras.models.load_model(LSTM_KERAS, compile=False)
142
+ with open(info_path, "r") as f:
143
  _LSTM_META = json.load(f)
144
+
145
+ # Validate keys
146
+ for k in ("lookback", "train_min", "train_max"):
147
+ if k not in _LSTM_META:
148
+ raise RuntimeError(f"Missing key '{k}' in {info_path}")
149
+
150
+ # Optional: feature_range
151
+ if "feature_range" not in _LSTM_META:
152
+ _LSTM_META["feature_range"] = [0.0, 1.0]
153
+
154
  return True
155
+
156
  except Exception as e:
157
+ if required:
158
+ raise RuntimeError(f"LSTM load failed: {e}")
159
  print(f"[WARN] LSTM load failed: {e}")
160
  _LSTM_MODEL, _LSTM_META = None, None
161
  return False
 
182
  (1) fair 1-step-ahead evaluation across TEST
183
  (2) recursive forecast for 'horizon' steps
184
  """
185
+ # MUST be loaded; raise if not
186
+ _maybe_load_lstm(required=True)
187
 
188
  import tensorflow as tf # assured by requirements
189
  model = _LSTM_MODEL
 
195
  train_vals = close_train.values.astype("float32")
196
  test_vals = close_test.values.astype("float32")
197
 
198
+ if len(train_vals) < L:
199
+ raise RuntimeError(
200
+ f"Not enough history for LSTM lookback L={L}. "
201
+ f"Increase 'How much history?' so train length β‰₯ {L}."
202
+ )
203
+
204
  train_s = _scale(train_vals, xmin, xmax)
205
  test_s = _scale(test_vals, xmin, xmax)
206
 
 
210
 
211
  test_start_idx = len(train_s) - L
212
  if test_start_idx < 0:
213
+ raise RuntimeError(
214
+ f"Not enough history for LSTM lookback L={L}. "
215
+ f"Increase 'How much history?' so train length β‰₯ {L}."
216
+ )
217
 
218
  X_test = X_all[test_start_idx : test_start_idx + len(test_s)]
219
  y_test = y_all[test_start_idx : test_start_idx + len(test_s)]
 
233
  x = window.reshape(1, L, 1)
234
  yhat_s = model.predict(x, verbose=0).reshape(-1)[0]
235
  fut.append(_unscale(yhat_s, xmin, xmax))
236
+ window = np.concatenate([window[1:], [yhat_s]], axis=0) # roll in scaled space
 
237
 
238
  return y_true, y_pred, np.asarray(fut)
239
 
 
259
  h['Date'] = pd.to_datetime(h['Date'])
260
  h_last = h.iloc[-252:] if len(h) > 252 else h
261
 
262
+ fig, ax = plt.subplots(figsize=( nine := 9, four := 4))
263
  ax.plot(h_last['Date'], h_last['Close ($)'], label="Historical Close", linewidth=2)
264
 
265
  if arima_fut is not None and not arima_fut.empty:
 
289
  Returns:
290
  - Historical table
291
  - Future ARIMA forecast table
292
+ - KPI markdown (ARIMA + LSTM)
293
+ - Chart (ARIMA + LSTM)
294
+ - Comparison table (ARIMA vs LSTM)
295
  """
296
  try:
297
+ # Require LSTM to be present & loadable (will raise otherwise)
298
+ _maybe_load_lstm(required=REQUIRE_LSTM)
299
+
300
  ticker = (ticker_choice or "").strip().upper()
301
  end = datetime.utcnow().date()
302
  start = end - timedelta(days=int(years_back * 365.25))
303
 
304
  df = fetch_data(ticker, start.isoformat(), end.isoformat())
305
  if len(df) < 80:
306
+ raise RuntimeError("Not enough data to evaluate. Increase 'How much history?'.")
 
 
307
 
 
308
  order = load_arima_order(default=(2, 1, 2))
309
+ split_idx = int(len(df) * 0.8)
310
 
311
  # ---------- ARIMA ----------
312
  train_s, test_s = df['Close'][:split_idx], df['Close'][split_idx:]
313
  y_true_a, y_pred_a = arima_roll_single_fit(train_s, test_s, order=order)
314
  ar_tbl = metrics_df(y_true_a, y_pred_a)
 
315
  fut_ar = arima_future_forecast(df['Close'], order, horizon_days)
316
 
317
  # Tables for UI (ARIMA)
 
323
  hist_out = format_prices_df(hist, "date", "close", "Close ($)")
324
  fut_ar_out = format_prices_df(fut_ar_df, "date", "forecast", "Forecast ($)")
325
 
326
+ # ---------- LSTM (REQUIRED) ----------
327
+ lstm_true, lstm_pred, fut_lstm = lstm_roll_eval_and_forecast(train_s, test_s, horizon_days)
328
+ lstm_tbl = metrics_df(lstm_true, lstm_pred)
329
+
330
+ fut_lstm_df = pd.DataFrame({"date": pd.Index(future_dates).astype(str),
331
+ "forecast": fut_lstm.reshape(-1,)})
332
+ fut_lstm_out = format_prices_df(fut_lstm_df, "date", "forecast", "Forecast ($)")
333
+
334
+ # Comparison table
335
+ comp_tbl = pd.DataFrame({
336
+ "Model": ["ARIMA", "LSTM"],
337
+ "RMSE": [ar_tbl["RMSE"][0], lstm_tbl["RMSE"][0]],
338
+ "MAE": [ar_tbl["MAE"][0], lstm_tbl["MAE"][0]],
339
+ "MAPE (%)": [ar_tbl["MAPE (%)"][0], lstm_tbl["MAPE (%)"][0]],
340
+ }).round(3)
341
+
342
+ # KPI text
 
 
 
 
 
 
 
 
 
 
 
 
343
  kpi = (
344
  f"**ARIMA {order}** | Test RMSE β‰ˆ {ar_tbl['RMSE'][0]:.2f}, "
345
  f"MAE β‰ˆ {ar_tbl['MAE'][0]:.2f}, MAPE β‰ˆ {ar_tbl['MAPE (%)'][0]:.2f}% "
346
+ f"| **Ticker:** {ticker} | **Forecast days:** {horizon_days}\n"
347
+ f"**LSTM** | Test RMSE β‰ˆ {lstm_tbl['RMSE'][0]:.2f}, "
348
+ f"MAE β‰ˆ {lstm_tbl['MAE'][0]:.2f}, MAPE β‰ˆ {lstm_tbl['MAPE (%)'][0]:.2f}%"
349
+ )
350
 
351
  # Chart overlay
352
  fig = make_chart(hist_out, fut_ar_out, fut_lstm_out)
 
357
  traceback.print_exc()
358
  err = f"❌ {str(e)}"
359
  empty = pd.DataFrame()
360
+ # Return an error message in KPI area so users immediately see what's wrong
361
  return empty, empty, err, None, pd.DataFrame()
362
 
363
  # -------------------------
 
368
  with gr.Blocks(title="DataSynthis_ML_JobTask", theme=gr.themes.Soft()) as demo:
369
  gr.Markdown(
370
  """
371
+ # DataSynthis_ML_JobTask β€” Stock Forecasts (ARIMA + LSTM)
372
+ **What it does:** Downloads recent daily prices, evaluates ARIMA and LSTM fairly,
373
+ and predicts the next trading days.
374
  **How to use:** Pick a stock, choose how many years of history and how far ahead to forecast, then press **Run Forecast**.
375
  """
376
  )
 
396
  run_btn = gr.Button("Run Forecast", variant="primary")
397
 
398
  gr.Markdown("### Results")
399
+ kpi = gr.Markdown(label="Model summary", value="")
400
  chart = gr.Plot(label="Chart (last year + forecast)")
401
  with gr.Accordion("Tables (click to expand)", open=False):
402
  hist_df = gr.Dataframe(label="Historical Prices", wrap=True, interactive=False)
 
409
  outputs=[hist_df, fut_df, kpi, chart, comp_df]
410
  )
411
 
 
 
 
 
 
 
 
 
 
 
 
412
  demo.launch()