One of the app's features lets you select NEOS 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 single-file client-side tool reading the generated ./api/neos static feed (NEOS ETF catalog, per-fund pages, official daily full-holdings CSV, NAV / market price / premium-discount, performance and distribution history, 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.
bunx degit daggerok/Neos#main ./12345 && cd $_ && bunx serve . -p 1234
open http://0:1234The published application is available at https://daggerok.github.io/Neos/. No build step, no bundler, no server — index.html carries the markup and a Babel-standalone bootstrap that transpiles app.tsx in the browser. The app reads only relative ./api/neos/** files generated by scripts/update-data.ts; the committed feed is complete, so the site works offline with no update run at all.
Run the updater with Bun:
bun test scripts/update-data.test.ts
./scripts/update-data.tsRun ./scripts/update-data.ts -h (or --help) to print every configuration variable with its default and usage examples.
The Update NEOS ETF data GitHub Actions workflow (.github/workflows/update-data.yml, workflow_dispatch only) exposes the same settings as manual inputs and commits api/neos/** and nothing else — a data run never rewrites app code or CI config. All supplied filters use AND logic.
| Block | Source | Notes |
|---|---|---|
| Fund universe, ticker, fund name, declared distribution frequency, distribution rate, 30-Day SEC Yield, management fee, net assets, inception date, asset class | neosfunds.com/#explore-etfs |
The server-rendered "Explore Our ETFs" table, which is also what fills the site's own tabs: Equity High Income, Boosted High Income, High Income Alternatives, Hedged Equity Income, Enhanced Fixed Income |
| CUSIP, ISIN, NAV, market price, premium / discount, 30-Day Median Bid-Ask Spread, primary exchange, shares outstanding, underlying exposure, management fee, total annual fund operating expenses | https://neosfunds.com/<ticker>/ |
The page's own Fund Details, Closing NAV Price, Closing Market Price and Premium / Discount panels. Underlying Exposure is absent on five funds (see below). |
| Holdings (daily) | https://neosfunds.com/wp-admin/admin-ajax.php?action=download_holdings_csv&ticker=<TICKER> |
The exact URL the fund page's Download Full Holdings button calls (etf-pages.js → etf_ajax.ajax_url), so the file is the official daily holdings sheet: Date, Account, StockTicker, Cusip, SecurityName, Shares, Price, MarketValue, Weightings, NetAssets, SharesOutstanding, CreationUnits, MoneyMarketFlag |
| NAV-basis performance (1 Mo/3 Mo/6 Mo/YTD/1 Yr/3 Yr/5 Yr/10 Yr/Inception, cumulative and annualized), monthly and quarter-end, plus the closing-market-price row and the benchmark row | The fund page Performance tables (#monthly-performance, #quarterly-performance) |
This feed publishes the NAV Performance row for every return metric, exactly as every other column-heading in the app assumes |
| Distribution history (declaration, ex-dividend, record, payable dates + amount per share) and the Distribution Information block (Distribution Rate, 12-Month Trailing Distribution Rate, Distribution Amount / Share in $ and %) | The fund page Distributions tabs |
Copied verbatim from the year-tabbed calendar, newest first; a month that is declared but not yet paid keeps NEOS's empty amount in the JSON and the app prints — for that cell rather than a blank |
| Growth-of-$10,000-at-NAV series since inception | The fund page Growth of $10,000 at NAV Since Inception chart (const dates / const navValues / const indexValues2) |
Stored as series metadata (start, end, point count); the app plots its own charts |
| Fund documents (prospectus, summary prospectus, SAI, annual and semi-annual report, fiscal Q1/Q3 portfolio holdings, tax insert, Form 8937) | The fund page Documents table and Form 8937 tab |
One row per published document; a document NEOS has not published yet stays null and the app prints — |
| Daily price history (Date / Close / Adj Close / Volume) | Yahoo Finance public chart API | Used for the price-history sheets; the fund's own official NAV and market price are always preferred where NEOS publishes them |
| Holdings fallback | SEC EDGAR Form N-PORT-P, NEOS ETF Trust CIK 0001848758 (file 811-23645) | Used only when a holdings CSV is unavailable (EDGAR_FALLBACK=1); off by default because SEC EDGAR answers GitHub runner IPs with 403 |
| Dividend-history fallback | Yahoo Finance dividend events | Only for a fund with no published Distribution History rows; the feed marks those rows distributions.fallback: true |
neosfunds.com throttles bursts (the TLS connection is reset), so requests are globally paced by REQUEST_SLEEP (default 1.5s) with bounded retries; the runner defaults to 2s.
The download is a real CSV served by WordPress, and it is the same file a visitor gets from the fund page. scripts/update-data.ts includes a small, dependency-free RFC-4180 reader (parseCsv) that tolerates the BOM, CRLF and quoted fields.
NEOS publishes no asset-class column, so the Watchlist's Asset Category is derived from each row's own fields — Cash (MoneyMarketFlag=Y or the Cash&Other line), Option (an OCC contract code such as SPXW 261001P07075000), Treasury (a T-bill or note), Fund (an ETF/trust the fund holds) and otherwise Equity — and that derivation is documented, tested and never applied to any other value. Every other cell is the provider's own text, including a written option's negative market value and share count.
Cusip is the identifier the CSV publishes, so it fills the shared Identifier column; option and T-bill rows key on it because they have no exchange ticker. The per-row NetAssets, SharesOutstanding and CreationUnits columns (identical on every row) are lifted to fund level.
The page publishes annualized 3 Yr / 5 Yr / 10 Yr figures and a cumulative Inception figure. The catalog carries both readings, computed from the published one with (1 + r)^n - 1 or (1 + TR)^(1/n) - 1, and returns.derivedFrom records that the basis is NEOS's own NAV Performance table. Nothing is extrapolated past a tenor the fund has not lived through: a fund younger than its 5-year mark publishes null, and the app prints —.
| Metric | Status | Reason |
|---|---|---|
| YTD / 1 Yr / 3 Yr / 5 Yr / 10 Yr returns | published from NEOS's own performance tables; — only for a tenor the fund is too young to report |
XBCI, XQQI and XSPI (Feb 2026 inception) have no YTD, 1 Yr or SI-annualized figure yet; 14 funds have no 3 Yr; 17 have no 5 Yr; SPYI is the 10 Yr case. This is exactly what the pages themselves print as -. |
| Underlying Exposure | — for HYBI, IAUI, NIHI, QQQH and SPYH |
Those five fund pages carry an Investment Objective paragraph instead of the Underlying Exposure row; the feed publishes null rather than guessing an index from the fund's name |
| FY-Q3 Portfolio Holdings | — for 9 funds |
Not published on the fund page yet |
| Annual Report / Supplemental Tax Information | — for XBCI, XQQI and XSPI |
The three February-2026 funds have not filed a first annual report yet |
| Form 8937 | — for IAUI, NIHI, XBCI, XQQI and XSPI |
Those five 8937 tabs are empty — NEOS has not filed one for them yet |
| 12-Month Trailing Distribution Rate | — for IAUI, MLPI, NEHI, NIHI, NLSI, SPYH, XBCI, XQQI and XSPI |
Their Distribution Information block publishes the Distribution Rate and the 30-Day SEC Yield only; the trailing row is absent even where the fund is older than 12 months (IAUI), so it is never inferred from the payout calendar |
| SEDOL / FIGI | always — |
NEOS publishes neither (the holdings CSV carries a CUSIP, and the fund page CUSIP/ISIN), so the app shows the documented data limitation |
| Coupon / Maturity | always — |
NEOS publishes no fixed-income holdings sheet with coupon or maturity columns |
| SEC Yield (30-day) | official fund page figure, — where the page prints -- |
Every NEOS fund currently publishes one; negative values (e.g. QQQI -0.05%) are kept verbatim |
| Inception Date | the lineup table's figure; the fund page's own panel date is kept as inceptionDateFundPage and explained by inceptionNote |
HYBI is the one fund where the two differ: the ETF converted from the WSTCM Credit Select Risk-Managed Fund on 09/27/2024 and assumed its performance, so the lineup table keeps 09/30/2014 — the date its published 5Y and 10Y returns are measured from |
| Premium / Discount | the fund page's own Premium Discount (%) row |
The (Market Price - NAV) / NAV quotient is stored alongside as premiumDiscountDerivedValue, and is used only if a page ever omits the row |
| Environment variable | Default | Meaning |
|---|---|---|
MAX_FETCHES |
0 |
Funds to process; 0 = full pass. A positive value resumes after the cursor in api/neos/update-state.json. |
TICKERS |
"" |
Space/comma separated ticker allowlist; ANDed with the other filters, never overriding them. |
CATEGORY |
"" |
Substring match on the NEOS asset-class group. |
AUM / TER / DIVIDEND_YIELD / SEC_YIELD |
"" |
min:max ranges; AUM also accepts nano/micro/small/mid/large. |
PERFORMANCE_YTD … PERFORMANCE_10Y / TOTAL_RETURN_YTD … TOTAL_RETURN_10Y |
"" |
min:max filters on the official NAV return, or on the derived cumulative total return. |
CONCURRENCY / REQUEST_SLEEP |
2 / 1.5 |
Politeness; neosfunds.com throttles bursts. |
HOLDINGS_PAGE_SIZE / HISTORY_PAGE_SIZE |
250 / 1000 |
Rows per generated JSON page. |
MAX_RETRIES |
3 |
Retries after the initial request for network errors and HTTP 408/425/429/5xx. |
HISTORY_RANGE |
max |
Yahoo chart range for the price-history sheets (max, 10y, 5y, …). |
EDGAR_FALLBACK |
off | Use N-PORT-P when a holdings CSV is unavailable. |
SKIP_YAHOO / SKIP_NEOS |
off | Skip the Yahoo Finance or neosfunds.com fetch stages. |
STORE_RAW_DOWNLOADS |
off | Keep one raw sample under api/neos/raw. |
SEC_UA |
repo URL | Declared User-Agent for SEC EDGAR requests. |
TICKERS combines with the AUM/TER/yield/return filters using AND logic; it does not override them. Funds not selected for a successful update keep their prior published metadata and data files, so a bounded or partly failed run can never empty the site.
MAX_FETCHES=10 ./scripts/update-data.ts
TICKERS="SPYI QQQI CSHI" ./scripts/update-data.ts
AUM="1B:" TER=":0.70" ./scripts/update-data.ts
CATEGORY="Fixed Income" ./scripts/update-data.tsThe browser app is intentionally build-free: index.html carries the markup, styles and bootstrap, and app.tsx is TypeScript compiled in the browser with Babel standalone — no build step, no bundler, no tsconfig.json needed. Bun runs TypeScript out of the box.
Verification before every publish: bun install --frozen-lockfile, bun test, and git diff --check.
| Brand | Where to get the data |
|---|---|
| abrdn (Aberdeen) | aberdeeninvestments.com | aberdeen |
| Amplify | amplifyetfs.com | Amplify |
| Fidelity | fidelity.com | Fidelity |
| 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 |
| ProShares | proshares.com | ProShares |
| Schwab | schwabassetmanagement.com | Schwab |
| SPDR | ssga.com | SPDR |
| VanEck | vaneck.com | VanEck |
| Vanguard | investor.vanguard.com | Vanguard |
| WisdomTree | wisdomtree.com | WisdomTree |
| Application | Data provider | Repository |
|---|---|---|
| abrdn (Aberdeen) | Official Aberdeen gateway + SEC N-PORT holdings fallback + Yahoo history/dividends | aberdeen |
| Amplify | Amplify ETFs (Firestore data feed) | Amplify |
| Fidelity | SEC EDGAR N-PORT-P + Yahoo Finance | Fidelity |
| 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 finder + detail pages + SEC EDGAR N-PORT-P | Goldman-Sachs |
| Invesco | invesco.com CSV downloads + Yahoo Finance | 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 |
| ProShares | proshares.com ETF finder + fund pages + official data host | ProShares |
| Schwab | schwabassetmanagement.com product pages + CSV exports | Schwab |
| SPDR | SSGA / State Street public feeds | SPDR |
| VanEck | vaneck.com ETF finder + product pages | VanEck |
| Vanguard | Vanguard product pages + SEC EDGAR N-PORT-P | Vanguard |
| WisdomTree | WisdomTree product table + SEC EDGAR N-PORT-P + Yahoo Finance | WisdomTree |
MIT — same as all sibling ETF repositories.
NEOS® and the fund names/tickers referenced here are trademarks of NEOS Investment Management LLC. This is an independent, unofficial tool; it is not affiliated with, endorsed by, or sponsored by NEOS. All data is reproduced from NEOS's own public pages and downloads, with public SEC EDGAR filings and Yahoo Finance as fallbacks, for research purposes. All other trademarks, including index names, are the property of their respective owners.