How to download and model financial data with Python

Hadrien Puche

Any financial analysis starts with data. Whether you want to analyze a stock, build a portfolio, measure risk, create a valuation model or develop trading strategies, the first step is always the same: obtaining financial data.

You could download data manually from websites such as Yahoo! Finance or Investing.com, but this quickly becomes tedious and time consuming. It also limits the amount of data you can work with.

Python allows us to automate this process and retrieve large amounts of financial information in just a few lines of code.

In this article, Hadrien Puche (ESSEC Business School, Grande École Program, Master in Management, 2023-2027) will help you understand how to:

  • Download historical stock prices with Python
  • Explore and visualize market data
  • Compute basic statistics and historical distributions
  • Compare multiple securities
  • Perform a case study to measure market risk (Beta and Epsilon)
  • Build the foundation needed for more advanced financial analysis

What financial data can we download?

Financial professionals use many different categories of data across individual assets as well as portfolios and funds.

Market data

  • Individual asset prices (e.g., individual stocks, corporate bonds)
  • Portfolios and funds (e.g., ETFs, mutual funds)
  • Currency exchange rates
  • Commodity prices
  • Bond yields

Company fundamentals

  • Revenue
  • Earnings
  • Margins
  • Cash flows

Macroeconomic data

  • Inflation
  • Interest rates
  • GDP growth
  • Unemployment

Alternative data

  • News
  • Social media sentiment
  • Satellite imagery
  • Credit card spending

Not all data sources are freely available. Many professional investors rely on paid platforms such as Bloomberg, FactSet, Capital IQ or Morningstar to access standardized, high-frequency, and point-in-time data.

Fortunately, stock market data specifically can easily be accessed for free using Python for research and learning purposes.

In this article, we will use the open-source YFinance library to download historical market data, that you will then be able to model and use for any financial analysis project you may have.

A step-by-step guide

Follow the next steps to download you first financial data with Python 🙂

Step 1: Installing the required libraries

If you have not yet installed Python, refer to the setup guide published earlier in this series to configure your execution environment (such as Jupyter Notebook or Anaconda).

Once your environment is ready, install the required packages:

pip install yfinance pandas numpy matplotlib

or inside Jupyter Notebook:

!pip install yfinance pandas numpy matplotlib

We will use:

  • yfinance to retrieve market data
  • pandas to manipulate data structures
  • numpy for financial and mathematical operations
  • matplotlib to create charts

Step 2: Import our Python packages

Every Python script starts by importing the packages we installed earlier. In Python, packages (or libraries) are reusable modules containing pre-written code and functions that extend Python’s core capabilities.

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

The aliases (yf, pd, np, plt) make the code shorter and easier to read.

Step 3: Download our first data

To download financial data, we use a ticker symbol, a unique combination of letters representing any publicly traded security. For example, AAPL represents Apple.

Whenever we use a Python function, we can customize the output by passing variables/parameters into the function, such as period (e.g., "5y" for 5 years) or specific start and end dates.

As an example, let us download daily data for Apple stock price over the last 5 years (Yahoo! Finance ticker: AAPL). The data will be stored in a data frame (df) that we can name df_apple.

df_aapl = yf.download("AAPL", period="5y")

print(df_aapl.head())

You will obtain this table with the following columns:

  • Close: closing price at the end of the trading day
  • High: highest price during the trading day
  • Low: lowest price during the trading day
  • Open: opening price at the beginning of the trading day
  • Volume: transaction volume during the trading day

An screenshot from VSC showing the output table of this Yfinance query

The data is stored in a Pandas DataFrame. This is a popular two-dimensional, tabular data structure with labeled axes (rows and columns).

To inspect its structure, type the following code:

df_aapl.info()

A screenshot from VSC showing the output of df_aapl.info()

Step 4: Using more precise queries for historical context

Instead of downloading a rolling period (like “5y”), we can isolate specific market events by passing exact start and end dates to the download function. As financial analysts, we routinely extract specific timeframes to understand how assets behave under macroeconomic stress.

For example, analyzing the COVID-19 market crash in early 2020 offers invaluable insights into extreme volatility, liquidity crunches, and rapid V-shaped recoveries. Let’s download and plot Apple’s stock specifically during the height of the pandemic shock (January to June 2020):

