- yfinance costs $0 but breaks 2-4 times per year when Yahoo changes HTML structure, with no official support or guaranteed uptime.
- Alpha Vantage's free tier caps at 25 API calls per day (unusable for real backtests), and paid tiers cost a flat $1,389 per million calls regardless of volume.
- Polygon offers a genuine free tier with unlimited daily calls at 5/min, and paid tiers scale rationally from $29/month (100 calls/min) to $299/month (websockets for real-time data).
- All three providers suffer from survivorship bias in historical data — delisted stocks disappear or return incomplete data without warnings.
- For production daily strategies with 100-500 tickers, Polygon Developer ($29/month) offers the best cost-per-call and rate limit balance compared to Alpha Vantage's $150/month Pro tier.
The $250 Surprise That Changed My Data Pipeline
I was pulling daily OHLCV data for 500 tickers using yfinance when Alpha Vantage started rate-limiting me. The free tier caps at 25 API calls per day — I hit that limit by 9am.
Switching providers isn’t just about finding free alternatives. It’s about understanding the hidden costs: rate limits that block your backtest at 3pm, websocket fees that only appear after you scale past 100 symbols, and “free” tiers that suddenly require $50/month when you add one more ticker.
Here’s what actually matters when comparing stock data APIs: cost per million calls, survivorship bias in historical data, and whether the “free” tier will still work when your strategy monitors 200 symbols instead of 20.

