Visitar URL original
GitHub - daggerok/Capital-Group: Capital Group ETF holdings-to-watchlist: static ./api/capital-group feed generated by a Bun updater from official Capital Group fund data, SEC N-PORT holdings fallback, Yahoo history fallback · GitHub
Skip to content

About

Capital Group ETF holdings-to-watchlist: static ./api/capital-group feed generated by a Bun updater from official Capital Group fund data, SEC N-PORT holdings fallback, Yahoo history fallback

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

81 Commits

Folders and files

Repository files navigation

Capital Group

One of the app's features lets you select Capital Group ETFs in the Watchlist and aggregate their holdings to see how often each ticker appears across the selected funds. Repeated holdings make overlapping exposure visible: the more selected funds include a ticker, the greater its potential influence on the portfolio; gains in that holding may help, while declines may hurt, and actual impact also depends on each fund's position size. Another feature makes it faster and easier to find funds with stronger growth over different periods, higher dividend yields or distributions, greater Total Return (price performance plus dividends), and other key performance metrics. A client-side tool that reads the generated ./api/capital-group static feed (Capital Group ETF catalog, per-fund server-rendered JSON, daily holdings XLSX and price/distribution JSON - official NAV returns, expenses, yields, complete daily holdings and whole-life NAV history - with SEC EDGAR N-PORT-P and Yahoo Finance as fallbacks) into a searchable ETF/asset-class catalog with per-fund tabs, watchlist aggregation, ticker copy and CSV/TXT export - the same look, feel, columns and business logic as the sibling applications.

Using Bun

bunx degit daggerok/Capital-Group#main ./12345 && cd $_
bun install
bun run serve

bun run serve starts the Parcel dev server (it copies api/ to dist/api first) and prints the local address. bun run build writes the static site to dist, bun run build-github-pages does the same with the /Capital-Group/ public URL used by the GitHub Pages workflow

The application is live at https://daggerok.github.io/Capital-Group/

Column types and filters

Every column of the ETF catalog and of the Watchlist, Holdings, History and Distributions tabs has a type: text (ABC), number (123), percentage (%), money ($), date (D), date and time (DT) or time of day (T). The type is detected from the texts the column shows (80% of the filled cells must agree, otherwise text) and is written in the badge next to the column title: click it to cycle the type, Shift+click to return to auto-detection. Dates are read as 2024-06-15, 6/15/2024, 15.06.2024, Jun 15, 2024 or 15-Jun-2024, date and time as 2024-06-15T09:30:00Z or 2024-06-15 09:30, time as 09:30, 16:00:00 or 9:30 PM

A row of filter inputs sits under the column headers (the Filters button hides it, Clear filters empties it). Filters of different columns are combined with AND, the search box applies on top, and Copy Tickers and the exports use the filtered rows. Filters and type overrides are remembered in the browser. Sticky # (next to Filters, off by default, remembered in the browser) numbers the rows by their rank in the table sorted by the current column before the column filters, so a filtered fund keeps its rank and the numbers keep gaps; the sort, the search and the category and blacklist choices rank again. The catalog starts sorted by Net Assets, largest first, unavailable values sort last in both directions, and every export starts with the # column. The red Clear button opens a dialog that lists what can be reset (the selection, searches, sort order, open tab, shown columns, column filters, remembered table views and the blacklist), all ticked the first time and afterwards as they were left at the last OK; Enter confirms, Esc or a click outside cancels, the theme is always kept, so the page looks like a first visit (also after a reload)

Inside one filter: a space means AND, a comma means OR, a leading ! means NOT, ? matches an empty or unavailable value and !? a value that is there; a value that is unavailable matches only ? and negated conditions. An unquoted space ends the value, so quote values that contain one (>="2024-06-15 09:30")

