Skip to main content

Downloading Financial Data

Modified

March 23, 2026

The Package yfinance

We’ll use the yfinance package to download stock price data from Yahoo! Finance. This Python library provides a simple interface for retrieving historical market data for stocks, indices, and other financial instruments. Before using it, you’ll need to install it first.

Anaconda

If you are using Anaconda you need to install yfinance first before you use it for the first time. In an Anaconda command prompt type:

pip install yfinance --upgrade

or in a Jupyter cell you can type:

!pip install yfinance --upgrade --quiet

Google Colab

In Google Colab, yfinance is pre-installed. If you ever need to upgrade to the latest version run this in a code cell:

!pip install yfinance --upgrade --quiet

Importing the Libraries

You can import the libraries as follows.

import matplotlib.pyplot as plt
import seaborn as sns
import yfinance as yf

sns.set_theme()

Data

In order to estimate asset pricing models, we need to get data on stock returns, for both a particular security and the market. An easy way to do the is to download this data from the web. We start by defining the security that we want to analyze and from which date we want to start the analysis.

To get the data, we use the function download() in yfinance. For example, to get the data for Microsoft (ticker: MSFT) we type:

yf.download(tickers='MSFT', auto_adjust=False, progress=False, multi_level_index=False)
Adj Close Close High Low Open Volume
Date
2026-10-09 535.070007 535.070007 537.219971 526.239990 528.239990 18500600
2026-10-12 541.150024 541.150024 542.739990 535.700012 537.530029 18314900
2026-10-13 543.450012 543.450012 551.150024 543.059998 544.640015 23852000
2026-10-14 533.690002 533.690002 545.750000 532.539978 545.270020 22324300
2026-10-15 539.440002 539.440002 541.130005 531.219971 533.320007 24151200
2026-10-16 529.049988 529.049988 543.820007 524.429993 541.309998 24845700
2026-10-19 533.520020 533.520020 535.570007 528.059998 528.700012 17994900
2026-10-20 531.869995 531.869995 534.210022 525.840027 533.909973 14576000
2026-10-21 532.950012 532.950012 533.280029 525.020020 529.440002 13940100
2026-10-22 521.080017 521.080017 533.599976 520.299988 533.140015 21266600
2026-10-23 524.510010 524.510010 525.770020 515.700012 517.750000 13671500
2026-10-26 532.599976 532.599976 533.250000 521.690002 527.400024 19021000
2026-10-27 531.330017 531.330017 534.700012 528.330017 532.349976 19532300
2026-10-28 531.010010 531.010010 535.000000 526.140015 531.820007 28594200
2026-10-29 530.140015 530.140015 541.289978 519.559998 540.219971 39333200
2026-10-30 521.909973 521.909973 526.000000 514.349976 525.270020 35652000
2026-11-02 507.119995 507.119995 529.630005 504.959991 524.020020 34743300
2026-11-03 520.989990 520.989990 523.440002 507.820007 509.010010 20010200
2026-11-04 555.799988 555.799988 559.150024 542.299988 543.700012 50807800
2026-11-05 535.929993 535.929993 557.619995 532.679993 554.940002 42507900
2026-11-06 530.609985 530.609985 538.330017 524.469971 525.690002 26938400
2026-11-09 543.570007 543.570007 547.479980 538.699585 544.484985 9328705

The output of the function download() is a Pandas dataframe. Therefore, all operations available on Pandas work on the dataframe just imported.

The yfinance library was recently updated so that the Close column now shows split and dividend-adjusted prices by default. To get both the unadjusted price and the adjusted price separately, we need to set auto_adjust=False when downloading data.

I use the option progress=False to suppress the status bar when downloading the data. The code runs just as fine without it.

The option multi_level_index=False flattens the column names to a single level. Without it, yfinance returns columns with two levels (price type and ticker), so you would need to write df['Close']['MSFT'] instead of simply df['Close'].

Also, note that by default, the dataset only includes data for the last month. We can specify an arbitrary starting date by adding, for example, start='2012-01-01'.

