Skip to content

Instantly share code, notes, and snippets.

@say4n
Last active April 8, 2026 23:08
Show Gist options
  • Select an option

  • Save say4n/0f0775cfcb492e883cf7252d09dec6da to your computer and use it in GitHub Desktop.

Select an option

Save say4n/0f0775cfcb492e883cf7252d09dec6da to your computer and use it in GitHub Desktop.
Compute real return instead of IRR on Vanguard UK's investment dashboard.
// ==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