Methodology

How every score on this site is computed. If you can't replay our number from the raw inputs, that's a bug — please report it.

Version 0.5.0 · last updated 2026-06-08

What the composite means

The composite score (0–100) is a weighted average of sub-scores, each derived from a single piece of fund data with a published formula and published thresholds. A 90 is meaningfully different from a 70, but do not interpret a 92 as "better than" a 90 in any actionable sense — the inputs themselves carry noise.

When a sub-score's input data is unavailable for a particular fund, the composite uses the per-category median as a fallback (as long as at least half the category's funds have the sub-score available). This avoids the old failure mode where missing data accidentally boosted the composite by renormalizing weights onto the remaining sub-scores — same-index funds with worse data coverage would outrank their peers that had better data. The sub-score itself is still displayed as N/A on the per-fund page; only the composite uses the imputed value. When a category has too little data to compute a median, the legacy renormalize-and-publish fallback kicks in.

Sub-scores live today

Cost

Linear scale on the fund's prospectus net expense ratio. 0 basis points → 100. 100 basis points → 0. Clamped outside that range. A 30 bp fund scores 70; a 5 bp fund scores 95.

Source: yfinance netExpenseRatio field, cross-checked against a curated baseline. Drift above 5 bps is flagged for editorial review.

Liquidity

Log-scaled net assets. ≥ $10B → 100. ≤ $50M → 0. Log scale because ETF AUM spans five orders of magnitude; linear would collapse the $500M-to-$5B range to noise.

Concentration

Linear scale on top-10 holdings as a percent of net assets. ≤ 10% → 100. ≥ 80% → 0. Sourced from the fund's most recent Form N-PORT-P filing on SEC EDGAR.

Tax efficiency

Weighted combination of three structural inputs that determine after-tax outcomes in a taxable account:

  • Asset class (50% weight) — qualified-dividend / long-term-capital-gain eligibility versus ordinary-income treatment. Bases are listed in the table below.
  • Wrapper (20% weight) — ETFs score 90 because in-kind creation/redemption rarely surfaces capital-gain distributions. Open-end mutual funds score 30 because realized gains pass through to all shareholders, including new ones.
  • Distribution rate (30% weight) — linear on trailing-12-month yield. 0% → 100, 8% → 0. Higher distributions create more annual tax events regardless of qualification. Exception: municipal-bond funds are scored 100 on this component because their distributions are federally tax-exempt and don't create the ordinary-income drag the penalty models.
Asset class Base Rationale
US equity (total / large / mid / small / value / growth)85Qualified dividends + long-term cap gains.
Sector / factor equity75–80Same equity tax treatment; elevated turnover.
International developed / total70Foreign tax withholding offsets some QDI advantage.
International emerging65Higher withholding; partial QDI.
Dividend-tilt equity65Intentionally high distributions, but qualified.
Thematic equity60Equity tax treatment, but high turnover and concentration risk.
Municipal bonds90Federally tax-exempt income; distribution penalty waived.
US Treasuries (short / intermediate / long)35Ordinary income, but state-tax exempt.
US aggregate / corporate / international bonds25Ordinary-income interest, fully taxable federally and at state.
US high-yield bonds20Ordinary income at the largest distribution rates.
TIPS20Phantom income — inflation accretion taxed annually unrealized.
US REITs30~80% non-qualified dividends at ordinary rates.
International real estate25REIT-style distributions plus foreign-withholding friction.
Preferred securities50Hybrid distributions: some qualified, some ordinary. Sits below dividend (65) because of the ordinary-share, above bonds (25) because the qualified portion gets LTCG treatment.
Asset allocation (funds-of-funds)60Blended equity + bond exposure; tax efficiency lands between the two ends. Concentration sub-score is N/A for this category — see methodology note below.
Gold (physical-backed) / Silver3528% collectibles rate at federal level.
Covered-call / buy-write income25Distributions are mostly short-term option premium taxed as ordinary income.
MLP wrapper funds (AMLP-style)251099-DIV distributions mostly ordinary income; the C-corp wrapper absorbs K-1/UBTI complexity but accrues deferred-tax liability that drags NAV vs. the underlying index.
Other commodities / fallback50Mixed treatment; conservative middle anchor.

Reproducible: pick a fund, look up its asset class in the table above, combine with the wrapper and yield numbers shown on the fund page. Portfolio turnover and 5-year cap-gains-distribution history are deliberately excluded — see what we don't score.

Sub-score Weight
Cost0.40
Tax efficiency0.20
Liquidity0.25
Concentration0.15

Tax-placement optimizer

The tax-placement tool decides which of your accounts (taxable / tax-deferred / Roth) should hold each fund in a target allocation. The algorithm is the Bogleheads-wiki canonical greedy fill, with the per-category numbers below — change either and the other changes too.

