Find cheaper alternatives to the ETFs and funds you already own

Fund twin finder

Find cheaper alternatives to the ETFs and funds you already own

Compare your ETFs and mutual funds against the lowest-cost alternatives tracking the same index. See whether FZROX beats VTI, FZILX beats VXUS, or SCHB beats what you're holding — and calculate exactly how much you're overpaying per year. Not advice.

Kate
Apr 21, 2026
Updated on:
Apr 21, 2026

Want to hook AI up to your finances?

Sign up for our MCP-only product, Truthifi Connect.

Try the MCP

Dashboard that compares your funds to others
Find cheaper alternatives to the ETFs and funds you already own

Fund twin finder

Find cheaper alternatives to the ETFs and funds you already own

Compare your ETFs and mutual funds against the lowest-cost alternatives tracking the same index. See whether FZROX beats VTI, FZILX beats VXUS, or SCHB beats what you're holding — and calculate exactly how much you're overpaying per year. Not advice.

Kate
Apr 21, 2026
Updated on:
Apr 21, 2026

Want to hook AI up to your finances?

Sign up for our MCP-only product, Truthifi Connect.

Try the MCP

Dashboard that compares your funds to others

Fund twin finder

Find cheaper alternatives to the ETFs and funds you already own

Kate
Apr 21, 2026
Updated on:
Apr 21, 2026

Want to hook AI up to your finances?

Sign up for our MCP-only product, Truthifi Connect.

Try the MCP

Dashboard that compares your funds to others

how to find cheaper ETF alternatives to my portfolio / expense ratio comparison tool / what is a good expense ratio for an ETF / FZROX vs VTI comparison / FZILX vs VXUS fee comparison / SCHB vs VTI cost difference / Fidelity zero funds alternative / cheapest index fund alternatives / AI tool to reduce investment fees / how much am I overpaying in fund fees

expense ratio comparison

what is a good expense ratio, SCHB vs VTI, VTI vs FZROX, FZILX vs VXUS, Fidelity zero funds, cheapest index funds, ETF fee comparison, lower cost index fund

In this article…

No headings found on page

Not a member yet?

Sign up and get connected in minutes—for free.

Sign up and get connected in minutes—for free.

Need help?

Need help?

Check out our helpful guides to get our MCP set up.

Check out our helpful guides to get our MCP set up.

About this prompt

Compare your ETFs and mutual funds against the lowest-cost alternatives tracking the same index. See whether FZROX beats VTI, FZILX beats VXUS, or SCHB beats what you're holding — and calculate exactly how much you're overpaying per year. Not advice.

Prompt details