Type Examples
Text bank contains, "two words", !bank, =exact, ^starts, ends$, /regex/, tech, health
Number, percentage, money >10, >=10 <50, =22 (matches what rounds to 22), !=22, 10..50, ..50, 10.., >1B and K M B T suffixes, an optional $ or %
Date, date and time >2024-06-01, 2024 (the whole year), 2024-06 (the whole month), 2024-01..2024-06, today, yesterday, -7d.. (the last 7 days), +2w, -3m, -1y
Time >09:30, 09:30..16:00, =12:00 (the whole minute)

The Columns menu next to Filters lists every column of the ETF table from the first to the last, all of them shown by default, with a search box and the All, Clear, Toggle and Reset buttons. Use and Ticker are listed but locked. Hiding a column only removes it from the table: the filters, the sorting, the exports and Copy Tickers still use it. The choice is remembered in the browser (localStorage, never the data) and the menu is shown on the ETF catalog only

The asset classes are one Asset classes multi-select next to the All ETFs pill instead of one tab per class: every class is selected by default (= all ETFs), Only or unchecking narrows the table, and the All ETFs pill is lit only while nothing narrows it (all or none of the classes checked); clicking the pill clears the selection. The choice is remembered in the browser (localStorage, never the data)

Updating the static Capital Group data

Run the updater with Bun:

bun scripts/update-data.ts

Run bun scripts/update-data.ts --help to print every control with its default and usage examples.

Defaults live in scripts/update-data.config.json and every control accepts a CAPITAL_GROUP_ prefix. Precedence, lowest to highest: file defaults < advanced JSON < nonblank workflow inputs < protected Actions variable (SEC_UA) or environment. An explicitly set environment variable wins even when empty and clears the control. Invalid values (unknown keys, non-scalars, multiline text, bad integers, booleans or ranges) are rejected before any request or write

The Update Capital Group ETF data GitHub Actions workflow runs weekly and exposes 24 individual inputs plus advanced, a JSON object for any other control. Blank inputs inherit the file defaults. All supplied filters use AND logic

Data sources

