Shaikat01 commited on
Commit
3c76f87
ยท
verified ยท
1 Parent(s): 928f9ee

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +356 -324
app.py CHANGED
@@ -1,325 +1,357 @@
1
- import gradio as gr
2
- import pandas as pd
3
- import numpy as np
4
- import plotly.graph_objects as go
5
- from datetime import datetime, timedelta
6
- import pickle
7
- import yfinance as yf
8
- from statsmodels.tsa.arima.model import ARIMA
9
- from prophet import Prophet
10
- from tensorflow import keras
11
- from sklearn.preprocessing import MinMaxScaler
12
- import warnings
13
- warnings.filterwarnings('ignore')
14
-
15
- # Load your saved models (update paths as needed)
16
- # For Hugging Face, these will be in the same directory as app.py
17
- def load_models():
18
- """Load all three models"""
19
- try:
20
- # Load ARIMA model
21
- with open('arima_model.pkl', 'rb') as f:
22
- arima_model = pickle.load(f)
23
-
24
- # Load Prophet model
25
- with open('prophet_model.pkl', 'rb') as f:
26
- prophet_model = pickle.load(f)
27
-
28
- # Load LSTM model and scaler
29
- lstm_model = keras.models.load_model('lstm_model.h5')
30
- with open('lstm_scaler.pkl', 'rb') as f:
31
- scaler = pickle.load(f)
32
-
33
- return arima_model, prophet_model, lstm_model, scaler
34
- except Exception as e:
35
- print(f"Error loading models: {e}")
36
- return None, None, None, None
37
-
38
- # Global variables for models
39
- arima_model, prophet_model, lstm_model, scaler = load_models()
40
- SEQ_LENGTH = 60 # Should match your training
41
-
42
- def fetch_stock_data(ticker, days=365):
43
- """Fetch stock data from Yahoo Finance"""
44
- try:
45
- end_date = datetime.now()
46
- start_date = end_date - timedelta(days=days)
47
- df = yf.download(ticker, start=start_date, end=end_date, progress=False)
48
- if df.empty:
49
- return None, f"No data found for ticker: {ticker}"
50
- df = df[['Close']].copy()
51
- df.columns = ['Price']
52
- return df, None
53
- except Exception as e:
54
- return None, str(e)
55
-
56
- def make_arima_forecast(data, days):
57
- """Make ARIMA forecast"""
58
- try:
59
- # Retrain ARIMA with recent data (or use loaded model)
60
- model = ARIMA(data['Price'], order=(1, 1, 1))
61
- fitted = model.fit()
62
- forecast = fitted.forecast(steps=days)
63
- return forecast.values
64
- except Exception as e:
65
- print(f"ARIMA Error: {e}")
66
- return None
67
-
68
- def make_prophet_forecast(data, days):
69
- """Make Prophet forecast"""
70
- try:
71
- # Prepare data for Prophet
72
- prophet_data = pd.DataFrame({
73
- 'ds': data.index,
74
- 'y': data['Price'].values
75
- })
76
-
77
- # Create and fit model
78
- model = Prophet(
79
- daily_seasonality=True,
80
- weekly_seasonality=True,
81
- yearly_seasonality=True,
82
- changepoint_prior_scale=0.05
83
- )
84
- model.fit(prophet_data)
85
-
86
- # Make forecast
87
- future = model.make_future_dataframe(periods=days)
88
- forecast = model.predict(future)
89
- return forecast['yhat'].tail(days).values
90
- except Exception as e:
91
- print(f"Prophet Error: {e}")
92
- return None
93
-
94
- def make_lstm_forecast(data, days, model, scaler, seq_length=60):
95
- """Make LSTM forecast"""
96
- try:
97
- # Scale the data
98
- scaled_data = scaler.transform(data[['Price']])
99
-
100
- # Prepare the last sequence
101
- last_sequence = scaled_data[-seq_length:].reshape(1, seq_length, 1)
102
-
103
- predictions = []
104
- current_sequence = last_sequence.copy()
105
-
106
- # Generate predictions day by day
107
- for _ in range(days):
108
- pred = model.predict(current_sequence, verbose=0)
109
- predictions.append(pred[0, 0])
110
-
111
- # Update sequence
112
- current_sequence = np.append(current_sequence[:, 1:, :],
113
- pred.reshape(1, 1, 1), axis=1)
114
-
115
- # Inverse transform predictions
116
- predictions = scaler.inverse_transform(np.array(predictions).reshape(-1, 1))
117
- return predictions.flatten()
118
- except Exception as e:
119
- print(f"LSTM Error: {e}")
120
- return None
121
-
122
- def create_forecast_plot(historical_data, forecasts, ticker, model_names):
123
- """Create interactive plotly chart"""
124
- fig = go.Figure()
125
-
126
- # Historical data
127
- fig.add_trace(go.Scatter(
128
- x=historical_data.index,
129
- y=historical_data['Price'],
130
- mode='lines',
131
- name='Historical Price',
132
- line=dict(color='blue', width=2)
133
- ))
134
-
135
- # Generate future dates
136
- last_date = historical_data.index[-1]
137
- future_dates = pd.date_range(start=last_date + timedelta(days=1),
138
- periods=len(forecasts[0]))
139
-
140
- # Plot forecasts
141
- colors = ['red', 'purple', 'orange']
142
- for i, (forecast, name) in enumerate(zip(forecasts, model_names)):
143
- if forecast is not None:
144
- fig.add_trace(go.Scatter(
145
- x=future_dates,
146
- y=forecast,
147
- mode='lines+markers',
148
- name=f'{name} Forecast',
149
- line=dict(color=colors[i], width=2, dash='dash'),
150
- marker=dict(size=6)
151
- ))
152
-
153
- fig.update_layout(
154
- title=f'{ticker} Stock Price Forecast',
155
- xaxis_title='Date',
156
- yaxis_title='Price ($)',
157
- hovermode='x unified',
158
- template='plotly_white',
159
- height=600,
160
- showlegend=True,
161
- legend=dict(
162
- yanchor="top",
163
- y=0.99,
164
- xanchor="left",
165
- x=0.01
166
- )
167
- )
168
-
169
- return fig
170
-
171
- def predict_stock(ticker, forecast_days, model_choice):
172
- """Main prediction function"""
173
- # Validate inputs
174
- if not ticker:
175
- return None, "Please enter a stock ticker symbol", None
176
-
177
- ticker = ticker.upper().strip()
178
-
179
- # Fetch data
180
- data, error = fetch_stock_data(ticker, days=730) # 2 years of data
181
- if error:
182
- return None, f"Error: {error}", None
183
-
184
- # Make forecasts based on model choice
185
- forecasts = []
186
- model_names = []
187
-
188
- if model_choice in ["All Models", "ARIMA"]:
189
- arima_forecast = make_arima_forecast(data, forecast_days)
190
- if arima_forecast is not None:
191
- forecasts.append(arima_forecast)
192
- model_names.append("ARIMA")
193
-
194
- if model_choice in ["All Models", "Prophet"]:
195
- prophet_forecast = make_prophet_forecast(data, forecast_days)
196
- if prophet_forecast is not None:
197
- forecasts.append(prophet_forecast)
198
- model_names.append("Prophet")
199
-
200
- if model_choice in ["All Models", "LSTM"] and lstm_model is not None:
201
- lstm_forecast = make_lstm_forecast(data, forecast_days, lstm_model, scaler, SEQ_LENGTH)
202
- if lstm_forecast is not None:
203
- forecasts.append(lstm_forecast)
204
- model_names.append("LSTM")
205
-
206
- if not forecasts:
207
- return None, "Failed to generate forecasts. Please try again.", None
208
-
209
- # Create plot
210
- fig = create_forecast_plot(data, forecasts, ticker, model_names)
211
-
212
- # Create forecast table
213
- future_dates = pd.date_range(
214
- start=data.index[-1] + timedelta(days=1),
215
- periods=forecast_days
216
- )
217
-
218
- forecast_df = pd.DataFrame({'Date': future_dates.strftime('%Y-%m-%d')})
219
- for forecast, name in zip(forecasts, model_names):
220
- forecast_df[f'{name} Prediction ($)'] = np.round(forecast, 2)
221
-
222
- # Summary statistics
223
- summary = f"""
224
- ๐Ÿ“Š **Forecast Summary for {ticker}**
225
-
226
- - Current Price: ${data['Price'].iloc[-1]:.2f}
227
- - Forecast Period: {forecast_days} days
228
- - Models Used: {', '.join(model_names)}
229
-
230
- **Predicted Price Range (Day {forecast_days}):**
231
- """
232
-
233
- for forecast, name in zip(forecasts, model_names):
234
- final_price = forecast[-1]
235
- change = ((final_price - data['Price'].iloc[-1]) / data['Price'].iloc[-1]) * 100
236
- summary += f"\n- {name}: ${final_price:.2f} ({change:+.2f}%)"
237
-
238
- return fig, summary, forecast_df
239
-
240
- # Create Gradio Interface
241
- with gr.Blocks(title="Stock Price Forecasting", theme=gr.themes.Soft()) as demo:
242
- gr.Markdown(
243
- """
244
- # ๐Ÿ“ˆ Stock Price Forecasting App
245
-
246
- Predict future stock prices using ARIMA, Prophet, and LSTM models.
247
- Enter a stock ticker symbol and select forecast parameters below.
248
-
249
- **Note:** Predictions are for educational purposes only. Not financial advice.
250
- """
251
- )
252
-
253
- with gr.Row():
254
- with gr.Column(scale=1):
255
- ticker_input = gr.Textbox(
256
- label="Stock Ticker Symbol",
257
- placeholder="e.g., AAPL, GOOGL, TSLA",
258
- value="AAPL"
259
- )
260
-
261
- forecast_days = gr.Slider(
262
- minimum=1,
263
- maximum=90,
264
- value=30,
265
- step=1,
266
- label="Forecast Days"
267
- )
268
-
269
- model_choice = gr.Radio(
270
- choices=["All Models", "ARIMA", "Prophet", "LSTM"],
271
- value="All Models",
272
- label="Select Model(s)"
273
- )
274
-
275
- predict_btn = gr.Button("๐Ÿ”ฎ Generate Forecast", variant="primary", size="lg")
276
-
277
- with gr.Column(scale=2):
278
- output_plot = gr.Plot(label="Forecast Visualization")
279
-
280
- with gr.Row():
281
- output_summary = gr.Markdown(label="Forecast Summary")
282
-
283
- with gr.Row():
284
- output_table = gr.Dataframe(
285
- label="Detailed Forecast",
286
- wrap=True,
287
- interactive=False
288
- )
289
-
290
- # Examples
291
- gr.Examples(
292
- examples=[
293
- ["AAPL", 30, "All Models"],
294
- ["GOOGL", 14, "Prophet"],
295
- ["TSLA", 60, "LSTM"],
296
- ["MSFT", 45, "ARIMA"],
297
- ],
298
- inputs=[ticker_input, forecast_days, model_choice],
299
- )
300
-
301
- # Connect the button to the function
302
- predict_btn.click(
303
- fn=predict_stock,
304
- inputs=[ticker_input, forecast_days, model_choice],
305
- outputs=[output_plot, output_summary, output_table]
306
- )
307
-
308
- gr.Markdown(
309
- """
310
- ---
311
- ### ๐Ÿ“š About the Models
312
-
313
- - **ARIMA**: Statistical model for time series forecasting
314
- - **Prophet**: Facebook's forecasting tool, excellent for seasonality
315
- - **LSTM**: Deep learning model that captures complex patterns
316
-
317
- ### โš ๏ธ Disclaimer
318
- This tool is for educational and research purposes only. Stock market predictions are inherently uncertain.
319
- Always conduct thorough research and consult with financial advisors before making investment decisions.
320
- """
321
- )
322
-
323
- # Launch the app
324
- if __name__ == "__main__":
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
325
  demo.launch()
 
