Fall ResetAmazon USFall reset deals: check better picks before checkoutAmazon US: today's deals, useful picks and quick comparisons.Check DealsSlow PC?RecommendedPC slow today? Run a repair scan before it gets worseResolve common Windows issues and optimize system performance.Scan NowFall ResetAmazon USWork and home upgrades are worth comparing todayAmazon US: today's deals, useful picks and quick comparisons.See Picks×
Skip to content
Blog

Stock Market Analysis with Pandas, yfinance, DataReader and Plotly for Beginners

By TheFinanceBase Team12 min read
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

The original Yahoo Finance example built around pandas-datareader is now a legacy pattern. For a current beginner workflow, use yfinance to download historical stock prices, pandas to clean and analyze them, and Plotly to build interactive charts. You can still use pandas-datareader for maintained sources such as FRED and Fama/French.

This tutorial builds a notebook that downloads several stocks, validates the resulting DataFrame, calculates returns, moving averages and drawdowns, compares performance fairly, and creates line and candlestick charts.

What you will build

By the end, you will have:

  • A historical price dataset for Google, Amazon, Microsoft, Apple and Meta.
  • Basic descriptive statistics and daily returns.
  • Normalized performance comparisons starting at 100.
  • Moving-average and drawdown calculations.
  • Interactive Plotly line, faceted and candlestick charts.
  • A practical understanding of when to use yfinance versus pandas-datareader.

The examples analyze historical data only. They are not real-time market feeds, trading signals or investment advice.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

What each library does

  • pandas supplies DataFrames, indexes, joins, rolling calculations and missing-data tools.
  • pandas-datareader is a connector for supported economic, policy, central-bank and factor datasets. It is not a guarantee that every historical provider still works.
  • yfinance downloads Yahoo-sourced historical market data through an independent open-source project. It is not an official Yahoo SDK. See the project notes and data-use warning.
  • Plotly Express is the high-level charting API for quick line and comparison charts.
  • Plotly Graph Objects provides lower-level control for candlesticks, multiple traces, overlays and customized axes.

Plotly’s line-chart documentation describes this distinction between Plotly Express and Graph Objects. Cufflinks, a legacy pandas-to-Plotly bridge used in some older tutorials, is unnecessary here.

#1 Best Overall
Trading: Technical Analysis Masterclass: Master the financial markets
  • Language: english
  • Book - trading: technical analysis masterclass: master the financial markets
  • It is made up of premium quality material.

Install the environment

Create an isolated virtual environment:

python -m venv .venv

Activate it on macOS or Linux:

source .venv/bin/activate

On Windows PowerShell:

.venvScriptsActivate.ps1

Install the packages used by the current stock-analysis workflow:

python -m pip install --upgrade pip
python -m pip install pandas yfinance plotly jupyterlab

Install pandas-datareader only if you also want the FRED or Fama/French examples:

python -m pip install pandas-datareader

Versions checked August 18, 2026 were pandas-datareader 0.11.1, yfinance 1.6.0 and Plotly 6.9.0. These versions will change. The pandas-datareader 0.11.1 release requires Python 3.11 or newer, while Plotly 6.9.0 requires Python 3.8 or newer. Check the current pandas-datareader, yfinance and Plotly package pages before creating a new environment.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

For a published notebook, record the installed versions:

python -m pip freeze > requirements-lock.txt

The old DataReader approach—and why it may fail

Older tutorials commonly used code like this:

import pandas_datareader.data as data

df = data.DataReader(
    "AAPL",
    "yahoo",
    start="2021-01-01",
    end="2026-01-01",
)

This is the pattern used by the matching 2021 tutorial, which also used the old FB symbol and Cufflinks. It may now fail because current pandas-datareader documentation focuses on maintained macroeconomic, policy, central-bank and factor sources; Yahoo is not presented as a maintained public reader.

That does not make the original teaching idea wrong. It means the data-access layer should be updated. Use yfinance for this stock-price tutorial and reserve pandas-datareader for sources it currently documents.

Download historical stock prices with yfinance

import yfinance as yf

 tickers = ["GOOG", "AMZN", "MSFT", "AAPL", "META"]

prices = yf.download(
    tickers=tickers,
    start="2021-01-01",
    end="2026-01-01",
    auto_adjust=False,
    progress=False,
)

prices.head()

META is the current ticker for Meta Platforms. New code should not use the former FB symbol.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The end value is commonly treated as an exclusive boundary. Always inspect the returned final date rather than assuming that January 1, 2026 is included. Also remember that this is historical data. It may be delayed, incomplete, rate-limited or affected by provider changes; it should not be described as a guaranteed real-time feed.