yf.download(tickers='MSFT', start='2012-01-01', auto_adjust=False, progress=False, multi_level_index=False)
Adj Close Close High Low Open Volume
Date
2012-01-03 20.833269 26.770000 26.959999 26.389999 26.549999 64731500
2012-01-04 21.323549 27.400000 27.469999 26.780001 26.820000 80516100
2012-01-05 21.541454 27.680000 27.730000 27.290001 27.379999 56081400
2012-01-06 21.876097 28.110001 28.190001 27.530001 27.530001 99455500
2012-01-09 21.588148 27.740000 28.100000 27.719999 28.049999 59706800
... ... ... ... ... ... ...
2026-11-03 520.989990 520.989990 523.440002 507.820007 509.010010 20010200
2026-11-04 555.799988 555.799988 559.150024 542.299988 543.700012 50807800
2026-11-05 535.929993 535.929993 557.619995 532.679993 554.940002 42507900
2026-11-06 530.609985 530.609985 538.330017 524.469971 525.690002 26938400
2026-11-09 543.570007 543.570007 547.479980 538.699585 544.484985 9328705

3735 rows × 6 columns

Close v/s Adjusted Close Price

The data query produces six columns. When estimating asset pricing models we will be interested in using the adjusted close (Adj Close) column since the price series is adjusted for stock splits and dividends. Let’s compare how the Adj Close differs from the Close price.

For this, let’s extract these two series and store them in a dataframe called df. The method df.loc[:, [column_label_1, column_label_2, ..., column_label_n]] selects multiple columns from the dataframe. In our example we want to select loc[:, ['Close', 'Adj Close']].

df = yf.download(tickers='MSFT', start='2012-01-01', auto_adjust=False, progress=False, multi_level_index=False).loc[:, ['Close', 'Adj Close']]

Sometimes in Python lines might get really long. A suggested way to split a long line of code into several smaller lines is to use parentheses around the code that you want to split and split whenever there is a dot.

df = (yf
      .download(tickers='MSFT', start='2012-01-01', auto_adjust=False, progress=False, multi_level_index=False)
      .loc[:, ['Close', 'Adj Close']]
      )

The previous line of code does exactly the same as the one we wrote originally, but might be easier to read and understand.

We can now compute cumulative percentage changes (cum_pct) for both series by applying to each column in df a function that divides everything by the first price. The apply() method applies a function along an axis of a DataFrame or a Series. The function can be a built-in Python function or a custom function that you define.

First, we define a function that takes a series x as input and divides the series by its initial value x[0].1

1 In the original code, I wrote x / x[0]. However, that syntax is being deprecated and the suggestion is to access the row number of the pandas dataframe using iloc.

def normalize(x):
    return x / x.iloc[0]

Then, we apply this function to our dataframe and plot it.

cum_pct = df.apply(normalize)
cum_pct.plot()
plt.title('Close v/s Adjusted Close of Microsoft')
plt.ylabel('Cumulative Return')
plt.show()

According to the graph, the impact of dividends is significant. Another way to see this is by actually computing the dividends from the two series. We will do this later.

Practice Problems

Problem 1 Plot the Close price for Nvidia Corporation (Ticker: NVDA) from July 1, 2015 until May 1, 2023.

Solution
df = yf.download(tickers='NVDA', start='2015-07-01', end='2023-05-01', auto_adjust=False, progress=False, multi_level_index=False).loc[:,['Close']]
df.plot()
plt.title('NVDA Stock Price')
plt.show()

Problem 2 Plot the S&P 500 (Ticker: ^GSPC) from January 1, 2018 until May 1, 2023. Since the S&P 500 is an index you can use either the Close or Adj Close, it does not make a difference.

Solution
df = yf.download(tickers='^GSPC', start='2018-01-01', end='2023-05-01', auto_adjust=False, progress=False, multi_level_index=False).loc[:,['Close']]
df.plot()
plt.title('S&P 500')
plt.show()

Problem 3 Imagine that you invest $100 in NVDA and AMD in January 1, 2015. Plot the evolution of each investment until May 1, 2023.

Solution

To plot the evolution of each investment, all we need to do is normalize the Adj Close evolution to 100 in January 1, 2015 for both stocks. We can use the same function we built before and multiply the result by 100. We use Adj Close instead of Close to account for stock splits and dividends.

df = (yf
      .download(tickers=['NVDA', 'AMD'], start='2015-01-01', end='2023-05-01', auto_adjust=False, progress=False)
      .loc[:, 'Adj Close']
      .apply(normalize)
      )*100
df.plot()
plt.title('Evolution of a $100 Investment in NVDA and AMD')
plt.ylabel('Portfolio Value ($)')
plt.show()