1
+ import gradio as gr
2
+ import pandas as pd
3
+ import numpy as np
4
+ import plotly.graph_objects as go
5
+ from datetime import datetime, timedelta
6
+ import pickle
7
+ import yfinance as yf
8
+ from statsmodels.tsa.arima.model import ARIMA
9
+ from prophet import Prophet
10
+ from tensorflow import keras
11
+ from sklearn.preprocessing import MinMaxScaler
12
+ import warnings
13
+ warnings.filterwarnings('ignore')
14
+
15
+ # Load your saved models (update paths as needed)
16
+ # For Hugging Face, these will be in the same directory as app.py
17
+ def load_models():
18
+ """Load all three models"""
19
+ try:
20
+ # Load ARIMA model
21
+ with open('arima_model.pkl', 'rb') as f:
22
+ arima_model = pickle.load(f)
23
+
24
+ # Load Prophet model
25
+ with open('prophet_model.pkl', 'rb') as f:
26
+ prophet_model = pickle.load(f)
27
+
28
+ # Load LSTM model and scaler
29
+ lstm_model = keras.models.load_model('lstm_model.h5')
30
+ with open('lstm_scaler.pkl', 'rb') as f:
31
+ scaler = pickle.load(f)
32
+
33
+ return arima_model, prophet_model, lstm_model, scaler
34
+ except Exception as e:
35
+ print(f"Error loading models: {e}")
36
+ return None, None, None, None
37
+
38
+ # Global variables for models
39
+ arima_model, prophet_model, lstm_model, scaler = load_models()
40
+ SEQ_LENGTH = 60 # Should match your training
41
+
42
+ def fetch_stock_data(ticker, days=365):
43
+ """Fetch stock data from Yahoo Finance"""
44
+ try:
45
+ end_date = datetime.now()
46
+ start_date = end_date - timedelta(days=days)
47
+
48
+ # Add retry logic and better error handling
49
+ import time
50
+ max_retries = 3
51
+ for attempt in range(max_retries):
52
+ try:
53
+ df = yf.download(ticker, start=start_date, end=end_date, progress=False, auto_adjust=True)
54
+ if not df.empty:
55
+ break
56
+ time.sleep(1) # Wait before retry
57
+ except Exception as e:
58
+ if attempt == max_retries - 1:
59
+ raise e
60
+ time.sleep(1)
61
+
62
+ if df.empty:
63
+ return None, f"No data found for ticker: {ticker}. Please verify the ticker symbol is correct."
64
+
65
+ # Handle both multi-level and single-level columns
66
+ if isinstance(df.columns, pd.MultiIndex):
67
+ df = df['Close'].to_frame()
68
+ elif 'Close' in df.columns:
69
+ df = df[['Close']].copy()
70
+ else:
71
+ # Try to find a price column
72
+ price_col = [col for col in df.columns if 'close' in col.lower()]
73
+ if price_col:
74
+ df = df[[price_col[0]]].copy()
75
+ else:
76
+ return None, f"Could not find price data for {ticker}"
77
+
78
+ df.columns = ['Price']
79
+ df = df.dropna()
80
+
81
+ if len(df) < 100:
82
+ return None, f"Insufficient data for {ticker}. Only {len(df)} days available."
83
+
84
+ return df, None
85
+ except Exception as e:
86
+ return None, f"Error fetching data for {ticker}: {str(e)}"
87
+
88
+ def make_arima_forecast(data, days):
89
+ """Make ARIMA forecast"""
90
+ try:
91
+ # Retrain ARIMA with recent data (or use loaded model)
92
+ model = ARIMA(data['Price'], order=(1, 1, 1))
93
+ fitted = model.fit()
94
+ forecast = fitted.forecast(steps=days)
95
+ return forecast.values
96
+ except Exception as e:
97
+ print(f"ARIMA Error: {e}")
98
+ return None
99
+
100
+ def make_prophet_forecast(data, days):
101
+ """Make Prophet forecast"""
102
+ try:
103
+ # Prepare data for Prophet
104
+ prophet_data = pd.DataFrame({
105
+ 'ds': data.index,
106
+ 'y': data['Price'].values
107
+ })
108
+
109
+ # Create and fit model
110
+ model = Prophet(
111
+ daily_seasonality=True,
112
+ weekly_seasonality=True,
113
+ yearly_seasonality=True,
114
+ changepoint_prior_scale=0.05
115
+ )
116
+ model.fit(prophet_data)
117
+
118
+ # Make forecast
119
+ future = model.make_future_dataframe(periods=days)
120
+ forecast = model.predict(future)
121
+ return forecast['yhat'].tail(days).values
122
+ except Exception as e:
123
+ print(f"Prophet Error: {e}")
124
+ return None
125
+
126
+ def make_lstm_forecast(data, days, model, scaler, seq_length=60):
127
+ """Make LSTM forecast"""
128
+ try:
129
+ # Scale the data
130
+ scaled_data = scaler.transform(data[['Price']])
131
+
132
+ # Prepare the last sequence
133
+ last_sequence = scaled_data[-seq_length:].reshape(1, seq_length, 1)
134
+
135
+ predictions = []
136
+ current_sequence = last_sequence.copy()
137
+
138
+ # Generate predictions day by day
139
+ for _ in range(days):
140
+ pred = model.predict(current_sequence, verbose=0)
141
+ predictions.append(pred[0, 0])
142
+
143
+ # Update sequence
144
+ current_sequence = np.append(current_sequence[:, 1:, :],
145
+ pred.reshape(1, 1, 1), axis=1)
146
+
147
+ # Inverse transform predictions
148
+ predictions = scaler.inverse_transform(np.array(predictions).reshape(-1, 1))
149
+ return predictions.flatten()
150
+ except Exception as e:
151
+ print(f"LSTM Error: {e}")
152
+ return None
153
+
154
+ def create_forecast_plot(historical_data, forecasts, ticker, model_names):
155
+ """Create interactive plotly chart"""
156
+ fig = go.Figure()
157
+
158
+ # Historical data
159
+ fig.add_trace(go.Scatter(
160
+ x=historical_data.index,
161
+ y=historical_data['Price'],
162
+ mode='lines',
163
+ name='Historical Price',
164
+ line=dict(color='blue', width=2)
165
+ ))
166
+
167
+ # Generate future dates
168
+ last_date = historical_data.index[-1]
169
+ future_dates = pd.date_range(start=last_date + timedelta(days=1),
170
+ periods=len(forecasts[0]))
171
+
172
+ # Plot forecasts
173
+ colors = ['red', 'purple', 'orange']
174
+ for i, (forecast, name) in enumerate(zip(forecasts, model_names)):
175
+ if forecast is not None:
176
+ fig.add_trace(go.Scatter(
177
+ x=future_dates,
178
+ y=forecast,
179
+ mode='lines+markers',
180
+ name=f'{name} Forecast',
181
+ line=dict(color=colors[i], width=2, dash='dash'),
182
+ marker=dict(size=6)
183
+ ))
184
+
185
+ fig.update_layout(
186
+ title=f'{ticker} Stock Price Forecast',
187
+ xaxis_title='Date',
188
+ yaxis_title='Price ($)',
189
+ hovermode='x unified',
190
+ template='plotly_white',
191
+ height=600,
192
+ showlegend=True,
193
+ legend=dict(
194
+ yanchor="top",
195
+ y=0.99,
196
+ xanchor="left",
197
+ x=0.01
198
+ )
199
+ )
200
+
201
+ return fig
202
+
203
+ def predict_stock(ticker, forecast_days, model_choice):
204
+ """Main prediction function"""
205
+ # Validate inputs
206
+ if not ticker:
207
+ return None, "Please enter a stock ticker symbol", None
208
+
209
+ ticker = ticker.upper().strip()
210
+
211
+ # Fetch data
212
+ data, error = fetch_stock_data(ticker, days=730) # 2 years of data
213
+ if error:
214
+ return None, f"Error: {error}", None
215
+
216
+ # Make forecasts based on model choice
217
+ forecasts = []
218
+ model_names = []
219
+
220
+ if model_choice in ["All Models", "ARIMA"]:
221
+ arima_forecast = make_arima_forecast(data, forecast_days)
222
+ if arima_forecast is not None:
223
+ forecasts.append(arima_forecast)
224
+ model_names.append("ARIMA")
225
+
226
+ if model_choice in ["All Models", "Prophet"]:
227
+ prophet_forecast = make_prophet_forecast(data, forecast_days)
228
+ if prophet_forecast is not None:
229
+ forecasts.append(prophet_forecast)
230
+ model_names.append("Prophet")
231
+
232
+ if model_choice in ["All Models", "LSTM"] and lstm_model is not None:
233
+ lstm_forecast = make_lstm_forecast(data, forecast_days, lstm_model, scaler, SEQ_LENGTH)
234
+ if lstm_forecast is not None:
235
+ forecasts.append(lstm_forecast)
236
+ model_names.append("LSTM")
237
+
238
+ if not forecasts:
239
+ return None, "Failed to generate forecasts. Please try again.", None
240
+
241
+ # Create plot
242
+ fig = create_forecast_plot(data, forecasts, ticker, model_names)
243
+
244
+ # Create forecast table
245
+ future_dates = pd.date_range(
246
+ start=data.index[-1] + timedelta(days=1),
247
+ periods=forecast_days
248
+ )
249
+
250
+ forecast_df = pd.DataFrame({'Date': future_dates.strftime('%Y-%m-%d')})
251
+ for forecast, name in zip(forecasts, model_names):
252
+ forecast_df[f'{name} Prediction ($)'] = np.round(forecast, 2)
253
+
254
+ # Summary statistics
255
+ summary = f"""
256
+ ๐Ÿ“Š **Forecast Summary for {ticker}**
257
+
258
+ - Current Price: ${data['Price'].iloc[-1]:.2f}
259
+ - Forecast Period: {forecast_days} days
260
+ - Models Used: {', '.join(model_names)}
261
+
262
+ **Predicted Price Range (Day {forecast_days}):**
263
+ """
264
+
265
+ for forecast, name in zip(forecasts, model_names):
266
+ final_price = forecast[-1]
267
+ change = ((final_price - data['Price'].iloc[-1]) / data['Price'].iloc[-1]) * 100
268
+ summary += f"\n- {name}: ${final_price:.2f} ({change:+.2f}%)"
269
+
270
+ return fig, summary, forecast_df
271
+
272
+ # Create Gradio Interface
273
+ with gr.Blocks(title="Stock Price Forecasting", theme=gr.themes.Soft()) as demo:
274
+ gr.Markdown(
275
+ """
276
+ # ๐Ÿ“ˆ Stock Price Forecasting App
277
+
278
+ Predict future stock prices using ARIMA, Prophet, and LSTM models.
279
+ Enter a stock ticker symbol and select forecast parameters below.
280
+
281
+ **Note:** Predictions are for educational purposes only. Not financial advice.
282
+ """
283
+ )
284
+
285
+ with gr.Row():
286
+ with gr.Column(scale=1):
287
+ ticker_input = gr.Textbox(
288
+ label="Stock Ticker Symbol",
289
+ placeholder="e.g., AAPL, GOOGL, TSLA",
290
+ value="AAPL"
291
+ )
292
+
293
+ forecast_days = gr.Slider(
294
+ minimum=1,
295
+ maximum=90,
296
+ value=30,
297
+ step=1,
298
+ label="Forecast Days"
299
+ )
300
+
301
+ model_choice = gr.Radio(
302
+ choices=["All Models", "ARIMA", "Prophet", "LSTM"],
303
+ value="All Models",
304
+ label="Select Model(s)"
305
+ )
306
+
307
+ predict_btn = gr.Button("๐Ÿ”ฎ Generate Forecast", variant="primary", size="lg")
308
+
309
+ with gr.Column(scale=2):
310
+ output_plot = gr.Plot(label="Forecast Visualization")
311
+
312
+ with gr.Row():
313
+ output_summary = gr.Markdown(label="Forecast Summary")
314
+
315
+ with gr.Row():
316
+ output_table = gr.Dataframe(
317
+ label="Detailed Forecast",
318
+ wrap=True,
319
+ interactive=False
320
+ )
321
+
322
+ # Examples
323
+ gr.Examples(
324
+ examples=[
325
+ ["AAPL", 30, "All Models"],
326
+ ["GOOGL", 14, "Prophet"],
327
+ ["TSLA", 60, "LSTM"],
328
+ ["MSFT", 45, "ARIMA"],
329
+ ],
330
+ inputs=[ticker_input, forecast_days, model_choice],
331
+ )
332
+
333
+ # Connect the button to the function
334
+ predict_btn.click(
335
+ fn=predict_stock,
336
+ inputs=[ticker_input, forecast_days, model_choice],
337
+ outputs=[output_plot, output_summary, output_table]
338
+ )
339
+
340
+ gr.Markdown(
341
+ """
342
+ ---
343
+ ### ๐Ÿ“š About the Models
344
+
345
+ - **ARIMA**: Statistical model for time series forecasting
346
+ - **Prophet**: Facebook's forecasting tool, excellent for seasonality
347
+ - **LSTM**: Deep learning model that captures complex patterns
348
+
349
+ ### โš ๏ธ Disclaimer
350
+ This tool is for educational and research purposes only. Stock market predictions are inherently uncertain.
351
+ Always conduct thorough research and consult with financial advisors before making investment decisions.
352
+ """
353
+ )
354
+
355
+ # Launch the app
356
+ if __name__ == "__main__":
357
  demo.launch()