Validate the download immediately

print(prices.columns)
print(prices.columns.names)
print(prices.index.dtype)
print(prices.shape)
print(prices.isna().sum())
print(prices.index.min(), prices.index.max())

These checks catch empty downloads, unexpected column layouts, missing history and date-range mistakes before they contaminate later calculations.

Adjusted and unadjusted prices

With auto_adjust=False, the returned data can include raw Open, High, Low and Close values plus separate adjustment-related fields such as adjusted close, dividends or splits, depending on the response.

Use auto_adjust=True when the goal is a straightforward historical comparison that accounts for the provider’s adjustment treatment. Use auto_adjust=False when you need to teach or inspect raw OHLC values, dividends and split adjustments separately.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Do not mix adjusted close with raw Open, High and Low in the same candlestick. An OHLC chart should use one consistent adjustment regime and one consistent security series.

Understand the DataFrame and its MultiIndex

When downloading multiple tickers, yfinance commonly returns hierarchical columns. The hierarchy may be arranged as (Price, Ticker) or (Ticker, Price), depending on library behavior and options. A MultiIndex is not an error: it represents more than one dimension in the columns.

Never assume the order. Inspect it:

print(prices.columns.names)
print(prices.columns)

If the first level contains fields such as Close, select the close prices like this:

close = prices["Close"].copy()

If the second level contains the fields, use a cross-section:

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
close = prices.xs("Close", axis=1, level=1).copy()

The method xs() means cross-section selection: it selects one value from a particular MultiIndex level.

A defensive helper works with either common layout:

def extract_field(data, field):
    if not hasattr(data.columns, "levels"):
        return data[[field]].copy()

    if field in data.columns.get_level_values(0):
        return data[field].copy()

    if field in data.columns.get_level_values(1):
        return data.xs(field, axis=1, level=1).copy()

    raise KeyError(f"{field!r} not found in columns")

close = extract_field(prices, "Close")
close = close.sort_index()
close = close.dropna(how="all")
close.head()

Keeping the MultiIndex is usually safer for a larger analysis. Flattening can make beginner code easier, but it can also hide whether a column means Close_AAPL or AAPL_Close:

Rank #3
Sale
How to Day Trade for a Living: A Beginner’s Guide to Trading Tools and Tactics, Money Management, Discipline and Trading Psychology (Stock Market Trading and Investing)
  • As a day trader, you can live and work anywhere in the world. You can decide when to work and when not to work.
  • You only answer to yourself. That is the life of the successful day trader. Many people aspire to it, but very few succeed. Day trading is not gambling or an online poker game.
  • To be successful at day trading you need the right tools and you need to be motivated, to work hard, and to persevere.
if hasattr(prices.columns, "levels"):
    prices.columns = [
        "_".join(str(part) for part in column).strip()
        for column in prices.columns.to_flat_index()
    ]

Analyze one stock with pandas

Descriptive statistics

close.describe()

This reports observations such as count, mean, standard deviation, minimum, quartiles and maximum. It describes the downloaded sample; it does not establish which company is a better investment.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Daily returns

daily_returns = close.pct_change().dropna()
daily_returns.head()

A return is the percentage change from one available observation to the next. It is generally more useful for comparing securities than their nominal quoted prices.

Cumulative growth of one dollar

growth = (1 + daily_returns).cumprod()
growth.tail()

This shows how a hypothetical one-unit investment would have grown if the calculated returns were compounded. It is a mathematical illustration, not a claim that the result includes every dividend, fee, tax or execution effect.

Period return

period_return = close.iloc[-1] / close.iloc[0] - 1
print(period_return)

This is a simple price-based period return. If you need a total-return comparison, use an appropriately adjusted series and state exactly how dividends and splits were treated.

Moving averages

moving_average_20 = close.rolling(20).mean()
moving_average_50 = close.rolling(50).mean()

The first rows are naturally missing because a 20- or 50-observation window is not yet available. A moving average summarizes recent prices; it does not predict the next price.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Drawdown

wealth = (1 + daily_returns).cumprod()
running_peak = wealth.cummax()
drawdown = wealth / running_peak - 1

drawdown.min()

Drawdown measures the decline from the previous running peak. It complements return and volatility by showing how deep a historical decline became.

Annualized volatility

annualized_volatility = daily_returns.std() * (252 ** 0.5)
print(annualized_volatility)

This approximation uses daily returns and the conventional estimate of 252 U.S. trading sessions per year. The number is not universal; label the frequency and convention whenever you report it. Volatility is not the same as risk-adjusted performance and does not account for an investor’s goals, liabilities or time horizon.

