In partnership with

Learn Business Buying & Scaling In 3 Days

NOVEMBER 2-4 | AUSTIN, TX

At Main Street Over Wall Street 2025, you’ll learn the exact playbook we’ve used to help thousands of “normal” people find, fund, negotiate, and buy profitable businesses that cash flow.

Use code BHP500 to save $500 on your ticket today (this event WILL sell out).

Click here to get your ticket, see the speaker list, schedule, and more

🚀 Your Investing Journey Just Got Better: Premium Subscriptions Are Here! 🚀

It’s been 4 months since we launched our premium subscription plans at GuruFinance Insights, and the results have been phenomenal! Now, we’re making it even better for you to take your investing game to the next level. Whether you’re just starting out or you’re a seasoned trader, our updated plans are designed to give you the tools, insights, and support you need to succeed.

Here’s what you’ll get as a premium member:

  • Exclusive Trading Strategies: Unlock proven methods to maximize your returns.

  • In-Depth Research Analysis: Stay ahead with insights from the latest market trends.

  • Ad-Free Experience: Focus on what matters most—your investments.

  • Monthly AMA Sessions: Get your questions answered by top industry experts.

  • Coding Tutorials: Learn how to automate your trading strategies like a pro.

  • Masterclasses & One-on-One Consultations: Elevate your skills with personalized guidance.

Our three tailored plans—Starter Investor, Pro Trader, and Elite Investor—are designed to fit your unique needs and goals. Whether you’re looking for foundational tools or advanced strategies, we’ve got you covered.

Don’t wait any longer to transform your investment strategy. The last 4 months have shown just how powerful these tools can be—now it’s your turn to experience the difference.

TSLA Testing Set Predictions with Confidence Intervals Chart

Stock price forecasting has always been an intriguing challenge for traders and analysts alike. The stock market is highly dynamic and volatile, with trends influenced by numerous factors.

In this article, we explore a powerful machine learning technique called Gated Recurrent Units (GRU), a type of Recurrent Neural Network (RNN), and demonstrate how it can be used to predict future stock prices based on historical data.

Why Retail Investors Always Buy at the Top

You buy after CNBC reports the story.
Wall Street bought when Reddit mentions spiked 3,968%.

You buy after "strong earnings."
Wall Street bought when insiders loaded up $31M beforehand.

You buy after "analyst upgrades."
Wall Street bought when Congress filed their positions first.

The pattern is obvious: You get yesterday's news. They get tomorrow's signals.

While you're reading quarterly reports, professionals track Reddit sentiment, Congressional trades, and insider purchases in real-time.

AltIndex monitors the same signals Wall Street uses: 50,000+ Reddit comments daily, every Congressional filing, insider transactions from 500+ companies.

Every week, we send you the stocks showing the strongest signals before they hit mainstream financial media.

And we’re giving you a 7 day free trial of our app, so you can see new stock narratives happening in real time.

Stop being the last to know.

Past performance does not guarantee future results. Investing involves risk including possible loss of principal.

Understanding GRU

Gated Recurrent Units (GRU) are a type of RNN that have gained popularity for time series forecasting tasks. Unlike traditional feed-forward networks, RNNs are designed to handle sequential data, making them ideal for time-based predictions like stock prices.

GRUs are particularly useful because they address the problem of long-term dependencies often seen in sequential data, with fewer parameters than the more complex Long Short-Term Memory (LSTM) networks. This makes GRUs computationally efficient while still providing strong performance in many forecasting tasks.

Despite their simplicity, GRUs often provide higher accuracy in forecasting tasks compared to traditional methods like Linear Regression, making them a powerful tool for predictive modeling

In our case, we use GRU to predict stock prices, specifically focusing on the Open, Close, Volume, Low, and High prices. By training a GRU model on historical stock data, we can forecast future prices and analyze trends in the stock market.

Key Considerations

While GRU models and other machine learning techniques can offer valuable insights and predictions, it’s important to remember that forecasting stock prices is inherently uncertain.

The stock market is influenced by numerous factors, including market sentiment, political events, and unforeseen global occurrences, which are difficult for any model to fully capture.