Shelter priority (fills tax-deferred)

priority = ttm_yield × (1 − qdi_fraction) + shelter_premium − (0.3 if foreign equity)

Higher priority fills tax-deferred first. The intuition: yield × non-qualified-share is the annual ordinary-income drag the shelter eliminates. The premium adds for categories where rate alone understates the cost — TIPS phantom inflation income, REIT distribution composition, and gold (the 28% collectibles rate on taxable cap gains converts to ordinary-income treatment when held in tax-deferred, which is usually cheaper). The 0.3 foreign- equity subtraction reflects that sheltering foreign holdings forfeits the foreign-tax credit — discussed in more detail below. Municipal-bond funds short-circuit the formula: pinned to taxable regardless.

Growth priority (fills Roth)

A category-level 0–100 ranking of long-run growth potential, used to fill Roth from whatever's left after tax-deferred is full. Higher = wants the tax-free compounding. Emerging markets (85), small caps (80), large-cap growth and sector technology (70–75) sit at the top; treasuries (15) and utilities (35) at the bottom. This is a qualitative ranking, not a return forecast — same caveat you'd hear from any honest financial educator.

Qualified-dividend approximations

Per-fund QDI percentages aren't published in a structured form we can ingest, so we use category-wide approximations drawn from the Vanguard and iShares year-end tax-disclosure summaries, extended to categories those issuers don't cover (thematic, factor tilts, single-state munis) using comparable equity-vs-bond reasoning:

Category QDI % Notes
US equity (total / large / mid / small / value / growth / dividend / sectors / factors)95%Substantially all distributions qualified.
International developed / total70%Partial qualification; foreign-tax credit in taxable.
International emerging50%Lower qualification rate; higher withholding.
Preferred securities70%Hybrid securities; QDI varies by issuer.
Thematic equity90%Equity treatment with elevated turnover; small shelter premium added.
Real estate (REITs)20%Mostly ordinary income; 199A deduction recovers some.
All US bond categories + TIPS + commodity0%Distributions taxed as ordinary income (munis tax-exempt).
Asset-allocation wrapper / unknown50%Middle anchor when composition isn't known.

Drag estimate

For each holding's dollars in a taxable account:

drag = dollars × (ttm_yield/100) × effective_rate

effective_rate = (1 − qdi) × (marginal + niit) + qdi × (ltcg + niit)

Long-term-cap-gains rate is approximated from your marginal bracket (0% if ≤ 12%, 20% if ≥ 37%, 15% otherwise). This is a coarse mapping — the actual IRS LTCG brackets are based on taxable income, not on marginal rate, so a household in the 22% bracket with low total income can sit at 0% LTCG. The number is in the right ballpark; the precise rate depends on your full return. NIIT (3.8%) is a statutory tax that applies once MAGI exceeds $200K single / $250K MFJ; the checkbox in the tool toggles whether to model it, not whether the tax is owed. Holdings in tax-deferred or Roth contribute zero drag (no current-year tax). Muni-bond funds contribute zero drag in taxable (federally tax-exempt). The naive-split baseline holds the same target allocation in every account, proportional to balances, for comparison.

Foreign equity and the FTC

International funds (VXUS, VEA, VWO, IEFA, IEMG, etc.) pay foreign tax at the source country before distributing dividends to US shareholders. When held in a taxable account, a US investor can claim that foreign tax as a credit on Form 1116, which approximately offsets the foreign withholding. When held in a tax-deferred or Roth account, the foreign-tax credit is forfeited — the foreign withholding becomes a permanent cost with no recovery mechanism.

This is fundamentally a placement decision rather than a drag-rate modifier, so the optimizer captures it through the shelter-priority subtraction (the 0.3 above) — which nudges foreign equity toward taxable in the fill order — rather than adjusting the per-dollar drag rate. The displayed drag number for foreign equity in taxable is therefore a small overstatement; a more precise model would credit back the recovered foreign withholding, but the dollar size depends on country-specific treaty rates and the investor's own tax situation, both of which this tool doesn't try to know.

Shelter premia (category bumps)

A few categories carry extra shelter priority on top of the yield × non-QDI math, where the headline rate alone understates the actual cost of taxable holding:

  • TIPS (+1.5): annual inflation accretion is taxed before realized — phantom income with no offsetting cash distribution.
  • High-yield bond (+1.0): very high ordinary-income distributions, no diversification benefit that a shelter would erase.
  • Covered-call income (+1.0): distributions are mostly short-term option premium taxed as ordinary income, not qualified dividends; the shelter eliminates that annual drag entirely.
  • REITs (+0.5): ~80% ordinary-income distributions; the 199A deduction lets you exclude 20% of the qualifying portion from taxable income, which trims the effective rate by roughly a fifth but doesn't close the gap to qualified dividends.
  • Gold / Silver (+0.5): 28% collectibles rate on taxable cap gains converts to ordinary-income treatment in tax-deferred (paid at withdrawal at marginal). For most marginal brackets that's a meaningful saving.
  • MLP wrappers (+0.5): 1099-DIV distributions are mostly ordinary income; shelter eliminates the annual drag (though the wrapper's deferred-tax accrual is a separate structural cost).
  • Preferred securities (+0.3): high yield with a mix of qualified and ordinary, hybrid security in capital structure.
  • Other commodity / thematic / momentum (+0.3–0.5): heavier turnover and unusual tax treatment (K-1s, ETN credit risk).

Algorithm

  1. Pin municipal-bond holdings to taxable up to its capacity.
  2. Sort remaining holdings by shelter priority descending; fill tax-deferred.
  3. From whatever spilled, sort by growth priority descending; fill Roth.
  4. Remainder fills taxable.

Greedy, not optimal. A holding with both high shelter priority and high growth priority could in principle land in the "wrong" shelter — the two priorities are anti-correlated in practice (bonds shelter well but grow slowly) so the greedy answer matches the LP-optimal answer in ~95% of real-world portfolios. Where it doesn't, the gap is small. State tax, AMT, RMDs, Roth conversion math, and per-fund cap-gains distribution history are not modeled.

Tracking quality (partial coverage)

A fund's job is to track its stated index. The closer it sticks to the benchmark, the cleaner the exposure. Annualized tracking error is the conventional measure — the standard deviation of monthly return differences between the fund and the index, scaled to a one-year horizon.

We don't license CRSP, FTSE Russell, or MSCI proprietary benchmark return data, so we can't compute tracking error against the index directly. Instead, where two or more catalog funds track the same named benchmark, the largest-AUM fund acts as the peer reference, and tracking error is measured between each cohort member and that reference. The reference's own TE-to-index is small on broad equity and Treasury cohorts (single-digit basis points), so peer-vs-peer TE approximates peer-vs-index TE there. The approximation degrades when the reference is itself a noisy tracker (e.g. currency-hedged international, silver, or any cohort where structural quirks dominate). Funds whose "benchmark" is a placeholder like "(actively managed — no index)" are excluded — they have nothing to track.

tracking_quality = 100 if TE ≤ 10 bps; 0 if TE ≥ 100 bps; linear between

The score is shown on per-ETF pages where computable. It's not currently part of the composite because the partial coverage would create ranking artifacts: funds with a peer reference would get a fifth sub-score while funds tracking a unique benchmark wouldn't, and the catalog-wide leaderboard would mix two different weighting schemes. The score is here as an additional reference signal, not a composite input. We'll revisit when coverage is broad enough that the comparison stops being apples-to-oranges.

Reference funds get tracking_quality = N/A by construction (a fund's TE against itself is zero by definition). Funds whose benchmark is unique in the catalog (BND vs. AGG track slightly different Bloomberg Aggregate variants, for example) also get N/A — we don't fake a comparison.

What we don't score

A few dimensions a serious passive investor might expect to see in a scoring framework aren't part of the composite. Each one is left out for a specific reason, and disclosing that is more honest than scoring it badly:

  • Bid-ask spread — relevant for low-AUM funds (5–20 bps on thin ETFs vs. under 1 bp on the broad-market workhorses). yfinance's snapshot bid/ask is captured at our 22:00-UTC refresh window, which is after the US market closes — and after-hours spreads widen dramatically (we measured a 33% snapshot on VLUE in late-session quotes that's well under 10 bps during the day). A trustworthy estimate needs an intraday quote source we don't currently have; the composite leaves spread out rather than score it from misleading data.
  • 5-year capital-gains distribution history — would differentiate funds within the same category on actual after-tax outcomes (an ETF distributing 2% in cap gains over the trailing five years is measurably worse than one distributing zero, even at the same trailing yield). yfinance returns empty data for every ETF we tested, so capturing this requires parsing issuer Form 8937 disclosures and N-CSR annual reports off EDGAR. The structural-tax-profile component covers most of the signal; adding distribution history would be a refinement, not a fix.
  • Portfolio turnover — surfaces through the shelter-premium bumps for high-turnover categories (thematic, momentum) but isn't a per-fund line item. N-PORT reports the year's purchases and sales as separate aggregates; converting those into a comparable turnover percent across fund types is well-defined for plain-vanilla equity but breaks down for bond funds and option-strategy ETFs. We rely on category-level turnover characteristics instead.

Data sources

  • SEC EDGAR Form N-PORT-P — official fund holdings. Filed monthly with the SEC, but only the third-month-of-quarter report is publicly disclosed (with a 60-day lag); the other two months are confidential.
  • yfinance — daily prices, dividends, expense ratios, AUM, basic fund metadata.
  • Curated baseline — issuer, category, and benchmark mapping maintained per fund; preserved as a fallback when scraped fields are missing. A small allow-list of tickers also has the curated expense ratio pinned over the scraped value when the scraped figure is structurally misleading (AMLP, currently): the GAAP "total annual expenses" line absorbs the C-corp wrapper's deferred-tax accrual, which is a real cost to NAV but not the fee the fund actually charges. The pinned 0.85% is the operational expense ratio. Similarly, a small set of tickers has TTM yield overridden when yfinance returns 0% on a fund that actually distributes (INDA, EPI — India ETFs).

We don't license data from any provider whose business depends on fund issuers paying for placement, ratings, or distribution access.

Revision history

  • v0.5.0 — Tracking-quality partial scoring and two follow-on corrections from a second multi-agent review pass. (1) Funds whose "benchmark" is a sentinel string like "(actively managed — no index)" are no longer scored for tracking quality — previously the build was grouping all ARK funds, all Avantis funds, etc. into accidental cohorts and publishing 0.0 scores. (2) Fund-of-funds wrappers (asset_allocation and covered_call_income) are now exempt from the concentration sub-score — by design they hold ~5 underlying ETFs at high weights, so the diversification signal the sub-score targets doesn't apply. AOA/AOR/AOM/AOK previously scored 0 on a sub-score that wasn't meaningful for them. (3) Distribution-rate component's linear region widened from 0–8% to 0–15% yield so covered-call funds (JEPI ~8%, QYLD ~11%, TLTW ~13%) differentiate properly inside their category rather than all saturating at the same floor. Plus recategorizations: MLPX moved back to sector_energy (C-corp midstream operator, not a K-1 wrapper); DEM/DGS joined DVYE in dividend; SPHB moved out of factor_value (high-beta is not a value strategy).
  • v0.4.0 — Three structural fixes from a multi-agent red-team pass. (1) Composite no longer renormalizes weights when a sub-score is missing; instead it imputes from the per-category median when that category has ≥ 50% data coverage. Previously SPLG/SPY (S&P 500, no parsed N-PORT) outranked VOO/IVV (same index, with N-PORT) purely because the missing concentration sub-score boosted the others — now SPLG sits at 90.2 in line with VOO at 89.8 instead of the previous 95.8. (2) New categories added to surface tax-treatment differences the previous taxonomy buried: mlp for MLP funds (AMLP, MLPX — moved out of sector_energy), covered_call_income for buy-write strategies (JEPI, JEPQ, QYLD, XYLD, KNG, TLTW — moved out of dividend / us_treasury_long), and commodity_silver for physical silver trusts (SLV, SIVR). Each carries a tax base that reflects the ordinary-income share of the distribution stream, which the old categorization papered over. (3) AMLP's published ER pinned to the operational 0.85% the fund actually charges rather than the yfinance-scraped 1.01% (which conflates deferred-tax accrual with the management fee).
  • 2026-05-21 data fix — Concentration sub-score went live for the ~395 funds with parsed holdings data. Most composite scores shifted by 1–5 points because they now incorporate four sub-scores instead of three. Funds whose top-10 weight runs concentrated (sector and thematic ETFs) moved down; broad-market and aggregate-bond funds with diffuse top-10s moved up. Background: a daily-refresh regression had been wiping holdings from the published catalog on each weekday rebuild — fixed in that push.
  • v0.3.0 — Expanded the tax-efficiency asset-class table to cover every category in the catalog (treasury intermediate, corporate IG, high-yield, municipal, international real estate, and the remaining GICS sectors). Municipal-bond funds now waive the distribution penalty because their distributions are federally tax-exempt. The practical effect: muni ETFs (VTEB, MUB) keep a well-deserved high score, while bond categories that previously had no tax anchor (and so were renormalized away from any tax drag) now sit in their correct band — VGIT moved from ~98 to ~87, HYG from ~70 to ~62.
  • v0.2.0 — Initial public tax-efficiency component with asset-class base + wrapper + distribution-rate combination.

What this site is not

  • This is not personalized investment advice. PlainIndex does not know your goals, taxes, risk tolerance, or time horizon.
  • Scores are relative. A high-scoring sector ETF is still a sector ETF; categorization matters as much as the number.
  • Past performance is not on this page on purpose. The scoring framework deliberately avoids backward-looking return data.