How do I compare two ETFs or funds programmatically?
Fetch both ISINs in one batch call, put TER, risk indicator, size, top holdings, sector and country weights and calendar returns side by side, then add the overlap endpoint for shared holdings. Code included.Updated 12 September 2026 · by FundFacts APIShort answer
Send both ISINs to POST https://fundfactsapi.com/api/v1/funds (or two GET calls on the Free plan) and read the same fields from each payload: headlineMetrics.ter, riskRating, keyFacts.aum, profile.category, topHoldings, sector, geography, calendarReturns and annualisedReturns. For shared holdings call GET /overlap?isins=A,B (Pro and above), which returns the overlap percentage and the common names. The MCP server's compare_funds tool does the same for AI assistants.
Fetch both
pythonimport os, requestsH = {"Authorization": f"Bearer {os.environ['FUNDFACTS_API_KEY']}"}r = requests.post("https://fundfactsapi.com/api/v1/funds", json={"isins": ["IE00B4L5Y983", "IE00B3RBWM25"], "wait": True}, headers=H, timeout=300)funds = {x["isin"]: x for x in r.json()["results"] if x["status"] == "ok"}
Put the figures side by side
pythondef row(label, get):return [label] + [get(funds[i]) for i in ("IE00B4L5Y983", "IE00B3RBWM25")]table = [row("Name", lambda f: f["name"]),row("Category", lambda f: f["data"]["profile"]["category"]),row("TER", lambda f: f["data"]["headlineMetrics"]["ter"]),row("Risk (1-7)", lambda f: f["data"]["riskRating"]),row("Size", lambda f: f["data"]["keyFacts"]["aum"]),row("Holdings", lambda f: f["data"]["keyFacts"]["holdings"]),row("Top 10 weight", lambda f: round(sum(h["weight"] for h in f["data"]["topHoldings"][:10]), 1)),row("US weight", lambda f: next((g["weight"] for g in f["data"]["geography"] if g["label"] == "United States"), None)),row("3y vol", lambda f: f["data"]["headlineMetrics"]["volatility3y"]),row("Max drawdown", lambda f: f["data"]["metrics"]["maxDrawdown"]),row("2025 return", lambda f: dict(zip(f["data"]["calendarReturns"]["years"], f["data"]["calendarReturns"]["fund"])).get("2025")),row("As of", lambda f: f["data"]["dataAsOf"]),]for r in table:print(f"{r[0]:<14} {str(r[1]):<28} {str(r[2]):<28}")
Shared holdings
bashcurl -s "https://fundfactsapi.com/api/v1/overlap?isins=IE00B4L5Y983,IE00B3RBWM25" -H "Authorization: Bearer $FUNDFACTS_API_KEY" \| jq '.pairs[0] | {overlap, sharedCount, top: .shared[:5]}'
overlap is the percentage of portfolio weight the two funds have in common (the sum over shared names of the smaller weight), shared lists each common holding with its weight in both. Pro and above; the free ETF overlap tool runs it for you in the browser.
Presenting it
Keep the strings as printed (a TER of "0.20%" next to "0.22%" reads better than 0.002), show dataAsOf under the table, and avoid ranking words: a comparison presents facts, not a recommendation. The comparison tool guide covers the UI and caching, and the ETF comparison website use case the SEO angle.
Verify it yourself
The demo endpoint returns the live payload for a fund that is already in the store, without a key. Everything on this page can be checked against it.bashcurl -s https://fundfactsapi.com/api/v1/demo/funds/IE00B4L5Y983 | jq '{name, asOf: .data.dataAsOf, ter: .data.headlineMetrics.ter, risk: .data.riskRating, top: .data.topHoldings[:3]}'