Skip to main content
Xenors Xenors
Home › Data Science › Project: Stock Price Forecasting with LSTM and Prophet

Module 13: Advanced Real-World Projects & CapstoneBy Ashok Kumar Yadav4 min read

Project: Stock Price Forecasting with LSTM and Prophet

Forecasting prices is a good way to practise time series modelling, and an honest way to learn how hard markets are to predict. Treat this as a modelling exercise, not trading advice.

Stock price chart with Prophet forecast band and LSTM predictions compared with actual prices
Quick answer: Download prices with yfinance, forecast with Prophet and an LSTM, then compare both against a naive last-value baseline using walk-forward validation. Markets are noisy, so a model that cannot beat the naive baseline has no predictive value.

Disclaimer and setup

This project is educational. Forecasts of prices are uncertain and are not financial advice. Past performance does not predict future returns.

Download daily prices
# pip install yfinance prophet scikit-learn torch pandas
import yfinance as yf, pandas as pd

raw = yf.download("RELIANCE.NS", start="2018-01-01", progress=False, auto_adjust=True)
close = raw["Close"].squeeze().dropna()
df = close.reset_index()
df.columns = ["ds", "y"]
print(df.tail())

Baseline first

On daily prices, 'tomorrow equals today' is surprisingly hard to beat. Any model must clear this bar.

Naive last-value forecast
import numpy as np
split = int(len(df) * 0.85)
train, test = df.iloc[:split], df.iloc[split:]
naive = test["y"].shift(1).fillna(train["y"].iloc[-1])
mae = lambda a, b: float(np.mean(np.abs(a - b)))
print("naive MAE:", round(mae(test["y"], naive), 2))

Forecast with Prophet

Prophet fits trend and seasonality and gives uncertainty intervals. It suits business time series better than efficient financial prices, which makes it a useful comparison.

Prophet fit and evaluation
from prophet import Prophet

m = Prophet(daily_seasonality=False, weekly_seasonality=True, yearly_seasonality=True)
m.fit(train)
future = m.make_future_dataframe(periods=len(test), freq="B")
fc = m.predict(future)
pred = fc.set_index("ds").loc[test["ds"], "yhat"].values
print("Prophet MAE:", round(mae(test["y"].values, pred), 2))

Forecast with an LSTM

Predict the next day's return from the previous 30 returns. Modelling returns instead of raw prices makes the series closer to stationary.

LSTM on normalised log returns
import torch, torch.nn as nn
ret = np.log(df["y"]).diff().dropna().values.astype("float32")
mu, sd = ret[:split].mean(), ret[:split].std()
r = (ret - mu) / sd
W = 30
X = np.stack([r[i:i+W] for i in range(len(r) - W)])[..., None]
y = r[W:]
Xt, yt = torch.tensor(X[:split-W]), torch.tensor(y[:split-W])
Xv, yv = torch.tensor(X[split-W:]), torch.tensor(y[split-W:])

class Net(nn.Module):
    def __init__(s):
        super().__init__(); s.l = nn.LSTM(1, 32, batch_first=True); s.o = nn.Linear(32, 1)
    def forward(s, x): return s.o(s.l(x)[0][:, -1]).squeeze(-1)

net = Net(); opt = torch.optim.Adam(net.parameters(), 1e-3)
for ep in range(10):
    for i in range(0, len(Xt), 64):
        opt.zero_grad()
        loss = nn.functional.mse_loss(net(Xt[i:i+64]), yt[i:i+64]); loss.backward(); opt.step()
with torch.no_grad():
    print("LSTM val MSE:", float(nn.functional.mse_loss(net(Xv), yv)),
          "| zero-return baseline:", float((yv ** 2).mean()))

Interpret results honestly

If the LSTM error is not lower than the zero-return baseline on unseen data, it has learned nothing useful. Use walk-forward validation, include transaction costs before drawing conclusions about strategies, and be cautious about backtests that look too good, since overfitting is the usual cause.

Hands-on practice

Run the examples in the downloadable Jupyter notebook. The extra checks below focus on mistakes that appear in real projects, not just syntax.

Use walk-forward splits

Practice example
series=list(range(12))
for cut in [6,8,10]:
    train=series[:cut]; test=series[cut:cut+2]
    print(len(train),test)
6 [6, 7]
8 [8, 9]
10 [10, 11]

Time-series validation must preserve chronology.

Compare against a naive baseline

Practice example
y=[101,103,102,106]
pred=[100,101,103,102]
mae=sum(abs(a-b) for a,b in zip(y,pred))/len(y)
print('MAE',mae)
MAE 2.0

Complex models should beat simple persistence or seasonal baselines.

Self-check

  • Can you explain why the first practice result is trustworthy rather than just plausible?
  • What would you monitor or test before using this Forecasting Project workflow in production?
  • Which assumption in the example would break first on a larger or messier dataset?

Frequently asked questions

Can LSTM predict stock prices accurately?

Not reliably. Prices are noisy and efficient markets leave little predictable signal, so always compare with a naive baseline.

Prophet or LSTM?

Prophet is quick and interpretable for trend and seasonality. LSTMs can model more complex patterns but need more data and careful validation.

Is this financial advice?

No. It is an educational modelling exercise only.

Primary references

Use these official sources when exact syntax, defaults or definitions matter beyond the examples here.

Written by Ashok Kumar Yadav
Ashok Kumar Yadav writes Xenors tutorials on applied AI, programming and data workflows. Every lesson is built around code you can run and check yourself.
GitHub · LinkedIn

Continue exploring