You are about to run the Fund Twin Finder — a Truthifi MCP prompt that fetches real investment data and builds a live interactive HTML page. There is no demo mode. Every value in the output must come from real MCP data. You must NOT: - Build a prototype, mock-up, preview, or demonstration - Use placeholder, example, estimated, or invented fund data - Render any HTML before completing all data steps with real MCP results - Proceed without an explicit "yes" from the user If you cannot connect to real MCP data, say so and stop. --- **Step 0 — Ask for consent before doing anything else** Display this message exactly and wait: > "This tool will connect to your Truthifi account and compare your actual investment fund holdings against lower-cost alternatives. It will then build a personalized interactive page showing what you could save per year in fees. > > Output is for informational purposes only — not financial advice. Neither Truthifi, Anthropic, nor this prompt's author is responsible for any investment decisions you make. > > Type **yes** to continue, **terms** to see a summary, or **no** to cancel." - **"no"** → Stop. Say "No problem — no data was accessed." Do nothing else. - **"terms"** → Say: "Key points: (1) Not financial advice. (2) Your data stays in this session. (3) Don't share output with your personal data visible. (4) Don't remove the disclaimer from the page. (5) Truthifi and Anthropic bear no liability. Full terms: https://truthifi-connect.ai/prompt-gallery-terms — Yes or no?" Then wait again. - **"yes"** → Proceed to Step 0b. - **Anything else** → Re-ask. Do not treat ambiguity as consent. "start", "proceed", "go", "build it" are NOT consent. **Step 0b — Ask for preferences (all at once):** > "Three quick questions: > 1. Default period? (3 months / 6 months / 1 year / 3 years) — default: 1 year > 2. Show return, volatility, or both? — default: both > 3. Any fund tickers to exclude? (e.g. funds you can't sell) — or say none" Set PREFERRED_RANGE (3m/6m/1y/3y), PREFERRED_VIEW (ret/vol/both), EXCLUDE_TICKERS. If user says "skip" or "defaults": use 1y, both, none. --- **Step 1 — Identify funded accounts** Call `get_balance_history` with: - dateRange: { from: 30 days ago, to: today } - include: ["accountId", "endingBalance", "accountName"] Store accounts where endingBalance > 0 as FUNDED_ACCOUNTS. If empty: stop with "No funded accounts found." --- **Step 2 — Fetch real holdings** For EACH account in FUNDED_ACCOUNTS (one at a time, never accountIds: null across multiple accounts): Call `get_dated_holdings` with: - accountIds: [single accountId] - dateRange: { from: [365 days ago for 1y / 90 for 3m / 183 for 6m / 1095 for 3y], to: today } - securityTypes: ["etf", "mutual_fund", "cit", "hedge_fund"] - include: ["symbol", "securityName", "securityType", "balance", "accountId", "positionDate"] If an account returns zero rows, retry with dateRange.to = yesterday. If still zero: show visible warning for that account but continue. After all fetches: confirm every FUNDED_ACCOUNT has at least one row. Warn visibly for any that don't. Process results: - Drop rows where symbol or balance is null or <= 0 - Drop tickers in EXCLUDE_TICKERS - Deduplicate by symbol across accounts — sum balances, use earliest positionDate - If zero holdings remain: stop with "No eligible investment funds found." Store as HOLDINGS: [{ symbol, securityName, balance, positionDate }] --- **Step 3 — Fetch performance data (optional)** Call `get_performance_history` with: - dateRange: { from: 365 days ago, to: today } - aggregate: true - include: ["totalReturn", "totalValue"] Store totalReturn and totalValue. If this call fails, continue without it. --- **Step 4 — Research alternatives using training knowledge** For each holding in HOLDINGS, identify the 3 lowest-cost alternatives tracking a similar index or strategy. Use training knowledge for expense ratios — most recent known prospectus values. Rank cheapest first. For each rival: { ticker, fullName, er (decimal), ret1y (%), vol (annualized %) } Also record ret1y and vol for each of the user's own holdings. Scale returns by period: - 3m: ret1y × 0.27 - 6m: ret1y × 0.52 - 1y: ret1y (base) - 3y: ret1y × 2.8 Do NOT hardcode any dollar amounts. All figures computed from real holdings data. --- **Step 5 — Identify 30-day changes** For each holding, determine: Category A (is-new / badge-new-alt): An alternative whose ER dropped or launched in the last 30 days. Only flag if drop >= 0.01 percentage points. Right-side value: computed saving + "potential saving". Category B (is-new-holding / badge-new-holding): A holding where positionDate is within 30 days AND a cheaper alternative exists. Right-side value: date added (MMM DD) + "date added" — NOT a dollar saving (ambiguous). Store as CHANGES: [{ ticker, category, meta, amount, sublabel, rowClass, badgeClass }] If CHANGES is empty: card 1 headline = "Nothing changed in the last 30 days. Your fund cost picture is the same as last month." --- **Step 6 — Compute savings** ``` annual_saving_per_fund = (holding.er - best_rival.er) * holding.balance A fund is an "opportunity" if: best_rival.er < holding.er AND (for PREFERRED_VIEW=ret: best_rival.ret1y > holding.ret1y) (for PREFERRED_VIEW=vol: best_rival.vol < holding.vol) (for PREFERRED_VIEW=both: either condition above) TOTAL_ER_NOW = sum of (holding.er * holding.balance) for all holdings TOTAL_ER_ALT = sum of (best_rival.er * holding.balance) for opportunity holdings + sum of (holding.er * holding.balance) for non-opportunity holdings TOTAL_SAVING = TOTAL_ER_NOW - TOTAL_ER_ALT OPP_COUNT = count of opportunity holdings ``` Round all dollar values to nearest dollar. --- **Step 7 — Build the page** DO NOT start this step until Steps 1–6 are complete with real data. Every value in the output — tickers, ERs, returns, volatility, savings, fund names — must come from the data above. Do not approximate or invent any value. Produce a complete standalone HTML file using the exact CSS and JS below. Output only the HTML — no explanation, no preamble. ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Fund Twin Finder</title> <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link href="https://fonts.googleapis.com/css2?family=Merriweather:ital,wght@0,300;0,400;0,700;1,300&family=Manrope:wght@400;500;600;700;800&display=swap" rel="stylesheet"> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200"/> <style> :root{--navy:#011827;--sub:#626F79;--muted:#9CAAB4;--bg:#F4F6F8;--surface:#FFFFFF;--border:#E3E8ED;--border-light:#EEF1F4;--blue:#0676C5;--blue-light:#E8F3FB;--blue-border:rgba(6,136,217,0.50);--shadow-card:-4px 0 24px 0 rgba(177,0,124,0.12),4px 0 24px 0 rgba(102,63,254,0.12),0 -4px 24px 0 rgba(38,197,223,0.12),4px 4px 24px 0 rgba(6,118,197,0.08);--shadow-sm:-2px 0 12px 0 rgba(177,0,124,0.06),2px 0 12px 0 rgba(102,63,254,0.06),0 -2px 12px 0 rgba(38,197,223,0.06),2px 2px 12px 0 rgba(6,118,197,0.05);--s1:8px;--s2:16px;--s3:24px;--s4:32px;--s5:40px;--s6:48px;--r-lg:16px;--r-md:12px;--r-pill:999px;--ff-serif:'Merriweather',Georgia,serif;--ff-body:'Manrope',system-ui,sans-serif;--color-01:#055E9E;--surface-01:#E1F5FF;--color-05:#347330;--surface-05:#E6F3E6;--color-09:#D32F2F;--surface-09:#FFEBEE;} @media(prefers-color-scheme:dark){:root{--navy:#dce8f0;--sub:#7a94a3;--muted:#4a6070;--bg:#0a1520;--surface:#101e2c;--border:#1a2f42;--border-light:#152638;--blue-light:#071e30;--color-01:#00A9FE;--surface-01:#055E9E;--color-05:#66B463;--surface-05:#0A3D2E;}} *,*::before,*::after{box-sizing:border-box;margin:0;padding:0;} body{font-family:var(--ff-body);background:var(--bg);color:var(--navy);-webkit-font-smoothing:antialiased;} .icon{font-family:'Material Symbols Rounded';font-style:normal;font-weight:300;font-size:20px;line-height:1;display:inline-flex;align-items:center;justify-content:center;font-variation-settings:'FILL' 0,'wght' 300,'GRAD' 0,'opsz' 20;user-select:none;flex-shrink:0;} .icon.fill{font-variation-settings:'FILL' 1,'wght' 400,'GRAD' 0,'opsz' 20;} .icon.sm{font-size:16px;} .tf-btn{display:flex;height:42px;padding:0 16px;flex-direction:column;justify-content:center;align-items:center;border-radius:100px;cursor:pointer;font-family:'Manrope',sans-serif;font-size:14px;font-weight:800;line-height:24px;letter-spacing:-0.25px;white-space:nowrap;transition:background 0.15s,transform 0.1s;flex-shrink:0;text-decoration:none;border:none;} .tf-btn-inner{display:flex;align-items:center;gap:6px;} .tf-btn:active{transform:scale(0.97);} .tf-btn-primary{background:var(--blue);color:#fff;} .tf-btn-primary:hover{background:#055fa0;} .tf-btn-outline{background:transparent;color:var(--blue);border:1px solid var(--blue-border);} .tf-btn-outline:hover{background:var(--blue-light);} .page-header{position:sticky;top:0;z-index:50;background:var(--surface);border-bottom:1px solid var(--border-light);box-shadow:var(--shadow-sm);} .page-header-inner{max-width:780px;margin:0 auto;padding:16px var(--s2) 12px;display:flex;flex-direction:column;gap:10px;} .page-header-row1{display:flex;align-items:center;justify-content:space-between;gap:var(--s2);} .page-header-title{font-family:var(--ff-serif);font-size:24px;font-weight:700;color:var(--navy);letter-spacing:-0.02em;line-height:1.2;} .page-header-actions{display:flex;align-items:center;gap:var(--s1);} .page-header-row2{display:flex;align-items:center;gap:var(--s1);} .tf-select{font-family:var(--ff-body);font-size:13px;font-weight:500;color:var(--navy);background:var(--surface);border:1px solid var(--border);border-radius:999px;padding:6px 28px 6px 12px;cursor:pointer;appearance:none;-webkit-appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24'%3E%3Cpath fill='%239CAAB4' d='M7 10l5 5 5-5z'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 6px center;transition:border-color 0.15s;} .tf-select:focus{border-color:var(--blue);outline:none;} .page{max-width:780px;margin:0 auto;padding:var(--s4) var(--s2) var(--s6);display:flex;flex-direction:column;gap:var(--s2);} .card{background:var(--surface);border-radius:var(--r-lg);box-shadow:var(--shadow-card);padding:var(--s4);} @media(max-width:600px){.card{padding:var(--s3);}} .card-head{display:flex;align-items:center;justify-content:space-between;gap:var(--s2);margin-bottom:var(--s2);} .card-headline{font-family:var(--ff-serif);font-size:clamp(20px,2.8vw,24px);font-weight:300;color:var(--navy);line-height:1.35;flex:1;} .card-headline strong{font-weight:700;} .toggle-btn{flex-shrink:0;display:flex;height:42px;padding:0 16px;flex-direction:column;justify-content:center;align-items:center;border-radius:100px;cursor:pointer;font-family:'Manrope',sans-serif;font-size:14px;font-weight:800;line-height:24px;letter-spacing:-0.25px;white-space:nowrap;transition:background 0.15s,transform 0.1s;background:transparent;color:var(--blue);border:1px solid var(--blue-border);} .toggle-btn:hover{background:var(--blue-light);} .toggle-btn:active{transform:scale(0.97);} .acc-rows{overflow:hidden;max-height:0;transition:max-height 0.4s cubic-bezier(0.16,1,0.3,1);} .acc-rows-alt{overflow:hidden;max-height:0;transition:max-height 0.3s cubic-bezier(0.16,1,0.3,1);} .hero-callout{display:flex;align-items:flex-start;gap:12px;border-radius:var(--r-md);padding:14px 16px;} .callout-icon{width:40px;height:40px;border-radius:10px;display:flex;align-items:center;justify-content:center;flex-shrink:0;} .callout-title{font-family:var(--ff-body);font-size:16px;font-weight:700;color:var(--navy);line-height:1.4;} .callout-sub{font-family:var(--ff-body);font-size:14px;font-weight:500;color:var(--sub);line-height:1.5;margin-top:4px;} .callout-neutral{background:#E1F5FF;} .callout-neutral .callout-icon{background:#055E9E;} .tf-badge{font-family:var(--ff-body);font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:0.04em;padding:4px 10px;border-radius:var(--r-pill);display:inline-flex;align-items:center;gap:4px;flex-shrink:0;} .badge-new-alt{background:#EDF9FF;color:#00A9FE;} .badge-new-holding{background:var(--surface-05);color:var(--color-05);} .fund-row-list{display:flex;flex-direction:column;border:1px solid var(--border-light);border-radius:var(--r-md);overflow:hidden;} .fund-row-item{display:flex;align-items:center;gap:14px;padding:13px 16px;border-left:3px solid var(--border);border-bottom:1px solid var(--border-light);background:var(--bg);} .fund-row-item.is-new{border-left-color:var(--color-01);background:var(--surface);} .fund-row-item.is-new-holding{border-left-color:var(--color-05);background:var(--surface);} .fund-row-item:last-child{border-bottom:none;} .fund-row-main{flex:1;min-width:0;} .fund-row-name{display:flex;align-items:center;gap:6px;flex-wrap:nowrap;margin-bottom:4px;overflow:hidden;} .fund-row-ticker{font-family:var(--ff-body);font-size:14px;font-weight:700;color:var(--navy);letter-spacing:-0.01em;} .fund-row-meta{font-family:var(--ff-body);font-size:12px;font-weight:400;color:var(--sub);margin-top:2px;line-height:1.5;} .fund-row-right{display:flex;flex-direction:column;align-items:flex-end;gap:2px;flex-shrink:0;min-width:104px;} .fund-row-amount{font-family:var(--ff-body);font-size:16px;font-weight:800;color:var(--navy);font-variant-numeric:tabular-nums;letter-spacing:-0.02em;} .fund-row-amount.green{color:var(--color-05);} .fund-row-amount.muted{font-size:13px;font-weight:500;color:var(--muted);} .fund-row-sublabel{font-family:var(--ff-body);font-size:12px;font-weight:400;color:var(--sub);} .chart-legend{display:flex;align-items:center;gap:var(--s3);margin:0 0 var(--s1);flex-wrap:wrap;} .chart-legend-item{display:flex;align-items:center;gap:7px;font-family:var(--ff-body);font-size:12px;font-weight:500;color:var(--sub);} .legend-line{width:24px;height:2px;flex-shrink:0;} .legend-line.solid{background:var(--color-01);} .legend-line.dashed-green{background:transparent;border-top:2px dashed var(--color-05);} .compare-table{width:100%;border-collapse:collapse;table-layout:fixed;} .compare-table thead th{font-family:var(--ff-body);font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:0.07em;color:var(--muted);padding:8px 12px;border-bottom:1px solid var(--border);background:var(--surface);white-space:nowrap;} .compare-table thead th.right{text-align:right;} .col-fund{width:190px;}.col-bar{width:auto;}.col-save{width:128px;} .row-your td{padding:14px 12px 7px;border-bottom:none;vertical-align:bottom;background:var(--surface);} .row-your td:first-child{border-left:3px solid var(--color-01);padding-left:10px;} .row-rival td{padding:7px 12px 14px;border-bottom:1px solid var(--border-light);background:var(--surface-05);vertical-align:top;} .row-rival td:first-child{border-left:3px solid var(--color-05);padding-left:10px;} td.save-cell{vertical-align:middle;text-align:right;padding:14px 20px;border-bottom:1px solid var(--border-light);background:var(--surface);border-left:1px solid var(--border-light);} .row-alt td{padding:7px 12px;border-bottom:1px solid var(--border-light);background:var(--bg);vertical-align:middle;} .row-alt td:first-child{border-left:3px solid var(--border);padding-left:10px;} .row-alt:last-child td{border-bottom:1px solid var(--border);} .fund-name{font-family:var(--ff-body);font-size:14px;font-weight:700;color:var(--navy);letter-spacing:-0.01em;} .fund-name.rival{font-size:13px;font-weight:700;color:var(--color-05);} .fund-name.alt{font-size:12px;font-weight:600;color:var(--sub);} .fund-er{font-family:var(--ff-body);font-size:12px;color:var(--sub);margin-top:2px;} .row-rival .fund-er{color:var(--color-05);} .er-free{color:var(--color-05);font-weight:700;} .bar-track{height:12px;border-radius:2px;background:var(--border-light);overflow:hidden;position:relative;min-width:40px;} .row-rival .bar-track{background:rgba(255,255,255,0.55);} .bar-fill{height:100%;border-radius:2px;position:absolute;left:0;top:0;transition:width 0.4s cubic-bezier(0.16,1,0.3,1);} .bar-your{background:var(--color-01);} .bar-rival-fill{background:var(--color-05);} .bar-num{font-family:var(--ff-body);font-size:13px;font-weight:600;color:var(--muted);margin-top:4px;font-variant-numeric:tabular-nums;display:flex;align-items:center;gap:4px;justify-content:flex-end;} .row-rival .bar-num{color:var(--color-05);} .delta-chip{font-family:var(--ff-body);font-size:10px;font-weight:700;padding:2px 6px;border-radius:3px;background:var(--color-05);color:#fff;white-space:nowrap;font-variant-numeric:tabular-nums;} .save-amount{font-family:var(--ff-body);font-size:22px;font-weight:800;color:var(--color-05);font-variant-numeric:tabular-nums;letter-spacing:-0.03em;line-height:1.1;white-space:nowrap;} .save-label{font-family:var(--ff-body);font-size:12px;font-weight:500;color:var(--sub);margin-top:3px;} .save-none{font-family:var(--ff-body);font-size:12px;font-weight:500;color:var(--muted);font-style:italic;} .expand-btn{display:inline-flex;align-items:center;gap:3px;margin-top:10px;font-family:var(--ff-body);font-size:11px;font-weight:600;color:var(--sub);background:transparent;border:1px solid var(--border);border-radius:var(--r-pill);cursor:pointer;padding:4px 10px;white-space:nowrap;transition:border-color 0.15s,color 0.15s,background 0.15s;} .expand-btn:hover{border-color:var(--blue-border);color:var(--blue);background:var(--blue-light);} .table-legend{display:flex;align-items:center;gap:var(--s3);margin-bottom:var(--s2);flex-wrap:wrap;} .table-legend-item{display:flex;align-items:center;gap:7px;font-family:var(--ff-body);font-size:12px;font-weight:500;color:var(--navy);} .legend-swatch{width:28px;height:10px;border-radius:2px;flex-shrink:0;} .explore-card{background:var(--surface);border-radius:var(--r-lg);box-shadow:var(--shadow-card);padding:var(--s3) var(--s4);} .explore-headline{font-family:var(--ff-serif);font-size:clamp(20px,2.8vw,24px);font-weight:300;color:var(--navy);line-height:1.35;margin-bottom:var(--s3);} .explore-headline strong{font-weight:700;} .explore-divider{border:none;border-top:1px solid var(--border-light);margin-bottom:var(--s2);} .explore-tile{display:flex;align-items:center;gap:14px;padding:var(--s2) 0;border-bottom:1px solid var(--border-light);} .explore-tile:last-child{border-bottom:none;} .explore-tile-icon{width:40px;height:40px;border-radius:10px;display:flex;align-items:center;justify-content:center;flex-shrink:0;} .explore-tile-body{flex:1;min-width:0;} .explore-tile-name{font-family:var(--ff-body);font-size:14px;font-weight:700;color:var(--navy);margin-bottom:3px;} .explore-tile-meta{font-family:var(--ff-body);font-size:12px;font-weight:400;color:var(--sub);} .explore-tile-btn{flex-shrink:0;display:inline-flex;height:36px;padding:0 16px;align-items:center;justify-content:center;line-height:1;border-radius:var(--r-pill);background:var(--blue);color:#fff;font-family:var(--ff-body);font-size:13px;font-weight:800;text-decoration:none;transition:background 0.15s;white-space:nowrap;} .explore-tile-btn:hover{background:#055fa0;} .cta-btn-inline{display:inline-flex;height:48px;padding:0 32px;align-items:center;justify-content:center;gap:8px;border-radius:100px;background:linear-gradient(112deg,#8C389E -3.89%,#4E66BD 52.5%,#0CACD5 108.89%);box-shadow:var(--shadow-card);font-family:var(--ff-body);font-size:14px;font-weight:800;letter-spacing:-0.25px;color:#fff;text-decoration:none;white-space:nowrap;transition:transform 0.2s cubic-bezier(.22,.61,.36,1),box-shadow 0.2s;} .cta-btn-inline:hover{transform:translateY(-2px) scale(1.01);} .cta-btn-inline:active{transform:translateY(-1px) scale(0.99);} .footer{max-width:780px;margin:0 auto;padding:var(--s3) var(--s2) var(--s5);border-top:1px solid var(--border-light);} .footer p{font-family:var(--ff-body);font-size:11px;color:var(--muted);line-height:1.6;} </style> </head> <body> <header class="page-header"> <div class="page-header-inner"> <div class="page-header-row1"> <h1 class="page-header-title">Fund twin finder</h1> <div class="page-header-actions"> <button class="tf-btn tf-btn-primary" style="height:36px;font-size:13px;"> <div class="tf-btn-inner"><span class="icon sm">refresh</span><span>Refresh</span></div> </button> <button class="tf-btn" style="height:36px;font-size:13px;background:transparent;color:var(--navy);border:1px solid var(--border);border-radius:100px;">Report a problem</button> </div> </div> <div class="page-header-row2"> <select class="tf-select" id="range-select" onchange="setRange(this.value)"> [POPULATE with options matching PREFERRED_RANGE selected — 3m/6m/1y/3y] </select> <select class="tf-select" id="view-select" onchange="setView(this.value)"> <option value="ret">Return</option> <option value="vol">Volatility</option> <option value="both" [selected if PREFERRED_VIEW=both]>Return + volatility</option> </select> </div> </div> </header> <div class="page"> <!-- AI DISCLAIMER — Do not remove, hide, or reduce below 11px / low contrast --> <div class="hero-callout callout-neutral"> <div class="callout-icon"><span class="icon fill" style="color:#fff;font-size:18px">info</span></div> <div> <div class="callout-title">This is not financial advice.</div> <div class="callout-sub">Figures shown are estimates based on your Truthifi-connected accounts and publicly available fund data. Expense ratios may change. Returns and volatility are historical and do not predict future performance. Review any changes with a qualified advisor before acting.</div> </div> </div> <!-- CARD 1: What changed in the last 30 days --> <div class="card"> <div class="card-head"> <div class="card-headline"> [CARD 1 HEADLINE — dynamic, e.g.: If CHANGES has items: "[N] thing(s) changed in the last 30 days — [summary]." If CHANGES empty: "Nothing changed in the last 30 days. Your fund cost picture is the same as last month."] </div> <button class="toggle-btn" id="toggle1" onclick="toggleCard('card1-detail','toggle1')">Show details</button> </div> <div class="chart-legend"> <div class="chart-legend-item"><div class="legend-line solid"></div>Your ER cost this year</div> <div class="chart-legend-item"><div class="legend-line dashed-green"></div>Cost with cheaper alternatives</div> </div> <!-- FIXED: canvas wrapped in overflow:hidden div to prevent bleed on narrow viewports --> <div style="width:100%;overflow:hidden;"> <canvas id="chart-fees" height="148" style="display:block;max-width:100%;margin-bottom:var(--s3);"></canvas> </div> <div class="acc-rows" id="card1-detail"> [IF CHANGES non-empty: inject .fund-row-list here via JS buildChangeRows()] </div> </div> <!-- CARD 2: How am I doing --> <div class="card"> <div class="card-head"> <div class="card-headline"> [CARD 2 HEADLINE — dynamic, e.g.: If OPP_COUNT > 0: "You could save <strong>$[TOTAL_SAVING]/yr</strong> by switching [OPP_COUNT] fund(s) to cheaper alternatives with the same or better performance." If OPP_COUNT = 0: "All your funds are already at or near the lowest available cost."] </div> <button class="toggle-btn" id="toggle2" onclick="toggleCard('card2-detail','toggle2')">Show details</button> </div> <div id="savings-chart-wrap" style="margin-bottom:var(--s3);"></div> <div class="acc-rows" id="card2-detail"> <div class="table-legend"> <div class="table-legend-item"><div class="legend-swatch" style="background:var(--color-01);"></div>Your current fund</div> <div class="table-legend-item"><div class="legend-swatch" style="background:var(--color-05);"></div>Best lower-cost alternative</div> </div> <div style="overflow-x:auto;"><div style="border:1px solid var(--border-light);border-radius:var(--r-md);overflow:hidden;margin-top:var(--s2);"> <table class="compare-table"> <thead><tr id="thead-row"> <th class="col-fund">Fund</th> <th class="col-bar right">Return (1yr)</th> <th class="col-bar right">Volatility <span style="font-weight:400;text-transform:none;letter-spacing:0;color:var(--muted);font-size:10px;">(lower = less risk)</span></th> <th class="col-save right">If you switched</th> </tr></thead> <tbody id="compare-tbody"></tbody> </table> </div></div> <div style="font-family:var(--ff-body);font-size:11px;color:var(--muted);margin-top:var(--s3);line-height:1.7;padding-top:var(--s2);border-top:1px solid var(--border-light);"> Returns and volatility are estimates. Expense ratios from fund prospectuses. Volatility = annualized standard deviation. Not financial advice. Past performance does not guarantee future results. </div> </div> </div> <!-- EXPLORE MORE CARD --> <div class="explore-card"> <div class="explore-headline">Across all your funds, you're paying an estimated <strong>$[TOTAL_ER_NOW]/yr</strong> in expense ratios.</div> <hr class="explore-divider"> <div class="explore-tile"> <div class="explore-tile-icon" style="background:var(--surface-09);"><span class="icon fill" style="color:var(--color-09);font-size:20px">receipt_long</span></div> <div class="explore-tile-body"><div class="explore-tile-name">Fees</div><div class="explore-tile-meta">See what you're paying across all accounts — advisory, fund, and platform fees</div></div> <a class="explore-tile-btn" href="https://truthifi.com/prompt-gallery" target="_blank">Explore prompts</a> </div> <div class="explore-tile"> <div class="explore-tile-icon" style="background:var(--surface-01);"><span class="icon fill" style="color:var(--color-01);font-size:20px">bar_chart</span></div> <div class="explore-tile-body"><div class="explore-tile-name">Investing</div><div class="explore-tile-meta">Analyze your holdings, allocation, and portfolio construction</div></div> <a class="explore-tile-btn" href="https://truthifi.com/prompt-gallery" target="_blank">Explore prompts</a> </div> <div class="explore-tile"> <div class="explore-tile-icon" style="background:var(--surface-01);"><span class="icon fill" style="color:var(--color-01);font-size:20px">trending_up</span></div> <div class="explore-tile-body"><div class="explore-tile-name">Performance</div><div class="explore-tile-meta">Benchmark your returns against indices and similar investors</div></div> <a class="explore-tile-btn" href="https://truthifi.com/prompt-gallery" target="_blank">Explore prompts</a> </div> <div class="explore-tile"> <div class="explore-tile-icon" style="background:var(--surface-05);"><span class="icon fill" style="color:var(--color-05);font-size:20px">account_balance</span></div> <div class="explore-tile-body"><div class="explore-tile-name">Asset allocation</div><div class="explore-tile-meta">Check how your portfolio is distributed across asset classes</div></div> <a class="explore-tile-btn" href="https://truthifi.com/prompt-gallery" target="_blank">Explore prompts</a> </div> </div> <div style="display:flex;justify-content:center;padding:var(--s1) 0;"> <a class="cta-btn-inline" href="https://app.truthifi.com" target="_blank"> <span class="icon sm">open_in_new</span>Open Truthifi </a> </div> <!-- AI DISCLAIMER — Do not remove, hide, or reduce below 11px / low contrast --> <div class="footer"> <p>AI-generated from your Truthifi-connected accounts. Not financial advice. Data shown is for informational purposes only. Full terms: <a href="https://truthifi-connect.ai/prompt-gallery-terms" style="color:var(--muted);">truthifi-connect.ai/prompt-gallery-terms</a></p> </div> </div> <script> // PALETTE CONSTANTS — match CSS :root above; switch for dark mode var C01 = '#055E9E'; var C05 = '#347330'; var BL = '#EEF1F4'; if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { C01 = '#00A9FE'; C05 = '#66B463'; BL = '#152638'; } // XSS helper — use for any dynamic string inserted via innerHTML function esc(s) { return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/'/g,'&#39;'); } // SCALE CONSTANTS var SCALE_3M = 0.27; var SCALE_6M = 0.52; var SCALE_3Y = 2.8; var BAR_MIN_PCT = 2; var BAR_MAX_MULT = 1.2; // ── POPULATE THESE FROM REAL MCP DATA (Steps 4-6) ── // Replace every value below with data from your Steps above. // Do not leave any placeholder values in the final output. var TOTAL_ER_NOW = [EXACT VALUE FROM STEP 6]; var TOTAL_ER_ALT = [EXACT VALUE FROM STEP 6]; var TOTAL_SAVING = [EXACT VALUE FROM STEP 6]; var OPP_COUNT = [EXACT VALUE FROM STEP 6]; // HOLDINGS: array built from Step 2 + Step 4 data // Each entry: { symbol, fullName, er, ret, vol, rivals: [{ticker, fullName, er, ret, vol, saving, isFree}] } var HOLDINGS = [POPULATE FROM STEPS 2 AND 4]; // CHANGES: array built from Step 5 data // Each entry: { ticker, rowClass, badge, badgeClass, name, meta, amount, sublabel, amtClass } // Empty array if nothing changed in 30 days. var CHANGES = [POPULATE FROM STEP 5]; var currentView = '[PREFERRED_VIEW from Step 0b]'; // ACCORDION TOGGLE function toggleCard(detailId, btnId) { var el = document.getElementById(detailId); var btn = document.getElementById(btnId); if (!el || !btn) return; var open = el.style.maxHeight && el.style.maxHeight !== '0px'; el.style.maxHeight = open ? '0px' : '900px'; btn.textContent = open ? 'Show details' : 'Hide details'; } // EXPAND ALT ROWS function toggleAlt(id) { var el = document.getElementById('alt-' + id); var btn = document.getElementById('btn-' + id); if (!el || !btn) return; var open = el.style.maxHeight && el.style.maxHeight !== '0px'; el.style.maxHeight = open ? '0px' : '300px'; var ic = btn.querySelector('.icon'); if (ic) ic.textContent = open ? 'expand_more' : 'expand_less'; var nodes = btn.childNodes; for (var i = 0; i < nodes.length; i++) { if (nodes[i].nodeType === 3) { nodes[i].textContent = open ? '2 more' : 'less'; break; } } } // BUILD CHANGE ROWS (card 1 accordion) — createElement pattern, no innerHTML for MCP data function buildChangeRows() { var list = document.getElementById('change-list'); if (!list || !CHANGES || CHANGES.length === 0) return; CHANGES.forEach(function(d) { var row = document.createElement('div'); row.className = 'fund-row-item ' + d.rowClass; var main = document.createElement('div'); main.className = 'fund-row-main'; var nameRow = document.createElement('div'); nameRow.className = 'fund-row-name'; var ticker = document.createElement('span'); ticker.className = 'fund-row-ticker'; ticker.textContent = d.ticker; var sep = document.createElement('span'); sep.style.cssText = 'color:var(--muted);font-size:13px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0;'; sep.textContent = ' \u00b7 ' + d.name; var badge = document.createElement('span'); badge.className = 'tf-badge ' + d.badgeClass; badge.textContent = d.badge; nameRow.appendChild(ticker); nameRow.appendChild(sep); nameRow.appendChild(badge); var meta = document.createElement('div'); meta.className = 'fund-row-meta'; meta.textContent = d.meta; main.appendChild(nameRow); main.appendChild(meta); var right = document.createElement('div'); right.className = 'fund-row-right'; var amt = document.createElement('span'); amt.className = 'fund-row-amount' + (d.amtClass ? ' ' + d.amtClass : ''); amt.textContent = d.amount; right.appendChild(amt); if (d.sublabel) { var sub = document.createElement('span'); sub.className = 'fund-row-sublabel'; sub.textContent = d.sublabel; right.appendChild(sub); } row.appendChild(main); row.appendChild(right); list.appendChild(row); }); } // BAR WIDTH — clamped, guarded against NaN/zero function barPct(value, maxVal) { if (!maxVal || maxVal === 0 || isNaN(maxVal) || isNaN(value)) return 0; return Math.max((value / maxVal) * 100, BAR_MIN_PCT); } // BUILD COMPARE TABLE (card 2 accordion) function buildCompareTable() { var tbody = document.getElementById('compare-tbody'); if (!tbody || !HOLDINGS || HOLDINGS.length === 0) return; while (tbody.firstChild) tbody.removeChild(tbody.firstChild); var allRet = [], allVol = []; HOLDINGS.forEach(function(h) { allRet.push(h.ret); allVol.push(h.vol); h.rivals.forEach(function(r) { allRet.push(r.ret); allVol.push(r.vol); }); }); var maxRet = Math.max.apply(null, allRet) * BAR_MAX_MULT; var maxVol = Math.max.apply(null, allVol) * BAR_MAX_MULT; var showRet = (currentView === 'ret' || currentView === 'both'); var showVol = (currentView === 'vol' || currentView === 'both'); // FIXED: derive colCount from actual visible columns so colspan is always correct // fund col (1) + ret col if shown + vol col if shown + save col (1) var colCount = 1 + (showRet ? 1 : 0) + (showVol ? 1 : 0) + 1; HOLDINGS.forEach(function(h) { var best = h.rivals[0]; var trY = document.createElement('tr'); trY.className = 'row-your'; var tdF = document.createElement('td'); var nm = document.createElement('div'); nm.className = 'fund-name'; nm.textContent = h.symbol; var er = document.createElement('div'); er.className = 'fund-er'; er.textContent = h.fullName + ' \u00b7 ' + (h.er * 100).toFixed(3).replace(/\.?0+$/, '') + '%/yr'; tdF.appendChild(nm); tdF.appendChild(er); trY.appendChild(tdF); if (showRet) { var tdR = document.createElement('td'); tdR.innerHTML = '<div class="bar-track"><div class="bar-fill bar-your" style="width:' + barPct(h.ret, maxRet).toFixed(1) + '%;"></div></div><div class="bar-num">+' + h.ret.toFixed(1) + '%</div>'; trY.appendChild(tdR); } if (showVol) { var tdV = document.createElement('td'); tdV.innerHTML = '<div class="bar-track"><div class="bar-fill bar-your" style="width:' + barPct(h.vol, maxVol).toFixed(1) + '%;"></div></div><div class="bar-num">' + h.vol.toFixed(1) + '%</div>'; trY.appendChild(tdV); } var tdS = document.createElement('td'); tdS.className = 'save-cell'; tdS.setAttribute('rowspan', '2'); if (best.saving > 0) { var sa = document.createElement('div'); sa.className = 'save-amount'; sa.textContent = '$' + best.saving + '/yr'; var sl = document.createElement('div'); sl.className = 'save-label'; sl.textContent = 'saved in fees'; tdS.appendChild(sa); tdS.appendChild(sl); } else { var sn = document.createElement('div'); sn.className = 'save-none'; sn.textContent = 'Already near-optimal'; tdS.appendChild(sn); } var eb = document.createElement('button'); eb.className = 'expand-btn'; eb.id = 'btn-' + h.symbol; (function(sym) { eb.onclick = function() { toggleAlt(sym); }; })(h.symbol); var ei = document.createElement('span'); ei.className = 'icon sm'; ei.textContent = 'expand_more'; eb.appendChild(ei); eb.appendChild(document.createTextNode('2 more')); tdS.appendChild(eb); trY.appendChild(tdS); tbody.appendChild(trY); var trR = document.createElement('tr'); trR.className = 'row-rival'; var tdRF = document.createElement('td'); var rn = document.createElement('div'); rn.className = 'fund-name rival'; rn.appendChild(document.createTextNode(best.ticker + ' \u00b7 ')); var ef = document.createElement('span'); ef.className = 'er-free'; ef.textContent = best.isFree ? 'FREE' : (best.er * 100).toFixed(3).replace(/\.?0+$/, '') + '%'; rn.appendChild(ef); var rf = document.createElement('div'); rf.className = 'fund-er'; rf.textContent = best.fullName; tdRF.appendChild(rn); tdRF.appendChild(rf); trR.appendChild(tdRF); if (showRet) { var rdR = document.createElement('td'); var rdelta = best.ret - h.ret; rdR.innerHTML = '<div class="bar-track"><div class="bar-fill bar-rival-fill" style="width:' + barPct(best.ret, maxRet).toFixed(1) + '%;"></div></div><div class="bar-num">+' + best.ret.toFixed(1) + '%' + (rdelta > 0 ? ' <span class="delta-chip">+' + esc(rdelta.toFixed(1)) + '%</span>' : '') + '</div>'; trR.appendChild(rdR); } if (showVol) { var rvV = document.createElement('td'); var vdelta = h.vol - best.vol; rvV.innerHTML = '<div class="bar-track"><div class="bar-fill bar-rival-fill" style="width:' + barPct(best.vol, maxVol).toFixed(1) + '%;"></div></div><div class="bar-num">' + best.vol.toFixed(1) + '%' + (vdelta > 0 ? ' <span class="delta-chip">\u2212' + esc(vdelta.toFixed(1)) + '%</span>' : '') + '</div>'; trR.appendChild(rvV); } tbody.appendChild(trR); var trA = document.createElement('tr'); // FIXED: use colCount (derived above) instead of hardcoded ternary — stays correct in all view modes var tdA = document.createElement('td'); tdA.setAttribute('colspan', String(colCount)); tdA.style.cssText = 'padding:0;border:none;'; var altDiv = document.createElement('div'); altDiv.className = 'acc-rows-alt'; altDiv.id = 'alt-' + h.symbol; if (h.rivals.length > 1) { var at = document.createElement('table'); at.style.cssText = 'width:100%;border-collapse:collapse;'; h.rivals.slice(1).forEach(function(r) { var ar = document.createElement('tr'); ar.className = 'row-alt'; var af = document.createElement('td'); af.className = 'col-fund'; var an = document.createElement('div'); an.className = 'fund-name alt'; an.textContent = r.ticker; var ae = document.createElement('div'); ae.className = 'fund-er'; ae.textContent = r.fullName + ' \u00b7 ' + (r.er === 0 ? 'FREE' : (r.er * 100).toFixed(3).replace(/\.?0+$/, '') + '%'); af.appendChild(an); af.appendChild(ae); ar.appendChild(af); if (showRet) { var arR = document.createElement('td'); arR.className = 'col-bar'; arR.innerHTML = '<div class="bar-track"><div class="bar-fill bar-rival-fill" style="width:' + barPct(r.ret, maxRet).toFixed(1) + '%;"></div></div><div class="bar-num" style="color:var(--sub);">+' + r.ret.toFixed(1) + '%</div>'; ar.appendChild(arR); } if (showVol) { var arV = document.createElement('td'); arV.className = 'col-bar'; arV.innerHTML = '<div class="bar-track"><div class="bar-fill bar-rival-fill" style="width:' + barPct(r.vol, maxVol).toFixed(1) + '%;"></div></div><div class="bar-num" style="color:var(--sub);">' + r.vol.toFixed(1) + '%</div>'; ar.appendChild(arV); } // FIXED: save placeholder always present + left border so it aligns with parent save column in all view modes var ae2 = document.createElement('td'); ae2.className = 'col-save'; ae2.style.borderLeft = '1px solid var(--border-light)'; ar.appendChild(ae2); at.appendChild(ar); }); altDiv.appendChild(at); } tdA.appendChild(altDiv); trA.appendChild(tdA); tbody.appendChild(trA); }); } // SAVINGS SVG CHART — viewBox scales crisply at any DPR function drawSavingsChart() { var wrap = document.getElementById('savings-chart-wrap'); if (!wrap) return; var nowVal = TOTAL_ER_NOW; var altVal = TOTAL_ER_ALT; var maxVal = Math.ceil(nowVal * 1.2 / 100) * 100; if (maxVal < 100) maxVal = 100; var labelW = 136; var valW = 72; var barH = 28; var gap = 14; var vbW = 600; var trackW = vbW - labelW - valW; var nowPct = (nowVal / maxVal) * trackW; var altPct = (altVal / maxVal) * trackW; var H = barH * 2 + gap + 28; var y1 = 4; var y2 = y1 + barH + gap; var gridStep = maxVal <= 400 ? 100 : 200; var gridLines = ''; for (var g = 0; g <= maxVal; g += gridStep) { var gx = (labelW + (g / maxVal) * trackW).toFixed(1); gridLines += '<line x1="' + gx + '" y1="' + y1 + '" x2="' + gx + '" y2="' + (y2 + barH) + '" stroke="' + BL + '" stroke-width="1"/>'; gridLines += '<text x="' + gx + '" y="' + (y2 + barH + 14) + '" text-anchor="middle" font-family="Manrope,sans-serif" font-size="10" fill="#9CAAB4">$' + g + '</text>'; } wrap.innerHTML = '<svg viewBox="0 0 ' + vbW + ' ' + H + '" xmlns="http://www.w3.org/2000/svg" style="width:100%;display:block;" aria-label="Annual fee comparison">' + gridLines + '<rect x="' + labelW + '" y="' + y1 + '" width="' + nowPct.toFixed(1) + '" height="' + barH + '" rx="4" fill="' + C01 + '"/>' + '<rect x="' + labelW + '" y="' + y2 + '" width="' + altPct.toFixed(1) + '" height="' + barH + '" rx="4" fill="' + C05 + '"/>' + '<text x="' + (labelW - 10) + '" y="' + (y1 + barH / 2 + 4) + '" text-anchor="end" font-family="Manrope,sans-serif" font-size="12" font-weight="700" fill="#011827">What you pay now</text>' + '<text x="' + (labelW - 10) + '" y="' + (y2 + barH / 2 + 4) + '" text-anchor="end" font-family="Manrope,sans-serif" font-size="12" font-weight="700" fill="#011827">With alternatives</text>' + '<text x="' + (labelW + nowPct + 8).toFixed(1) + '" y="' + (y1 + barH / 2 + 4) + '" font-family="Manrope,sans-serif" font-size="13" font-weight="800" fill="' + C01 + '">$' + nowVal + '/yr</text>' + '<text x="' + (labelW + altPct + 8).toFixed(1) + '" y="' + (y2 + barH / 2 + 4) + '" font-family="Manrope,sans-serif" font-size="13" font-weight="800" fill="' + C05 + '">$' + altVal + '/yr</text>' + '</svg>'; } // FEE DRAG CANVAS CHART — DPR-aware // FIXED: use clientWidth (excludes padding) not getBoundingClientRect which can fire before layout // FIXED: initFeesChart uses requestAnimationFrame loop instead of ResizeObserver disconnect-on-first-entry // to guarantee a real layout width before drawing function drawFeesChart() { var canvas = document.getElementById('chart-fees'); if (!canvas) return; var ctx = canvas.getContext('2d'); var dpr = window.devicePixelRatio || 1; var wrapper = canvas.parentElement; var W = Math.floor(wrapper.clientWidth || wrapper.offsetWidth || 700); if (W <= 0) W = 700; var H = 148; canvas.width = W * dpr; canvas.height = H * dpr; canvas.style.width = W + 'px'; canvas.style.height = H + 'px'; ctx.scale(dpr, dpr); var current = [], cheaper = []; for (var m = 0; m < 12; m++) { current.push(Math.round(TOTAL_ER_NOW * (m / 11))); cheaper.push(Math.round(TOTAL_ER_ALT * (m / 11))); } var maxY = Math.ceil(TOTAL_ER_NOW * 1.15 / 100) * 100; var pl = 44; var pr = 16; var pt = 12; var pb = 32; var cw = W - pl - pr; var ch = H - pt - pb; function xP(i) { return pl + (i / 11) * cw; } function yP(v) { return pt + ch - (v / maxY) * ch; } ctx.font = '10px Manrope,sans-serif'; ctx.fillStyle = '#9CAAB4'; ctx.textAlign = 'right'; var yStep = maxY <= 400 ? 100 : 200; for (var yv = 0; yv <= maxY; yv += yStep) { var yPos = yP(yv); ctx.fillText('$' + yv, pl - 6, yPos + 3); ctx.beginPath(); ctx.strokeStyle = BL; ctx.lineWidth = 1; ctx.moveTo(pl, yPos); ctx.lineTo(pl + cw, yPos); ctx.stroke(); } ctx.beginPath(); ctx.moveTo(xP(0), yP(current[0])); for (var i = 1; i < 12; i++) ctx.lineTo(xP(i), yP(current[i])); ctx.lineTo(xP(11), yP(0)); ctx.lineTo(xP(0), yP(0)); ctx.closePath(); ctx.fillStyle = 'rgba(6,118,197,0.08)'; ctx.fill(); ctx.beginPath(); ctx.strokeStyle = C01; ctx.lineWidth = 2; ctx.lineJoin = 'round'; current.forEach(function(v, j) { j === 0 ? ctx.moveTo(xP(j), yP(v)) : ctx.lineTo(xP(j), yP(v)); }); ctx.stroke(); ctx.beginPath(); ctx.setLineDash([4, 4]); ctx.strokeStyle = C05; ctx.lineWidth = 1.5; cheaper.forEach(function(v, j) { j === 0 ? ctx.moveTo(xP(j), yP(v)) : ctx.lineTo(xP(j), yP(v)); }); ctx.stroke(); ctx.setLineDash([]); var months = ['Jan', 'Mar', 'May', 'Jul', 'Sep', 'Nov']; ctx.fillStyle = '#9CAAB4'; ctx.textAlign = 'center'; [0, 2, 4, 6, 8, 10].forEach(function(idx, j) { ctx.fillText(months[j], xP(idx), H - 6); }); } function initFeesChart() { var c = document.getElementById('chart-fees'); if (!c) return; function tryDraw() { var w = c.parentElement ? (c.parentElement.clientWidth || c.parentElement.offsetWidth) : 0; if (w > 0) { drawFeesChart(); } else { requestAnimationFrame(tryDraw); } } requestAnimationFrame(tryDraw); window.addEventListener('resize', function() { if (document.getElementById('chart-fees')) drawFeesChart(); }); } function setRange(v) { /* re-fetch and re-render in live output */ } function setView(v) { currentView = v; var t = document.getElementById('thead-row'); if (!t) return; if (v === 'both') { t.innerHTML = '<th class="col-fund">Fund</th><th class="col-bar right">Return (1yr)</th><th class="col-bar right">Volatility <span style="font-weight:400;text-transform:none;letter-spacing:0;color:var(--muted);font-size:10px;">(lower = less risk)</span></th><th class="col-save right">If you switched</th>'; } else if (v === 'ret') { t.innerHTML = '<th class="col-fund">Fund</th><th class="col-bar right" colspan="2">Return (1yr)</th><th class="col-save right">If you switched</th>'; } else { t.innerHTML = '<th class="col-fund">Fund</th><th class="col-bar right" colspan="2">Volatility <span style="font-weight:400;text-transform:none;letter-spacing:0;color:var(--muted);font-size:10px;">(lower = less risk)</span></th><th class="col-save right">If you switched</th>'; } buildCompareTable(); } // INIT if (CHANGES && CHANGES.length > 0) { var cl = document.getElementById('card1-detail'); if (cl) { var fl = document.createElement('div'); fl.className = 'fund-row-list'; fl.id = 'change-list'; cl.appendChild(fl); buildChangeRows(); } } buildCompareTable(); drawSavingsChart(); initFeesChart(); </script> </body> </html> ``` Replace every `[BRACKETED PLACEHOLDER]` above with the exact values from your data steps before outputting the final file. There must be no brackets remaining in the output.

