import { useState, useEffect } from 'react'; // Province tax rates - simplified for demonstration purposes const PROVINCIAL_TAX_RATES = { "Ontario": { brackets: [0, 49231, 98463, 150000, 220000], rates: [0.0505, 0.0915, 0.1116, 0.1216, 0.1316], surtax: { threshold: 4991, rate: 0.2 } }, "British Columbia": { brackets: [0, 45654, 91310, 104835, 127299, 172602, 240716], rates: [0.0506, 0.077, 0.105, 0.1229, 0.147, 0.168, 0.205] }, "Alberta": { brackets: [0, 142292, 170349, 227668, 341502], rates: [0.10, 0.12, 0.13, 0.14, 0.15] }, }; // Federal tax brackets and rates const FEDERAL_TAX_RATES = { brackets: [0, 53359, 106717, 165430, 235675], rates: [0.15, 0.205, 0.26, 0.29, 0.33] }; // Constants for government benefits const OAS_BASE_ANNUAL = 8496; // Base annual OAS payment at age 65 const OAS_CLAWBACK_THRESHOLD = 86912; // 2023 threshold for OAS clawback const OAS_CLAWBACK_RATE = 0.15; // 15% clawback rate const OAS_DEFERRAL_INCREASE = 0.0073; // 0.73% increase per month of deferral (8.76% per year) const OAS_MAX_DEFERRAL_AGE = 70; // Maximum age to defer OAS const CPP_BASE_ANNUAL = 15000; // Base annual CPP payment at age 65 const CPP_EARLY_REDUCTION = 0.0060; // 0.6% reduction per month if taken early const CPP_DEFERRAL_INCREASE = 0.0070; // 0.7% increase per month of deferral const CPP_MIN_AGE = 60; // Earliest age to take CPP const CPP_STD_AGE = 65; // Standard retirement age const CPP_MAX_AGE = 70; // Maximum age to defer CPP const RRIF_MIN_WITHDRAWAL = { // Simplified RRIF minimum withdrawal rates "71": 0.0528, "72": 0.0540, "73": 0.0553, "74": 0.0567, "75": 0.0582, "80": 0.0654, "85": 0.0771, "90": 0.0949, "95": 0.1200 }; export default function RetirementCalculator() { // Client Information const [clientName, setClientName] = useState(''); const [province, setProvince] = useState('Ontario'); const [currentAge, setCurrentAge] = useState(55); const [retirementAge, setRetirementAge] = useState(65); const [lifeExpectancy, setLifeExpectancy] = useState(95); // Investment Assets const [tfsa, setTfsa] = useState(100000); const [rrsp, setRrsp] = useState(500000); const [nonReg, setNonReg] = useState(200000); const [corp, setCorp] = useState(0); // Financial Assumptions const [returnRate, setReturnRate] = useState(5); const [inflationRate, setInflationRate] = useState(2); // Retirement Phases const [phases, setPhases] = useState([ { id: 1, name: "Active Retirement", targetIncome: 80000, startAge: 65, endAge: 75 }, { id: 2, name: "Moderate Retirement", targetIncome: 70000, startAge: 76, endAge: 85 }, { id: 3, name: "Later Retirement", targetIncome: 60000, startAge: 86, endAge: 95 } ]); // CPP & OAS const [cppBaseAmount, setCppBaseAmount] = useState(15000); const [oasOptimization, setOasOptimization] = useState(true); const [cppOptimization, setCppOptimization] = useState(true); const [manualCppStartAge, setManualCppStartAge] = useState(65); const [manualOasStartAge, setManualOasStartAge] = useState(65); const [optimizedCppStartAge, setOptimizedCppStartAge] = useState(65); const [optimizedOasStartAge, setOptimizedOasStartAge] = useState(65); // Results const [withdrawalPlan, setWithdrawalPlan] = useState([]); const [valueCreated, setValueCreated] = useState(0); const [cppOasOptimizationValue, setCppOasOptimizationValue] = useState(0); const [showResults, setShowResults] = useState(false); // Update a retirement phase const updatePhase = (id, field, value) => { const updatedPhases = phases.map(phase => phase.id === id ? { ...phase, [field]: parseInt(value) || 0 } : phase ); setPhases(updatedPhases); }; // Calculate CPP amount based on start age const calculateCppAmount = (startAge) => { if (startAge === CPP_STD_AGE) return cppBaseAmount; let adjustmentFactor = 0; if (startAge < CPP_STD_AGE) { // Early CPP reduction adjustmentFactor = (CPP_STD_AGE - startAge) * 12 * CPP_EARLY_REDUCTION * -1; } else { // Deferred CPP increase adjustmentFactor = (startAge - CPP_STD_AGE) * 12 * CPP_DEFERRAL_INCREASE; } return cppBaseAmount * (1 + adjustmentFactor); }; // Calculate OAS amount based on start age const calculateOasAmount = (startAge) => { if (startAge < 65) return 0; // OAS not available before 65 if (startAge === 65) return OAS_BASE_ANNUAL; // Deferred OAS increase (max 5 years) const deferralMonths = Math.min((startAge - 65) * 12, (OAS_MAX_DEFERRAL_AGE - 65) * 12); const deferralIncrease = deferralMonths * OAS_DEFERRAL_INCREASE; return OAS_BASE_ANNUAL * (1 + deferralIncrease); }; // Calculate tax based on income and province const calculateTax = (income, province) => { let federalTax = 0; let provincialTax = 0; // Calculate federal tax for (let i = 0; i < FEDERAL_TAX_RATES.brackets.length; i++) { const currBracket = FEDERAL_TAX_RATES.brackets[i]; const nextBracket = FEDERAL_TAX_RATES.brackets[i + 1] || Infinity; const rate = FEDERAL_TAX_RATES.rates[i]; if (income > currBracket) { federalTax += (Math.min(income, nextBracket) - currBracket) * rate; } } // Calculate provincial tax const provincialRates = PROVINCIAL_TAX_RATES[province]; if (provincialRates) { for (let i = 0; i < provincialRates.brackets.length; i++) { const currBracket = provincialRates.brackets[i]; const nextBracket = provincialRates.brackets[i + 1] || Infinity; const rate = provincialRates.rates[i]; if (income > currBracket) { provincialTax += (Math.min(income, nextBracket) - currBracket) * rate; } } // Apply Ontario surtax if applicable if (province === "Ontario" && provincialRates.surtax && provincialTax > provincialRates.surtax.threshold) { provincialTax += (provincialTax - provincialRates.surtax.threshold) * provincialRates.surtax.rate; } } return federalTax + provincialTax; }; // Calculate OAS clawback const calculateOASClawback = (income, oasAmount) => { if (income <= OAS_CLAWBACK_THRESHOLD) return 0; return Math.min(oasAmount, (income - OAS_CLAWBACK_THRESHOLD) * OAS_CLAWBACK_RATE); }; // Get RRIF minimum withdrawal rate for a given age const getRRIFRate = (age) => { if (age < 72) return 0; if (age >= 95) return RRIF_MIN_WITHDRAWAL["95"]; // Get closest age rate const ages = Object.keys(RRIF_MIN_WITHDRAWAL).map(Number); const closestAge = ages.reduce((prev, curr) => Math.abs(curr - age) < Math.abs(prev - age) ? curr : prev ); return RRIF_MIN_WITHDRAWAL[closestAge.toString()]; }; // Optimize CPP and OAS start ages const optimizeCppOasTiming = () => { const retirementYears = lifeExpectancy - retirementAge + 1; let bestCppStartAge = 65; let bestOasStartAge = 65; let maxLifetimeIncome = 0; // Try different CPP start ages for (let cppAge = Math.max(CPP_MIN_AGE, retirementAge); cppAge <= CPP_MAX_AGE; cppAge++) { // Try different OAS start ages for (let oasAge = Math.max(65, retirementAge); oasAge <= OAS_MAX_DEFERRAL_AGE; oasAge++) { let lifetimeIncome = 0; // Calculate total after-tax income over retirement for (let age = retirementAge; age <= lifeExpectancy; age++) { const cppAmount = age >= cppAge ? calculateCppAmount(cppAge) : 0; const oasAmount = age >= oasAge ? calculateOasAmount(oasAge) : 0; // Find the applicable phase for this age const phase = phases.find(p => age >= p.startAge && age <= p.endAge); const targetIncome = phase ? phase.targetIncome : 0; // Simplified tax calculation for optimization const totalIncome = cppAmount + oasAmount + Math.max(0, targetIncome - cppAmount - oasAmount); const tax = calculateTax(totalIncome, province); const oasClawback = calculateOASClawback(totalIncome, oasAmount); lifetimeIncome += totalIncome - tax - oasClawback; } // Update if this combination provides better lifetime income if (lifetimeIncome > maxLifetimeIncome) { maxLifetimeIncome = lifetimeIncome; bestCppStartAge = cppAge; bestOasStartAge = oasAge; } } } return { bestCppStartAge, bestOasStartAge }; }; // Calculate the withdrawal plan const calculateWithdrawalPlan = () => { // Run CPP and OAS optimization if enabled if (cppOptimization || oasOptimization) { const { bestCppStartAge, bestOasStartAge } = optimizeCppOasTiming(); setOptimizedCppStartAge(bestCppStartAge); setOptimizedOasStartAge(bestOasStartAge); } // Select active CPP and OAS start ages const activeCppStartAge = cppOptimization ? optimizedCppStartAge : manualCppStartAge; const activeOasStartAge = oasOptimization ? optimizedOasStartAge : manualOasStartAge; // Calculate CPP and OAS values const activeCppAmount = calculateCppAmount(activeCppStartAge); const activeOasAmount = calculateOasAmount(activeOasStartAge); // Initialize assets let currentTFSA = tfsa; let currentRRSP = rrsp; let currentNonReg = nonReg; let currentCorp = corp; const plan = []; let totalTaxPaid = 0; let totalTaxSaved = 0; // IMPORTANT CHANGE: Add strategic RRSP drawdown before age 72 // Calculate how much RRSP we need to melt down each year to avoid excessive RRIF minimums let strategicRrspMeltdown = 0; if (retirementAge < 72) { // Calculate future RRSP value at age 72 with no withdrawals const yearsToRrifAge = 72 - retirementAge; const futureRrspValue = rrsp * Math.pow(1 + returnRate/100, yearsToRrifAge); // Calculate what RRSP value at 72 would result in RRIF minimums roughly matching desired income // This is a simplified approach to prevent excessive forced withdrawals const targetRrspAtRrifAge = Math.min( futureRrspValue, // Estimate a reasonable RRSP value that won't create excessive minimum withdrawals 1000000 ); // If future value exceeds target, calculate annual drawdown needed if (futureRrspValue > targetRrspAtRrifAge && yearsToRrifAge > 0) { // Calculate how much to withdraw annually to reach target RRSP value by age 72 // This is a simplified calculation that doesn't account for compounding effects precisely strategicRrspMeltdown = (futureRrspValue - targetRrspAtRrifAge) / yearsToRrifAge; } } // For each year until life expectancy for (let age = retirementAge; age <= lifeExpectancy; age++) { // Find the applicable phase for this age const phase = phases.find(p => age >= p.startAge && age <= p.endAge); const targetIncome = phase ? phase.targetIncome : 0; // Calculate CPP & OAS for this year const cppIncome = age >= activeCppStartAge ? activeCppAmount : 0; const oasIncome = age >= activeOasStartAge ? activeOasAmount : 0; // Income needed from investments const incomeNeeded = Math.max(0, targetIncome - cppIncome - oasIncome); // RRIF minimum withdrawal if applicable const rrifMinRate = getRRIFRate(age); const rrifMinWithdrawal = currentRRSP * rrifMinRate; // Initialize withdrawals let tfsaWithdrawal = 0; let rrspWithdrawal = 0; let nonRegWithdrawal = 0; let corpWithdrawal = 0; // Tax-efficient withdrawal strategy let remainingIncome = incomeNeeded; // 1. First, take RRIF minimum if required (age 72+) if (age >= 72) { rrspWithdrawal = rrifMinWithdrawal; remainingIncome -= rrifMinWithdrawal; } // CRITICAL CHANGE: Add strategic RRSP meltdown before age 72 else if (age < 72 && strategicRrspMeltdown > 0) { // Set a base RRSP withdrawal as part of the meltdown strategy const baseMeltdownAmount = Math.min(strategicRrspMeltdown, currentRRSP * 0.1); // Limit to 10% per year rrspWithdrawal = baseMeltdownAmount; remainingIncome -= baseMeltdownAmount; } // 2. Use non-registered dividends/capital gains for low tax if (remainingIncome > 0 && currentNonReg > 0) { const optimalNonRegWithdrawal = Math.min(remainingIncome, currentNonReg * 0.04); nonRegWithdrawal = optimalNonRegWithdrawal; remainingIncome -= optimalNonRegWithdrawal; } // 3. Use TFSA (tax-free) if (remainingIncome > 0 && currentTFSA > 0) { const optimalTFSAWithdrawal = Math.min(remainingIncome, currentTFSA); tfsaWithdrawal = optimalTFSAWithdrawal; remainingIncome -= optimalTFSAWithdrawal; } // 4. Balance between RRSP and non-registered if (remainingIncome > 0) { // Calculate optimal RRSP withdrawal to avoid OAS clawback const taxableIncome = cppIncome + oasIncome + rrspWithdrawal + nonRegWithdrawal * 0.5; const roomToOASClawback = Math.max(0, OAS_CLAWBACK_THRESHOLD - taxableIncome); // Strategic RRSP withdrawal const optimalRRSPWithdrawal = Math.min( remainingIncome, Math.max(0, roomToOASClawback), currentRRSP - rrspWithdrawal ); rrspWithdrawal += optimalRRSPWithdrawal; remainingIncome -= optimalRRSPWithdrawal; // Use more non-registered if needed if (remainingIncome > 0 && currentNonReg - nonRegWithdrawal > 0) { const additionalNonReg = Math.min(remainingIncome, currentNonReg - nonRegWithdrawal); nonRegWithdrawal += additionalNonReg; remainingIncome -= additionalNonReg; } // Use corporate if available if (remainingIncome > 0 && currentCorp > 0) { corpWithdrawal = Math.min(remainingIncome, currentCorp); remainingIncome -= corpWithdrawal; } // Use RRSP for any remaining needs if (remainingIncome > 0 && currentRRSP - rrspWithdrawal > 0) { const additionalRRSP = Math.min(remainingIncome, currentRRSP - rrspWithdrawal); rrspWithdrawal += additionalRRSP; remainingIncome -= additionalRRSP; } } // IMPORTANT ADDITION: If there's no remaining income needed but we have significant RRSP that would // cause large RRIF minimums later, continue strategic meltdown withdrawals if (remainingIncome <= 0 && age < 72 && strategicRrspMeltdown > 0) { const currentTaxableIncome = cppIncome + oasIncome + rrspWithdrawal + nonRegWithdrawal * 0.5; // Calculate additional RRSP room up to a reasonable tax bracket const reasonableTaxLimit = OAS_CLAWBACK_THRESHOLD; // Use OAS threshold as a reasonable limit const additionalRrspRoom = Math.max(0, reasonableTaxLimit - currentTaxableIncome); if (additionalRrspRoom > 0 && currentRRSP > rrspWithdrawal) { const additionalMeltdown = Math.min( additionalRrspRoom, strategicRrspMeltdown - rrspWithdrawal, currentRRSP - rrspWithdrawal ); if (additionalMeltdown > 0) { rrspWithdrawal += additionalMeltdown; } } } // Calculate total income and tax const totalIncome = cppIncome + oasIncome + rrspWithdrawal + nonRegWithdrawal * 0.5 + corpWithdrawal; const taxPaid = calculateTax(totalIncome, province); const oasClawback = calculateOASClawback(totalIncome, oasIncome); const afterTaxIncome = totalIncome - taxPaid - oasClawback; // Calculate the alternative strategy (basic withdrawal) const naiveTaxableIncome = cppIncome + oasIncome + incomeNeeded; const naiveTaxPaid = calculateTax(naiveTaxableIncome, province); const naiveOASClawback = calculateOASClawback(naiveTaxableIncome, oasIncome); // Calculate tax savings const taxSavings = naiveTaxPaid + naiveOASClawback - taxPaid - oasClawback; totalTaxSaved += taxSavings; totalTaxPaid += taxPaid + oasClawback; // Update assets for next year currentTFSA = (currentTFSA - tfsaWithdrawal) * (1 + returnRate/100); currentRRSP = (currentRRSP - rrspWithdrawal) * (1 + returnRate/100); currentNonReg = (currentNonReg - nonRegWithdrawal) * (1 + returnRate/100); currentCorp = (currentCorp - corpWithdrawal) * (1 + returnRate/100); // Add year to plan plan.push({ age, targetIncome, cppIncome: Math.round(cppIncome), oasIncome: Math.round(oasIncome), tfsaWithdrawal: Math.round(tfsaWithdrawal), rrspWithdrawal: Math.round(rrspWithdrawal), nonRegWithdrawal: Math.round(nonRegWithdrawal), corpWithdrawal: Math.round(corpWithdrawal), totalIncome: Math.round(totalIncome), taxPaid: Math.round(taxPaid), oasClawback: Math.round(oasClawback), afterTaxIncome: Math.round(afterTaxIncome), taxSavings: Math.round(taxSavings), tfsaBalance: Math.round(currentTFSA), rrspBalance: Math.round(currentRRSP), nonRegBalance: Math.round(currentNonReg), corpBalance: Math.round(currentCorp) }); } // Calculate CPP/OAS optimization value if (cppOptimization || oasOptimization) { // Run calculation with standard CPP/OAS ages for comparison const standardCppAmount = calculateCppAmount(65); const standardOasAmount = calculateOasAmount(65); let standardTotalAfterTax = 0; // Simple calculation of lifetime benefits for (let age = retirementAge; age <= lifeExpectancy; age++) { const cppIncome = age >= 65 ? standardCppAmount : 0; const oasIncome = age >= 65 ? standardOasAmount : 0; // Find the applicable phase for this age const phase = phases.find(p => age >= p.startAge && age <= p.endAge); const targetIncome = phase ? phase.targetIncome : 0; // Simplified tax calculation const totalIncome = cppIncome + oasIncome + Math.max(0, targetIncome - cppIncome - oasIncome); const tax = calculateTax(totalIncome, province); const oasClawback = calculateOASClawback(totalIncome, oasIncome); standardTotalAfterTax += totalIncome - tax - oasClawback; } // Calculate optimized lifetime benefits let optimizedTotalAfterTax = 0; for (const year of plan) { optimizedTotalAfterTax += year.afterTaxIncome; } setCppOasOptimizationValue(Math.round(optimizedTotalAfterTax - standardTotalAfterTax)); } setWithdrawalPlan(plan); setValueCreated(totalTaxSaved); setShowResults(true); }; return (
The calculator will determine the optimal age to start CPP based on your retirement income needs and life expectancy.
The calculator will determine the optimal age to start OAS based on your retirement income needs and potential clawback considerations.
This represents the estimated lifetime financial benefit created by your financial advisor.
Tax Optimization Savings: ${new Intl.NumberFormat().format(valueCreated)}
Lifetime tax and benefit savings through optimized withdrawal sequencing.
CPP/OAS Timing Value: ${new Intl.NumberFormat().format(cppOasOptimizationValue)}
Additional value created by optimizing government benefit start dates.
CPP Start Age: {cppOptimization ? optimizedCppStartAge : manualCppStartAge}
CPP Annual Amount: ${new Intl.NumberFormat().format(Math.round(calculateCppAmount(cppOptimization ? optimizedCppStartAge : manualCppStartAge)))}
{cppOptimization && optimizedCppStartAge !== 65 && ({optimizedCppStartAge < 65 ? `Taking CPP early provides cash flow when needed most, despite the reduced payment.` : `Delaying CPP increases your lifetime benefit by ${Math.round((optimizedCppStartAge - 65) * 8.4)}% compared to age 65.`}
)}OAS Start Age: {oasOptimization ? optimizedOasStartAge : manualOasStartAge}
OAS Annual Amount: ${new Intl.NumberFormat().format(Math.round(calculateOasAmount(oasOptimization ? optimizedOasStartAge : manualOasStartAge)))}
{oasOptimization && optimizedOasStartAge !== 65 && ({`Delaying OAS increases your payment by ${Math.round((optimizedOasStartAge - 65) * 7.2)}% compared to age 65, and helps avoid clawbacks.`}
)}Age | Target | CPP/OAS | TFSA | RRSP/RRIF | Non-Reg | Tax | After-Tax | Savings |
---|---|---|---|---|---|---|---|---|
{year.age} | ${new Intl.NumberFormat().format(year.targetIncome)} | ${new Intl.NumberFormat().format(year.cppIncome + year.oasIncome)} | ${new Intl.NumberFormat().format(year.tfsaWithdrawal)} | ${new Intl.NumberFormat().format(year.rrspWithdrawal)} | ${new Intl.NumberFormat().format(year.nonRegWithdrawal)} | ${new Intl.NumberFormat().format(year.taxPaid + year.oasClawback)} | ${new Intl.NumberFormat().format(year.afterTaxIncome)} | ${new Intl.NumberFormat().format(year.taxSavings)} |
This approach balances withdrawals across account types and years, smoothing income to minimize lifetime taxes rather than just annual taxes.
RRSP Meltdown Strategy: The plan systematically draws down RRSP assets before age 72 to prevent excessive RRIF minimum withdrawals later in retirement, which would otherwise force you into higher tax brackets and trigger unnecessary OAS clawbacks.
Early Retirement Strategy: The plan prioritizes non-registered withdrawals and TFSA withdrawals in early retirement to optimize tax efficiency while strategically drawing down RRSP assets.
Government Benefits Strategy: { cppOptimization && oasOptimization ? `Taking CPP at age ${optimizedCppStartAge} and OAS at age ${optimizedOasStartAge} optimizes your lifetime benefit based on your income needs and life expectancy.` : `Strategically timing government benefits helps maximize your after-tax income over your lifetime.` }
RRSP/RRIF Strategy: Starting at age 72, we manage the required minimum RRIF withdrawals, which are now more manageable due to our earlier meltdown strategy. This prevents large tax liabilities in later years and preserves OAS benefits.
OAS Preservation: The withdrawal sequence is designed to minimize income that would trigger OAS clawbacks, preserving this valuable government benefit. When possible, we structure withdrawals to keep income below the $86,912 threshold.
Tax Bracket Management: Throughout retirement, withdrawals are structured to keep you in lower tax brackets when possible, smoothing taxable income across years to minimize lifetime taxes rather than just annual taxes.
Longevity Protection: This strategy helps ensure you won't run out of money, by optimizing which accounts to draw from first and which to preserve for longer-term growth.