Correlation, covariance, beta, volatility and true holdings overlap for 4,700+ US-listed stocks and ETFs, recomputed every trading day and published as a static site with a free JSON API.
Live site: https://www.pairbook.io CSV download: https://www.pairbook.io/data/ API documentation: https://www.pairbook.io/api/ OpenAPI 3.1 spec: https://www.pairbook.io/api/openapi.json
The full dataset is two CSV files, rebuilt after every US close:
| File | Rows | Contents |
|---|---|---|
| pairs.csv | 52,358 | Correlations over 1, 3 and 5 years, annualized covariance, holdings overlap |
| symbols.csv | 4,755 | Beta against SPY, annualized volatility, trailing returns, sector or fund group |
import pandas as pd
pairs = pd.read_csv("https://www.pairbook.io/data/pairs.csv")Licensed CC BY 4.0. An archived, citable snapshot carries a DOI: 10.5281/zenodo.22124847, also mirrored on Kaggle and Hugging Face.
Two funds can move together and also hold the same stocks. PairBook measures both sides of that question:
- Pearson correlation of weekly returns over 1, 3 and 5 year windows, for 52,000+ tracked asset pairs, with a rolling one-year correlation series.
- Annualized covariance and volatility, and beta against SPY.
- True holdings overlap between ETFs, computed as the sum of minimum common weights from issuer portfolio disclosures.
- A live in-browser calculator for any of the 11 million possible pairs, built on the published weekly return series.
No key, no signup, CORS enabled. Attribution is the only ask.
curl https://www.pairbook.io/api/v1/pairs/aapl-vs-msft.json| Endpoint | Returns |
|---|---|
GET /api/v1/index.json |
Universe: every symbol with name, kind and canonical slug |
GET /api/v1/symbols/{slug}.json |
One asset: beta, volatility, returns, correlated partners, diversifiers |
GET /api/v1/pairs/{a}-vs-{b}.json |
One pair: correlations, covariance, holdings overlap |
GET /api/v1/series/{slug}.json |
Weekly return series (up to 156 weeks) |
GET /api/v1/badge/{a}-vs-{b}.svg |
Embeddable SVG badge with the pair's 3-year correlation |
A Python pipeline renders roughly 57,000 static pages plus the API files. No server, no database at runtime.
pipeline/fetch_prices.py 5y of daily adjusted closes, cached per symbol
pipeline/fetch_holdings.py ETF portfolio files from issuer disclosures
pipeline/compute.py correlations, betas, overlaps, pair selection
pipeline/build.py renders site/ (pages, API, sitemaps, robots)
pipeline/validate.py post-build QA gate (structured data, titles, links)
pipeline/indexnow.py notifies IndexNow-compatible search engines
Run everything locally:
python3 -m venv .venv
./.venv/bin/pip install numpy pandas jinja2 requests openpyxl pyarrow markupsafe curl_cffi
./update.sh.github/workflows/daily-refresh.yml runs the full pipeline after the US
close on trading days and deploys the result. The price cache persists between
runs; the core universe refreshes daily and the long tail rotates across the
week to keep request volumes reasonable.
Deployments go through the CLI and CI only. Git auto-deploys are disabled on
purpose (vercel.json at the repository root).
- Assets need roughly ten months of price history before they are published, so figures are never computed on a handful of observations.
- Series with stale prices or sub-dollar median prices are excluded from partner lists and rankings to avoid phantom correlations.
- Company names from exchange symbol directories are sanitized at load time.
- A validation pass runs after every build and fails the deploy on structured data errors, malformed titles or missing canonicals.
- A date bump requires a real content change.
lastmodis gated on a content hash (data/lastmod.json); never hand-edit dates and never bypass the gate. - Hidden text is never a tactic. Nothing is served to crawlers that a reader does not see.
- Generated prose keeps its variant system (
vary()inpipeline/build.py), seeded by ticker and never by date, so wording differs across pages without churning between builds. - No hardcoded figures in templates; every number comes from the dataset.
- Sitemap exposure is phased (
sitemap_exposeinconfig.json): extended segments only enter the index once the front line indexes healthily. data/lastmod.jsonis CI-owned state. Local builds regenerate it against stale data; never commit it from a workstation. The nightly workflow is the only writer that persists it.
End-of-day market data comes from public quote endpoints; fund portfolios come from issuer disclosures; the universe comes from official exchange symbol directories. The site publishes derived metrics only and does not redistribute raw source tables. Everything on the site and the core API is free, with attribution. Nothing here is investment advice.