The money-weighted return calculation (MWRR) is a measure of the performance of an investment that accounts for the timing and size of cash flows into and out of the portfolio. Unlike the Time-Weighted Return (TWRR), which isolates the manager's performance from the investor's deposit and withdrawal decisions, the money-weighted return reflects the actual internal rate of return (IRR) experienced by the individual investor.
This metric is particularly useful for investors who actively contribute to or withdraw from their accounts. It answers the question: "What annual interest rate would I need on a bank account to match the final dollar value I have today, given all my deposits and withdrawals?"
Money-Weighted Return Formula and Explanation
Mathematically, the money-weighted return is the discount rate ($r$) that sets the net present value (NPV) of all cash flows equal to zero. This is equivalent to the Internal Rate of Return (IRR) formula extended for irregular time intervals (often called XIRR).
Practical Examples of Money-Weighted Return Calculation
Example 1: The Lucky Timer
Investor A starts with $10,000 on January 1st. The market drops 10%, leaving them with $9,000. On June 1st, they deposit another $100,000. The market then rallies 20%. Because the majority of their money ($100,000) participated in the rally, their money-weighted return calculation will be significantly higher than the time-weighted return, reflecting that their timing added value.
Example 2: The Unlucky Withdrawal
Investor B starts with $50,000. The portfolio grows by 10% to $55,000. They withdraw $30,000 to buy a car. The remaining $25,000 subsequently drops by 50%. Even though the fund manager might report a modest loss for the year, Investor B's personal money-weighted return will be terrible because they withdrew capital before the drop, but the remaining balance took a massive hit relative to its size.
How to Use This Calculator
Enter Initial Investment: Input the date you opened the account and the starting amount.
Add Cash Flows: Click "Add Cash Flow" for every deposit or withdrawal.
Select "Deposit" if you added money.
Select "Withdrawal" if you took money out.
Enter Ending Value: Input the current date (or end date of the period) and the final portfolio value.
Calculate: Press the button to see your annualized return.
Key Factors That Affect Results
Timing of Flows: Deposits made just before a market rally increase MWRR. Deposits made before a crash decrease it.
Size of Flows: Large cash flows have a heavier "weight" in the calculation. A 50% gain on $100 is less impactful than a 10% gain on $1,000,000.
Market Volatility: High volatility combined with frequent cash flows creates the largest divergence between MWRR and TWRR.
Investment Duration: Short-term calculations can be extremely volatile and annualized numbers may look unrealistically high or low.
Fees and Taxes: If cash flows are net of fees, the return will be lower. Ensure you consistently use either gross or net figures.
Inflation: This calculator provides the nominal return. To find the real return, you must subtract the inflation rate.
Frequently Asked Questions (FAQ)
What is the difference between MWRR and TWRR?
TWRR (Time-Weighted Return) eliminates the effect of cash flows to measure the manager's skill. MWRR (Money-Weighted Return) includes the effect of cash flows to measure the investor's actual experience.
Can MWRR be negative while the market is up?
Yes. If you deposit a large sum of money right before a market dip, your personal return could be negative even if the market was up for the year prior to your deposit.
Why does the calculator show an error?
The calculation requires an iterative solution. If cash flows are erratic or dates are invalid, the mathematical solver may not converge. Ensure dates are chronological and amounts are valid.
Is this the same as XIRR in Excel?
Yes, this calculator uses the same mathematical logic as the XIRR function in Excel or Google Sheets.
How do I handle dividends?
If dividends are reinvested, ignore them (they are part of the ending balance). If they are withdrawn as cash, treat them as a "Withdrawal" cash flow.
What is a good money-weighted return?
A "good" return depends on your risk profile and benchmark. Generally, outpacing inflation (3-4%) plus a risk premium (4-6%) is considered a solid long-term target for equities.
Does this calculate CAGR?
If there are no intermediate cash flows, MWRR equals CAGR (Compound Annual Growth Rate). With cash flows, MWRR is the more accurate metric.
Why is the result annualized?
Standard financial reporting annualizes returns to make them comparable across different time periods. A 10% return over 6 months is roughly 21% annualized.
// Initialize default dates
window.onload = function() {
var today = new Date();
var lastYear = new Date();
lastYear.setFullYear(today.getFullYear() – 1);
document.getElementById('endDate').valueAsDate = today;
document.getElementById('startDate').valueAsDate = lastYear;
// Add one empty cash flow row by default
addCashFlowRow();
};
function addCashFlowRow() {
var container = document.getElementById('cashFlowContainer');
var div = document.createElement('div');
div.className = 'cash-flow-row';
var html = ";
html += '
';
html += '
Deposit (In)Withdrawal (Out)
';
html += '
';
html += '';
div.innerHTML = html;
container.appendChild(div);
}
function removeRow(btn) {
var row = btn.parentNode;
row.parentNode.removeChild(row);
}
function resetCalculator() {
document.getElementById('startAmount').value = ";
document.getElementById('endAmount').value = ";
document.getElementById('cashFlowContainer').innerHTML = ";
addCashFlowRow();
document.getElementById('resultsSection').style.display = 'none';
document.getElementById('globalError').style.display = 'none';
var today = new Date();
var lastYear = new Date();
lastYear.setFullYear(today.getFullYear() – 1);
document.getElementById('endDate').valueAsDate = today;
document.getElementById('startDate').valueAsDate = lastYear;
}
function getDaysDiff(d1, d2) {
var t2 = d2.getTime();
var t1 = d1.getTime();
return (t2 – t1) / (24 * 3600 * 1000);
}
// XIRR Implementation using Newton-Raphson
function calculateXIRR(transactions) {
var guess = 0.1; // 10% initial guess
var x0 = guess;
var x1 = 0.0;
var err = 1e-5;
var maxIter = 100;
var i = 0;
// Sort transactions by date
transactions.sort(function(a, b) {
return a.date – b.date;
});
var t0 = transactions[0].date;
for (i = 0; i < maxIter; i++) {
var fValue = 0.0;
var fDerivative = 0.0;
for (var j = 0; j < transactions.length; j++) {
var p = transactions[j].amount;
var d = getDaysDiff(t0, transactions[j].date) / 365.0;
// Avoid division by zero or negative base issues
var base = 1.0 + x0;
if (base <= 0) base = 0.000001;
fValue += p / Math.pow(base, d);
fDerivative += -d * p / Math.pow(base, d + 1);
}
if (Math.abs(fDerivative) < 1e-10) {
break;
}
x1 = x0 – fValue / fDerivative;
if (Math.abs(x1 – x0) = endDate) {
errorDiv.innerText = "Start date must be before end date.";
errorDiv.style.display = 'block';
return;
}
// Build Transaction Array
// Convention: Outflows (Investments) are negative, Inflows (Withdrawals/Final Value) are positive
var transactions = [];
// Initial Investment (Outflow)
transactions.push({
date: startDate,
amount: -1 * Math.abs(startAmt),
type: 'Initial'
});
// Intermediate Flows
var cfRows = document.getElementsByClassName('cash-flow-row');
// Note: The first and last rows in DOM are inputs, intermediate are in container
// We specifically target the dynamic container rows
var container = document.getElementById('cashFlowContainer');
var dynamicRows = container.getElementsByClassName('cash-flow-row');
var totalDeposits = startAmt;
var totalWithdrawals = 0;
for (var i = 0; i 0) {
var dDate = new Date(dStr);
if (dDate endDate) {
errorDiv.innerText = "Cash flow dates must be between start and end dates.";
errorDiv.style.display = 'block';
return;
}
if (type === 'deposit') {
transactions.push({ date: dDate, amount: -1 * amt, type: 'Deposit' });
totalDeposits += amt;
} else {
transactions.push({ date: dDate, amount: amt, type: 'Withdrawal' });
totalWithdrawals += amt;
}
}
}
// Ending Value (Treated as Inflow)
transactions.push({
date: endDate,
amount: Math.abs(endAmt),
type: 'Ending Value'
});
// 2. Calculate XIRR
var mwrr = 0;
try {
mwrr = calculateXIRR(transactions);
} catch (e) {
errorDiv.innerText = "Calculation failed. Please check your inputs.";
errorDiv.style.display = 'block';
return;
}
if (isNaN(mwrr) || !isFinite(mwrr)) {
errorDiv.innerText = "Could not converge to a result. Try adjusting dates or amounts.";
errorDiv.style.display = 'block';
return;
}
// 3. Display Results
document.getElementById('resultsSection').style.display = 'block';
// Format Percentage
var percentage = (mwrr * 100).toFixed(2) + "%";
document.getElementById('mwrrResult').innerText = percentage;
// Net Profit
// Profit = (Ending Value + Withdrawals) – (Initial + Deposits)
var netProfit = (endAmt + totalWithdrawals) – totalDeposits;
var profitClass = netProfit >= 0 ? '#28a745' : '#dc3545';
var profitSign = netProfit >= 0 ? '+' : '-';
document.getElementById('netProfit').innerText = profitSign + "$" + Math.abs(netProfit).toFixed(2);
document.getElementById('netProfit').style.color = profitClass;
document.getElementById('totalInvested').innerText = "$" + totalDeposits.toFixed(2);
var years = getDaysDiff(startDate, endDate) / 365.0;
document.getElementById('periodYears').innerText = years.toFixed(1) + " Years";
// 4. Update Table
updateTable(transactions);
// 5. Update Chart
drawChart(transactions, startDate, endDate);
}
function updateTable(transactions) {
var tbody = document.querySelector('#scheduleTable tbody');
tbody.innerHTML = ";
// Sort for display
var sorted = transactions.slice().sort(function(a,b){ return a.date – b.date; });
for (var i = 0; i < sorted.length; i++) {
var t = sorted[i];
var tr = document.createElement('tr');
var dateStr = t.date.toLocaleDateString();
var amountStr = "$" + Math.abs(t.amount).toFixed(2);
var typeStr = t.type;
// Color coding
var color = t.amount < 0 ? '#004a99' : '#28a745'; // Blue for out, Green for in
tr.innerHTML = '
' + dateStr + '
' + typeStr + '
' + (t.amount < 0 ? '-' : '+') + amountStr + '
';
tbody.appendChild(tr);
}
}
function drawChart(transactions, startDate, endDate) {
var container = document.getElementById('chartContainer');
container.innerHTML = "; // Clear
// Setup SVG
var width = container.clientWidth;
var height = container.clientHeight;
var padding = 40;
var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("width", "100%");
svg.setAttribute("height", "100%");
svg.setAttribute("viewBox", "0 0 " + width + " " + height);
// Determine scales
var totalDays = getDaysDiff(startDate, endDate);
if (totalDays === 0) totalDays = 1;
var maxAmt = 0;
for(var i=0; i maxAmt) maxAmt = Math.abs(transactions[i].amount);
}
// Draw Axis Lines
var lineX = document.createElementNS("http://www.w3.org/2000/svg", "line");
lineX.setAttribute("x1", padding);
lineX.setAttribute("y1", height – padding);
lineX.setAttribute("x2", width – padding);
lineX.setAttribute("y2", height – padding);
lineX.setAttribute("stroke", "#ccc");
svg.appendChild(lineX);
// Draw Bars
var sorted = transactions.slice().sort(function(a,b){ return a.date – b.date; });
for(var i=0; i<sorted.length; i++) {
var t = sorted[i];
var daysFromStart = getDaysDiff(startDate, t.date);
var xPos = padding + (daysFromStart / totalDays) * (width – 2 * padding);
var barHeight = (Math.abs(t.amount) / maxAmt) * (height – 2 * padding);
var yPos = (height – padding) – barHeight;
var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
rect.setAttribute("x", xPos – 5); // Center bar
rect.setAttribute("y", yPos);
rect.setAttribute("width", 10);
rect.setAttribute("height", barHeight);
// Color: Negative (Investment) = Blue, Positive (Return) = Green
rect.setAttribute("fill", t.amount < 0 ? "#004a99" : "#28a745");
// Tooltip title
var title = document.createElementNS("http://www.w3.org/2000/svg", "title");
title.textContent = t.type + ": $" + Math.abs(t.amount).toFixed(2) + " on " + t.date.toLocaleDateString();
rect.appendChild(title);
svg.appendChild(rect);
}
// Add Start/End Date Labels
var textStart = document.createElementNS("http://www.w3.org/2000/svg", "text");
textStart.setAttribute("x", padding);
textStart.setAttribute("y", height – 10);
textStart.setAttribute("font-size", "12");
textStart.setAttribute("fill", "#666");
textStart.textContent = startDate.toLocaleDateString();
svg.appendChild(textStart);
var textEnd = document.createElementNS("http://www.w3.org/2000/svg", "text");
textEnd.setAttribute("x", width – padding);
textEnd.setAttribute("y", height – 10);
textEnd.setAttribute("font-size", "12");
textEnd.setAttribute("fill", "#666");
textEnd.setAttribute("text-anchor", "end");
textEnd.textContent = endDate.toLocaleDateString();
svg.appendChild(textEnd);
container.appendChild(svg);
}
function copyResults() {
var mwrr = document.getElementById('mwrrResult').innerText;
var profit = document.getElementById('netProfit').innerText;
var text = "Money-Weighted Return Calculation Results:\n";
text += "Annualized Return: " + mwrr + "\n";
text += "Net Profit: " + profit + "\n";
text += "Generated by Financial Calculator.";
var tempInput = document.createElement("textarea");
tempInput.value = text;
document.body.appendChild(tempInput);
tempInput.select();
document.execCommand("copy");
document.body.removeChild(tempInput);
alert("Results copied to clipboard!");
}