The predictions generated by our GRU model are based solely on historical data, and past performance is not always an accurate indicator of future results. Additionally, machine learning models are not immune to overfitting, especially with complex, volatile data like stock prices. It’s crucial to continuously monitor model performance and adjust parameters as needed.

Install Libraries

You can find all the code for this tutorial Here.

It is available for our paid subscribers on any plan

To forecast stock prices with GRU, you’ll need Python along with a few essential libraries. In your python environment, install the following libraries:

pip install numpy pandas matplotlib yfinance tensorflow scikit-learn tabulate

Once the libraries have been installed, add the following imports to your jupyter notebook.

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import yfinance as yf
from datetime import datetime

from sklearn.preprocessing import MinMaxScaler
from sklearn.metrics import mean_absolute_error, r2_score, mean_squared_error
from tabulate import tabulate

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import GRU, Dense, Dropout

Data Preparation

The first step in any machine learning project is data collection and preparation. For stock price forecasting, we need historical stock data, which we obtain using the yfinance library. We use features like closing price, opening price, trading volume, and high/low prices of stocks over a certain period.

Select Stock and Date Range

# Define the stock symbol and the date range for our data
stock_symbol = 'TSLA'
start_date = '2015-01-01'
end_date = datetime.today().strftime('%Y-%m-%d')  # Sets end date to today's date
print(f"Ticker: {stock_symbol}\nStart Date: {start_date}\nEnd Date: {end_date}")

Download Data

We now download the data and select the relevant columns including setting the Date index. In our case we use monthly data, but you can select any interval:

# df = yf.download(stock_symbol, start=start_date, end=end_date, interval='1wk')
df = yf.download(stock_symbol, start=start_date, end=end_date, interval='1mo')
# df = yf.download(stock_symbol, start=start_date, end=end_date, interval='1d')

# Select the desired columns (first level of MultiIndex)
df.columns = df.columns.get_level_values(0)

# Keep only the columns you are interested in
df = df[['Open', 'Close', 'Volume', 'Low', 'High']]

# If the index already contains the dates, rename the index
df.index.name = 'Date'  # Ensure the index is named "Date"
    
# Resetting the index if necessary
df.reset_index(inplace=True)

# Ensure that the index is of type datetime
df['Date'] = pd.to_datetime(df['Date'])

# Set the 'Date' column as the index again (in case it's reset)
df.set_index('Date', inplace=True)

df.head()

Visualize Chart

We visualize the stock chart in the following manner:

# Set dark mode for the plot
plt.style.use('dark_background')  # Use dark background style

# Plot the closing price
plt.figure(figsize=(14, 6))

plt.plot(df['Close'], label='Closing Price', color='blue')  # Change color for visibility

# Add title, labels, and legend with appropriate colors for dark mode
plt.title(f'{stock_symbol} Closing Price', color='white')
plt.xlabel('Date', color='white')
plt.ylabel('Price ($)', color='white')
plt.legend(facecolor='gray', edgecolor='white', labelcolor='white')
plt.grid()

# Save the plot in 300dpi
plt.savefig(f'{stock_symbol}_stock_chart.png', dpi=300)

# Show the plot
plt.show()

TSLA Stock Closing Price Chart

Seeking impartial news? Meet 1440.

Every day, 3.5 million readers turn to 1440 for their factual news. We sift through 100+ sources to bring you a complete summary of politics, global events, business, and culture, all in a brief 5-minute email. Enjoy an impartial news experience.

Data Normalization

Once the data is collected, it needs to be normalized. Normalization scales the values of the data to a standard range, usually between 0 and 1. This step is crucial when working with neural networks, as it helps the model converge faster and improves accuracy.

# Normalize the data (scale between 0 and 1)
scaler = MinMaxScaler()
scaled_data = scaler.fit_transform(df[['Close', 'Open', 'Volume', 'Low', 'High']])  # Use all features for predictionSliding Window Technique

Prepare Sequence-to-Sequence Data

