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.
Returns = Inflation + Asset Delta. Housing: +1%, Stocks: +3%, TFSA/RSP: +3.5%
When you have positive cash flow (income > expenses), here's how excess money gets allocated:
Priority: Pay credit card debt first (18% interest)
Credit Cards: $200/month minimumAnnual limit: $6,500 | Tax-free growth and withdrawals
TFSA: Priority after debtAnnual limit: 18% of income (max $31,560) | Tax-deferred growth
RSP: After TFSA maxedExtra payments to principal (4% guaranteed return)
Mortgage: Before risky investmentsTaxable investment accounts after debt and tax-advantaged accounts
Stocks: Market risk investments lastWhen expenses exceed income (retirement years), withdrawals happen in reverse priority order to preserve tax-advantaged accounts:
Each asset type earns returns based on inflation rate plus a specific delta:
Every year, each account balance changes based on these components:
Closing Balance = Opening Balance + Growth + Contributions - Withdrawals
Accurate after-tax income calculations using 2025 marginal tax rates for all provinces and territories:
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