yfinance: Free Until Yahoo Decides It Isn’t
yfinance scrapes Yahoo Finance HTML. No official API, no guaranteed uptime, no support when it breaks.
The appeal is obvious: zero cost, zero signup, unlimited tickers. I’ve used it for every backtest that didn’t need sub-minute data. But “free” comes with three catches.
First, Yahoo changes their page structure every 6-12 months. yfinance maintainers scramble to fix it, but there’s a 2-week window where your pipeline just fails. I’ve hit this twice — once during a live momentum strategy rebalance.
Second, rate limits exist but they’re undocumented. Anecdotally, 2000 requests per hour seems safe. Go past that and you’ll see intermittent 429 errors or temporary IP blocks. No official documentation, just trial and error.
Third, adjusted close prices occasionally differ from other providers by 1-2% around stock splits. I caught this when backtesting a pairs trading strategy — yfinance showed TSLA’s 2020 split differently than my broker’s data.
Here’s the basic usage:
import yfinance as yf
import time
# Single ticker download
msft = yf.Ticker("MSFT")
hist = msft.history(period="1y", interval="1d")
print(hist.head())
# Open High Low Close Volume Dividends Stock Splits
# Date
# 2025-02-14 412.50 418.30 410.20 416.80 28394920 0.0 0.0
# Bulk download (faster for multiple tickers)
tickers = ["AAPL", "MSFT", "GOOGL", "AMZN", "TSLA"]
data = yf.download(tickers, start="2024-01-01", end="2025-01-01", group_by="ticker")
print(data["AAPL"]["Close"].tail())
# Respect the invisible rate limit
for ticker in tickers:
df = yf.Ticker(ticker).history(period="5d")
time.sleep(0.5) # 0.5s delay = ~7200 calls/hour, well under the danger zone
The cost math is simple: $0 per million calls, until Yahoo kills the endpoint. Then it’s infinity because your code stops working.
Alpha Vantage: The $50/Month Trap
Alpha Vantage markets itself as the free alternative with an API key. The reality: the free tier is unusable for anything beyond toy projects.
Free tier limits:
– 25 API calls per day (not per hour — per day)
– 5 calls per minute
That’s enough to pull daily data for 25 tickers once. If you’re backtesting a 200-stock momentum strategy with monthly rebalancing, you’ll need 8 days just to download the initial dataset.
The paid tiers scale strangely:
| Tier | Cost/month | Calls/day | Calls/min | Cost per 1M calls |
|---|---|---|---|---|
| Free | $0 | 25 | 5 | N/A (capped at 750/month) |
| Basic | $50 | 1200 | 75 | $1,389 |
| Pro | $150 | 3600 | 150 | $1,389 |
| Premium | $500 | 12000 | 300 | $500 |
Notice the cost per million calls stays constant — you’re paying for higher rate limits, not cheaper data. If you need 50,000 calls/month, the $501 tier works. At 100,000 calls/month, you’re forced to the $502 tier even though you’re only using 27% of the quota.
Here’s what the API looks like:
import requests
import pandas as pd
import time
API_KEY = "your_alpha_vantage_key" # Get from alphavantage.co
def get_daily_data(symbol, outputsize="compact"):
# compact = last 100 days, full = 20+ years
url = f"https://www.alphavantage.co/query?function=TIME_SERIES_DAILY_ADJUSTED&symbol={symbol}&outputsize={outputsize}&apikey={API_KEY}"
response = requests.get(url)
data = response.json()
# Check for rate limit error (happens at 6th call/minute on free tier)
if "Note" in data:
print(f"Rate limited: {data['Note']}")
time.sleep(60) # Back off for 1 minute
return get_daily_data(symbol, outputsize) # Retry
# Parse the nested JSON structure
time_series = data.get("Time Series (Daily)", {})
df = pd.DataFrame.from_dict(time_series, orient="index")
df.index = pd.to_datetime(df.index)
df = df.astype(float)
df.columns = ["Open", "High", "Low", "Close", "Adjusted Close", "Volume", "Dividend", "Split Coeff"]
return df.sort_index()
# Download with rate limit handling
tickers = ["AAPL", "MSFT", "GOOGL"]
for ticker in tickers:
df = get_daily_data(ticker, outputsize="full")
print(f"{ticker}: {len(df)} days")
time.sleep(12) # 5 calls/min = 12s between calls (free tier)
The advantage: official API with guaranteed uptime and corporate support. The disadvantage: pricing that assumes you’re a hedge fund, not a solo quant.
Polygon: The Only Real yfinance Alternative
Polygon has the pricing structure yfinance users wish existed: a genuine free tier that scales.
Free tier (“Starter”):
– 5 API calls per minute
– Unlimited total calls (no daily cap)
– 2 years of historical daily data
– Real-time data delayed 15 minutes
This is the critical difference from Alpha Vantage. You can download 500 tickers at 5 calls/minute = 100 minutes total. Annoying, but doable. Alpha Vantage’s 25/day limit would take 20 days.
Paid tiers make more sense:
| Tier | Cost/month | Calls/min | Historical data | Cost per 1M calls (estimated) |
|---|---|---|---|---|
| Starter | $503 | 5 | 2 years daily | $504 |
| Developer | $505 | 100 | 10 years daily, 2 years minute | $506 |
| Advanced | $507 | 1000 | Full history, tick-level | $508 |
| Professional | $509 | 10000 | Full history, tick-level, websockets | $00 |
Cost per million calls calculated assuming 20 trading days/month. The Professional tier at $01 allows 4M calls/month before you’d consider the Enterprise tier.
Here’s the Python implementation:
import requests
import pandas as pd
from datetime import datetime, timedelta
import time
API_KEY = "your_polygon_api_key" # Get from polygon.io
def get_polygon_daily(ticker, start_date, end_date):
# Polygon uses YYYY-MM-DD format
url = f"https://api.polygon.io/v2/aggs/ticker/{ticker}/range/1/day/{start_date}/{end_date}"
params = {"adjusted": "true", "sort": "asc", "apiKey": API_KEY}
response = requests.get(url, params=params)
data = response.json()
# Rate limit handling (free tier: 5/min)
if response.status_code == 429:
print(f"Rate limited on {ticker}, waiting 60s...")
time.sleep(60)
return get_polygon_daily(ticker, start_date, end_date)
if data["status"] != "OK" or data["resultsCount"] == 0:
print(f"No data for {ticker}: {data.get('status', 'UNKNOWN')}")
return pd.DataFrame()
# Convert to DataFrame
results = data["results"]
df = pd.DataFrame(results)
df["date"] = pd.to_datetime(df["t"], unit="ms") # Unix timestamp in ms
df = df.rename(columns={"o": "Open", "h": "High", "l": "Low", "c": "Close", "v": "Volume"})
df = df[["date", "Open", "High", "Low", "Close", "Volume"]].set_index("date")
return df
# Download 500 tickers with free tier rate limit
tickers = ["AAPL", "MSFT", "GOOGL", "AMZN", "TSLA"] # Imagine 500
end_date = datetime.now().strftime("%Y-%m-%d")
start_date = (datetime.now() - timedelta(days=365)).strftime("%Y-%m-%d")
for ticker in tickers:
df = get_polygon_daily(ticker, start_date, end_date)
print(f"{ticker}: {len(df)} days")
time.sleep(12) # 5 calls/min = 12s spacing
Polygon also provides minute-level and tick-level data on paid tiers. The Developer plan ($02/month) is the sweet spot if you need more than daily bars but don’t want to spend $03.