# Isolate the COVID-19 crash and initial recovery phase
covid_crash = yf.download("AAPL", start="2020-01-01", end="2020-06-30")

# Plot the isolated data
plt.figure(figsize=(10, 5))
plt.plot(covid_crash.index, covid_crash["Close"], color="#d9534f", linewidth=2)
plt.title("AAPL Stock Price - COVID-19 Crash & Recovery (Early 2020)")
plt.xlabel("Date")
plt.ylabel("Price ($)")
plt.grid(True, linestyle="--", alpha=0.6)
plt.show()

The output of the previous code cell

We could use this same technique to analyze other pivotal periods, such as:

  • A central bank interest rate tightening cycle (e.g., the Fed’s 2022-2023 rate hikes)
  • The 2008 Global Financial Crisis (if analyzing older datasets)
  • Specific earnings announcement windows

Step 5: Downloading multiple stocks at the same time

Downloading multiple stocks is necessary for financial analysis that often requires comparing securities, building a portfolio, or testing trading strategies like pair trading.

Instead of issuing separate requests for each stock (which risks hitting API rate limits or misaligning dates), it is far more efficient to fetch all tickers at once in a single batch query.

To make this practical, let’s download the data for the “Magnificent Seven”. These seven mega-cap tech companies (Apple, Microsoft, Alphabet, Amazon, Meta, Nvidia, and Tesla) have heavily dominated market capitalization and driven a massive portion of the S&P 500’s returns in recent years.

# Define the Magnificent 7 tickers
mag7_tickers = ["AAPL", "MSFT", "GOOGL", "AMZN", "META", "NVDA", "TSLA"]

# Download the closing prices for all 7 stocks simultaneously
prices = yf.download(mag7_tickers, period="5y")["Close"]
 
print(prices.head())

The result is now again a matrix where each column represents a stock, and each row represents a trading day.

Screenshot of the output of the previous cell

This table format is ideal for portfolio analysis, benchmarking, and performance comparisons, and can be used to draw any kind of graphs.

Note that the table’s columns are displayed in two groups. It’s a limitation of the Jupyter notebook. If you want to have a more convenient view over your dataframe, you can save it as a .csv file and open it.

# save the dataframe as a csv
prices.to_csv('mag_7_data.csv ')

Screenshot of the output of the previous cell

Now that you successfully downloaded your financial data, let’s see how you can clean it and then use it.

Inspecting and cleaning the dataset

Financial datasets are rarely perfect. You will frequently encounter missing values (represented as NaN, or “Not a Number”) in datasets. These gaps usually occur due to trading halts, early market closures (like the day before Thanksgiving), or simple transmission glitches from the data provider.

If left unaddressed, these missing data points will break your mathematical functions and severely distort your return and volatility calculations. The code below checks how many missing values exist in each column, and then removes (drops) any rows containing them. In some situations you might want to to forward-fill these gaps to preserve the timeline, but dropping them is the safest thing to do for now.

# Check for missing values
print(df_aapl.isnull().sum())
# Clean missing values by dropping rows with NaNs
df_aapl = df_aapl.dropna()
# Print the first rows of the dataframe
df_aapl.head()
df_aapl.head()

A screenshot from VSC showing the output of the cleaning code cell

To view the last rows of the dataframe, replace head() by tail():

Output of the VSC cell when we switch to tail()

Visualizing the stock price with graphs or charts

Let’s create our first graph to visualize the evolution of Apple stock price.

plt.figure(figsize=(10, 5))
plt.plot(df_aapl.index, df_aapl["Close"], label="AAPL Close Price")
plt.title("Apple Stock Price")
plt.xlabel("Date")
plt.ylabel("Price ($)")
plt.legend()
plt.show()

A screenshot of VSC with the stock price visualization cell output

We have now:

  • Downloaded market data from Yahoo! Finance
  • Stored and cleaned the data in a dataframe
  • Created a time-series plot to visualize stock prices

These core steps form the basis of empirical financial research and quantitative models.

Computing basic statistics & historical distributions

