Last active
April 8, 2026 23:08
-
-
Save say4n/0f0775cfcb492e883cf7252d09dec6da to your computer and use it in GitHub Desktop.
Compute real return instead of IRR on Vanguard UK's investment dashboard.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name Vanguard UK Absolute Return Calculator | |
| // @namespace Violentmonkey Scripts | |
| // @match https://secure.vanguardinvestor.co.uk/* | |
| // @grant none | |
| // @version 1 | |
| // @author say4n | |
| // @description Native UI version: Blends seamlessly with Vanguard's aesthetic. | |
| // ==/UserScript== | |
| (function() { | |
| 'use strict'; | |
| const debug = (msg, obj) => console.log(`[VanguardCalc] ${msg}`, obj || | |
| ''); | |
| function parseCurrency(text) { | |
| if (!text) return 0; | |
| const cleaned = text.replace(/[£,\s]/g, '') | |
| .replace(/[–—]/g, '-'); | |
| return parseFloat(cleaned) || 0; | |
| } | |
| function isPerformancePage() { | |
| return window.location.href.toLowerCase() | |
| .includes('/investments/personals'); | |
| } | |
| function calculateAbsoluteReturn() { | |
| if (!isPerformancePage()) return; | |
| const figures = Array.from(document.querySelectorAll( | |
| '.stat-row .figure span')); | |
| if (figures.length < 3) return; | |
| const netContributionText = figures[1]?.innerText; | |
| const gainText = figures[2]?.innerText; | |
| if (!netContributionText || netContributionText === '£0.00') { | |
| debug('Values not yet loaded or zero.'); | |
| return; | |
| } | |
| const netContribution = parseCurrency(netContributionText); | |
| const gain = parseCurrency(gainText); | |
| if (netContribution === 0) return; | |
| const absoluteReturn = (gain / netContribution) * 100; | |
| debug(`Calculated: ${absoluteReturn}%`); | |
| displayResult(absoluteReturn, gain, netContribution); | |
| } | |
| function displayResult(percentage, gain, cost) { | |
| const target = document.querySelector('.stat-return'); | |
| if (!target) { | |
| debug('Target .stat-return not found'); | |
| return; | |
| } | |
| let existing = document.getElementById('vm-absolute-return-box'); | |
| if (existing) existing.remove(); | |
| const resultDiv = document.createElement('div'); | |
| resultDiv.id = 'vm-absolute-return-box'; | |
| // Re-use Vanguard's native stat class for structural harmony | |
| resultDiv.className = 'stat-return'; | |
| // Add a tasteful top margin to separate it from the stat above | |
| resultDiv.style.marginTop = '24px'; | |
| const isPositive = gain >= 0; | |
| const colorClass = isPositive ? 'text-positive' : 'text-negative'; | |
| const fallbackColor = !isPositive ? 'color: #d12100;' : | |
| ''; // Fallback red just in case .text-negative isn't defined | |
| // Formatting currency properly (e.g. £5,044.94) | |
| const formatCurrency = (num) => num.toLocaleString('en-GB', { | |
| minimumFractionDigits: 2, | |
| maximumFractionDigits: 2 | |
| }); | |
| // Mimic Vanguard's exact HTML structure: .label then .figure | |
| resultDiv.innerHTML = ` | |
| <div class="label">Simple absolute return</div> | |
| <div class="figure ${colorClass}" style="${fallbackColor}"> | |
| ${percentage.toFixed(2)}% | |
| </div> | |
| <div class="label" style="margin-top: 8px; font-size: 12px; line-height: 1.4;"> | |
| Profit: £${formatCurrency(gain)} <br> | |
| Cost: £${formatCurrency(cost)} | |
| </div> | |
| `; | |
| target.parentElement.appendChild(resultDiv); | |
| debug('Injected Native UI'); | |
| } | |
| let timeout; | |
| const observer = new MutationObserver(() => { | |
| clearTimeout(timeout); | |
| timeout = setTimeout(() => { | |
| if (isPerformancePage() && !document | |
| .getElementById('vm-absolute-return-box')) { | |
| calculateAbsoluteReturn(); | |
| } | |
| }, 500); | |
| }); | |
| observer.observe(document.body, { | |
| childList: true, | |
| subtree: true | |
| }); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment