Skip to main content

ucits-etf-issuer-holdings

ci contract PyPI npm license

Give it the ISIN of a European (UCITS) ETF, get back every holding the issuer itself publishes for that fund: name, weight, ISIN, country, sector, currency, as-of date. Python and TypeScript, one JSON shape, six issuers: iShares, Vanguard, Xtrackers, Amundi, SPDR, Invesco.

It is for the people building portfolio trackers and analysis tools who need a fund's look-through (country, sector or single-name exposure through funds) and have found that the free sources stop at the top ten holdings, cover US funds only, or broke this year.

pip install ucits-etf-issuer-holdings        # Python 3.10+
npm install ucits-etf-issuer-holdings        # Node 20+, no runtime dependency

The TypeScript package runs on Node.js 20 or later only: it reads the cache and the xlsx files with node:fs, node:zlib and node:crypto, so it does not run in a browser or a webview.

Quickstart

from ucits_etf_issuer_holdings import get_holdings

fund = get_holdings("IE00B4L5Y983")  # iShares Core MSCI World
print(fund.fund_name, fund.as_of, len(fund.holdings))
for h in fund.top(3):
    print(f"{h.weight:6.2f}  {h.country}  {h.name}")
print(fund.weights_by("country")[:3])
iShares Core MSCI World UCITS ETF 2026-10-30 1271
  5.62  US  APPLE
  5.23  US  NVIDIA
  4.09  US  MICROSOFT
[('US', 72.63149), ('JP', 5.74119), ('GB', 3.50965)]

You do not need to know the issuer: the client asks each one in turn. Pass issuer="ishares" to ask one directly.

Try it offline

The package ships trimmed copies of real issuer answers. The same code runs on them without a network, which is also how you test your own code against this library:

from ucits_etf_issuer_holdings import HoldingsClient
from ucits_etf_issuer_holdings.testing import RECORDED_ISINS, recorded_transport

client = HoldingsClient(transport=recorded_transport())
for isin in RECORDED_ISINS:
    fund = client.get(isin)
    print(isin, fund.issuer, fund.as_of, len(fund.holdings), fund.fund_name)

Look-through of a portfolio

from ucits_etf_issuer_holdings import HoldingsClient, look_through
from ucits_etf_issuer_holdings.testing import recorded_transport

positions = {"IE00B4L5Y983": 6000, "IE00BK5BQT80": 3000, "LU0274211480": 1000}
client = HoldingsClient(transport=recorded_transport())  # drop transport= for live data
funds = {isin: client.get(isin) for isin in positions}
for country, amount in look_through(positions, funds, by="country")[:5]:
    print(country, round(amount, 2))