To evaluate stock performance and risk, we compute basic descriptive statistics for both prices and financial returns: minimum, maximum, mean, variance, standard deviation, skewness, and kurtosis.

# Calculate daily percentage returns
df_aapl['Return'] = df_aapl['Close'].pct_change()

# Compute summary statistics for Price and Returns
stats_df = pd.DataFrame({
    'Metric': ['Min', 'Max', 'Mean', 'Variance', 'Std Dev', 'Skewness', 'Kurtosis'],
    'Price ($)': [
        df_aapl['Close'].min().item(),
        df_aapl['Close'].max().item(),
        df_aapl['Close'].mean().item(),
        df_aapl['Close'].var().item(),
        df_aapl['Close'].std().item(),
        df_aapl['Close'].skew().item(),
        df_aapl['Close'].kurtosis().item()
    ],
    'Daily Return': [
        df_aapl['Return'].min().item(),
        df_aapl['Return'].max().item(),
        df_aapl['Return'].mean().item(),
        df_aapl['Return'].var().item(),
        df_aapl['Return'].std().item(),
        df_aapl['Return'].skew().item(),
        df_aapl['Return'].kurtosis().item()
    ]
})

print(stats_df)

Plotting Historical Distributions

Histograms display the frequency distribution of prices and daily returns, helping us inspect price trends, distribution symmetry, and tail risks.

fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# Price distribution
axes[0].hist(df_aapl['Close'].dropna(), bins=30, color='skyblue', edgecolor='black')
axes[0].set_title('Historical Price Distribution')
axes[0].set_xlabel('Price ($)')
axes[0].set_ylabel('Frequency')

# Return distribution
axes[1].hist(df_aapl['Return'].dropna(), bins=50, color='salmon', edgecolor='black')
axes[1].set_title('Historical Daily Return Distribution')
axes[1].set_xlabel('Daily Return')
axes[1].set_ylabel('Frequency')

plt.tight_layout()
plt.show()

Harmonizing stock prices and computing returns

All stocks have different nominal prices. If Tesla trades at $350 and Nvidia at $220, it does not mean that Tesla is worth more than Nvidia or performed better.

To establish an accurate comparison across these assets, we must execute two fundamental computations:

  1. Price harmonization: we normalize all historical time series to a base index of 100, to ensure a standardized starting point.
  2. Return calculation: we compute the periodic returns to get the actual performance in % rather than the absolute variation.
normalized = prices / prices.iloc[0] * 100

plt.figure(figsize=(10, 5))
plt.plot(normalized.index, normalized)
plt.title("Performance Comparison (Base = 100)")
plt.xlabel("Date")
plt.ylabel("Growth of $100")
plt.legend(prices.columns)
plt.show()

the output of the previous cell

We can also compute daily returns across all stocks:

returns = prices.pct_change().dropna()
print(returns.head())

the output of the previous cell

The chart now shows how much each investment would have grown from the same starting value.

This is a standard technique used by portfolio managers and equity analysts.

Case Study: The Capital Asset Pricing Model (CAPM)

In empirical finance, evaluating an individual asset requires isolating the return generated by the broader market from the return specific to the company itself. The Capital Asset Pricing Model (CAPM) provides the foundational framework to decompose this risk.

The model decomposes the return of an individual asset over a given time-period in three components: the risk-free rate, a market systematic factor and a firm specific factor. The model is expressed through the following equation:

rt = rf + β(rm – rf) + εt

Where:

  • rt is the return of the stock (e.g., Apple).
  • rf is the risk-free interest rate (e.g., the 13-week Treasury Bill, ^IRX).
  • β (Beta) represents the stock’s sensitivity to market movements (systematic risk).
  • rm – rf is the excess return of the market index (e.g., the S&P 500, ^GSPC).
  • εt (Epsilon) represents the idiosyncratic return associated to firm-specific risk not explained by the market.

By downloading these three time series simultaneously, we can calculate the stock’s Beta and isolate its firm-specific residual risk.

# Download asset (AAPL), market benchmark (S&P 500), and risk-free rate (13-week T-Bill)
market_data = yf.download(["AAPL", "^GSPC", "^IRX"], start="2022-01-01", end="2024-12-31")["Close"].dropna()

