How to download historical stock prices in Python
Three lines with yfinance, or a contract that will still work next quarter — and the split-and-dividend decision you have to make either way.
For research, yfinance needs no key and three lines of code, but it is unofficial, delayed and its rate limit is published nowhere. For anything with users, buy a contract: Tiingo and EODHD start around thirty dollars a month and Alpaca is free on one exchange. Whichever you pick, decide before you download whether you want prices adjusted for splits and dividends, because the default differs by provider.
The short way
With yfinance, installed with pip install yfinance, this is the whole of it:
import yfinance as yf
prices = yf.download("AAPL MSFT", start="2020-01-01", end="2026-09-01")
prices.to_csv("prices.csv")
You get a pandas DataFrame, no key, no account and no bill. That is why every tutorial uses it, and for reading a chart or sketching a strategy it is the correct answer.
One parameter in there is doing more than it looks. auto_adjust defaults to True, so the
prices you just downloaded are already adjusted for splits and dividends — which is usually what
you want and is emphatically not what the exchange printed on the day. Pass
auto_adjust=False to get the raw series, and actions=True to get the splits and dividends
alongside it so you can do the adjusting yourself.
What the options are
The choice is not really between libraries. It is between having a contract and not having one.
No contract. yfinance reads Yahoo's undocumented endpoints. There is no agreement behind it, no support, no uptime commitment and no published quota, and when Yahoo changes something every user breaks simultaneously until a volunteer maintainer ships a release.
A cheap contract. Tiingo sells end-of-day equity history back to 1962; its free Starter plan is 50 requests an hour, 1,000 a day and 500 unique symbols a month, and the $30 Power plan raises that to 10,000 an hour — personal and internal use only, with the commercial licence at $50. EODHD covers 60-plus exchanges from $19.99 a month, and its unit of account is worth reading twice: the 100,000 daily allowance is calls, not requests, and one fundamentals or options request costs ten of them.
A broker's feed. Alpaca Market Data is free forever at 200 requests a minute, but the free tier is one exchange — IEX — and it withholds SIP data from the last 15 minutes. The $99 Algo Trader Plus plan is the full SIP and OPRA at 10,000 requests a minute. If you are going to trade through Alpaca anyway, this is one account rather than two.
The exchange feed itself. Databento is the shape when bars are not the product and you want the order book, billed per gigabyte of what it sends you rather than per month. Its Python client needs Python 3.10 or later; there is no Go, Java, C# or JavaScript client at all.
Free with no strings, in a narrow lane. Alpha Vantage gives a real key with real terms for nothing, at 25 requests a day — small, but a contract, and verified open-source and educational projects are exempt from the cap entirely.
Where this breaks
Splits and dividends. This is the one that makes a correct-looking script wrong.
A stock that splits four-for-one drops 75% overnight without anybody losing money. A dividend takes the price down by roughly its own size on the ex-date. A price series that records those two events as returns will show you crashes that never happened, and a backtest run on it will either buy the dip or stop out — repeatedly, and always on the same days.
So every provider offers an adjusted series, and here is the part that costs the afternoon: they do not agree, and they do not default the same way. yfinance adjusts by default. Others return the raw series unless you ask. Some adjust for splits only and leave dividends alone, which is a third answer again. Downloading the same ticker from two providers and diffing the closes is a perfectly reasonable first thing to do, and the difference you find is not a bug in either of them. Why adjusted close differs is the full version of this.
Two smaller walls behind it. Intraday history is short: yfinance cannot go past the last 60 days at any intraday interval, and most cheap plans are end-of-day only. And rate limits bind on the backfill, not on the daily job — pulling five years for 500 tickers is 500 requests in a burst, which is ten hours on Tiingo's free tier and twenty days on Alpha Vantage's.
If you outgrow this
The signals that you have, in the order they usually arrive.
Your script broke and nobody owes you anything. That is the moment to stop using an unofficial endpoint, whatever it costs. Any of the contracted providers above is under ten dollars a month at the entry tier.
You need delisted tickers. A universe assembled from the names that exist today has survivorship bias baked into every backtest run on it. Norgate Data carries delisted securities and index constituent membership, which is the fix.
You need the same data your broker fills against. Then the question is no longer historical and you are shopping for a live feed, where the exchange's fee schedule rather than the vendor's price list sets the number — see why real-time stock data is so expensive.
You are redistributing. The moment a price reaches somebody who is not you — a public page, a newsletter, an app — you need a licence that says so, and personal-use plans like Tiingo's Power tier explicitly do not. Read that line before building, not after.
The rest of the field is in market data APIs, narrowable by latency, coverage and price.
The tools that do this
In the order this page recommends trying them. Paid placement does not affect this order.
yfinance
No key, no account, three lines. Unofficial and delayed — right for research, wrong under anything with users.
The Python library everyone starts with — free, unofficial, and not something to build on.
FreeFree tierOpen source
Tiingo
Daily bars back to 1962 on a contract. Free is 50 requests an hour; $30 a month lifts it to 10,000.
End-of-day equity history back to 1962, plus crypto, forex and news, from $30 a month.
$30/moFree tier
EODHD
60-plus exchanges from $19.99 a month, with the daily allowance metered in calls rather than requests.
End-of-day and fundamentals for 60+ exchanges worldwide, at a hobbyist price.
$19.99/moFree tier
Alpaca Market Data
Free forever, but on the IEX feed alone and 200 requests a minute. $99 a month buys the full SIP.
Free IEX data forever, full SIP and OPRA for a flat $99 a month.
$99/moFree tier
Databento
For when bars are not enough and you want the exchange feed itself, billed by the gigabyte.
Full order book and tick history from exchange feeds, billed by the gigabyte.
$199/mo
FAQ
Is yfinance allowed for a commercial project?
Its own README says the Yahoo Finance API is intended for personal use, and Yahoo's developer terms forbid deriving income from the APIs without written permission. The Apache-2.0 licence covers the library's code, not the data it returns. For anything you charge for, the question is not whether it works but whose terms you are operating under, and the answer there is nobody's.
What rate limit does yfinance have?
None that is published. Neither the library's documentation nor Yahoo's terms give a number, and the terms reserve rate limits to Yahoo's sole discretion. You find the ceiling by hitting it, at which point the library raises YFRateLimitError. Wrapping calls in a requests_cache session used to soften this and is now refused outright by yfinance itself.
How far back does intraday data go?
On Yahoo through yfinance, the documentation states plainly that intraday data cannot extend past the last 60 days, whatever interval you ask for. If you need minute bars from three years ago, no free source has them and the question becomes which archive to buy — which is a different page from this one.
Should I store the adjusted price or the raw one?
Store the raw price and the corporate actions separately, and adjust when you read. An adjusted series is a derived view that changes every time a new split or dividend lands, so a file of adjusted closes downloaded last year no longer matches the same request today. That is not corruption; it is what adjustment means.
Sources
- yfinance API reference — yfinance.download — yfinance project, read
- About Market Data API — Alpaca, read
The catalogue next door
This page names a handful of cards. The rest of them are in Stock Market Data APIs, each filled in against the same schema, with the fields to narrow it yourself.
Last updated . Corrected in place: this is a reference page, not a dated post.