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