In August 2024, IEX Cloud emailed its customers to say it was shutting down. Thousands of hobbyist quants and small funds had built pipelines on it, and they all got the same lesson at once: your market data vendor is a dependency like any other, and it can be deprecated. A good chunk of that traffic moved to Polygon.io, the Atlanta company founded by Quinton Pike in 2017 that has spent the years since positioning itself as the developer-first data vendor.
Polygon serves stocks, options, indices, forex and crypto through a consistent REST API and websocket feeds, with an official Python client and a free tier that is genuinely usable for learning: five API calls per minute and end-of-day data going back years, at the time of writing. Paid stock plans start at $29 per month and scale up to real-time consolidated feeds.
This tutorial takes you from an empty environment to a working pandas workflow: getting a key, installing the client, pulling daily and minute bars, computing returns and a moving average, and a look at streaming. It finishes with the gotchas the marketing page does not mention.
What Polygon Offers, and What the Free Tier Really Gets You
Polygon's coverage splits into asset-class products, each with its own subscription. For equities, the practical tiers look like this at the time of writing (check the pricing page before committing, because data vendors reprice often):
| Tier | Price | Rate limit | Data freshness | History |
|---|---|---|---|---|
| Basic (free) | $0 | 5 calls/min | End of day | 2 years |
| Starter | $29/mo | Unlimited | 15-min delayed | 5 years |
| Developer | $79/mo | Unlimited | 15-min delayed + trades | 10 years |
| Advanced | $199/mo | Unlimited | Real time | 20+ years |
The free tier's five-calls-per-minute cap sounds tight, and for anything interactive it is. But for research it is workable: one call can return up to 50,000 aggregate bars, which is roughly 200 years of daily data or several weeks of minute data for a single ticker. Fetch once, cache to disk, and the rate limit mostly stops mattering.
Websocket streaming, options chains and tick-level trades and quotes sit behind the paid tiers. If your immediate goal is to learn the workflow and backtest daily strategies, the free tier is enough. If you want to run anything live intraday, budget for Starter at minimum.
Getting a Key and Installing the Client
Sign up at polygon.io with an email address; no card is required for the free tier. Your API key appears on the dashboard immediately. Treat it like a password: do not hardcode it in scripts you might commit, and prefer an environment variable.
# Install the official Python client (and pandas for the workflow later) pip install polygon-api-client pandas # Store your key in the environment rather than in code export POLYGON_API_KEY="your_key_here" # add to ~/.zshrc to persist
The client picks up the environment variable automatically, so connecting is two lines:
import os from polygon import RESTClient client = RESTClient(os.environ["POLYGON_API_KEY"]) # Smoke test: fetch details for one ticker details = client.get_ticker_details("AAPL") print(details.name, "|", details.market_cap)
If that prints Apple's name and market cap, you are connected. If you get a 401, the key is wrong; if you get a 429, you have already met the rate limiter (more on that later).
Fetching Daily Aggregates
Polygon calls OHLCV bars "aggregates", and one endpoint serves every bar size: you specify a multiplier and a timespan, so daily bars are 1 x day and five-minute bars are 5 x minute. The client's list_aggs handles pagination for you and yields bar objects.
from polygon import RESTClient import os client = RESTClient(os.environ["POLYGON_API_KEY"]) bars = client.list_aggs( ticker="AAPL", multiplier=1, timespan="day", from_="2024-01-02", to="2025-12-31", adjusted=True, # split- and dividend-adjusted prices sort="asc", limit=50000, ) for bar in list(bars)[:3]: print(bar.timestamp, bar.open, bar.high, bar.low, bar.close, bar.volume)
Two details worth noticing. The adjusted=True flag returns prices adjusted for splits, which is what you want for research (an unadjusted Apple series has a cliff at every split). And timestamp is a Unix timestamp in milliseconds, UTC - not a date string, and not New York time. Every off-by-one-day bug in a Polygon pipeline traces back to that field.
Minute bars use the same call with timespan="minute". Note the volume: two years of minute bars for a liquid name is around 390 bars per session times 500 sessions, close to 200,000 rows, so you will page through several responses even at the 50,000-bar limit. The client's iterator does this transparently, but each page is an API call, which counts against the free tier's five per minute.
minute_bars = client.list_aggs( ticker="AAPL", multiplier=1, timespan="minute", from_="2025-11-03", to="2025-11-07", adjusted=True, limit=50000, ) print(sum(1 for _ in minute_bars), "minute bars fetched")
A Small Pandas Workflow: Returns and a Moving Average
Raw bar objects are awkward to analyse. The natural next step is a DataFrame indexed by date, from which returns, moving averages and everything else in pandas for financial data analysis follows directly.
import pandas as pd rows = [ { "date": pd.Timestamp(bar.timestamp, unit="ms", tz="UTC"), "open": bar.open, "high": bar.high, "low": bar.low, "close": bar.close, "volume": bar.volume, } for bar in client.list_aggs( "AAPL", 1, "day", "2024-01-02", "2025-12-31", adjusted=True, limit=50000, ) ] df = pd.DataFrame(rows).set_index("date").sort_index() # Daily simple returns and a 20-day moving average df["return"] = df["close"].pct_change() df["ma_20"] = df["close"].rolling(20).mean() ann_vol = df["return"].std() * (252 ** 0.5) print(df[["close", "return", "ma_20"]].tail()) print(f"Annualised volatility: {ann_vol:.1%}")
From here you are in ordinary quant-research territory: resample minute bars to hourly with df.resample("1h"), compute rolling correlations across tickers, or hand the frame to a backtesting engine. If that is the direction you are heading, our Backtrader tutorial picks up exactly where this DataFrame leaves off.
One practical habit: cache what you fetch. A parquet file per ticker per year (df.to_parquet) means you hit the API once and iterate on your analysis locally, which is faster for you and keeps you clear of the rate limit.
Websocket Streaming in Brief
For live applications, REST polling is the wrong shape; Polygon's websocket feeds push trades, quotes and second-by-second aggregates to you as they happen. The same library includes a client:
from polygon import WebSocketClient from polygon.websocket.models import Market ws = WebSocketClient( api_key=os.environ["POLYGON_API_KEY"], market=Market.Stocks, subscriptions=["A.AAPL", "A.MSFT"], # per-second aggregates ) def handle(msgs): for m in msgs: print(m.symbol, m.close, m.volume) ws.run(handle) # blocks; run in its own process in real systems
Two caveats before you build on this. Real-time equity websockets require a paid plan (the free tier gets you delayed streams at best, and entitlements change, so check current terms). And a production consumer needs reconnection logic, heartbeat monitoring and backpressure handling; the ten-line version above is a demo, not an architecture. The general patterns for consuming market data feeds are covered in our guide to REST APIs for financial data.
Reference Data: Splits, Dividends and Corporate Actions
Prices are half the job. The endpoints that quietly save your research from silent errors are the reference ones, and they are available on the free tier.
# Stock splits - the reason your unadjusted chart has cliffs in it for split in client.list_splits(ticker="AAPL", limit=10): print(split.execution_date, split.split_from, "for", split.split_to) # Dividend history with ex-dates, which is what total return needs for div in client.list_dividends(ticker="AAPL", limit=5): print(div.ex_dividend_date, div.cash_amount, div.frequency) # Was the market even open that day? holidays = client.get_market_holidays() print(holidays[0].date, holidays[0].name, holidays[0].status)
Three habits these endpoints support. Store the splits table alongside unadjusted prices so you control adjustment yourself and your cached history stops silently disagreeing with fresh fetches. Use dividend ex-dates to build total-return series, because price return alone understates a decade of a dividend payer's performance by a wide margin. And check the market calendar before declaring a data gap a bug: your pipeline is not broken on Thanksgiving, it is Thursday.
There is also a ticker news endpoint (headlines with timestamps and tickers attached) that people use for event studies and sentiment work, and a full reference universe under list_tickers if you need to enumerate what exists. None of it is glamorous. All of it is the difference between a dataset and a pile of numbers.
Polygon vs the Alternatives
No single data vendor is right for every project, and the honest comparison depends on what you are optimising for.
| Source | Cost | Strengths | Weaknesses |
|---|---|---|---|
| Polygon | Free tier; $29+/mo | Consistent API, good docs, tick data on paid tiers, official client | US-centric equities; real time costs real money |
| yfinance | Free | Zero setup, global coverage, fine for coursework | Unofficial Yahoo scraper; no SLA, breaks without notice |
| Alpha Vantage | Free tier; paid from $50/mo | Long history, fundamentals endpoints | Free tier now around 25 requests/day at the time of writing; awkward JSON |
| Databento / Tiingo | Usage-based / from $10/mo | Institutional-quality depth (Databento); cheap clean EOD (Tiingo) | Costs scale with usage; smaller ecosystems |
The pattern that serves most people learning Python for finance: use yfinance for quick experiments where data quality is not the point, and move to Polygon (or Tiingo) the moment you care about reliability, minute bars or a contract that says the API will exist next quarter. The IEX Cloud shutdown is the cautionary tale for building anything durable on a free service with no revenue attached to your usage.
One Polygon feature worth knowing about before you write a thousand-ticker download loop: the paid tiers include flat files, bulk daily archives of trades, quotes and aggregates served from S3-compatible storage. If your real need is "the whole market, every day" rather than "this ticker, right now", downloading one compressed file per session beats paging through the REST API by orders of magnitude, in both time and rate-limit budget. Research pipelines and REST APIs are different tools; plenty of teams use the flat files for history and keep the API for the live edge.
Rate Limits, Gotchas and Honest Caveats
Every data pipeline eventually fails at 2am for one of the reasons below. Read this section before yours does.
The rate limiter returns 429s, and pagination counts. Five calls per minute on the free tier includes every page of a paginated request. A naive loop over 100 tickers will hit the wall at ticker five. Either sleep 12 seconds between calls, use a limiter library, or batch your universe into a nightly cache job. The client raises an exception on 429 rather than silently retrying, which is the right behaviour but still yours to handle.
Timestamps are milliseconds, UTC. Convert deliberately and localise to America/New_York if you are aligning to the trading session. Daily bars carry the timestamp of the session start in UTC, which can display as the previous evening in US time if you convert carelessly.
Adjusted data rewrites history. With adjusted=True, a split changes every historical price for that ticker, so yesterday's cached file and today's fetch can disagree. Store unadjusted plus a splits table, or accept that caches must be invalidated on corporate actions.
Free-tier data is end of day. You cannot see today's session until after the close, and delayed entitlements on cheaper paid tiers mean any live signal is at least 15 minutes stale. Design around what your tier actually delivers, not what the API shape implies.
Coverage and survivorship. Polygon's history for delisted tickers is decent but not a substitute for a point-in-time universe; backtests built on "tickers I can fetch today" quietly exclude the companies that died, which flatters every strategy. That bias is a property of your research design, and no vendor fixes it for you by default.
Prices are current at the time of writing. Tier limits, request caps and entitlements above are as published in mid-2026 and have changed before. Confirm against the live pricing page before you architect around any specific number.
None of this is unusual - every vendor in the table has an equivalent list. The difference between a hobby pipeline and a dependable one is mostly which of these you handled before they happened.
Fetch once, cache always, and treat the vendor as replaceable. The IEX Cloud customers who did that in 2024 migrated in an afternoon.
Skip the £25k programme - try the alternative
Master's programmes are slow and expensive. Quantt is a self-paced alternative built around the actual skills firms hire for: Python, mathematics, derivatives pricing, options Greeks and live trading-system design. 50+ courses, interactive coding tests, and the same end-state as a one-year MFE - in your evenings.
No prerequisites - start at any level