For training a model on sequential data, the sliding window technique is widely used. This method involves creating a fixed-size window that moves across the dataset, allowing the model to learn from previous time steps and make predictions for future steps.

In our case, we use a window of 30 timesteps of stock data to predict the next timestep’s price. The sliding window creates input-output pairs where the model uses the past 30 timesteps of data to predict the price on the next timestep.

# Function to create sliding window dataset
def create_sliding_window(data, window_size):
    X, y = [], []
    for i in range(len(data) - window_size):
        X.append(data[i:i+window_size])  # Append the entire window of all features
        y.append(data[i + window_size])  # Append the target window for the next day (all features)
    return np.array(X), np.array(y)

# Define sliding window size
window_size = 30  # 30 timesteps of data to predict the next day's data

# Prepare data
X, y = create_sliding_window(scaled_data, window_size)

# Reshape X to (samples, timesteps, features) for GRU
X = X.reshape(X.shape[0], X.shape[1], X.shape[2])  # X.shape[2] will be 5 because we have 5 features

Data Split

The dataset is then split into training and testing sets, with 80% used for training to ensure the model has ample data for learning while reserving 20% for validation and evaluation.


# Split data into training and testing sets (80% train, 20% test)
train_size = int(len(X) * 0.8)
X_train, X_test = X[:train_size], X[train_size:]
y_train, y_test = y[:train_size], y[train_size:]

print(f"Training Samples: {X_train.shape[0]}, Testing Samples: {X_test.shape[0]}")

Building the GRU Model

Once the data is prepared, a GRU model is built to predict multiple features (Open, Close, Volume, Low, High) of the stock data. The model starts with a GRU layer that processes sequential data with 50 units and uses the tanh activation function to introduce non-linearity.

The return_sequences=False ensures that the output of the GRU layer is a single vector, not a sequence, as the model predicts the values for all features at once. A Dropout layer with a rate of 0.3 is included to reduce overfitting by randomly setting a fraction of input units to zero during training. The output layer is a Dense layer with 5 units, corresponding to the five features we aim to predict.

The model is compiled using the Adam optimizer for efficient gradient-based optimization and Mean Squared Error (MSE) as the loss function to minimize prediction errors. Additionally, Mean Absolute Error (MAE) is tracked as a metric to evaluate the model’s performance during training.

The key advantage of using GRUs here is that they are well-suited for time series forecasting, making them ideal for predicting future stock prices based on historical patterns.

# Build the GRU model to predict all features
model = Sequential([
    GRU(50, activation='tanh', return_sequences=False, input_shape=(window_size, 5)),  # Input shape for 5 features
    Dropout(0.3),  # Regularization to prevent overfitting
    Dense(5)  # Output layer for predicting 5 features (Open, Close, Volume, Low, High)
])

# Compile the model
model.compile(optimizer='adam', loss='mse', metrics=['mae'])

Training the Model

Once the model is built, the next step is training. We use the training data to teach the model how to make predictions.

# Train the model
history = model.fit(X_train, y_train, epochs=300, batch_size=32, validation_data=(X_test, y_test))

Training Results

# Plot model loss during training
plt.figure(figsize=(14, 6))
plt.plot(history.history['loss'], label='Training Loss', color='blue')
plt.plot(history.history['val_loss'], label='Validation Loss', color='orange')
plt.title("Model Loss During Training")
plt.xlabel("Epochs")
plt.ylabel("Loss")
plt.legend()
plt.grid(True)
plt.savefig(f'{stock_symbol}_model_loss.png')
plt.show()

Model Loss During Training

Evaluation

We evaluate the performance of our trained model on the test set. We make predictions for the entire test set, then inverse scale the predicted and actual values to obtain the original stock prices.

For each of the five features (Open, Close, Volume, Low, High), we compute three key error metrics: Mean Absolute Error (MAE), Mean Squared Error (MSE), and the R² Score. These metrics provide insight into the accuracy of the predictions and how well the model is performing for each feature.

# Predictions for the entire test set
test_predictions = model.predict(X_test)

# Inverse scale the predictions (for multiple features)
test_predictions_actual = scaler.inverse_transform(test_predictions)  # Shape: (samples, 5)

