QuantumLearner commited on
Commit
4524156
·
verified ·
1 Parent(s): 0f38b09

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +482 -0
app.py ADDED
@@ -0,0 +1,482 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import numpy as np
3
+ import pandas as pd
4
+ import yfinance as yf
5
+ import plotly.graph_objects as go
6
+ from sklearn.preprocessing import MinMaxScaler
7
+ from keras.models import Sequential
8
+ from keras.layers import LSTM, Dense, Dropout
9
+ import warnings
10
+ warnings.filterwarnings("ignore")
11
+ import datetime
12
+
13
+ # Define the StockPredictor class with the plot_predictions function exactly as in your code
14
+ class StockPredictor:
15
+ def __init__(self, ticker, start_date, end_date, look_back=60):
16
+ self.ticker = ticker
17
+ self.start_date = start_date
18
+ self.end_date = end_date
19
+ self.look_back = look_back
20
+ self.data = None
21
+ self.scaler = MinMaxScaler(feature_range=(0, 1))
22
+ self.model = None
23
+ self.X, self.Y = None, None
24
+ self.predictions = None
25
+ self.predicted_dates = None
26
+ self.features = None # To store the list of features
27
+
28
+ def load_data(self):
29
+ self.data = yf.download(self.ticker, start=self.start_date, end=self.end_date)
30
+ print(f'Data loaded for {self.ticker} from {self.start_date} to {self.end_date}')
31
+
32
+ def calculate_wma(self, prices, window):
33
+ weights = np.arange(1, window + 1)
34
+ return prices.rolling(window).apply(lambda x: np.dot(x, weights) / weights.sum(), raw=True)
35
+
36
+ def calculate_hull_moving_average(self, close_prices, window=14):
37
+ """Function to calculate the Hull Moving Average (HMA)."""
38
+ half_length = int(window / 2)
39
+ sqrt_length = int(np.sqrt(window))
40
+
41
+ wma_half = self.calculate_wma(close_prices, half_length)
42
+ wma_full = self.calculate_wma(close_prices, window)
43
+ raw_hma = 2 * wma_half - wma_full
44
+ hma = self.calculate_wma(raw_hma, sqrt_length)
45
+ return hma
46
+
47
+ def calculate_rolling_zscore(self, close_prices, window=20):
48
+ """Function to calculate rolling Z-Score."""
49
+ rolling_mean = close_prices.rolling(window=window).mean()
50
+ rolling_std = close_prices.rolling(window=window).std()
51
+ z_score = (close_prices - rolling_mean) / rolling_std
52
+ return z_score
53
+
54
+ def calculate_technical_indicators(self):
55
+ """Calculate technical indicators for the historical data."""
56
+ # Hull Moving Average
57
+ self.data['HMA'] = self.calculate_hull_moving_average(self.data['Close'], window=14)
58
+
59
+ # Bollinger Bands
60
+ rolling_std = self.data['Close'].rolling(window=20).std()
61
+ self.data['BB_upper'] = self.data['HMA'] + (rolling_std * 2)
62
+ self.data['BB_lower'] = self.data['HMA'] - (rolling_std * 2)
63
+
64
+ # Relative Strength Index (RSI)
65
+ delta = self.data['Close'].diff(1)
66
+ gain = delta.where(delta > 0, 0)
67
+ loss = -delta.where(delta < 0, 0)
68
+ avg_gain = gain.rolling(window=14).mean()
69
+ avg_loss = loss.rolling(window=14).mean()
70
+ rs = avg_gain / avg_loss
71
+ self.data['RSI'] = 100 - (100 / (1 + rs))
72
+
73
+ # Rolling Z-Score
74
+ self.data['Z_Score'] = self.calculate_rolling_zscore(self.data['Close'], window=20)
75
+
76
+ # Fill missing values
77
+ self.data.fillna(method='bfill', inplace=True)
78
+
79
+ def prepare_data(self):
80
+ self.calculate_technical_indicators()
81
+
82
+ # Select features
83
+ features = ['Close', 'HMA', 'BB_upper', 'BB_lower', 'RSI', 'Z_Score']
84
+ self.features = features
85
+ data = self.data[features]
86
+ data_scaled = self.scaler.fit_transform(data)
87
+
88
+ X, Y = [], []
89
+ for i in range(self.look_back, len(data_scaled)):
90
+ X.append(data_scaled[i - self.look_back:i])
91
+ Y.append(data_scaled[i, 0]) # Assuming 'Close' is the target
92
+ self.X, self.Y = np.array(X), np.array(Y)
93
+ print('Data prepared for training.')
94
+
95
+ def build_model(self):
96
+ self.model = Sequential()
97
+ self.model.add(LSTM(units=300, return_sequences=True, input_shape=(self.X.shape[1], self.X.shape[2])))
98
+ self.model.add(Dropout(0.2))
99
+ self.model.add(LSTM(units=300))
100
+ self.model.add(Dropout(0.2))
101
+ self.model.add(Dense(1))
102
+ self.model.compile(loss='mean_squared_error', optimizer='adam')
103
+ print('Model built and compiled.')
104
+
105
+ def train_model(self, epochs=10, batch_size=64):
106
+ self.model.fit(self.X, self.Y, epochs=epochs, batch_size=batch_size, verbose=1)
107
+ print('Model trained.')
108
+
109
+ def make_predictions(self):
110
+ self.predictions = self.model.predict(self.X)
111
+ # Inverse transform predictions
112
+ # We need to inverse transform the Close price
113
+ # Since we have multiple features, we need to create a full array
114
+ predictions_extended = np.zeros((self.predictions.shape[0], len(self.features)))
115
+ predictions_extended[:, 0] = self.predictions[:, 0]
116
+ predictions_inverse = self.scaler.inverse_transform(predictions_extended)
117
+ self.predictions = predictions_inverse[:, 0]
118
+
119
+ # Similarly for Y
120
+ Y_extended = np.zeros((self.Y.shape[0], len(self.features)))
121
+ Y_extended[:, 0] = self.Y
122
+ Y_inverse = self.scaler.inverse_transform(Y_extended)
123
+ self.Y = Y_inverse[:, 0]
124
+ # Store corresponding dates
125
+ self.predicted_dates = self.data.index[self.look_back:]
126
+ print('Predictions made and stored.')
127
+
128
+ def forecast_future(self, days=5, n_mc=100):
129
+ # Start with the last 'look_back' periods from data
130
+ last_sequence = self.data[self.features].iloc[-self.look_back:].copy()
131
+ future_predictions = []
132
+ future_std = []
133
+
134
+ for _ in range(days):
135
+ # Scale the input data
136
+ last_sequence_scaled = self.scaler.transform(last_sequence)
137
+ # Reshape to (1, look_back, num_features)
138
+ input_data = last_sequence_scaled.reshape(1, self.look_back, len(self.features))
139
+
140
+ # Perform N_mc stochastic forward passes
141
+ predictions = []
142
+ for _ in range(n_mc):
143
+ prediction = self.model(input_data, training=True)
144
+ predictions.append(prediction.numpy()[0, 0])
145
+
146
+ predictions = np.array(predictions)
147
+ mean_prediction = predictions.mean()
148
+ std_prediction = predictions.std()
149
+ future_std.append(std_prediction)
150
+
151
+ # Create an array to inverse transform
152
+ prediction_extended = np.zeros((1, len(self.features)))
153
+ prediction_extended[0, 0] = mean_prediction
154
+ predicted_price = self.scaler.inverse_transform(prediction_extended)[0, 0]
155
+ future_predictions.append(predicted_price)
156
+
157
+ # Prepare the next input
158
+ # Create a new row with the predicted price and updated technical indicators
159
+ new_row = {}
160
+ new_row['Close'] = predicted_price
161
+
162
+ # Append the new predicted price to the sequence
163
+ # Use pd.concat instead of append
164
+ new_row_df = pd.DataFrame([new_row], index=[last_sequence.index[-1] + pd.Timedelta(days=1)])
165
+ last_sequence = pd.concat([last_sequence, new_row_df])
166
+
167
+ # Recalculate technical indicators on the updated last_sequence
168
+ last_close_prices = last_sequence['Close']
169
+ # HMA
170
+ hma_series = self.calculate_hull_moving_average(last_close_prices)
171
+ last_sequence['HMA'] = hma_series
172
+ # Bollinger Bands
173
+ rolling_std = last_close_prices.rolling(window=20).std()
174
+ last_sequence['BB_upper'] = last_sequence['HMA'] + (rolling_std * 2)
175
+ last_sequence['BB_lower'] = last_sequence['HMA'] - (rolling_std * 2)
176
+ # RSI
177
+ delta = last_close_prices.diff(1)
178
+ gain = delta.where(delta > 0, 0)
179
+ loss = -delta.where(delta < 0, 0)
180
+ avg_gain = gain.rolling(window=14).mean()
181
+ avg_loss = loss.rolling(window=14).mean()
182
+ rs = avg_gain / avg_loss
183
+ last_sequence['RSI'] = 100 - (100 / (1 + rs))
184
+ # Z_Score
185
+ z_score_series = self.calculate_rolling_zscore(last_close_prices)
186
+ last_sequence['Z_Score'] = z_score_series
187
+
188
+ # Fill any missing values
189
+ last_sequence.fillna(method='bfill', inplace=True)
190
+
191
+ # Keep only the last 'look_back' periods
192
+ last_sequence = last_sequence.iloc[-self.look_back:]
193
+
194
+ # Generate future dates
195
+ future_dates = pd.date_range(start=self.data.index[-1] + pd.Timedelta(days=1), periods=days, freq='B')
196
+
197
+ # Calculate confidence intervals
198
+ future_std = np.array(future_std).reshape(-1, 1)
199
+ # Scale the standard deviations appropriately
200
+ future_std_scaled = future_std * (self.scaler.data_max_[0] - self.scaler.data_min_[0])
201
+
202
+ # Create a DataFrame with future dates and predicted prices
203
+ future_df = pd.DataFrame({
204
+ 'Date': future_dates,
205
+ 'Predicted_Close': future_predictions,
206
+ 'Std_Dev': future_std_scaled.flatten()
207
+ })
208
+
209
+ # Calculate 68% and 95% confidence intervals
210
+ future_df['CI_68_Lower'] = future_df['Predicted_Close'] - future_df['Std_Dev']
211
+ future_df['CI_68_Upper'] = future_df['Predicted_Close'] + future_df['Std_Dev']
212
+ future_df['CI_95_Lower'] = future_df['Predicted_Close'] - 2 * future_df['Std_Dev']
213
+ future_df['CI_95_Upper'] = future_df['Predicted_Close'] + 2 * future_df['Std_Dev']
214
+
215
+ print('Future predictions made and stored with confidence intervals.')
216
+ return future_df
217
+
218
+ def plot_predictions(self, future_predictions_df):
219
+ # Create plotly figure
220
+ fig = go.Figure()
221
+
222
+ # Plot actual prices
223
+ fig.add_trace(go.Scatter(
224
+ x=self.data.index,
225
+ y=self.data['Close'],
226
+ mode='lines',
227
+ name='Actual Price'
228
+ ))
229
+
230
+ # Plot predicted prices for historical data
231
+ fig.add_trace(go.Scatter(
232
+ x=self.predicted_dates,
233
+ y=self.predictions,
234
+ mode='lines',
235
+ name='Predicted Price'
236
+ ))
237
+
238
+ # Plot future predictions
239
+ fig.add_trace(go.Scatter(
240
+ x=future_predictions_df['Date'],
241
+ y=future_predictions_df['Predicted_Close'],
242
+ mode='lines',
243
+ name=f'Future {len(future_predictions_df)} Days Prediction',
244
+ line=dict(dash='dash')
245
+ ))
246
+
247
+ # Plot 95% confidence interval as shaded area
248
+ fig.add_trace(go.Scatter(
249
+ x=future_predictions_df['Date'].tolist() + future_predictions_df['Date'][::-1].tolist(),
250
+ y=future_predictions_df['CI_95_Upper'].tolist() + future_predictions_df['CI_95_Lower'][::-1].tolist(),
251
+ fill='toself',
252
+ fillcolor='rgba(255, 192, 0, 0.2)',
253
+ line=dict(color='rgba(255,255,255,0)'),
254
+ hoverinfo='skip',
255
+ showlegend=True,
256
+ name='95% Confidence Interval'
257
+ ))
258
+
259
+ # Plot 68% confidence interval as shaded area (on top of 95%)
260
+ fig.add_trace(go.Scatter(
261
+ x=future_predictions_df['Date'].tolist() + future_predictions_df['Date'][::-1].tolist(),
262
+ y=future_predictions_df['CI_68_Upper'].tolist() + future_predictions_df['CI_68_Lower'][::-1].tolist(),
263
+ fill='toself',
264
+ fillcolor='rgba(0, 100, 80, 0.2)',
265
+ line=dict(color='rgba(255,255,255,0)'),
266
+ hoverinfo='skip',
267
+ showlegend=True,
268
+ name='68% Confidence Interval'
269
+ ))
270
+
271
+ # Determine if the final prediction is bullish or bearish
272
+ last_actual_price = self.data['Close'].iloc[-1]
273
+ final_predicted_price = future_predictions_df['Predicted_Close'].iloc[-1]
274
+ bullish = final_predicted_price > last_actual_price
275
+
276
+ # Add a triangle marker at the end of the actual price plot, colored based on bullish/bearish
277
+ fig.add_trace(go.Scatter(
278
+ x=[self.data.index[-1]],
279
+ y=[last_actual_price],
280
+ mode='markers',
281
+ marker=dict(
282
+ color='green' if bullish else 'red',
283
+ size=12,
284
+ symbol='triangle-up' if bullish else 'triangle-down'
285
+ ),
286
+ name='Bullish' if bullish else 'Bearish'
287
+ ))
288
+
289
+ # Add labels for the final confidence intervals and final price, placed next to the lines
290
+ annotations = []
291
+
292
+ # Set x offset for labels to avoid overlapping with the data points
293
+ x_offset = 40 # Adjust as needed
294
+
295
+ # Final predicted price annotation
296
+ annotations.append(dict(
297
+ x=future_predictions_df['Date'].iloc[-1],
298
+ y=final_predicted_price,
299
+ xref='x', yref='y',
300
+ text=f"Final Predicted Price: ${final_predicted_price:.2f}",
301
+ showarrow=False,
302
+ xanchor='left',
303
+ xshift=x_offset
304
+ ))
305
+
306
+ # 68% Confidence Interval Upper Bound Annotation
307
+ annotations.append(dict(
308
+ x=future_predictions_df['Date'].iloc[-1],
309
+ y=future_predictions_df['CI_68_Upper'].iloc[-1],
310
+ xref='x', yref='y',
311
+ text=f"68% CI Upper: ${future_predictions_df['CI_68_Upper'].iloc[-1]:.2f}",
312
+ showarrow=False,
313
+ xanchor='left',
314
+ xshift=x_offset
315
+ ))
316
+
317
+ # 68% Confidence Interval Lower Bound Annotation
318
+ annotations.append(dict(
319
+ x=future_predictions_df['Date'].iloc[-1],
320
+ y=future_predictions_df['CI_68_Lower'].iloc[-1],
321
+ xref='x', yref='y',
322
+ text=f"68% CI Lower: ${future_predictions_df['CI_68_Lower'].iloc[-1]:.2f}",
323
+ showarrow=False,
324
+ xanchor='left',
325
+ xshift=x_offset
326
+ ))
327
+
328
+ # 95% Confidence Interval Upper Bound Annotation
329
+ annotations.append(dict(
330
+ x=future_predictions_df['Date'].iloc[-1],
331
+ y=future_predictions_df['CI_95_Upper'].iloc[-1],
332
+ xref='x', yref='y',
333
+ text=f"95% CI Upper: ${future_predictions_df['CI_95_Upper'].iloc[-1]:.2f}",
334
+ showarrow=False,
335
+ xanchor='left',
336
+ xshift=x_offset
337
+ ))
338
+
339
+ # 95% Confidence Interval Lower Bound Annotation
340
+ annotations.append(dict(
341
+ x=future_predictions_df['Date'].iloc[-1],
342
+ y=future_predictions_df['CI_95_Lower'].iloc[-1],
343
+ xref='x', yref='y',
344
+ text=f"95% CI Lower: ${future_predictions_df['CI_95_Lower'].iloc[-1]:.2f}",
345
+ showarrow=False,
346
+ xanchor='left',
347
+ xshift=x_offset
348
+ ))
349
+
350
+ # Update layout with annotations
351
+ fig.update_layout(
352
+ title=f'Stock Price Prediction for {self.ticker}',
353
+ xaxis_title='Date',
354
+ yaxis_title='Price',
355
+ hovermode='x unified',
356
+ annotations=annotations
357
+ )
358
+
359
+ # Return the figure
360
+ return fig
361
+
362
+ # Streamlit App
363
+ st.set_page_config(layout="wide")
364
+ st.title("Deep Learning-Based Asset Price Forecasts")
365
+
366
+ # Include a short description in the main body of the app
367
+ st.markdown("""
368
+ This tool forecasts future stock prices and cryptocurrency pairs using a deep neural network with a large number of proprietary historical external variables. The model provides both 68% and 95% confidence intervals to represent uncertainty.
369
+ """)
370
+
371
+ with st.expander("Additional Trading Tools and Analysis", expanded=False):
372
+ st.markdown("""
373
+ To implement the forecasts into trading, users are advised to further combine the predictions with the following tools to assess the latest asset price patterns:
374
+ - [Expected Stock Price Movement Using Volatility Multipliers](https://entreprenerdly.com/expected-stock-price-movement-using-volatility-multipliers/)
375
+ - [Future Stock Price Movements with Monte Carlo Simulations](https://entreprenerdly.com/future-stock-price-movements-with-monte-carlo-simulations/)
376
+ - [Technical Analysis with Trading Indicators](https://entreprenerdly.com/technical-analysis/)
377
+
378
+ Additionally, comparing the results with Entreprenerdly's algorithmic indicators can provide further insights for making the final decision.
379
+ """)
380
+
381
+ # Sidebar
382
+ st.sidebar.title("Input Parameters")
383
+
384
+ with st.sidebar.expander("How to Use", expanded=False):
385
+ st.write("""
386
+ **Instructions:**
387
+ - Enter the stock ticker symbol or crypto pair you want to forecast (e.g., AAPL or BTC-USD).
388
+ - Select the number of days ahead you want to forecast using the slider. We recommend choosing a small number.
389
+ - Click on "Run Model" to generate the predictions and view the results.
390
+ """)
391
+
392
+ with st.sidebar.expander("Input Parameters", expanded=True):
393
+ ticker = st.text_input(
394
+ "Stock Ticker",
395
+ value="AAPL",
396
+ help="Enter the stock ticker symbol or Cryptocurrency pair you want to forecast (e.g., AAPL, BTC-USD)."
397
+ )
398
+ forecast_days = st.slider(
399
+ "Forecast Horizon (Days)",
400
+ min_value=1,
401
+ max_value=10,
402
+ value=5,
403
+ help="Select the number of future days to forecast. We recommend choosing a smaller number, as longer horizons generally result in higher prediction errors."
404
+ )
405
+
406
+ # Set end date to today's date plus one in the backend
407
+ end_date = (datetime.date.today() + datetime.timedelta(days=1)).strftime("%Y-%m-%d")
408
+ start_date = '2015-01-01' # Fixed start date
409
+
410
+ # Run button
411
+ run_button = st.sidebar.button("Run Model")
412
+
413
+ if run_button:
414
+ # Progress bar
415
+ progress_bar = st.progress(0)
416
+ status_text = st.empty()
417
+
418
+ # Instantiate and run the predictor
419
+ predictor = StockPredictor(
420
+ ticker,
421
+ start_date,
422
+ end_date,
423
+ look_back=60
424
+ )
425
+ status_text.text("Loading data...")
426
+ predictor.load_data()
427
+ progress_bar.progress(10)
428
+
429
+ status_text.text("Preparing data...")
430
+ predictor.prepare_data()
431
+ progress_bar.progress(30)
432
+
433
+ status_text.text("Building model...")
434
+ predictor.build_model()
435
+ progress_bar.progress(50)
436
+
437
+ status_text.text("Training model...")
438
+ predictor.train_model(epochs=10, batch_size=64)
439
+ progress_bar.progress(80)
440
+
441
+ status_text.text("Making predictions...")
442
+ predictor.make_predictions()
443
+ progress_bar.progress(90)
444
+
445
+ status_text.text("Forecasting future prices...")
446
+ future_predictions_df = predictor.forecast_future(days=forecast_days, n_mc=100)
447
+ progress_bar.progress(100)
448
+
449
+ status_text.text("Generating plot...")
450
+ fig = predictor.plot_predictions(future_predictions_df)
451
+
452
+ # Clear the progress bar and status text
453
+ progress_bar.empty()
454
+ status_text.empty()
455
+
456
+ # Display the plot
457
+ st.plotly_chart(fig)
458
+
459
+ # Include a short interpretation of the results
460
+ last_actual_price = predictor.data['Close'].iloc[-1]
461
+ final_predicted_price = future_predictions_df['Predicted_Close'].iloc[-1]
462
+ bullish = final_predicted_price > last_actual_price
463
+
464
+ st.write(f"### Final Predicted Price: ${final_predicted_price:.2f}")
465
+ if bullish:
466
+ st.success("The model predicts a **bullish** trend over the forecast horizon.")
467
+ else:
468
+ st.error("The model predicts a **bearish** trend over the forecast horizon.")
469
+
470
+ st.write("""
471
+ **Interpretation of Results:**
472
+ The plot above shows the actual stock prices, the predicted prices from the model, and the future forecasted prices with confidence intervals. The confidence intervals represent the uncertainty in the model's predictions. A bullish indicator suggests that the stock price is expected to rise, while a bearish indicator suggests a decline.
473
+ """)
474
+
475
+
476
+ hide_streamlit_style = """
477
+ <style>
478
+ #MainMenu {visibility: hidden;}
479
+ footer {visibility: hidden;}
480
+ </style>
481
+ """
482
+ st.markdown(hide_streamlit_style, unsafe_allow_html=True)