Forecast Stock Prices with LSTM in Python: Complete Project Guide from Data Prep to Deployment
If you’ve ever watched a news anchor brag about “the next big thing” in the market and thought, “I could have seen that coming,” you’re not alone. Predicting stock prices feels like trying to read the future, but with the right tools—like an LSTM network—you can turn a hunch into a data‑driven guess that actually makes sense.
Why LSTM?
Long Short‑Term Memory (LSTM) networks belong to the family of recurrent neural networks (RNNs). In plain English, they are designed to remember patterns over time. Stock prices are a classic time‑series problem: today’s price depends on yesterday’s, last week’s, maybe even last year’s trends. Traditional feed‑forward models forget that order, but LSTMs keep a “memory” of past values, making them a natural fit for financial forecasting.
The Project Roadmap
Below is the step‑by‑step flow we’ll follow:
- Gather historical price data
- Clean and scale the data
- Create sequences for the LSTM
- Build and train the model
- Evaluate performance
- Deploy as a simple Flask API
Let’s dive in.
1. Data Collection
For a quick demo I used Yahoo Finance’s free API via the yfinance Python package. It’s a handy way to pull daily OHLCV (Open, High, Low, Close, Volume) data without signing up for a paid service.
import yfinance as yf
import pandas as pd
ticker = "AAPL"
df = yf.download(ticker, start="2015-01-01", end="2024-01-01")
df = df[['Close']] # we only need closing price for now
df.head()
The resulting DataFrame has a Date index and a single column called Close. Feel free to add other columns later; they can improve the model, but they also add complexity.
2. Data Preparation
2.1 Handling Missing Values
Financial data sometimes has gaps (holidays, market closures). A simple forward‑fill works well:
df.ffill(inplace=True)
2.2 Scaling
Neural networks love numbers that sit roughly between 0 and 1. We’ll use MinMaxScaler from scikit‑learn.
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
scaled = scaler.fit_transform(df)
2.3 Train‑Test Split
We keep the most recent 20% of data for testing, mimicking a real‑world scenario where the model predicts future values.
train_size = int(len(scaled) * 0.8)
train, test = scaled[:train_size], scaled[train_size:]
3. Creating Sequences
LSTMs need input shaped as samples × timesteps × features. We’ll use a sliding window of 60 days (about three months) to predict the next day’s price.
import numpy as np
def create_sequences(data, window=60):
X, y = [], []
for i in range(len(data) - window):
X.append(data[i:i+window])
y.append(data[i+window])
return np.array(X), np.array(y)
X_train, y_train = create_sequences(train)
X_test, y_test = create_sequences(test)
Now X_train has shape (num_samples, 60, 1)—the extra dimension is for the single feature (Close price).
4. Building the LSTM Model
I like to keep the architecture simple: one LSTM layer followed by a dense output layer.
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import LSTM, Dense, Dropout
model = Sequential([
LSTM(50, return_sequences=False, input_shape=(X_train.shape[1], 1)),
Dropout(0.2),
Dense(1)
])
model.compile(optimizer='adam', loss='mean_squared_error')
model.summary()
The return_sequences=False flag tells the LSTM to output only the last hidden state, which is enough for a single‑step forecast. The dropout layer helps prevent over‑fitting—think of it as a gentle reminder to the model not to memorize the training data.
5. Training the Model
Training for 30 epochs usually gives a decent result for this toy example. Adjust the number based on your hardware and patience.
history = model.fit(
X_train, y_train,
epochs=30,
batch_size=32,
validation_split=0.1,
verbose=1
)
You’ll see the loss decreasing on both training and validation sets. If validation loss starts rising while training loss keeps falling, you’re over‑fitting—time to add more dropout or reduce epochs.
6. Evaluation
First, we predict on the test set and then invert the scaling to get actual price values.
pred_scaled = model.predict(X_test)
pred = scaler.inverse_transform(pred_scaled)
actual = scaler.inverse_transform(y_test)
A quick visual check:
import matplotlib.pyplot as plt
plt.figure(figsize=(12,5))
plt.plot(actual, label='Actual')
plt.plot(pred, label='Predicted')
plt.title('AAPL Close Price: Actual vs Predicted')
plt.legend()
plt.show()
You’ll notice the lines are close but not perfect—stock markets are noisy! A common metric is Root Mean Squared Error (RMSE). Lower is better.
from sklearn.metrics import mean_squared_error
import math
rmse = math.sqrt(mean_squared_error(actual, pred))
print(f'RMSE: {rmse:.2f}')
If the RMSE is within a few dollars for a high‑priced stock, you’ve done a respectable job for a first pass.
7. Deploying the Model
7.1 Saving the Model
model.save('lstm_stock_model.h5')
import joblib
joblib.dump(scaler, 'scaler.save')
7.2 Simple Flask API
Below is a minimal Flask app that loads the model and returns a one‑day‑ahead forecast for a given ticker. In practice you’d add error handling, logging, and maybe a caching layer.
from flask import Flask, request, jsonify
import tensorflow as tf
import numpy as np
import joblib
import yfinance as yf
app = Flask(__name__)
model = tf.keras.models.load_model('lstm_stock_model.h5')
scaler = joblib.load('scaler.save')
WINDOW = 60
def get_recent_prices(ticker):
df = yf.download(ticker, period='90d')[['Close']]
df.ffill(inplace=True)
return scaler.transform(df)
@app.route('/predict', methods=['GET'])
def predict():
ticker = request.args.get('ticker', default='AAPL')
data = get_recent_prices(ticker)
recent_seq = data[-WINDOW:].reshape(1, WINDOW, 1)
pred_scaled = model.predict(recent_seq)
pred_price = scaler.inverse_transform(pred_scaled)[0][0]
return jsonify({'ticker': ticker, 'predicted_close': round(pred_price, 2)})
if __name__ == '__main__':
app.run(debug=True)
Run the script, hit http://127.0.0.1:5000/predict?ticker=MSFT, and you’ll get a JSON response with tomorrow’s predicted closing price. It’s a fun way to turn a notebook experiment into a tiny web service.
8. Things to Keep in Mind
- Data leakage: Never let future data sneak into the training set. The sliding window approach helps avoid this.
- Feature engineering: Adding technical indicators (moving averages, RSI) can boost performance, but they also increase the risk of over‑fitting.
- Model stability: Stock markets are influenced by news, earnings, and macro events—none of which are captured in price history alone. Treat the model as a guide, not a crystal ball.
- Regulation: If you plan to use predictions for real trading, check the legal requirements in your jurisdiction.
That’s it—a full walk‑through from raw price data to a deployable LSTM service. I hope this project gives you a solid foundation to experiment further, perhaps by trying bidirectional LSTMs, attention mechanisms, or even transformer models. The world of finance is noisy, but with clear code and a disciplined workflow, you can turn that noise into insight.
- → Implementing the One-In-One-Out Rule in Python: A Step-by-Step Guide for Cleaner Code @codeflowinsights
- → How to Build Your First End-to-End Machine Learning Project in Python @datascitrial
- → Build Your First Python Automation: A Step‑by‑Step Guide to Saving Hours with Simple Scripts @pythonstarter
- → A Step-by-Step Guide to Adding a Local LLM to Your Python App @techfrontier
- → Create a Beginner‑Friendly Data Visualization: Plotting Your First Chart with Matplotlib in 10 Minutes @pythonstarter