# Compute daily percentage returns for the stock and the market
returns_df = market_data[["AAPL", "^GSPC"]].pct_change().dropna()
 
# Convert the annualized risk-free yield (^IRX) to a daily rate
daily_rf = (market_data["^IRX"] / 100) / 252
returns_df["Rf"] = daily_rf
 
# Calculate the excess returns: (r_t - r_f) and (r_m - r_f)
excess_aapl = returns_df["AAPL"] - returns_df["Rf"]
excess_market = returns_df["^GSPC"] - returns_df["Rf"]
 
# Compute Market Beta: Covariance(stock, market) / Variance(market)
cov_matrix = np.cov(excess_aapl, excess_market)
beta = cov_matrix[0, 1] / cov_matrix[1, 1]
 
# Isolate Epsilon (the firm-specific residual risk)
# Rearranging the CAPM equation: epsilon = (r_t - r_f) - beta * (r_m - r_f)
epsilon = excess_aapl - (beta * excess_market)
 
print(f"Calculated Beta: {beta:.4f}")
print(f"Mean Firm-Specific Return (Epsilon): {epsilon.mean():.6f}")
print(f"Idiosyncratic Risk (Epsilon Std Dev): {epsilon.std():.4f}")

Common pitfalls

When working with market data, beginners often run into the same issues:

  • Using the wrong ticker symbol
  • Comparing stocks without normalizing prices
  • Forgetting that markets are closed on weekends and holidays
  • Failing to clean and handle missing values (NaN) in the dataset
  • Ignoring stock splits and dividends

Overall, don’t forget to always inspect and clean your data before starting your analysis.

Exercises

Exercise 1: Basic data retrieval and price visualization (MSFT)

Microsoft is a mature mega-cap technology company, and a cornerstone of most global equity portfolios. Retrieving and inspecting its historical data is a perfect starting point to practice basic YFinance commands.

Using the ticker symbol MSFT, download the last five years of daily market data.

Your tasks:

  • Use the appropriate pandas functions to display the first 5 rows and the last 5 rows of the dataset to verify data integrity (checking for correct start/end dates).
  • Generate a line chart plotting the closing price over the entire 5-year period to visualize its long-term market trend.

Exercise 2: time-series extraction and volume analysis on Tesla (TSLA)

Tesla is renowned for its high historical volatility and massive retail trading interest. The 2022-2024 window was particularly eventful for growth and electric vehicle stocks, marked by shifting supply chains and a rapid rise in interest rates. Isolating this exact timeframe allows us to analyze the stock’s behavior under changing macroeconomic conditions.

Using the ticker symbol TSLA, extract the market data for the precise calendar period from January 1, 2022, to December 31, 2024 (using the start and end parameters).

Your tasks:

  • Identify the peak (highest closing price) and the trough (lowest closing price) over this period to grasp the magnitude of the stock’s price swings.
  • Calculate the average daily trading volume, a fundamental metric used by analysts to assess market liquidity and ongoing investor interest.

Exercise 3: Comparative performance and risk profiling on Chinese tech companies

Chinese technology stocks often experience unique market cycles driven by distinct domestic regulatory environments and macroeconomic factors. Using their US-listed ADRs (American Depositary Receipts), compare the performance and risk characteristics of three major players:

  • Alibaba (BABA)
  • Baidu (BIDU)
  • PDD Holdings (PDD)

Questions:

  1. Which stock achieved the highest total cumulative return?
  2. Which stock was most volatile (highest standard deviation of daily returns)?
  3. Which one offered the best risk-adjusted profile (e.g., highest Sharpe ratio) over the period?

Download the solutions

To help you check your work and experiment further, you can download the complete Jupyter Notebook containing the full code, charts, and commentary for all exercises.

Download Solutions (.ipynb)

What’s next?

Now that you know how to download financial data, perform basic computations, and control for market risk, you are ready to delve into advanced quantitative and corporate finance topics.

About the Author

This article was written in September 2026 by Hadrien PUCHE (ESSEC Business School, Grande École Program, Master in Management, 2023-2027).

   ▶ Discover all articles by Hadrien PUCHE