Compare stocks fairly: normalize the starting point

A raw-price chart can mislead. A $500 stock is not automatically outperforming a $50 stock, because share prices depend on splits, share structure and company history.

normalized = close.div(close.iloc[0]).mul(100)
normalized.head()

Every series now starts at 100. The endpoint answers: “What would the starting index be worth under this price series?” It does not automatically include dividends or fees.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

You can also compare cumulative returns:

cumulative_returns = (1 + daily_returns).cumprod() - 1
cumulative_returns.tail()

Any statement such as “the best-performing stock” must be limited to the selected dates, price basis, adjustment method and dataset. Historical outperformance is not a forecast.

Create interactive Plotly line charts

One stock

import plotly.express as px

ticker = "AAPL"

fig = px.line(
    close,
    x=close.index,
    y=ticker,
    title=f"{ticker} closing price",
    labels={"x": "Date", ticker: "Price"},
)

fig.update_layout(hovermode="x unified")
fig.show()

Plotly connects points in the order supplied. Sort time-series data before charting:

close = close.sort_index()

Otherwise, an unsorted index can produce a line that appears to move backward in time.

Several stocks on one normalized chart

fig = px.line(
    normalized,
    x=normalized.index,
    y=normalized.columns,
    title="Normalized stock performance",
    labels={
        "value": "Indexed value (start = 100)",
        "variable": "Ticker",
    },
)

fig.update_layout(hovermode="x unified")
fig.show()

Hovering over a date displays the series together, which makes the chart easier to inspect than separate static images.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Faceted charts

Facets give each ticker its own panel. This is useful when a combined chart becomes visually crowded or when nominal price levels matter.

long_close = (
    close.reset_index()
         .rename(columns={"index": "Date"})
         .melt(
             id_vars="Date",
             var_name="Ticker",
             value_name="Close",
         )
         .dropna()
)

fig = px.line(
    long_close,
    x="Date",
    y="Close",
    facet_col="Ticker",
    facet_col_wrap=2,
    title="Closing prices by ticker",
)

fig.show()

Build a candlestick chart

A candlestick represents one security over each time interval:

  • The body spans the opening and closing prices.
  • The wick, or shadow, spans the interval’s high and low.
  • Color is a display convention for the relationship between open and close; it is not itself a trading signal.

First extract one ticker’s OHLC data. The exact selection depends on the MultiIndex order, so inspect the columns first. If tickers are the second level:

aapl = prices.xs("AAPL", axis=1, level=1)

required = {"Open", "High", "Low", "Close"}
missing = required - set(aapl.columns)
if missing:
    raise ValueError(f"Missing OHLC fields: {missing}")

Then plot it with Graph Objects:

import plotly.graph_objects as go

fig = go.Figure(
    data=[
        go.Candlestick(
            x=aapl.index,
            open=aapl["Open"],
            high=aapl["High"],
            low=aapl["Low"],
            close=aapl["Close"],
            name="AAPL",
        )
    ]
)

fig.update_layout(
    title="AAPL candlestick chart",
    xaxis_rangeslider_visible=False,
    yaxis_title="Price",
)

fig.show()

See Plotly’s candlestick API documentation for the dedicated chart type.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Candlesticks show what happened during each interval; they do not predict what happens next. The chart can also be misleading if dates are missing, OHLC values are inconsistent, or adjusted and unadjusted fields are mixed.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Add a moving-average overlay

aapl_close = close["AAPL"]

fig = go.Figure()

fig.add_trace(
    go.Scatter(
        x=aapl_close.index,
        y=aapl_close,
        mode="lines",
        name="Close",
    )
)

fig.add_trace(
    go.Scatter(
        x=aapl_close.index,
        y=aapl_close.rolling(50).mean(),
        mode="lines",
        name="50-day moving average",
    )
)

fig.update_layout(
    title="AAPL close and 50-day moving average",
    hovermode="x unified",
)

fig.show()

Add volume carefully

volume = extract_field(prices, "Volume")
volume.head()

Volume is a separate series and may have missing values or provider-specific conventions. If you combine it with price in a custom figure, verify that both series share the same dates and ticker and that the chart’s axes make the units clear.

Use pandas-datareader where it is currently maintained

pandas-datareader remains useful for economic and factor data. For example, retrieve the U.S. 10-year Treasury constant-maturity rate from FRED:

import pandas_datareader.data as web

fred = web.DataReader(
    "DGS10",
    "fred",
    start="2021-01-01",
    end="2026-01-01",
)