by is one of country, sector, currency, asset_class, or isin for a single-name concentration (a line without an ISIN, such as Vanguard's, is counted under its name). Each position is spread over its fund's holdings by weight.

TypeScript

import { getHoldings, weightsBy } from "ucits-etf-issuer-holdings";

const fund = await getHoldings("IE00BK5BQT80"); // Vanguard FTSE All-World
console.log(fund.fund_name, fund.as_of, fund.holdings.length);
console.log(weightsBy(fund, "sector").slice(0, 3));

The objects are the same JSON as Python's fund.to_dict(), described by schema/fund.schema.json. Offline replay: import { recordedTransport } from "ucits-etf-issuer-holdings/testing".

Command line

ucits-holdings IE00B5M1WJ87 --format table
ucits-holdings IE00B4L5Y983 --by country --format csv > exposure.csv

What you get

{
  "isin": "IE00B4L5Y983",
  "issuer": "ishares",
  "fund_name": "iShares Core MSCI World UCITS ETF",
  "as_of": "2026-10-30",
  "currency": "USD",
  "source_url": "https://www.ishares.com/...",
  "fetched_at": "2026-11-02T09:41:12Z",
  "stale": false,
  "holdings": [
    {"name": "APPLE", "weight": 5.6239, "isin": "US0378331005", "ticker": "AAPL",
     "country": "US", "country_name": "United States", "sector": "Information Technology",
     "currency": "USD", "asset_class": "equity", "asset_class_raw": "Equity"}
  ]
}

weight is a percent of the fund. country is an ISO 3166 alpha-2 code mapped from the issuer's own country column (country of risk where the issuer says so). asset_class is one of equity, fixed_income, cash, derivative, fund, real_estate, commodity or other (a label the library does not know yet), mapped from the issuer's own label, which asset_class_raw keeps as published. as_of is the date of the holdings, fetched_at the time the issuer's file was downloaded (for a cached copy, when it was stored). A field the issuer does not publish is null, never guessed.

Issuer Found by Holdings file What the issuer does not give Updated
iShares fund list of the German site product data API nothing: every field is filled daily
Vanguard fund list of the German site GraphQL API, paged ISIN and currency per line month end
Xtrackers ISIN xlsx export ticker per line; fund name, fund currency daily
Amundi ISIN product data API ticker per line daily
SPDR fund list of the German site xlsx export ticker and asset class per line; fund currency daily
Invesco ISIN holdings API ticker, country, sector and asset class per line; fund name, fund currency daily

On 2026-11-02, through a proxy, scripts/measure_coverage.py fetched one large fund per issuer (the contract test funds): the weights sum to 99.0 to 100 percent, and every issuer that publishes a country gives one for 100 percent of the weight (Invesco's file has no country). Run it again to check today's files.

Errors and caching

  • InvalidISIN: the check digit or the shape is wrong.
  • FundNotFound: no issuer answered with holdings. Its reasons say, per issuer, whether the fund is not theirs (NotListed) or the issuer could not be asked (IssuerUnavailable, with what it answered).
  • Fund lists are cached for a day and holdings for six hours under ~/.cache/ucits-etf-issuer-holdings (cache_dir=None turns it off; a transport you pass is not cached unless you also pass a cache_dir). An answer is kept only when it parses, so a broken file never replaces a good copy. When an issuer is down, refuses (403), times out (408), rate limits (429) or answers with something the library cannot read, and an older copy exists, you get that copy with fund.stale == True rather than an error.
  • HoldingsClient is a context manager (with HoldingsClient() as client:), or call client.close(), to release its connections.
  • Proxies: the Python client reads HTTPS_PROXY and ALL_PROXY (install httpx[socks] for SOCKS).

Issuers change their sites without notice. A contract test fetches one real fund per issuer every morning (contract workflow), so a moved endpoint shows up as a red badge the same day.

Why this one

Option UCITS issuers Full holdings By ISIN Free, open Maintained
ucits-etf-issuer-holdings 6 yes, the issuer's file yes MIT daily contract tests
etf-scraper no (US providers) yes by ticker yes last release 2025-03
justETF based scrapers (justetf-scraping, Apify actors) yes top 10 yes varies break with the site
etfpy, pyetfdb-scraper no (etfdb.com, US) top holdings by ticker yes
OpenBB no (US providers) provider dependent by ticker yes yes
Trackinsight yes yes yes no, behind a firewall that broke Ghostfolio's enhancer
wealthfolio/asset-profiles partly aggregated by ticker data CC-BY-NC-SA last push 2026-05
Intrinio, FactSet, Eulerpool yes yes yes paid yes

The usual workaround is a hand-kept map of issuer endpoints inside each app (see Aurelio's issuers.py). This library is that map, shared and tested every day, in Python (Aurelio's language) and TypeScript (Ghostfolio's). Trackers that cannot load either package, such as Wealthfolio (a Rust core with a webview front end) or Portfolio Performance (Java), can call the ucits-holdings command line.

Plugging it into a tracker

Most trackers store a fund's exposure as a list of {code, weight} pairs with weights as fractions. That is one call:

import { getHoldings, weightsBy } from "ucits-etf-issuer-holdings";

async function countryProfile(isin: string) {
  const fund = await getHoldings(isin);
  return weightsBy(fund, "country")
    .filter(([code]) => code !== null)
    .map(([code, pct]) => ({ code, weight: pct / 100 }));
}

The same pattern gives sectors ("sector") or a single-name concentration across a portfolio (look_through(..., by="isin") in Python, lookThrough(..., "isin") in TypeScript). Open requests this answers: Ghostfolio #5924 (ETF country and holdings data), Wealthfolio #827 (UCITS look-through), Portfolio Performance #1665 (asset allocation from the holdings list; its Java code can call the ucits-holdings CLI with --by country --format csv).

Notes

  • Swap based ETFs (for example "Amundi MSCI World Swap") publish their substitute basket, not the index they track. The file is returned as published; check the fund name.
  • Terms of use: these are the files each issuer serves to any visitor of its public product pages. The library fetches one file per fund on demand, caches it, and identifies itself in its user agent. You are responsible for respecting each issuer's website terms in your use.
  • Not investment data you can rely on for trading decisions: see each issuer's disclaimer.

Contributing

Issuer moved something, or you want another issuer? See CONTRIBUTING.md. The endpoints and the shape of every answer are documented in docs/PLAN.md.

License

MIT

Metadata

Release files for ucits-etf-issuer-holdings 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ucits-etf-issuer-holdings 0.1.0
File Size Uploaded Provenance Actions
ucits_etf_issuer_holdings-0.1.0.tar.gz 123.8 kB Without provenance Details for ucits_etf_issuer_holdings-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for ucits-etf-issuer-holdings 0.1.0
File Interpreter ABI Platform Actions
ucits_etf_issuer_holdings-0.1.0-py3-none-any.whl Python 3 none any Details for ucits_etf_issuer_holdings-0.1.0-py3-none-any.whl

Total release size: 202.1 kB

Release files / ucits_etf_issuer_holdings-0.1.0.tar.gz

Download URL ucits_etf_issuer_holdings-0.1.0.tar.gz
Size 123.8 kB
Tags Source
SHA-256 checksum
How to use checksums
12519b80b11711291eca79aef03919febcd0f6872a6c68f9a67e93f5f5e5c8b3
BLAKE2b-256 checksum
How to use checksums
00e7737d3625c48db4c29453cdd5a3142682dc5beaaefbad4ccf57908b68e0e4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.13.4 {"installer":{"name":"uv","version":"0.13.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / ucits_etf_issuer_holdings-0.1.0-py3-none-any.whl

Download URL ucits_etf_issuer_holdings-0.1.0-py3-none-any.whl
Size 78.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
dc787ce24b21bf28a53e45d3021659032af0c7800ec6020a10b23a69aac7ed29
BLAKE2b-256 checksum
How to use checksums
eb0f575f7e0da9512e35d682775aac376b572b52c634911f9b8c0e426d51ccc5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.13.4 {"installer":{"name":"uv","version":"0.13.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Security for ucits-etf-issuer-holdings 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page