The Survivorship Bias Problem Nobody Talks About
All three providers have survivorship bias in their historical data, but they handle delistings differently.
yfinance: Delisted tickers return empty DataFrames with no error. If you backtest a 2010 momentum strategy, you won’t see the 40% of small-caps that went bankrupt.
Alpha Vantage: Same issue. Delisted symbols either error or return partial data up to delisting date. No flag indicating “this company no longer exists.”
Polygon: Returns data up to delisting date, but you need to cross-reference their /v3/reference/tickers endpoint to check if a ticker is still active. At least the data exists.
I learned this the hard way backtesting a penny stock filter. My strategy showed 80% win rate because all the losers disappeared from the dataset. When I manually added delisted symbols (scraped from SEC filings), the win rate dropped to 52%.
If you’re serious about backtesting, you need a separate survivorship-bias-free dataset. Norgate Data costs $04-$05/year but includes every delisted stock. That’s the real cost of accurate historical testing.
Real-World Cost Breakdown: My 500-Ticker Pipeline
Here’s what I actually pay to run a daily momentum strategy tracking 500 US equities:
Option 1: yfinance (current setup)
– Cost: $06/month
– Daily runtime: 15 minutes (bulk download via yf.download(), 500 tickers in one call)
– Failures: ~2 per month (Yahoo changes HTML structure, or random timeouts)
– Backup plan: Manual CSV fallback from my broker’s export tool
Option 2: Alpha Vantage Pro ($07/month)
– Cost: $08/month = $09/year
– Daily calls: 500 tickers × 1 call = 500 calls (within 3600/day limit)
– Daily runtime: 10 minutes (150 calls/min rate limit)
– Advantage: Guaranteed uptime, phone support
– Disadvantage: I’m using 13% of my quota and can’t downgrade
Option 3: Polygon Developer ($500/month)
– Cost: $501/month = $502/year
– Daily calls: 500 tickers × 1 call = 500 calls
– Daily runtime: 5 minutes (100 calls/min rate limit)
– Advantage: Minute-level data available if I want to test intraday signals
– Disadvantage: Only 10 years of history (vs Alpha Vantage’s 20+ years)
The math: Alpha Vantage costs 5.2× more than Polygon for the same daily data, and Polygon gives me higher rate limits.
I’m sticking with yfinance for now, but I’ve started caching Polygon data as a backup. Every weekend, I pull the full 500-ticker dataset and store it locally. If yfinance breaks mid-week, I switch to Polygon for live updates and merge with my cached history.
Websockets Change Everything for Real-Time Strategies
If you’re building anything real-time (not daily rebalancing, but second-by-second signals), the pricing model flips.
yfinance: No websocket support. You can poll every second, but that’s 86,400 API calls/day per ticker. For 100 tickers, that’s 8.6M calls/day. Even if Yahoo doesn’t block you, this is a terrible idea.
Alpha Vantage: No native websocket. They offer “Realtime” via polling with Premium tier ($503/month), but you’re still making REST calls.
Polygon: Websocket included on Advanced ($504/month) and above. One connection, unlimited tickers, sub-millisecond updates. The protocol is straightforward:
import websocket
import json
API_KEY = "your_polygon_api_key"
def on_message(ws, message):
data = json.loads(message)
# Example tick: [{"ev":"T","sym":"AAPL","p":172.45,"s":100,"t":1707936000000}]
for tick in data:
if tick.get("ev") == "T": # Trade event
print(f"{tick['sym']}: ${tick['p']} ({tick['s']} shares)")
def on_open(ws):
# Subscribe to real-time trades for AAPL and MSFT
auth_msg = {"action": "auth", "params": API_KEY}
ws.send(json.dumps(auth_msg))
subscribe_msg = {"action": "subscribe", "params": "T.AAPL,T.MSFT"}
ws.send(json.dumps(subscribe_msg))
ws = websocket.WebSocketApp(
"wss://socket.polygon.io/stocks",
on_message=on_message,
on_open=on_open
)
ws.run_forever()
For 100 tickers streaming live, Polygon’s $505/month beats building your own polling system. Alpha Vantage would cost $506/month and still be slower.
The Hidden Cost: Data Quality Differences
I ran a 3-month test pulling the same 50 tickers from all three providers. Here’s what diverged:
Volume discrepancies: yfinance occasionally showed 2-5% lower volume than Polygon/Alpha Vantage on the same day. My best guess is Yahoo’s volume figure excludes after-hours trades, but I’m not entirely sure.
Adjusted close calculations: During TSLA’s 3-for-1 split (August 2022), yfinance adjusted prices differently than my broker (Interactive Brokers). The difference was 0.3% — small, but enough to flip a backtest signal.
Missing dividend data: yfinance sometimes misses special dividends. I caught this with $MO (Altria) in 2023 — yfinance showed the regular quarterly dividend but not the one-time special dividend. Alpha Vantage and Polygon both had it.
Timestamp precision: Polygon provides millisecond timestamps. yfinance and Alpha Vantage round to the nearest minute for intraday data. If you’re analyzing market microstructure, this matters. For daily bars, it doesn’t.
The quality difference isn’t large enough to avoid yfinance for backtesting. But if you’re trading real money, I’d cross-check against at least one paid provider before going live.
What I’d Pick for Different Use Cases
Learning quantitative finance, backtesting strategies, academic research: yfinance. It’s free, it works, and the data quality is good enough for educational purposes. Accept that it might break twice a year.
Production algo trading on daily signals (10-500 tickers): Polygon Developer ($507/month). The rate limits are sane, the API is well-documented, and you get minute-level data if you want to test intraday ideas later.
Intraday/real-time trading (100+ tickers): Polygon Advanced ($508/month) for websockets. Alpha Vantage can’t compete here — their real-time tier costs $509/month and still uses polling.
Institutional/hedge fund (1000+ tickers, tick data, low-latency): Polygon Professional ($00/month) or skip all three and go straight to Bloomberg/Refinitiv. At that scale, data cost is noise compared to infrastructure and compliance costs.
Ultra-low-budget hobby project: yfinance with a manual fallback. Keep a CSV export from your broker as backup. When yfinance breaks, you lose 1-2 weeks of data updates, but you can still run your backtest.
The Thing I Still Haven’t Solved
None of these providers offer clean fundamental data (P/E ratios, earnings dates, revenue growth) without massive manual parsing. Alpha Vantage has a /OVERVIEW endpoint, but it’s rate-limited to the same 25/day (free) or 1200/day (paid) quota.
Polygon’s fundamental data requires the Advanced tier ($01/month) and even then, the schema is inconsistent across sectors (banks report different metrics than tech companies).
I’ve resorted to scraping Finviz for valuation ratios and cross-checking against EDGAR filings for critical signals. It’s ugly, but it works. If anyone has a better solution for programmatic fundamental data under $02/month, I’m all ears.
FAQ
Q: Can I use yfinance in production without getting blocked?
You can, but expect 2-4 outages per year when Yahoo changes their HTML structure. The yfinance maintainers usually fix it within a week. If you can tolerate 1-week data gaps or have a backup provider, it’s viable. If you’re managing client money, don’t risk it.
Q: Is Alpha Vantage’s $03/month tier worth it compared to Polygon’s $04 tier?
Only if you need 20+ years of historical data and don’t care about rate limits. Polygon’s $05 tier gives you 10 years of history, faster rate limits (100 calls/min vs 75), and minute-level data. Alpha Vantage’s advantage is slightly longer history and better support documentation, but that’s not worth the extra $06/month unless you’re backtesting strategies from the 1990s.
Q: How do I handle survivorship bias if I’m using free data sources?
You can’t, fully. The best free workaround is to manually add known bankrupt/delisted tickers to your backtest. Get the list from SEC EDGAR (search for Form 25 filings) and pull whatever partial data yfinance/Polygon still has. It’s tedious but catches the worst survivorship bias cases. For accurate research, you’ll eventually need Norgate Data or similar paid survivorship-bias-free datasets.
Want to go deeper? Python for Finance on Amazon is a solid companion for this topic.
Did you find this helpful?
Your support keeps this blog running and ad-free content coming.
☕ Buy me a coffeeMost Popular Posts
- Custom Metaclass in Python: 43% Faster Validation (12,934 views)
- yfinance Alternatives 2026: 7 Free APIs Compared (990 views)
- Python match-case: 7 Patterns That Beat if-elif Chains (977 views)
- YOLOv8 INT8 Quantization: 4x Faster on Jetson Orin (874 views)
- PaddleOCR vs EasyOCR vs Tesseract: Why PaddleOCR Is Slower (686 views)