Spaces:
Running
Running
Create app.py
Browse files
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)
|