Block Source
Catalog (all US Capital Group ETFs) Official ETF catalog, currently 25 fund links
Fund facts Server-rendered Next.js JSON in each fund page (no browser execution)
Holdings per fund /api/investments/investment-service/v1/etfs/{TICKER}/download/daily-holdings?audience=advisor on www.capitalgroup.com (full XLSX, parsed with built-in zlib)
Daily history, distributions Same API base, premium-discount-details?fromDate=YYYY-MM-DD&toDate=YYYY-MM-DD and historical-distributions?include=all
Fallback SEC EDGAR N-PORT-P for holdings (only the fund's own filing and only when strictly newer than the published holdings), Yahoo Finance chart API for history of funds that have no official history yet; a fund whose issuer data fails keeps its previous complete publication

The published feed covers all 25 catalog funds. SEC and Yahoo fallbacks are used only when the issuer data is unavailable

Metrics and caveats

Official figures come from Capital Group: NAV returns, expense ratios, 30-day SEC yield, net assets and the daily NAV and market price history. Yahoo Finance data is used only as a fallback and is an estimate (adjusted close based), not an official NAV. Unavailable values are published as unavailable (null or —), never as 0; a real zero is kept as zero. No tickers are excluded from the catalog

The issuer's first HTML request can redirect to a public cookie bootstrap; the updater repeats the request using an in-memory same-origin session. No credentials, cookies or entire HTML documents are stored. Some endpoints mentioned in issuer JavaScript return 404 directly; the implementation uses verified HTML payloads and working public downloads instead. The issuer currently repeats month-end data in quarterlyReturns; non-quarter-end dates are not mislabeled as quarter-end returns. Holdings weights are converted from fractions to percent without forcing them to sum to 100%; issuer rounding is preserved. Different funds have different registrant CIKs (CGUS 0001870102; CGCP/CGMU 0001870117); the SEC fallback resolves the fund's own series, not an arbitrary latest trust filing.

  • The holdings XLSX gives the local exchange code of every listing and names no exchange (Marubeni 8002.0, AstraZeneca's London line AZN next to its U.S. ADR, Telenor and TE Connectivity could both be TEL), so a ticker is published only for an equity with a U.S. listing: a U.S. ISIN (ADRs and New York shares included) or a CINS, the CUSIP number a foreign issuer gets for U.S. trading (Royal Caribbean, Linde, Accenture). Foreign listings, preferred stock, bonds, cash, futures, swaps and forwards have Ticker - and keep their CUSIP or ISIN as the Identifier, so the Watchlist never merges two companies
  • SEC EDGAR N-PORT-P holdings are used only when the issuer holdings are unavailable, EDGAR_FALLBACK is on, the filing names the fund's own series (a fund missing from SEC's fund ticker table is found through its registrant and matched by name) and its report period is strictly newer than the published holdings (the newest report period first, read from the trust's own EDGAR folder). N-PORT identifiers are the position's own (a derivative never takes its reference security's CUSIP or ticker), a ticker is set only for U.S. common stock traded in USD and never on two different securities, and a missing weight or value stays an empty cell, never 0
  • A fund younger than one year (calendar anniversary of its inception, inclusive) publishes no 1-year or since-inception return, and no 3, 5 or 10-year one before those anniversaries, official or derived; the month-end and quarter-end tables follow the same rule
  • Dates are read as plain calendar text (08/21/2026, Aug 21 2026, ISO), never through the local time zone, so local runs match CI

Each fund carries a derived metrics object that powers the catalog columns shared with the sibling sites:

  • ytd / tr1y - official YTD and 1-year returns -> YTD Return, TR 1Y

  • cagr3y / cagr5y / cagr10y - published annualized 3Y/5Y/10Y figures -> CAGR 3Y/5Y/10Y

  • tr3y / tr5y / tr10y - cumulative 3Y/5Y/10Y figures (1 + CAGR)^n - 1 -> TR 3Y/5Y/10Y

  • siAnn - since-inception annualized -> SI Ann.; only for funds at least one year old (derived values need one year of full history). Figures for a horizon longer than the fund's age at the returns date (for example 3Y for a fund under three years old, which Capital Group still publishes) are null, official or derived

  • dividendYield - indicated yield (latest distribution × payments per year ÷ price; semi-annual is 2)

  • dividendYieldBasis - code of the definition behind dividendYield, null exactly when the yield is null (the free text stays in meta.yields.dividendYieldKind); a retained yield keeps its own code:

    Code Meaning for Capital Group
    indicated updater estimate: latest distribution x payments per year / market price (what every fund uses today; Capital Group publishes no yield in the fund facts)
    official-trailing-12m issuer-published trailing 12-month yield (used only if the facts ever carry one)
    official-distribution-rate issuer-published distribution rate (same)
    official-other issuer-published yield of an unrecognized kind
    computed-trailing-12m not used by this brand
  • secYield - 30-day SEC yield when published; null otherwise (an honest null is never replaced by an older number; the previous value survives only an issuer outage). The *Text fields are null when the value is unavailable

  • returnsBasis - mandatory non-empty label of how the returns are computed: official Capital Group NAV total returns, or derived from the daily NAV history, or an estimate from Yahoo adjusted closes (mixed cases say which periods are derived or estimated); never empty or -

  • performanceAsOf - mandatory ISO YYYY-MM-DD date the returns are as of: the issuer performance table (month-end) date for official returns, the last price date of the derived series otherwise, and the oldest of the two for mixed values; it is not the NAV date, and is null only when truly unknown

  • terValue is the NET expense ratio (after waivers; the gross one only when no net figure exists) and terGrossValue the GROSS ratio; meta.expenseRatio carries value, net and gross

  • Each fund is either fully refreshed or kept exactly as published: when a required source fails (fund facts, daily holdings, official daily history) the fund's previous complete state stays untouched and the run reports it as failed (non-zero exit), so the workflow can safely commit a partial run. A fund never published before is written best-effort. When the daily price API is newer than the fund-facts payload, its NAV, market price and premium/discount (and date) win

  • Issuer facts never go backward: capitalgroup.com serves the fund pages from a Next.js ISR cache that can return a weeks-old prerender, so an AUM, SEC yield (with the unsubsidized yield and distribution rate), month-end or quarter-end return dated before the published one is dropped, the published fact is kept and the run prints a [ product ] warning

  • Catalog funds without published data get a row with dataFile: null and a complete metrics object of nulls. NEW FUNDS: A, B is printed (and added to $GITHUB_STEP_SUMMARY) when a fund is published for the first time, also when its first read failed and a later run succeeds; NEW FUNDS (not published yet): C names new catalog funds whose first read failed. A catalog-only row that the live catalog no longer lists (for example an old mistyped TICKERS entry) is dropped

Update controls

Control Default Meaning
MAX_FETCHES 0 Batch size: a positive value continues after the committed cursor in api/capital-group/update-state.json (the furthest fund started in queue order, advancing even when the batch failed or was filtered; one cursor per filter scope, so a TICKERS run never touches the full-feed cursor); funds whose published figures already fail the data filters are skipped without using up a slot; 0 is a full pass over every selected fund and resets the full-feed cursor. The run stops starting new funds after 25 minutes and still writes the index
REQUEST_SLEEP 3 Minimum delay in seconds between outgoing request starts within each worker, including retries and issuer redirects
CONCURRENCY 1 Number of independently paced fund workers; there is no global request-start queue
AUM : Net assets range; each bound is a USD amount, a K/M/B/T amount or one of nano, micro, small, mid, large; a nonblank bound that is not a number is an error
TER : Expense ratio range in percent (min:max)
DIVIDEND_YIELD : Indicated dividend yield range in percent
SEC_YIELD : Published 30-day SEC yield range in percent
TICKERS empty Space, comma or semicolon separated ticker allowlist, for example CGUS CGCP CGMU; empty means all; a ticker that is neither in the live catalog nor a published fund is an error before any fund request (nothing is written)
HOLDINGS_PAGE_SIZE 250 Rows in each generated holdings JSON page
HISTORY_PAGE_SIZE 1000 Rows in each generated history JSON page
MAX_RETRIES 2 Retries after the first request, integer of at least 1; 403, 408, 425, 429 and 5xx are retried with backoff, other issuer HTTP errors fail promptly
HISTORY_RANGE max Yahoo fallback chart window (explicit period1/period2), max or Ny (for example 5y); official history always covers inception onward and a shorter window never shrinks a published series: the older published rows are kept (Adj Close rescaled by the overlapping days) and count for the 3, 5, 10-year and since-inception returns, and a window that does not join them keeps the published history
PERFORMANCE_YTD, PERFORMANCE_1Y, PERFORMANCE_3Y, PERFORMANCE_5Y, PERFORMANCE_10Y : NAV return ranges in percent, annualized for 3Y and longer
TOTAL_RETURN_YTD, TOTAL_RETURN_1Y, TOTAL_RETURN_3Y, TOTAL_RETURN_5Y, TOTAL_RETURN_10Y : Cumulative total return ranges in percent
STORE_RAW_DOWNLOADS false Keep financial JSON snapshots under each selected fund's raw/; never cookies or auth headers
CATALOG_URL official catalog URL Optional catalog mirror URL
SEC_UA daggerok ETF feed daggerok@gmail.com User-Agent for SEC requests; redacted in config logs; the repository Actions variable SEC_UA overrides it when nonblank
SKIP_YAHOO false Skip Yahoo Finance history and dividends; keep published data
SKIP_ISSUER false Use the published catalog and only fallbacks; never delete cached data
EDGAR_FALLBACK true Enable the fund-specific SEC N-PORT holdings fallback
VERBOSE false Show per-provider fallback and retry diagnostics
USE_SYSTEM_CA auto TLS trust store: auto restarts the updater once with Bun's --use-system-ca when a request fails with an untrusted-certificate error; true always uses the system CA store; false never restarts. Not an individual workflow input: use advanced, the config file or the CLI environment.

Catalog discovery runs before fund workers start. CONCURRENCY=15 REQUEST_SLEEP=3 permits up to 15 independently paced workers, not one global request every three seconds; actual throughput still depends on network latency and provider throttling. Not individually exposed as workflow inputs, but reachable through advanced JSON: CATALOG_URL, SEC_UA, SKIP_ISSUER, STORE_RAW_DOWNLOADS, VERBOSE and USE_SYSTEM_CA

TICKERS combines with the other filters using AND logic; it does not override them. Funds not selected for a successful update keep their prior published metadata and data files

Examples

MAX_FETCHES=10 bun scripts/update-data.ts
TICKERS="CGUS CGCP CGMU" bun scripts/update-data.ts
AUM="1B:" TER=":0.5" bun scripts/update-data.ts
PERFORMANCE_1Y="15:" bun scripts/update-data.ts

Workflow advanced input example: {"TICKERS":"","VERBOSE":true}

TypeScript and verification

The browser app is src/main.tsx (TypeScript), src/index.html and src/index.css (Tailwind v4), bundled by Parcel into dist; GitHub Pages is deployed from dist by .github/workflows/github-pages.yml. There is no tsconfig.json: Bun runs the updater TypeScript out of the box

Verification before every publish:

bun install --frozen-lockfile
bun test
bun build --target=bun scripts/update-data.ts --outfile=/dev/null
git diff --check

bun test also covers the parsers, the control resolver, README and --help parity and the workflow shape

Brands table

Brand Where to get the data
AAM aamlive.com | AAM
abrdn (Aberdeen) aberdeeninvestments.com | aberdeen
Amplify amplifyetfs.com | Amplify
ARK Invest ark-funds.com | ARK
Capital Group capitalgroup.com | Capital-Group
Fidelity fidelity.com | Fidelity
First Trust ftportfolios.com | First-Trust
Franklin Templeton franklintempleton.com | Franklin
Global X globalxetfs.com/explore | Global-X
Goldman Sachs am.gs.com | Goldman-Sachs
Invesco invesco.com | Invesco
iShares ishares.com | iShares
JPMorgan am.jpmorgan.com | JPMorgan
NEOS neosfunds.com | Neos
Northern Trust etfs.ntam.northerntrust.com | Northern-Trust
Pacer ETFs paceretfs.com | Pacer
Parametric eatonvance.com | Parametric
ProShares proshares.com | ProShares
Schwab schwabassetmanagement.com | Schwab
SP Funds sp-funds.com | SP-Funds
SPDR ssga.com | SPDR
Sprott ETFs sprottetfs.com | Sprott
Tema ETFs temaetfs.com | Tema
Themes ETFs themesetfs.com/etfs | Themes
VanEck vaneck.com | VanEck
Vanguard investor.vanguard.com | Vanguard
VictoryShares vcm.com VictoryShares ETFs | VictoryShares
WisdomTree wisdomtree.com | WisdomTree
Xtrackers etf.dws.com | Xtrackers

Sibling applications

Application Data provider Repository
AAM Official AAM catalog/detail HTML + full holdings XLS + SEC N-PORT holdings fallback + Yahoo market history/dividends AAM
abrdn (Aberdeen) Official Aberdeen gateway + SEC N-PORT holdings fallback + Yahoo history/dividends aberdeen
Amplify Amplify ETFs Firestore data feed + SEC EDGAR N-PORT-P holdings fallback + Yahoo Finance history/dividends Amplify
ARK Invest ark-funds.com fund pages + overview/NAV-history/performance JSON + official daily holdings CSV + SEC EDGAR N-PORT-P holdings fallback + Yahoo Finance distributions/history fallback ARK
Capital Group Official Capital Group fund data + SEC N-PORT holdings fallback + Yahoo history fallback Capital-Group
Fidelity SEC EDGAR N-PORT-P + Yahoo Finance Fidelity
First Trust ftportfolios.com official ETF list + fund summary, holdings, distribution and price-history export pages + SEC EDGAR N-PORT-P holdings fallback + Yahoo Finance history fallback First-Trust
Franklin Templeton franklintempleton.com ETF listings + product pages + SEC EDGAR N-PORT-P Franklin
Global X globalxetfs.com Next.js catalog and fund pages + dated full-holdings CSV Global-X
Goldman Sachs am.gs.com fund data API (catalog, fund details, daily full holdings) + SEC EDGAR N-PORT-P holdings fallback Goldman-Sachs
Invesco invesco.com fund pages and sitemap + official Invesco fund API (monthly returns, NAV, AUM, yields, daily holdings, expense ratio) + SEC EDGAR N-PORT-P holdings fallback + Yahoo Finance history/dividends Invesco
iShares iShares (BlackRock) product workbooks iShares
JPMorgan am.jpmorgan.com fund explorer + product-data JSON JPMorgan
NEOS neosfunds.com lineup table + official fund pages + daily holdings CSV Neos
Northern Trust etfs.ntam.northerntrust.com funds list + per-fund CSV/JSON downloads Northern-Trust
Pacer ETFs paceretfs.com product catalog and fund pages (Cloudflare WAF; r.jina.ai proxy fallback) + SEC EDGAR N-PORT-P (Pacer Funds Trust) + Yahoo Finance history/dividends Pacer
Parametric eatonvance.com ETF catalog and Parametric product pages + SEC EDGAR N-PORT-P holdings + Yahoo Finance history/dividends Parametric
ProShares proshares.com ETF finder + fund pages + official data host ProShares
Schwab schwabassetmanagement.com product pages + CSV exports Schwab
SP Funds sp-funds.com homepage catalog, fund pages and daily holdings CSV + SEC EDGAR N-PORT-P holdings fallback + Yahoo Finance history/dividends SP-Funds
SPDR SSGA / State Street public feeds SPDR
Sprott ETFs sprottetfs.com fund pages + SEC EDGAR N-PORT-P (Sprott Funds Trust) + Yahoo Finance history/dividends Sprott
Tema ETFs Tema official fund pages + dated daily holdings CSV; SEC EDGAR N-PORT-P holdings fallback only + Yahoo Finance price/history/dividend fallback Tema
Themes ETFs themesetfs.com catalog + daily holdings CSV + Yahoo Finance history/dividends + SEC N-PORT-P holdings fallback Themes
VanEck vaneck.com ETF finder + product pages VanEck
Vanguard Vanguard product pages + SEC EDGAR N-PORT-P Vanguard
VictoryShares VCM VictoryShares catalog and product JSON + SEC EDGAR N-PORT-P holdings fallback + Yahoo Finance adjusted-market-price history VictoryShares
WisdomTree WisdomTree product table + SEC EDGAR N-PORT-P + Yahoo Finance WisdomTree
Xtrackers Official DWS catalog/US sitemap + PDP/XLSX + SEC N-PORT-P holdings fallback + Yahoo Finance daily prices/history/dividends Xtrackers

License

MIT - same as all sibling ETF repositories

Capital Group® and American Funds® and the fund names/tickers referenced here are trademarks of The Capital Group Companies, Inc. This is an independent, unofficial tool; it is not affiliated with, endorsed by, or sponsored by Capital Group. All data is reproduced from Capital Group's own public fund pages and downloads, public SEC EDGAR filings and Yahoo Finance for research purposes. All other trademarks, including index names, are the property of their respective owners.

About

Capital Group ETF holdings-to-watchlist: static ./api/capital-group feed generated by a Bun updater from official Capital Group fund data, SEC N-PORT holdings fallback, Yahoo history fallback

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages