Calculation Summary

Our retirement calculator uses sophisticated financial modeling with inflation-based returns. Each asset type earns inflation + a specific delta, while expenses automatically adjust for inflation over time.

Key Features
  • Inflation-based asset returns
  • Asset-specific performance deltas
  • Automatic expense inflation adjustment
  • Canadian provincial tax calculations
  • TFSA and RSP contribution limits
  • Intelligent debt payment priority
Inflation-Based Returns
Conservative: 2% inflation Average: 2.5% inflation Optimistic: 3% inflation

Returns = Inflation + Asset Delta. Housing: +1%, Stocks: +3%, TFSA/RSP: +3.5%

Cash Flow Allocation Priority

When you have positive cash flow (income > expenses), here's how excess money gets allocated:

1. High-Interest Debt Payment

Priority: Pay credit card debt first (18% interest)

2. Tax-Free Savings Account (TFSA)

Annual limit: $6,500 | Tax-free growth and withdrawals

3. Registered Retirement Savings Plan (RSP)

Annual limit: 18% of income (max $31,560) | Tax-deferred growth

4. Mortgage Acceleration

Extra payments to principal (4% guaranteed return)

5. Stock Portfolio & Other Investments

Taxable investment accounts after debt and tax-advantaged accounts

Retirement Withdrawal Strategy

When expenses exceed income (retirement years), withdrawals happen in reverse priority order to preserve tax-advantaged accounts:

1. Cash Account
Tax-Free
No tax implications
2. Other Accounts
Taxable
Provincial + Federal rates
3. Stock Portfolio
Taxable
Provincial + Federal rates
4. RSP/RRSP
Fully Taxable
Provincial + Federal rates
5. TFSA (Last Resort)
Tax-Free
No tax, but lose contribution room forever
Tax Calculation: For taxable withdrawals, the system uses your actual provincial and federal tax brackets to calculate the precise gross amount needed. For example, if you need $1,000 net in Ontario and you're in the 43.41% marginal bracket, the system withdraws $1,767 gross ($1,000 net + $767 tax).

Inflation-Based Asset Returns

Each asset type earns returns based on inflation rate plus a specific delta:

Asset Return Rates
Housing:
Inflation + 1%
TFSA/RSP:
Inflation + 3.5%
Stocks:
Inflation + 3%
Cash:
Inflation + 0.5%
Example (2.5% inflation):
Housing:
3.5% return
TFSA/RSP:
6% return
Stocks:
5.5% return
Cash:
3% return
Expenses: All expenses automatically increase by the inflation rate each year, providing realistic long-term projections.

Account Movement Tracking

Every year, each account balance changes based on these components:

Opening Balance
Starting value for the year
Growth/Interest
Asset-specific returns applied
Contributions
Money added during year
Withdrawals
Money removed for expenses
Formula: Closing Balance = Opening Balance + Growth + Contributions - Withdrawals

Canadian Tax Calculations

Accurate after-tax income calculations using 2025 marginal tax rates for all provinces and territories:

Federal Tax Brackets (2025)
  • 15% on income up to $55,867
  • 20.5% on income $55,868 to $111,733
  • 26% on income $111,734 to $173,205
  • 29% on income $173,206 to $246,752
  • 33% on income over $246,752
Provincial Variations
  • Lowest rates: Alberta, Nunavut (~20% total)
  • Average rates: Most provinces (~30-40%)
  • Highest rates: Quebec, Nova Scotia (~53% top bracket)

Source Code: calculator.py

Complete implementation of the retirement calculation engine:

from datetime import datetime
from models import UserSession, Asset, Liability, Income, Expense
from tax_calculator import CanadianTaxCalculator

class RetirementCalculator:
    """Financial calculator for retirement planning"""
    
    def __init__(self, userid):
        self.userid = userid
        self.user_session = UserSession.query.filter_by(userid=userid).first()
        self.assets = Asset.query.filter_by(userid=userid).all()
        self.liabilities = Liability.query.filter_by(userid=userid).all()
        self.income = Income.query.filter_by(userid=userid).all()
        self.expenses = Expense.query.filter_by(userid=userid).all()
        
        # Initialize tax calculator
        self.tax_calculator = CanadianTaxCalculator()
        
        # Inflation-based return calculation: inflation_rate + delta
        self.inflation_rates = {
            'High': 0.03,     # 3% inflation
            'Average': 0.025, # 2.5% inflation
            'Low': 0.02       # 2% inflation
        }
        
        # Asset-specific return deltas (above inflation)
        self.asset_deltas = {
            'HOUSE': 0.01,           # Housing: inflation + 1%
            'TFSA': 0.035,           # TFSA: inflation + 3.5%
            'RSP': 0.035,            # RSP: inflation + 3.5%
            'RRSP': 0.035,           # RRSP: inflation + 3.5%
            'STOCK PORTFOLIO': 0.03, # Stocks: inflation + 3%
            'STOCKS': 0.03,          # Stocks: inflation + 3%
            'OTHER': 0.025,          # Other: inflation + 2.5%
            'CASH': 0.005            # Cash: inflation + 0.5%
        }
    
    def get_inflation_rate(self):
        """Get the inflation rate based on user selection"""
        scenario = self.user_session.expected_returns if self.user_session else 'Average'
        return self.inflation_rates.get(scenario, 0.025)
    
    def get_return_rate_for_asset(self, asset_type):
        """Get the expected return rate for a specific asset type"""
        inflation_rate = self.get_inflation_rate()
        asset_type_upper = asset_type.upper()
        delta = self.asset_deltas.get(asset_type_upper, 0.025)  # Default to 2.5% above inflation
        return inflation_rate + delta
    
    def calculate_asset_growth(self, initial_amount, years, return_rate):
        """Calculate compound growth of assets"""
        return initial_amount * ((1 + return_rate) ** years)
    
    def calculate_liability_reduction(self, initial_amount, years, payment_rate=0.05):
        """Calculate liability reduction assuming minimum payments"""
        # Simplified assumption: liabilities reduce by 5% per year
        return max(0, initial_amount * ((1 - payment_rate) ** years))
    
    def get_income_for_year(self, year):
        """Get total income for a specific year"""
        total_income = 0
        current_year = datetime.now().year
        
        for income_entry in self.income:
            # Check if this income applies to the given year
            start_year = income_entry.start_year or current_year
            end_year = income_entry.end_year or (current_year + 100)  # Default to far future
            
            if start_year <= year <= end_year:
                # Convert to CAD if needed (simplified - assuming 1:1 for now)
                amount = income_entry.amount
                if income_entry.currency != 'CAD':
                    amount = amount * 1.0  # Placeholder for currency conversion
                total_income += amount
        
        return total_income
    
    def calculate_after_tax_income(self, gross_income, year):
        """Calculate after-tax income for a given year"""
        if not self.user_session or not self.user_session.province:
            # Default to Ontario if no province specified
            province = 'ON'
        else:
            province = self.user_session.province
        
        # Calculate tax and return after-tax income
        tax_result = self.tax_calculator.calculate_tax(gross_income, province)
        return tax_result['after_tax_income']
    
    def get_expenses_for_year(self, year):
        """Get total expenses for a specific year, adjusted for inflation"""
        total_expenses = 0
        current_year = datetime.now().year
        inflation_rate = self.get_inflation_rate()
        
        for expense in self.expenses:
            # Check if this expense applies to the given year
            start_year = expense.start_year or current_year
            end_year = expense.end_year or (current_year + 100)  # Default to far future
            
            if start_year <= year <= end_year:
                # Convert to CAD if needed (simplified - assuming 1:1 for now)
                amount = expense.amount
                if expense.currency != 'CAD':
                    amount = amount * 1.0  # Placeholder for currency conversion
                
                # Adjust for inflation from current year to target year
                years_from_now = year - current_year
                if years_from_now > 0:
                    amount = amount * ((1 + inflation_rate) ** years_from_now)
                
                total_expenses += amount
        
        return total_expenses
    
    def calculate(self):
        """Perform the retirement calculation"""
        if not self.user_session:
            raise ValueError("User session not found")
        
        current_year = datetime.now().year
        current_age = self.user_session.current_age
        planned_retirement_age = self.user_session.planned_retirement_age
        
        if not current_age or not planned_retirement_age:
            raise ValueError("Current age and planned retirement age are required")
        
        inflation_rate = self.get_inflation_rate()
        results = []
        
        # Calculate initial totals
        initial_assets = sum(asset.amount for asset in self.assets)
        initial_liabilities = sum(liability.amount for liability in self.liabilities)
        
        # Calculate year by year until age 100
        max_age = 100
        years_to_calculate = max_age - current_age
        
        for year_offset in range(years_to_calculate + 1):
            year = current_year + year_offset
            age = current_age + year_offset
            
            # Calculate assets with average growth rate (simplified for basic calculation)
            average_return_rate = inflation_rate + 0.035  # Use average delta of 3.5%
            total_assets = self.calculate_asset_growth(initial_assets, year_offset, average_return_rate)
            
            # Calculate liabilities with reduction
            total_liabilities = self.calculate_liability_reduction(initial_liabilities, year_offset)
            
            # Get income and expenses for this year (expenses automatically adjusted for inflation)
            gross_income = self.get_income_for_year(year)
            expenses = self.get_expenses_for_year(year)
            
            # Calculate after-tax income using provincial tax rates
            after_tax_income = self.calculate_after_tax_income(gross_income, year)
            
            # Calculate net worth and net income
            net_worth = total_assets - total_liabilities
            net_income = after_tax_income - expenses
            
            # Add net income to assets for next year's calculation
            if year_offset > 0:
                initial_assets += net_income
            
            results.append({
                'year': year,
                'age': age,
                'total_assets': round(total_assets, 2),
                'total_liabilities': round(total_liabilities, 2),
                'net_worth': round(net_worth, 2),
                'gross_income': round(gross_income, 2),
                'after_tax_income': round(after_tax_income, 2),
                'expenses': round(expenses, 2),
                'net_income': round(net_income, 2)
            })
        
        return results
    
    def calculate_detailed(self):
        """Perform detailed retirement calculation with individual account tracking"""
        if not self.user_session:
            raise ValueError("User session not found")
        
        current_year = datetime.now().year
        current_age = self.user_session.current_age
        planned_retirement_age = self.user_session.planned_retirement_age
        
        if not current_age or not planned_retirement_age:
            raise ValueError("Current age and planned retirement age are required")
        
        inflation_rate = self.get_inflation_rate()
        results = []
        
        # Calculate year by year until age 100
        max_age = 100
        years_to_calculate = max_age - current_age
        
        # Initialize asset values with current amounts
        asset_values = {asset.id: asset.amount for asset in self.assets}
        liability_values = {liability.id: liability.amount for liability in self.liabilities}
        
        # Track previous year's balances for proper contribution calculation
        previous_asset_values = {asset.id: asset.amount for asset in self.assets}
        
        # Track cumulative TFSA and RSP contributions for limits
        tfsa_contribution_room = 0  # Will calculate based on age
        rsp_contribution_room = 0   # Will calculate based on income
        
        for year_offset in range(years_to_calculate + 1):
            year = current_year + year_offset
            age = current_age + year_offset
            
            # Calculate individual asset growth with asset-specific rates
            asset_details = {}
            total_assets = 0
            for asset in self.assets:
                # Apply growth to the current balance (includes previous contributions)
                if year_offset == 0:
                    current_value = asset_values[asset.id]
                else:
                    # Growth is applied to the balance at the start of each year
                    asset_return_rate = self.get_return_rate_for_asset(asset.asset_type)
                    current_value = asset_values[asset.id] * (1 + asset_return_rate)
                    asset_values[asset.id] = current_value  # Update for next iteration
                
                asset_details[asset.id] = {
                    'name': asset.name,
                    'type': asset.asset_type,
                    'value': round(current_value, 2)
                }
                total_assets += current_value
            
            # Cash accounts are now handled as regular assets, no special case needed
            
            # Initialize liability values for this year (before any payments)
            liability_details = {}
            total_liabilities = 0
            
            # Apply interest first if not the first year
            if year_offset > 0:
                for liability in self.liabilities:
                    current_value = liability_values[liability.id]
                    if current_value > 0:
                        if liability.liability_type.upper() == 'MORTGAGE':
                            # Apply 4% annual interest
                            current_value *= 1.04
                        elif liability.liability_type.upper() == 'CREDIT CARD':
                            # Apply higher interest rate for credit cards (assume 18%)
                            current_value *= 1.18
                        else:
                            # Other debts - assume 6% interest
                            current_value *= 1.06
                        liability_values[liability.id] = current_value
            
            # Calculate individual income streams
            income_details = {}
            total_gross_income = 0
            total_after_tax_income = 0
            total_tax_on_income = 0
            for income_entry in self.income:
                start_year = income_entry.start_year or current_year
                end_year = income_entry.end_year or (current_year + 100)
                
                if start_year <= year <= end_year:
                    gross_amount = income_entry.amount
                    # Calculate individual income tax for this stream
                    if not self.user_session or not self.user_session.province:
                        province = 'ON'
                    else:
                        province = self.user_session.province
                    
                    tax_result = self.tax_calculator.calculate_tax(gross_amount, province)
                    after_tax_amount = tax_result['after_tax_income']
                    tax_amount = tax_result['total_tax']
                    
                    income_details[income_entry.id] = {
                        'name': income_entry.name,
                        'type': income_entry.income_type,
                        'gross_amount': round(gross_amount, 2),
                        'tax_amount': round(tax_amount, 2),
                        'after_tax_amount': round(after_tax_amount, 2)
                    }
                    total_gross_income += gross_amount
                    total_after_tax_income += after_tax_amount
                    total_tax_on_income += tax_amount
                else:
                    income_details[income_entry.id] = {
                        'name': income_entry.name,
                        'type': income_entry.income_type,
                        'gross_amount': 0,
                        'tax_amount': 0,
                        'after_tax_amount': 0
                    }
            
            # Calculate individual expenses (adjusted for inflation)
            expense_details = {}
            total_expenses = 0
            for expense in self.expenses:
                start_year = expense.start_year or current_year
                end_year = expense.end_year or (current_year + 100)
                
                if start_year <= year <= end_year:
                    amount = expense.amount
                    # Adjust for inflation from current year to target year
                    years_from_now = year - current_year
                    if years_from_now > 0:
                        original_amount = amount
                        amount = amount * ((1 + inflation_rate) ** years_from_now)
                        # Debug: Show inflation adjustment
                        print(f"Year {year}: Expense '{expense.name}' inflated from ${original_amount:,.2f} to ${amount:,.2f} (inflation: {inflation_rate*100:.1f}%)")
                    
                    expense_details[expense.id] = {
                        'name': expense.name,
                        'amount': round(amount, 2)
                    }
                    total_expenses += amount
                else:
                    expense_details[expense.id] = {
                        'name': expense.name,
                        'amount': 0
                    }
            
            # Calculate totals
            net_worth = total_assets - total_liabilities
            net_income = total_after_tax_income - total_expenses
            
            # Handle negative cash flow - need to withdraw from accounts
            if net_income < 0:
                deficit = abs(net_income)
                withdrawal_details = {}
            
            # Calculate contribution room for this year
            if year_offset == 0:
                # Initial TFSA room calculation (simplified: $6,500/year since 18, max $88,000 as of 2024)
                tfsa_years_eligible = max(0, age - 18)
                tfsa_contribution_room = min(88000, tfsa_years_eligible * 6500)  # 2024 limits
                
                # RSP room calculation (18% of previous year income, simplified)
                # For first year, estimate based on current income
                estimated_income = self.get_income_for_year(year)
                rsp_contribution_room = min(31560, estimated_income * 0.18)  # 2024 RSP limit
            else:
                # Add annual contribution room
                tfsa_contribution_room += 6500  # Annual TFSA room
                prev_year_income = self.get_income_for_year(year - 1)
                rsp_contribution_room += min(31560, prev_year_income * 0.18)
            
            # Handle cash flow - either withdrawals (negative) or investments (positive)
            contribution_details = {}
            
            # Handle negative cash flow first - withdraw from accounts in reverse priority  
            total_tax_on_withdrawals = 0
            total_taxable_withdrawals = 0
            if net_income < 0:
                deficit = abs(net_income)
                withdrawal_details = {}
                
                # Find account types for withdrawals (reverse priority order)
                other_accounts = [asset for asset in self.assets if asset.asset_type.upper() == 'OTHER']
                cash_accounts = [asset for asset in self.assets if asset.asset_type.upper() == 'CASH']
                stock_accounts = [asset for asset in self.assets if asset.asset_type.upper() in ['STOCK PORTFOLIO', 'STOCKS']]
                rsp_accounts = [asset for asset in self.assets if asset.asset_type.upper() in ['RSP', 'RRSP']]
                tfsa_accounts = [asset for asset in self.assets if asset.asset_type.upper() == 'TFSA']
                
                # Calculate total taxable withdrawals needed first, then allocate across accounts
                # This ensures proper tax calculation when switching between accounts
                total_taxable_needed = 0
                province = self.user_session.province if self.user_session else 'ON'
                
                # 1. Withdraw from Cash accounts first (tax-free)
                if deficit > 0 and cash_accounts:
                    for cash in cash_accounts:
                        if deficit > 0 and asset_values[cash.id] > 0:
                            withdrawal = min(deficit, asset_values[cash.id])
                            asset_values[cash.id] -= withdrawal
                            deficit -= withdrawal
                            if 'Cash Withdrawal' not in withdrawal_details:
                                withdrawal_details['Cash Withdrawal'] = 0
                            withdrawal_details['Cash Withdrawal'] += withdrawal
                
                # Calculate total gross withdrawal needed to cover the deficit
                # Use binary search to find the right gross amount considering cumulative tax
                if deficit > 0:
                    low, high = deficit, deficit * 2.0  # Start with reasonable bounds
                    
                    for _ in range(15):  # Binary search iterations
                        mid = (low + high) / 2
                        # Calculate tax on total taxable income plus this withdrawal
                        combined_income = total_gross_income + mid
                        tax_result = self.tax_calculator.calculate_tax(combined_income, province)
                        
                        # Net from this withdrawal = gross withdrawal - (total tax - tax on income only)
                        income_tax_result = self.tax_calculator.calculate_tax(total_gross_income, province)
                        withdrawal_tax = tax_result['total_tax'] - income_tax_result['total_tax']
                        net_from_withdrawal = mid - withdrawal_tax
                        
                        if abs(net_from_withdrawal - deficit) < 1:  # Close enough
                            break
                        elif net_from_withdrawal < deficit:
                            low = mid
                        else:
                            high = mid
                    
                    total_taxable_needed = mid
                    total_withdrawal_tax = tax_result['total_tax'] - income_tax_result['total_tax']
                    
                    # Now allocate this withdrawal across accounts in priority order
                    remaining_to_withdraw = total_taxable_needed
                    
                    # 2. Withdraw from Other accounts (taxable)
                    if remaining_to_withdraw > 0 and other_accounts:
                        for other in other_accounts:
                            if remaining_to_withdraw > 0 and asset_values[other.id] > 0:
                                actual_withdrawal = min(remaining_to_withdraw, asset_values[other.id])
                                
                                asset_values[other.id] -= actual_withdrawal
                                remaining_to_withdraw -= actual_withdrawal
                                
                                if 'Other Withdrawal' not in withdrawal_details:
                                    withdrawal_details['Other Withdrawal'] = 0
                                withdrawal_details['Other Withdrawal'] += actual_withdrawal
                
                    # 3. Withdraw from Stock Portfolio accounts (taxable)
                    if remaining_to_withdraw > 0 and stock_accounts:
                        for stock in stock_accounts:
                            if remaining_to_withdraw > 0 and asset_values[stock.id] > 0:
                                actual_withdrawal = min(remaining_to_withdraw, asset_values[stock.id])
                                
                                asset_values[stock.id] -= actual_withdrawal
                                remaining_to_withdraw -= actual_withdrawal
                                
                                if 'Stock Withdrawal' not in withdrawal_details:
                                    withdrawal_details['Stock Withdrawal'] = 0
                                withdrawal_details['Stock Withdrawal'] += actual_withdrawal
                
                    # 4. Withdraw from RSP accounts (fully taxable)
                    if remaining_to_withdraw > 0 and rsp_accounts:
                        for rsp in rsp_accounts:
                            if remaining_to_withdraw > 0 and asset_values[rsp.id] > 0:
                                actual_withdrawal = min(remaining_to_withdraw, asset_values[rsp.id])
                                
                                asset_values[rsp.id] -= actual_withdrawal
                                remaining_to_withdraw -= actual_withdrawal
                                
                                if 'RSP Withdrawal' not in withdrawal_details:
                                    withdrawal_details['RSP Withdrawal'] = 0
                                withdrawal_details['RSP Withdrawal'] += actual_withdrawal
                    
                    # Update totals for proper tax calculation
                    total_taxable_withdrawals = total_taxable_needed - remaining_to_withdraw
                    total_tax_on_withdrawals = total_withdrawal_tax * (total_taxable_withdrawals / total_taxable_needed) if total_taxable_needed > 0 else 0
                    deficit = remaining_to_withdraw  # Any remaining deficit after taxable withdrawals
                
                # 5. Finally, withdraw from TFSA accounts (tax-free, last resort)
                if deficit > 0 and tfsa_accounts:
                    for tfsa in tfsa_accounts:
                        if deficit > 0 and asset_values[tfsa.id] > 0:
                            withdrawal = min(deficit, asset_values[tfsa.id])
                            asset_values[tfsa.id] -= withdrawal
                            deficit -= withdrawal
                            if 'TFSA Withdrawal' not in withdrawal_details:
                                withdrawal_details['TFSA Withdrawal'] = 0
                            withdrawal_details['TFSA Withdrawal'] += withdrawal
                
                # Update contribution details to show withdrawals
                for key, value in withdrawal_details.items():
                    contribution_details[key] = -value  # Negative to show withdrawal
                
                # After withdrawals, there's no remaining income for investments
                remaining_income = 0
            
            # Handle positive cash flow - pay debts first, then invest
            elif net_income > 0:
                remaining_income = net_income
                
                # Priority 1: Pay down high-interest debt (Credit Cards first)
                credit_card_debts = [l for l in self.liabilities if l.liability_type.upper() == 'CREDIT CARD']
                for cc_debt in credit_card_debts:
                    if liability_values[cc_debt.id] > 0 and remaining_income > 0:
                        # Pay minimum $200/month or remaining balance, whichever is less
                        annual_payment = min(2400, remaining_income, liability_values[cc_debt.id])
                        liability_values[cc_debt.id] = max(0, liability_values[cc_debt.id] - annual_payment)
                        remaining_income -= annual_payment
                        if 'Credit Card Payments' not in contribution_details:
                            contribution_details['Credit Card Payments'] = 0
                        contribution_details['Credit Card Payments'] += annual_payment
                
                # Priority 2: TFSA and RSP contributions (after debt payments)
                # Find account types
                tfsa_accounts = [asset for asset in self.assets if asset.asset_type.upper() == 'TFSA']
                rsp_accounts = [asset for asset in self.assets if asset.asset_type.upper() in ['RSP', 'RRSP']]
                stock_accounts = [asset for asset in self.assets if asset.asset_type.upper() in ['STOCK PORTFOLIO', 'STOCKS']]
                other_accounts = [asset for asset in self.assets if asset.asset_type.upper() == 'OTHER']
                
                # 1. TFSA contributions first (tax-free growth)
                tfsa_contribution = min(remaining_income, tfsa_contribution_room)
                if tfsa_contribution > 0 and tfsa_accounts:
                    # Distribute among TFSA accounts
                    per_tfsa = tfsa_contribution / len(tfsa_accounts)
                    for tfsa in tfsa_accounts:
                        asset_values[tfsa.id] += per_tfsa
                    tfsa_contribution_room -= tfsa_contribution
                    remaining_income -= tfsa_contribution
                    contribution_details['TFSA'] = tfsa_contribution
                    # Debug: Show TFSA contribution
                    print(f"Year {year}: TFSA contribution of ${tfsa_contribution:,.2f} added to contribution_details")
                
                # 2. RSP contributions (tax deductible, but we're using after-tax money)
                # Note: In reality, RSP contributions would reduce taxable income
                rsp_contribution = min(remaining_income, rsp_contribution_room)
                if rsp_contribution > 0 and rsp_accounts:
                    per_rsp = rsp_contribution / len(rsp_accounts)
                    for rsp in rsp_accounts:
                        asset_values[rsp.id] += per_rsp
                    rsp_contribution_room -= rsp_contribution
                    remaining_income -= rsp_contribution
                    contribution_details['RSP'] = rsp_contribution
                
                # 3. Extra mortgage payments (priority after tax-advantaged accounts)
                if remaining_income > 0:
                    mortgage_debts = [l for l in self.liabilities if l.liability_type.upper() == 'MORTGAGE']
                    if mortgage_debts:
                        # Calculate additional mortgage payment
                        # Split remaining income among mortgages
                        per_mortgage = remaining_income / len(mortgage_debts)
                        for mortgage in mortgage_debts:
                            if liability_values[mortgage.id] > 0:
                                payment = min(per_mortgage, liability_values[mortgage.id])
                                liability_values[mortgage.id] = max(0, liability_values[mortgage.id] - payment)
                                remaining_income -= payment
                                if 'Extra Mortgage Payments' not in contribution_details:
                                    contribution_details['Extra Mortgage Payments'] = 0
                                contribution_details['Extra Mortgage Payments'] += payment
                
                # 4. Stock Portfolio accounts (after mortgages are paid down)
                if remaining_income > 0 and stock_accounts:
                    per_stock = remaining_income / len(stock_accounts)
                    for stock in stock_accounts:
                        asset_values[stock.id] += per_stock
                    contribution_details['Stocks'] = remaining_income
                    remaining_income = 0
                
                # 5. Other accounts or Cash On Hand (final overflow)
                if remaining_income > 0:
                    if other_accounts:
                        per_other = remaining_income / len(other_accounts)
                        for other in other_accounts:
                            asset_values[other.id] += per_other
                        contribution_details['Other'] = remaining_income
                    else:
                        # Add to Cash On Hand account
                        cash_accounts = [asset for asset in self.assets if asset.asset_type.upper() == 'CASH']
                        if cash_accounts:
                            # Add to first cash account (Cash On Hand)
                            asset_values[cash_accounts[0].id] += remaining_income
                            contribution_details['Cash'] = remaining_income
                    remaining_income = 0
            
            # Generate final liability details after all payments
            for liability in self.liabilities:
                current_value = max(0, liability_values[liability.id])
                liability_details[liability.id] = {
                    'name': liability.name,
                    'type': liability.liability_type,
                    'value': round(current_value, 2)
                }
                total_liabilities += current_value
            
            # Create detailed account movement tracking
            account_movements = {}
            for asset in self.assets:
                if year_offset == 0:
                    opening_balance = asset.amount
                    growth = 0
                    withdrawal = 0
                    closing_balance = asset_values[asset.id]
                    
                    # Calculate actual contribution for first year
                    contribution = 0
                    if asset.asset_type.upper() == 'TFSA':
                        contribution = contribution_details.get('TFSA', 0) / max(1, len([a for a in self.assets if a.asset_type.upper() == 'TFSA']))
                    elif asset.asset_type.upper() in ['RSP', 'RRSP']:
                        contribution = contribution_details.get('RSP', 0) / max(1, len([a for a in self.assets if a.asset_type.upper() in ['RSP', 'RRSP']]))
                    elif asset.asset_type.upper() in ['STOCK PORTFOLIO', 'STOCKS']:
                        contribution = contribution_details.get('Stocks', 0) / max(1, len([a for a in self.assets if a.asset_type.upper() in ['STOCK PORTFOLIO', 'STOCKS']]))
                    elif asset.asset_type.upper() == 'OTHER':
                        contribution = contribution_details.get('Other', 0) / max(1, len([a for a in self.assets if a.asset_type.upper() == 'OTHER']))
                    elif asset.asset_type.upper() == 'CASH':
                        contribution = contribution_details.get('Cash', 0) / max(1, len([a for a in self.assets if a.asset_type.upper() == 'CASH']))
                else:
                    # Calculate movements for this year
                    asset_return_rate = self.get_return_rate_for_asset(asset.asset_type)
                    prev_balance = previous_asset_values[asset.id]
                    growth = prev_balance * asset_return_rate if year_offset > 0 else 0
                    
                    # Calculate net contribution/withdrawal for this asset
                    contribution = 0
                    withdrawal = 0
                    
                    # Check contributions by asset type
                    if asset.asset_type.upper() == 'TFSA':
                        contribution = contribution_details.get('TFSA', 0) / max(1, len([a for a in self.assets if a.asset_type.upper() == 'TFSA']))
                        withdrawal = abs(contribution_details.get('TFSA Withdrawal', 0)) / max(1, len([a for a in self.assets if a.asset_type.upper() == 'TFSA']))
                    elif asset.asset_type.upper() in ['RSP', 'RRSP']:
                        contribution = contribution_details.get('RSP', 0) / max(1, len([a for a in self.assets if a.asset_type.upper() in ['RSP', 'RRSP']]))
                        withdrawal = abs(contribution_details.get('RSP Withdrawal', 0)) / max(1, len([a for a in self.assets if a.asset_type.upper() in ['RSP', 'RRSP']]))
                    elif asset.asset_type.upper() in ['STOCK PORTFOLIO', 'STOCKS']:
                        contribution = contribution_details.get('Stocks', 0) / max(1, len([a for a in self.assets if a.asset_type.upper() in ['STOCK PORTFOLIO', 'STOCKS']]))
                        withdrawal = abs(contribution_details.get('Stock Withdrawal', 0)) / max(1, len([a for a in self.assets if a.asset_type.upper() in ['STOCK PORTFOLIO', 'STOCKS']]))
                    elif asset.asset_type.upper() == 'OTHER':
                        contribution = contribution_details.get('Other', 0) / max(1, len([a for a in self.assets if a.asset_type.upper() == 'OTHER']))
                        withdrawal = abs(contribution_details.get('Other Withdrawal', 0)) / max(1, len([a for a in self.assets if a.asset_type.upper() == 'OTHER']))
                    elif asset.asset_type.upper() == 'CASH':
                        contribution = contribution_details.get('Cash', 0) / max(1, len([a for a in self.assets if a.asset_type.upper() == 'CASH']))
                        withdrawal = abs(contribution_details.get('Cash Withdrawal', 0)) / max(1, len([a for a in self.assets if a.asset_type.upper() == 'CASH']))
                    
                    opening_balance = prev_balance
                    closing_balance = asset_values[asset.id]
                    
                    # Verify contribution calculation: closing = opening + growth + contribution - withdrawal
                    calculated_contribution = closing_balance - opening_balance - growth + withdrawal
                    if abs(calculated_contribution - contribution) > 0.01:  # Allow small rounding differences
                        contribution = calculated_contribution
                
                account_movements[asset.id] = {
                    'name': asset.name,
                    'type': asset.asset_type,
                    'opening_balance': round(opening_balance, 2),
                    'growth': round(growth, 2),
                    'contribution': round(contribution, 2),
                    'withdrawal': round(withdrawal, 2),
                    'closing_balance': round(closing_balance, 2)
                }
            
            # Cash accounts are now handled as regular assets, no special tracking needed
            
            # Calculate total taxable income and overall tax info
            total_taxable_income = total_gross_income + total_taxable_withdrawals
            total_tax_amount = total_tax_on_income + total_tax_on_withdrawals
            
            # Calculate marginal tax rate if there's taxable income
            marginal_tax_rate = 0
            effective_tax_rate = 0
            if total_taxable_income > 0:
                province = self.user_session.province if self.user_session else 'ON'
                # Calculate marginal rate by testing a small increment
                base_tax = self.tax_calculator.calculate_tax(total_taxable_income, province)
                increment_tax = self.tax_calculator.calculate_tax(total_taxable_income + 1000, province)
                marginal_tax_rate = (increment_tax['total_tax'] - base_tax['total_tax']) / 1000
                
                # Calculate effective tax rate (total tax / total income)
                effective_tax_rate = total_tax_amount / total_taxable_income
            
            results.append({
                'year': year,
                'age': age,
                'total_assets': round(total_assets, 2),
                'total_liabilities': round(total_liabilities, 2),
                'net_worth': round(net_worth, 2),
                'total_gross_income': round(total_gross_income, 2),
                'total_after_tax_income': round(total_after_tax_income, 2),
                'total_expenses': round(total_expenses, 2),
                'net_income': round(net_income, 2),
                'asset_details': asset_details,
                'liability_details': liability_details,
                'income_details': income_details,
                'expense_details': expense_details,
                'contribution_details': contribution_details,
                'tfsa_room_remaining': tfsa_contribution_room,
                'rsp_room_remaining': rsp_contribution_room,
                'account_movements': account_movements,
                'tax_info': {
                    'total_taxable_income': round(total_taxable_income, 2),
                    'income_from_streams': round(total_gross_income, 2),
                    'income_from_withdrawals': round(total_taxable_withdrawals, 2),
                    'total_tax_amount': round(total_tax_amount, 2),
                    'tax_on_income': round(total_tax_on_income, 2),
                    'tax_on_withdrawals': round(total_tax_on_withdrawals, 2),
                    'marginal_tax_rate': round(marginal_tax_rate * 100, 2),
                    'effective_tax_rate': round(effective_tax_rate * 100, 2)
                }
            })
            
            # Update previous year values for next iteration
            previous_asset_values = asset_values.copy()
        
        return results

All calculations are estimates for planning purposes. Consult a financial advisor for personalized advice.

Try the Calculator