saibhossain commited on
Commit
89feb89
·
1 Parent(s): 2a450b0

Initial commit: Gradio app with ARIMA/Prophet/LSTM + data + artifacts

Browse files
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ *.csv filter=lfs diff=lfs merge=lfs -text
README.md CHANGED
@@ -1,14 +1,16 @@
1
- ---
2
- title: DataSynthis ML JobTask
3
- emoji: 📚
4
- colorFrom: pink
5
- colorTo: gray
6
- sdk: gradio
7
- sdk_version: 5.48.0
8
- app_file: app.py
9
- pinned: false
10
- license: mit
11
- short_description: 'Apple Stock Price Forecasting '
12
- ---
13
-
14
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
1
+ # DataSynthis_ML_JobTask – Gradio Space
2
+
3
+ **Models:** ARIMA, Prophet, LSTM (multivariate)
4
+ **Data:** AAPL daily prices (2015–2025)
5
+
6
+ ## How it works
7
+ - **Forecast tab:** Choose horizon (7–90d). See future projections overlayed on history.
8
+ - **Backtest tab:** Evaluate models on the last N days (RMSE & MAPE table).
9
+
10
+ ## Files
11
+ - `data/AAPL_stock_2015_2025.csv`
12
+ - `models/arima_model.pkl`, `models/prophet_model.pkl`, `models/lstm_model.h5`, `models/lstm_scaler.pkl`
13
+ - `app.py`, `requirements.txt`
14
+
15
+ > LSTM multi-step uses naive future covariates for demo. For production, predict covariates or use a univariate target model.
16
+
app.py ADDED
@@ -0,0 +1,335 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import warnings
3
+ warnings.filterwarnings("ignore")
4
+
5
+ import numpy as np
6
+ import pandas as pd
7
+ import matplotlib.pyplot as plt
8
+
9
+ import gradio as gr
10
+
11
+ from sklearn.preprocessing import MinMaxScaler
12
+ from sklearn.metrics import mean_squared_error, mean_absolute_percentage_error
13
+
14
+ import joblib
15
+ from statsmodels.tsa.arima.model import ARIMA
16
+
17
+ # Prophet can be heavy; we load lazily inside functions to avoid slow import on startup
18
+ try:
19
+ from prophet import Prophet
20
+ PROPHET_AVAILABLE = True
21
+ except Exception:
22
+ PROPHET_AVAILABLE = False
23
+
24
+ # TensorFlow (CPU) for LSTM
25
+ os.environ["TF_CPP_MIN_LOG_LEVEL"] = "2"
26
+ try:
27
+ from tensorflow.keras.models import load_model
28
+ TENSORFLOW_AVAILABLE = True
29
+ except Exception:
30
+ TENSORFLOW_AVAILABLE = False
31
+
32
+
33
+ # ----------------------------
34
+ # Load data & artifacts
35
+ # ----------------------------
36
+ DATA_PATH = "data/AAPL_stock_2015_2025.csv"
37
+ df = pd.read_csv(DATA_PATH)
38
+ df["Date"] = pd.to_datetime(df["Date"])
39
+ df = df.sort_values("Date").set_index("Date")
40
+
41
+ features = ['Open','High','Low','Close','Volume']
42
+ for c in features:
43
+ df[c] = pd.to_numeric(df[c], errors="coerce")
44
+ df = df.dropna()
45
+
46
+ series_close = df[["Close"]].copy()
47
+
48
+ # models
49
+ ARIMA_PATH = "models/arima_model.pkl"
50
+ PROPHET_PATH = "models/prophet_model.pkl"
51
+ LSTM_PATH = "models/lstm_model.h5"
52
+
53
+ arima_model = None
54
+ prophet_model = None
55
+ lstm_model = None
56
+
57
+ if os.path.exists(ARIMA_PATH):
58
+ try:
59
+ arima_model = joblib.load(ARIMA_PATH)
60
+ except Exception:
61
+ arima_model = None
62
+
63
+ if PROPHET_AVAILABLE and os.path.exists(PROPHET_PATH):
64
+ try:
65
+ prophet_model = joblib.load(PROPHET_PATH)
66
+ except Exception:
67
+ prophet_model = None
68
+
69
+ if TENSORFLOW_AVAILABLE and os.path.exists(LSTM_PATH):
70
+ try:
71
+ lstm_model = load_model(LSTM_PATH, compile=False)
72
+ except Exception:
73
+ lstm_model = None
74
+
75
+
76
+ # ----------------------------
77
+ # Helpers
78
+ # ----------------------------
79
+ def plot_series_with_forecasts(history_df, forecast_dict, title):
80
+ plt.figure(figsize=(12,5))
81
+ plt.plot(history_df.index, history_df.values, label="Actual", linewidth=1.5)
82
+ for name, (idx, vals) in forecast_dict.items():
83
+ plt.plot(idx, vals, label=name, linewidth=1.5)
84
+ plt.title(title)
85
+ plt.xlabel("Date")
86
+ plt.ylabel("Close (USD)")
87
+ plt.legend()
88
+ plt.tight_layout()
89
+ return plt.gcf()
90
+
91
+ def backtest_split_last_n(n_days):
92
+ """Return train/test splits for backtest using last n days as test."""
93
+ train = series_close.iloc[:-n_days, 0]
94
+ test = series_close.iloc[-n_days:, 0]
95
+ return train, test
96
+
97
+ def safe_mape(y_true, y_pred):
98
+ return mean_absolute_percentage_error(y_true, y_pred)
99
+
100
+
101
+ # ----------------------------
102
+ # ARIMA
103
+ # ----------------------------
104
+ def forecast_arima(horizon, retrain_if_needed=True):
105
+ global arima_model
106
+ if arima_model is None and retrain_if_needed:
107
+ # fit a reasonable default quickly
108
+ model = ARIMA(series_close.squeeze(), order=(5,1,0))
109
+ arima_model = model.fit()
110
+ if arima_model is None:
111
+ raise RuntimeError("ARIMA model not available and retrain disabled.")
112
+ fc = arima_model.forecast(steps=horizon)
113
+ idx = pd.date_range(series_close.index[-1] + pd.Timedelta(days=1), periods=horizon, freq="D")
114
+ return idx, fc.values
115
+
116
+ def backtest_arima(n_days, retrain_if_needed=True):
117
+ train, test = backtest_split_last_n(n_days)
118
+ model = ARIMA(train, order=(5,1,0))
119
+ fit = model.fit()
120
+ pred = fit.forecast(steps=len(test)).values
121
+ rmse = np.sqrt(mean_squared_error(test.values, pred))
122
+ mape = safe_mape(test.values, pred)
123
+ idx = test.index
124
+ return idx, pred, rmse, mape
125
+
126
+
127
+ # ----------------------------
128
+ # Prophet
129
+ # ----------------------------
130
+ def forecast_prophet(horizon, retrain_if_needed=True):
131
+ global prophet_model
132
+ if not PROPHET_AVAILABLE:
133
+ raise RuntimeError("Prophet not installed in this Space.")
134
+
135
+ if prophet_model is None and retrain_if_needed:
136
+ p_df = df.reset_index()[["Date","Close"]]
137
+ p_df.columns = ["ds","y"]
138
+ model = Prophet(daily_seasonality=True)
139
+ model.fit(p_df)
140
+ prophet_model = model
141
+
142
+ if prophet_model is None:
143
+ raise RuntimeError("Prophet model not available and retrain disabled.")
144
+
145
+ future = pd.date_range(series_close.index[-1] + pd.Timedelta(days=1), periods=horizon, freq="D")
146
+ future_df = pd.DataFrame({"ds": future})
147
+ forecast = prophet_model.predict(future_df)
148
+ return future, forecast["yhat"].values
149
+
150
+ def backtest_prophet(n_days, retrain_if_needed=True):
151
+ if not PROPHET_AVAILABLE:
152
+ raise RuntimeError("Prophet not installed in this Space.")
153
+
154
+ p_df = df.reset_index()[["Date","Close"]]
155
+ p_df.columns = ["ds","y"]
156
+ train = p_df.iloc[:-n_days]
157
+ test = p_df.iloc[-n_days:]
158
+
159
+ model = Prophet(daily_seasonality=True)
160
+ model.fit(train)
161
+ future = model.make_future_dataframe(periods=len(test))
162
+ fc = model.predict(future).tail(len(test))["yhat"].values
163
+
164
+ rmse = np.sqrt(mean_squared_error(test["y"].values, fc))
165
+ mape = safe_mape(test["y"].values, fc)
166
+ return test["ds"].values, fc, rmse, mape
167
+
168
+
169
+ # ----------------------------
170
+ # LSTM (multivariate) – recursive forecast with naive covariates
171
+ # ----------------------------
172
+ WINDOW = 60
173
+
174
+ def prepare_scaled_features():
175
+ scaler = MinMaxScaler().fit(df[features].values)
176
+ scaled = scaler.transform(df[features].values)
177
+ return scaler, scaled
178
+
179
+ def forecast_lstm(horizon):
180
+ if not (TENSORFLOW_AVAILABLE and lstm_model is not None):
181
+ raise RuntimeError("LSTM model not available in this Space.")
182
+
183
+ scaler, scaled = prepare_scaled_features()
184
+ window = scaled[-WINDOW:].copy()
185
+ preds_scaled_close = []
186
+
187
+ # naive strategy
188
+ for _ in range(horizon):
189
+ x = np.expand_dims(window, axis=0) # (1, 60, 5)
190
+ pred_scaled_close = lstm_model.predict(x, verbose=0)[0,0]
191
+ preds_scaled_close.append(pred_scaled_close)
192
+
193
+ next_vec = window[-1].copy()
194
+ next_vec[3] = pred_scaled_close # Close
195
+ next_vec[0] = pred_scaled_close # Open ~ Close
196
+ next_vec[1] = pred_scaled_close # High ~ Close
197
+ next_vec[2] = pred_scaled_close # Low ~ Close
198
+ # Volume unchanged
199
+ window = np.vstack([window[1:], next_vec])
200
+
201
+ # inverse-transform Close
202
+ dummy = np.zeros((len(preds_scaled_close), len(features)))
203
+ dummy[:,3] = np.array(preds_scaled_close)
204
+ preds_close = scaler.inverse_transform(dummy)[:,3]
205
+
206
+ idx = pd.date_range(series_close.index[-1] + pd.Timedelta(days=1), periods=horizon, freq="D")
207
+ return idx, preds_close
208
+
209
+ def backtest_lstm(n_days):
210
+ if not (TENSORFLOW_AVAILABLE and lstm_model is not None):
211
+ raise RuntimeError("LSTM model not available in this Space.")
212
+
213
+ scaler, scaled = prepare_scaled_features()
214
+ test_scaled = scaled[-(n_days + WINDOW):]
215
+ preds = []
216
+ gts = []
217
+
218
+ for i in range(WINDOW, len(test_scaled)):
219
+ window = test_scaled[i-WINDOW:i].copy()
220
+ x = np.expand_dims(window, axis=0)
221
+ pred_scaled_close = lstm_model.predict(x, verbose=0)[0,0]
222
+ preds.append(pred_scaled_close)
223
+ gts.append(test_scaled[i,3]) # true close (scaled)
224
+
225
+ preds = np.array(preds)
226
+ gts = np.array(gts)
227
+
228
+ # inverse-transform Close
229
+ dummy_p = np.zeros((len(preds), len(features))); dummy_p[:,3] = preds
230
+ dummy_t = np.zeros((len(gts), len(features))); dummy_t[:,3] = gts
231
+ pred_close = scaler.inverse_transform(dummy_p)[:,3]
232
+ true_close = scaler.inverse_transform(dummy_t)[:,3]
233
+
234
+ rmse = np.sqrt(mean_squared_error(true_close, pred_close))
235
+ mape = safe_mape(true_close, pred_close)
236
+ idx = df.index[-n_days:]
237
+ return idx, pred_close[-n_days:], rmse, mape
238
+
239
+
240
+ # ----------------------------
241
+ # Gradio UI
242
+ # ----------------------------
243
+ def do_forecast(horizon, arima, prophet, lstm):
244
+ forecasts = {}
245
+ messages = []
246
+
247
+ if arima:
248
+ try:
249
+ idx, vals = forecast_arima(horizon)
250
+ forecasts["ARIMA"] = (idx, vals)
251
+ except Exception as e:
252
+ messages.append(f"ARIMA error: {e}")
253
+
254
+ if prophet:
255
+ try:
256
+ idx, vals = forecast_prophet(horizon)
257
+ forecasts["Prophet"] = (idx, vals)
258
+ except Exception as e:
259
+ messages.append(f"Prophet error: {e}")
260
+
261
+ if lstm:
262
+ try:
263
+ idx, vals = forecast_lstm(horizon)
264
+ forecasts["LSTM (Multivariate)"] = (idx, vals)
265
+ except Exception as e:
266
+ messages.append(f"LSTM error: {e}")
267
+
268
+ if not forecasts:
269
+ return None, "No model produced a forecast. Check build logs or dependencies."
270
+
271
+ fig = plot_series_with_forecasts(series_close, forecasts, f"Forecast {horizon} days ahead")
272
+ return fig, "\n".join(messages) if messages else "OK"
273
+
274
+ def do_backtest(test_days, arima, prophet, lstm):
275
+ rows = []
276
+ overlays = {}
277
+
278
+ if arima:
279
+ try:
280
+ idx, pred, rmse, mape = backtest_arima(test_days)
281
+ overlays["ARIMA"] = (idx, pred)
282
+ rows.append(["ARIMA", rmse, mape])
283
+ except:
284
+ rows.append(["ARIMA", None, None])
285
+
286
+ if prophet:
287
+ try:
288
+ idx, pred, rmse, mape = backtest_prophet(test_days)
289
+ overlays["Prophet"] = (pd.to_datetime(idx), pred)
290
+ rows.append(["Prophet", rmse, mape])
291
+ except:
292
+ rows.append(["Prophet", None, None])
293
+
294
+ if lstm:
295
+ try:
296
+ idx, pred, rmse, mape = backtest_lstm(test_days)
297
+ overlays["LSTM (Multivariate)"] = (idx, pred)
298
+ rows.append(["LSTM (Multivariate)", rmse, mape])
299
+ except:
300
+ rows.append(["LSTM (Multivariate)", None, None])
301
+
302
+ hist = series_close.iloc[-test_days:]
303
+ fig = plot_series_with_forecasts(hist, overlays, f"Backtest on last {test_days} days")
304
+
305
+ table = pd.DataFrame(rows, columns=["Model", "RMSE", "MAPE"])
306
+ return fig, table
307
+
308
+
309
+ with gr.Blocks(title="DataSynthis_ML_JobTask") as demo:
310
+ gr.Markdown("# 📈 Multivariate Stock Forecasting (AAPL)\nCompare ARIMA, Prophet, and LSTM.")
311
+ with gr.Tab("Forecast"):
312
+ with gr.Row():
313
+ horizon = gr.Slider(7, 90, value=30, step=1, label="Forecast horizon (days)")
314
+ with gr.Row():
315
+ arima_c = gr.Checkbox(True, label="ARIMA")
316
+ prophet_c = gr.Checkbox(True, label="Prophet")
317
+ lstm_c = gr.Checkbox(True, label="LSTM (Multivariate)")
318
+ run_btn = gr.Button("Run Forecast")
319
+ out_plot = gr.Plot()
320
+ out_msg = gr.Textbox(label="Status / Notes")
321
+ run_btn.click(fn=do_forecast, inputs=[horizon, arima_c, prophet_c, lstm_c], outputs=[out_plot, out_msg])
322
+
323
+ with gr.Tab("Backtest"):
324
+ with gr.Row():
325
+ test_days = gr.Slider(30, 180, value=60, step=5, label="Backtest period (last N days)")
326
+ with gr.Row():
327
+ arima_b = gr.Checkbox(True, label="ARIMA")
328
+ prophet_b = gr.Checkbox(True, label="Prophet")
329
+ lstm_b = gr.Checkbox(True, label="LSTM (Multivariate)")
330
+ back_btn = gr.Button("Run Backtest")
331
+ back_plot = gr.Plot()
332
+ back_table = gr.Dataframe(headers=["Model", "RMSE", "MAPE"])
333
+ back_btn.click(fn=do_backtest, inputs=[test_days, arima_b, prophet_b, lstm_b], outputs=[back_plot, back_table])
334
+
335
+ demo.launch()
data/AAPL_stock_2015_2025.csv ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:fb0f8422ef10135b88c06ef7165a12df41c68167c1c840d8a116d2dc589bc631
3
+ size 237526
models/arima_model.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6984d2822c3ece842e82673cf5158ef1394b1d9510c74070b4493284123b0558
3
+ size 17054738
models/lstm_model.h5 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d6b5c4d3056fee849fab2b2f38136039da730b8eac02335c292ce5ef4a4cd3d5
3
+ size 1513952
models/prophet_model.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:997f79b8bdb9c987b3e82ef7817561f6a9664c65a79b62135833fc080d6d7418
3
+ size 205459
requirements.txt ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ gradio==4.44.0
2
+ pandas==2.2.2
3
+ numpy==1.26.4
4
+ scikit-learn==1.5.1
5
+ matplotlib==3.8.4
6
+ statsmodels==0.14.2
7
+ prophet==1.1.5
8
+ cmdstanpy==1.2.4
9
+ ujson==5.10.0
10
+ tensorflow-cpu==2.12.0
11
+ joblib==1.4.2