# Inverse scale the actual values (y_test) for the features
y_test_actual = scaler.inverse_transform(y_test)  # Shape: (samples, 5)

# Features of interest
features = ['Open', 'Close', 'Volume', 'Low', 'High']

# Initialize a list to store the results
results = []

# Iterate through each feature
for i, feature in enumerate(features):
    mae = mean_absolute_error(y_test_actual[:, i], test_predictions_actual[:, i])
    mse = mean_squared_error(y_test_actual[:, i], test_predictions_actual[:, i])
    r2 = r2_score(y_test_actual[:, i], test_predictions_actual[:, i])
    # Append results to the list
    results.append([feature, mae, mse, r2])

# Display the results using tabulate
headers = ['Feature', 'Mean Absolute Error (MAE)', 'Mean Squared Error (MSE)', 'R² Score']
print(tabulate(results, headers=headers, tablefmt="rounded_grid"))

Example Output

╭───────────┬─────────────────────────────┬────────────────────────────┬────────────╮
 Feature      Mean Absolute Error (MAE)    Mean Squared Error (MSE)    R² Score 
├───────────┼─────────────────────────────┼────────────────────────────┼────────────┤
 Open                      32.8234                   2313.12           0.424669 
├───────────┼─────────────────────────────┼────────────────────────────┼────────────┤
 Close                     10.642                     211.392          0.886607 
├───────────┼─────────────────────────────┼────────────────────────────┼────────────┤
 Volume                     3.98857e+08                 2.36673e+17   -0.178101 
├───────────┼─────────────────────────────┼────────────────────────────┼────────────┤
 Low                       24.0988                    949.857          0.537887 
├───────────┼─────────────────────────────┼────────────────────────────┼────────────┤
 High                      31.418                    2124.54           0.542972 
╰───────────┴─────────────────────────────┴────────────────────────────┴────────────╯

Model Testing with Confidence Interval

The actual and predicted closing prices are extracted and plotted.

Additionally, we calculate the 95% confidence interval around the predicted closing prices, assuming the standard deviation is known. This confidence interval provides a range in which we can expect the actual values to fall, reflecting the uncertainty in the predictions.

The plot displays both the actual and predicted closing prices, along with the shaded area representing the confidence interval, allowing us to visually assess the model’s performance.

# Extract only the 'Close' column for actual and predicted prices
y_test_actual_close = y_test_actual[:, features.index('Close')]  # Actual closing prices
test_predictions_close = test_predictions_actual[:, features.index('Close')]  # Predicted closing prices

# Calculate 95% confidence interval
# Assuming standard deviation is known
std_dev = np.std(test_predictions_close - y_test_actual_close)
confidence_upper = test_predictions_close + 1.96 * std_dev
confidence_lower = test_predictions_close - 1.96 * std_dev

# Plot the testing predictions for the 'Close' prices with confidence interval
plt.figure(figsize=(14, 6))

# Plot actual and predicted prices
plt.plot(range(len(y_test_actual_close)), y_test_actual_close, label="Actual Test Closing Prices", color='blue')
plt.plot(range(len(test_predictions_close)), test_predictions_close, label="Predicted Test Closing Prices", color='lime', linewidth=2)

# Add confidence interval shading
plt.fill_between(range(len(test_predictions_close)), confidence_lower, confidence_upper, color='gray', alpha=0.3, label='95% Confidence Interval')

# Add title, labels, and legend
plt.title(f"{stock_symbol} Testing Set Closing Prices with Confidence Interval", fontsize=16)
plt.xlabel("Time", fontsize=12)
plt.ylabel("Stock Price", fontsize=12)
plt.legend()

# Display grid and show the plot
plt.grid(True)
plt.savefig(f'{stock_symbol}_test_predictions_close.png')
plt.show()

Predicted Closing Prices on The Test Set with Confidence Intervals

Forecasting Future Stock Prices

This section predicts the stock prices for the next 30 timesteps using the trained GRU model. The prediction starts by using the last window_size days of the dataset as the initial input for the model. The model then generates predictions for the subsequent days, and the sliding window approach is employed to update the input with each new prediction.

