Stock Market Prediction¶

In this example, we'll be exploring how we can use Linear Regression to predict stock prices thirty days into the future.

You probably won't get rich with this algorithm, but it is still rather satisfying to watch your computer predict the price of your favourite stock.

Getting started¶

Work through the notebook cell by cell; if you prefer a script, copy the code into a new stock_forecast.py file and run it from this folder.

We need a few dependencies. If you do not have them installed, run pip install <dependency> on the command line.

In [1]:
from pathlib import Path

import numpy as np
import pandas as pd

from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from sklearn import preprocessing

Stock data & dataframe¶

We use Meta Platforms (ticker META, the company formerly known as Facebook). A snapshot of roughly ten years of daily adjusted close prices is stored next to this notebook in data/META_daily.csv, so the notebook runs offline and always gives the same numbers in class.

(Adjusted close accounts for splits and dividends, which is what we want when we model a price series.)

In [2]:
csv_path = Path("data") / "META_daily.csv"

df = pd.read_csv(csv_path, index_col="Date", parse_dates=True)
print(f"{len(df)} trading days, {df.index.min().date()} to {df.index.max().date()}")
df.tail()
2514 trading days, 2016-09-12 to 2026-09-11
Out[2]:
Adj. Close
Date
2026-09-04 616.770020
2026-09-08 613.479980
2026-09-09 653.690002
2026-09-10 644.380005
2026-09-11 648.030029

Optional: fetch fresh data yourself¶

The cell below downloads live data with yfinance instead of reading the snapshot. It needs a network connection (and pip install yfinance), so it is not run by default -- change the flag to True if you want today's prices. Everything after this point works the same either way.

In [3]:
FETCH_LIVE_DATA = False  # set to True to download fresh prices instead of the snapshot

if FETCH_LIVE_DATA:
    import yfinance as yf

    live = yf.Ticker("META").history(period="10y", interval="1d", auto_adjust=True)
    live.index = live.index.tz_localize(None).normalize()
    live.index.name = "Date"
    df = live[["Close"]].rename(columns={"Close": "Adj. Close"})
    print(f"downloaded {len(df)} trading days up to {df.index.max().date()}")

We only need the Adj. Close column for our predictions.

In [4]:
df = df[['Adj. Close']]
print(df.tail())
            Adj. Close
Date                  
2026-09-04  616.770020
2026-09-08  613.479980
2026-09-09  653.690002
2026-09-10  644.380005
2026-09-11  648.030029

Now, let's set up our forecasting. We want to predict 30 days into the future, so we'll set a variable forecast_out equal to that. Then, we need to create a new column in our dataframe which serves as our label, which, in machine learning, is known as our output. To fill our output data with data to be trained upon, we will set our prediction column equal to our Adj. Close column, but shifted 30 units up.

In [5]:
forecast_out = int(30)  # predicting 30 days into the future
df['Prediction'] = df[['Adj. Close']].shift(-forecast_out)  # label column, data shifted 30 units up

You can see the new dataframe by printing it:

In [6]:
print(df.tail())
            Adj. Close  Prediction
Date                              
2026-09-04  616.770020         NaN
2026-09-08  613.479980         NaN
2026-09-09  653.690002         NaN
2026-09-10  644.380005         NaN
2026-09-11  648.030029         NaN

Defining features & labels¶

Our X will be an array consisting of our Adj. Close values, and so we want to drop the Prediction column. We also want to scale our input values. Scaling our features allows us to normalize the data.

In [7]:
X = np.array(df.drop(columns=['Prediction']))
X = preprocessing.scale(X)

Now, if you printed the dataframe after we created the Prediction column, you saw that for the last 30 days there were NaNs, i.e. no label data. We'll set a new input variable to these days and remove them from the X array.

In [8]:
X_forecast = X[-forecast_out:]  # set X_forecast equal to the last 30 days
X = X[:-forecast_out]           # remove the last 30 from X

To define our y, or output, we will set it equal to our array of the Prediction values and remove the last 30 days where we don't have any pricing data.

In [9]:
y = np.array(df['Prediction'])
y = y[:-forecast_out]

Linear regression¶

Finally, prediction time! First, we'll want to split our testing and training data sets, and set our test_size equal to 20% of the data. The training set contains our known outputs, or prices, that our model learns on, and our test dataset is there to test our model's predictions against what it learned from the training set.

In [10]:
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)

Now we can initiate our Linear Regression model and fit it with the training data. After training, to test the accuracy of the model, we "score" it using the testing data. We get an $r^2$ (coefficient of determination) reading based on how far the predicted price was from the actual price in the test data set.

In [11]:
# Training
clf = LinearRegression()
clf.fit(X_train, y_train)
# Testing
confidence = clf.score(X_test, y_test)
print("confidence: ", confidence)
confidence:  0.9494217584326416

Lastly, we can predict our X_forecast values -- the 30 trading days that follow the end of our data:

In [12]:
forecast_prediction = clf.predict(X_forecast)
print(forecast_prediction)
[559.58009951 592.70890893 590.4364384  591.25652591 592.37301034
 594.54664172 597.33290965 601.48267195 581.45516711 597.38229958
 592.32356112 571.69336827 546.69607388 549.02788387 548.83026485
 552.85157755 561.86246027 572.76046375 578.77762857 573.7978895
 580.63514085 575.02310431 581.14887798 595.28766858 612.90436405
 618.92152886 615.6708515  655.39979355 646.20118303 649.80753748]

What's next?¶

Try to plot your data using matplotlib, next to the realised prices. Make your predictions more advanced by including more features -- volume, moving averages, the prices of related stocks.

And keep the result in perspective: a high $r^2$ here mostly says that tomorrow's price looks a lot like today's, not that we can beat the market.