How to use this

Connect your accounts through Truthifi, then run this prompt in any AI that supports your Truthifi MCP. The AI asks a few optional setup questions—default period, which metrics to show, and any funds to exclude—then fetches your current holdings and builds the comparison. What you get: an interactive page showing each of your ETFs and mutual funds paired against its closest lower-cost alternative. Return and volatility bars sit side by side so you can see whether the cheaper fund actually performs differently. A summary at the top shows how many funds have a cheaper option and projects your total annual fee savings based on your real balances. Money market and cash-equivalent funds are excluded automatically. If you hold funds you can't sell—employer plan restrictions, tax implications—you can exclude specific tickers before the comparison runs.

Why it matters

Expense ratios feel abstract until you do the math. A 0.03% fund costs $60/year on a $200,000 portfolio. A 0.00% alternative costs nothing. That gap compounds over decades. The harder question is: what counts as a good expense ratio, and is your specific fund beating its cheaper alternatives on return and volatility, or just costing more? Most tools answer this generically. This prompt answers it against your actual holdings—every fund you own, compared to its closest lower-cost equivalent, using your real balances to calculate the dollar difference. FZROX and FZILX have 0.00% expense ratios and track similar indexes to funds millions of investors already hold. SCHB charges 0.03% and closely mirrors VTI at the same cost. Whether any of these are right for your situation depends on your accounts, your tax picture, and your plan options—but this prompt surfaces the comparison so you can have that conversation with actual numbers in front of you.

Not advice

This prompt is a data display tool, not a recommendation engine. It shows that a lower-cost fund with similar characteristics exists. It doesn't tell you to buy it, sell what you have, or that switching is right for your situation. There are real reasons you might keep a higher-cost fund: it's in an employer plan with limited options, there are tax implications to selling, or it plays a specific role in your allocation that a cheaper alternative doesn't replicate exactly. Return and volatility figures for rival funds are AI estimates based on training data, not live prices. Always verify directly with the fund provider before making any decision. Outputs are AI-generated and for informational purposes only. They are not financial advice. Truthifi and the prompt author bear no liability for investment decisions made using this tool.