# Number of days to predict into the future
future_timesteps = 30

# Use the last 'window_size' days from the dataframe as the starting input
last_window = scaled_data[-window_size:]  # Shape: (window_size, n_features)
current_input = last_window.reshape(1, window_size, scaled_data.shape[1])  # Reshape for model input (samples, timesteps, features)

# Predict the entire future sequence in one go
future_predictions = []

for _ in range(future_timesteps):
    # Predict the next value
    next_prediction = model.predict(current_input, verbose=0)
    future_predictions.append(next_prediction[0])  # Append the prediction (for all features)

    # Slide the window: Remove the oldest value and append the new prediction
    next_input = np.append(current_input[0, 1:, :], next_prediction, axis=0)  # Shape (window_size, n_features)
    current_input = next_input.reshape(1, window_size, scaled_data.shape[1])

# Inverse transform future predictions to get actual prices (only 'Close' column)
future_predictions = scaler.inverse_transform(np.array(future_predictions).reshape(-1, scaled_data.shape[1]))

# Extract only the 'Close' column for future predictions
future_predictions_close = future_predictions[:, features.index('Close')]

# Calculate 95% confidence interval for future predictions
std_dev = np.std(test_predictions_close - y_test_actual_close)  # Use test set residuals to estimate std deviation
confidence_upper_future = future_predictions_close + 1.96 * std_dev
confidence_lower_future = future_predictions_close - 1.96 * std_dev

# Generate future dates starting after the last date in the dataset
last_date = df.index[-1]
future_dates = pd.date_range(start=last_date + pd.Timedelta(days=1), periods=future_timesteps)

Visualizing the Forecast

One of the most important steps in any forecasting task is to visualize the results. In our case, we plot both the historical stock prices and the forecasted future prices on the same graph. This helps to visualize how well the model’s predictions match the actual data, and the confidence intervals provide additional context.

By visualizing the predicted prices alongside the actual values, we can more easily assess the accuracy of our model and understand how well it performs over time.

# Plot historical data and forecasted future data on the same chart
plt.figure(figsize=(14, 6))

# Plot the historical closing prices
plt.plot(df.index, df['Close'], label="Historical Closing Prices", color="blue")

# Plot the forecasted future closing prices as a line starting from the last historical point
plt.plot(
    [df.index[-1]] + list(future_dates),  # Combine last historical date with future dates
    [df['Close'].iloc[-1]] + list(future_predictions_close),  # Combine last historical price with future predictions
    label="Forecasted Closing Prices", color="lime", linewidth=2
)

# Add confidence interval shading for future predictions
plt.fill_between(
    future_dates,
    confidence_lower_future,
    confidence_upper_future,
    color='gray',
    alpha=0.3,
    label='95% Confidence Interval'
)

# Add title, labels, and legend
plt.title(f"Stock Price Forecast for {stock_symbol}", fontsize=16)
plt.xlabel("Date", fontsize=12)
plt.ylabel("Stock Price", fontsize=12)
plt.legend()

# Display grid and show the plot
plt.grid(True)
plt.savefig(f'{stock_symbol}_forecast.png')
plt.show()

TSLA Price Forecast Chart

Conclusion

In this article, we’ve demonstrated how to use a Gated Recurrent Unit (GRU) model to predict stock prices based on historical data. We walked through the steps of data preparation, model building, training, evaluation, and forecasting future prices. The power of GRUs lies in their ability to handle sequential data efficiently and provide accurate predictions for time series problems.

Stock price forecasting is inherently uncertain, but using machine learning techniques like GRUs provides valuable insights and allows us to make more informed predictions. As always, it’s essential to keep in mind that no model can predict the stock market perfectly, but it can help identify trends and make data-driven decisions.

The full code for this tutorial can be found here. I encourage you to explore it and adapt it to your own use cases.

Thank you for reading, and I hope this article helped you understand how to apply GRUs to stock price forecasting. Feel free to reach out with any questions or comments.

Keep Reading

No posts found