fred.head()

For Fama/French factor data:

from pandas_datareader import data as web

factors = web.DataReader(
    "F-F_Research_Data_Factors",
    "famafrench",
    start="2021-01-01",
    end="2026-01-01",
)

factors[0].head()

The current remote-data documentation identifies FRED and Fama/French among its documented sources and describes the project’s focus on macroeconomic, policy, central-bank and factor-style data.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Troubleshooting

“DataReader(…, ‘yahoo’, …)” fails

Install yfinance and replace the retrieval layer:

python -m pip install yfinance
import yfinance as yf

df = yf.download(
    "AAPL",
    start="2021-01-01",
    end="2026-01-01",
    progress=False,
)

FB returns no data

Use META in new examples. Ticker symbols can change, so old notebooks often require updates even when the analysis itself is sound.

MultiIndex selection raises KeyError

print(df.columns)
print(df.columns.names)

Select the field by the level where it actually appears, or use the defensive extract_field() helper above.

Missing values appear

Possible causes include market holidays, different trading calendars, a ticker with shorter history, temporary provider failures, delistings, renamed securities or dates outside an instrument’s available history. Do not blindly forward-fill OHLC data. Investigate missing observations before calculating returns.

A Plotly chart does not render

Try the normal display call:

fig.show()

If your notebook needs an explicit renderer:

import plotly.io as pio

pio.renderers.default = "notebook_connected"

To open figures in a local browser:

pio.renderers.default = "browser"

A candlestick looks wrong

aapl[["Open", "High", "Low", "Close"]].dtypes
aapl[["Open", "High", "Low", "Close"]].isna().sum()

Confirm that the index is datetime-like and that all four fields come from the same ticker and adjustment regime.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Data limitations and responsible interpretation

This notebook describes a selected historical window. It does not account automatically for:

  • Dividends, fees, taxes or trading costs.
  • Corporate-action treatment beyond the selected adjustment setting.
  • Survivorship bias from analyzing securities that remain available today.
  • Look-ahead bias in any future backtest.
  • Different exchange calendars and missing observations.
  • Provider outages, rate limits or changes to public endpoints.

“Best performing” therefore needs a precise definition: dates, price or total-return basis, adjustment method, benchmark and risk measure. A higher return may have come with a much larger drawdown or volatility.

yfinance is suitable for educational, exploratory and personal analysis, but its project page describes it as independent from Yahoo and warns that data access is subject to applicable terms. It should not be treated as a production market-data contract for regulated systems, commercial redistribution or latency-sensitive trading.

Where to run the notebook

Local JupyterLab gives you the most control and makes environment pinning straightforward. Google Colab is a convenient browser-based option for beginners who do not want to configure Python locally. Kaggle Notebooks can be useful for public datasets and shareable educational work, but neither hosted environment should be assumed to provide persistent, private or production-grade execution.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Useful next steps

  • Compare each stock with a relevant benchmark index.
  • Calculate rolling volatility and correlations.
  • Use dividend-aware total-return data when that is the analytical goal.
  • Build a portfolio with explicit weights and rebalancing rules.
  • Backtest only with strict controls against look-ahead and survivorship bias.
  • Turn the figures into a Dash application. Plotly’s documentation points to Dash for analytical web apps.
  • Use a licensed commercial API when you need contractual uptime, defined corporate-action handling, redistribution rights or intraday access.

For readers who want to publish or privately share a dashboard, Plotly Cloud or Plotly Studio may be relevant. Pricing seen August 18, 2026 listed a free plan and a Pro plan at $29 per creator seat per month or $290 annually, but limits and prices change; verify the current official pricing page before purchasing. Local Plotly charts do not require Plotly Cloud.

Choosing a data source

Goal Practical choice Main qualification
Beginner historical stock exploration yfinance Independent project and public-provider terms apply.
Interest rates and macroeconomic series pandas-datareader with FRED Use the supported source and inspect its frequency and missing dates.
Academic factor research pandas-datareader with Fama/French Understand the dataset’s frequency, definitions and factor conventions.
Production or commercial distribution A licensed direct market-data provider Compare coverage, quotas, adjustments, rights, support and SLA.
Fixed reproducible tutorial data A versioned CSV file Document its origin, download date and adjustment method.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Written by TheFinanceBase Team

The Team behind TheFinanceBase.

Add your note

Your email address will not be published. Required fields are marked *

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Recommended PC Tool
Recommended PC Tool
PC Slower Than It Used to Be?Free scan - under a minute
Crashes, No Sound, or Screen Glitches?Free driver scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.