def OnRetirement(self, person): self.ympe_fractions.sort(reverse=True) working_years = len(self.ympe_fractions) dropout_years = world.CPP_GENERAL_DROPOUT_FACTOR * working_years cpp_earning_history_length = working_years - dropout_years whole_year_index = math.floor(cpp_earning_history_length) cpp_average_earnings = (sum(self.ympe_fractions[:whole_year_index]) + self.ympe_fractions[whole_year_index] * (cpp_earning_history_length - whole_year_index) ) / cpp_earning_history_length # Calculate the average nominal YMPE for the previous 5 years (excluding current year) nominal_ympe_history = [ utils.Indexed(world.YMPE, person.year - i, 1 + world.PARGE) * person.cpi_history[-(i + 1)] for i in range(1, world.MPEA_YEARS + 1) ] indexed_mpea = sum(nominal_ympe_history) / world.MPEA_YEARS if person.age == world.CPP_EXPECTED_RETIREMENT_AGE: self.benefit_amount = cpp_average_earnings * indexed_mpea * world.CPP_RETIREMENT_BENEFIT_FRACTION elif person.age < world.CPP_EXPECTED_RETIREMENT_AGE: self.benefit_amount = ( cpp_average_earnings * indexed_mpea * world.CPP_RETIREMENT_BENEFIT_FRACTION * (1 - (world.CPP_EXPECTED_RETIREMENT_AGE - person.age) * world.AAF_PRE65)) elif person.age > world.CPP_EXPECTED_RETIREMENT_AGE: self.benefit_amount = ( cpp_average_earnings * indexed_mpea * world.CPP_RETIREMENT_BENEFIT_FRACTION * (1 + min(world.AAF_POST65_YEARS_CAP, person.age - world.CPP_EXPECTED_RETIREMENT_AGE) * world.AAF_POST65))
def CalcAmount(self, year_rec): if year_rec.is_employed: current_ympe = utils.Indexed(world.YMPE, year_rec.year, 1 + world.PARGE) * year_rec.cpi earnings = max( random.normalvariate( current_ympe * world.EARNINGS_YMPE_FRACTION, world.YMPE_STDDEV * current_ympe), 0) return earnings else: return 0
def CalcPayrollDeductions(self, year_rec): """Calculates and stores EI premium and CPP employee controbutions""" # CPP employee contribution earnings = sum(receipt.amount for receipt in year_rec.incomes if receipt.income_type == incomes.INCOME_TYPE_EARNINGS) year_rec.pensionable_earnings = max( 0, min( utils.Indexed(world.YMPE, year_rec.year, 1 + world.PARGE) * year_rec.cpi, earnings) - world.YBE) year_rec.cpp_contribution = year_rec.pensionable_earnings * world.CPP_EMPLOYEE_RATE # EI premium year_rec.insurable_earnings = min( earnings, utils.Indexed(world.EI_MAX_INSURABLE_EARNINGS, year_rec.year, 1 + world.PARGE) * year_rec.cpi) year_rec.ei_premium = year_rec.insurable_earnings * world.EI_PREMIUM_RATE return year_rec
def CalcAmount(self, year_rec): if year_rec.is_employed: earnings_capacity = utils.Indexed( world.YMPE, year_rec.year, 1 + world.PARGE) * year_rec.cpi * world.EARNINGS_YMPE_FRACTION earnings = max( random.normalvariate(earnings_capacity, world.YMPE_STDDEV * earnings_capacity), 0) return earnings else: return 0
def testIndexed(self): self.assertAlmostEqual(utils.Indexed(100, world.BASE_YEAR + 1, 1.10), 110) self.assertAlmostEqual(utils.Indexed(100, world.BASE_YEAR, 123), 100) self.assertAlmostEqual(utils.Indexed(100, world.BASE_YEAR + 1), 101)
def AnnualUpdate(self, year_rec): if not year_rec.is_retired: self.ympe_fractions.append( year_rec.pensionable_earnings / (utils.Indexed(world.YMPE, year_rec.year, 1 + world.PARGE) * year_rec.cpi))
def AnnualReview(self, year_rec): """End of year calculations for a live person""" period = self.Period(year_rec) self.period_years[period] += 1 cpi = year_rec.cpi if self.real_values else 1 self.accumulators.UpdateConsumption(year_rec.consumption / cpi, self.year, self.retired, period) earnings = sum(receipt.amount for receipt in year_rec.incomes if receipt.income_type == incomes.INCOME_TYPE_EARNINGS) cpp = sum(receipt.amount for receipt in year_rec.incomes if receipt.income_type == incomes.INCOME_TYPE_CPP) ei_benefits = sum(receipt.amount for receipt in year_rec.incomes if receipt.income_type == incomes.INCOME_TYPE_EI) gis = sum(receipt.amount for receipt in year_rec.incomes if receipt.income_type == incomes.INCOME_TYPE_GIS) oas = sum(receipt.amount for receipt in year_rec.incomes if receipt.income_type == incomes.INCOME_TYPE_OAS) assets = sum(fund.amount for fund in self.funds.values()) gross_income = sum(receipt.amount for receipt in year_rec.incomes) + sum( receipt.amount for receipt in year_rec.withdrawals) rrsp_withdrawals = sum( receipt.amount for receipt in year_rec.withdrawals if receipt.fund_type in (funds.FUND_TYPE_RRSP, funds.FUND_TYPE_BRIDGING)) tfsa_withdrawals = sum(receipt.amount for receipt in year_rec.withdrawals if receipt.fund_type == funds.FUND_TYPE_TFSA) nonreg_withdrawals = sum( receipt.amount for receipt in year_rec.withdrawals if receipt.fund_type == funds.FUND_TYPE_NONREG) total_withdrawals = rrsp_withdrawals + tfsa_withdrawals + nonreg_withdrawals rrsp_deposits = sum(receipt.amount for receipt in year_rec.deposits if receipt.fund_type in (funds.FUND_TYPE_RRSP, funds.FUND_TYPE_BRIDGING)) tfsa_deposits = sum(receipt.amount for receipt in year_rec.deposits if receipt.fund_type == funds.FUND_TYPE_TFSA) nonreg_deposits = sum(receipt.amount for receipt in year_rec.deposits if receipt.fund_type == funds.FUND_TYPE_NONREG) savings = rrsp_deposits + tfsa_deposits + nonreg_deposits ympe = utils.Indexed(world.YMPE, year_rec.year, 1 + world.PARGE) if gross_income < world.LICO_SINGLE_CITY_WP * year_rec.cpi: self.gross_income_below_lico_years += 1 if assets <= 0: self.no_assets_years += 1 if self.age >= world.MINIMUM_RETIREMENT_AGE and not self.retired: self.accumulators.earnings_late_working_summary.UpdateOneValue( earnings / cpi) self.accumulators.lifetime_withdrawals_less_savings.UpdateOneValue( (total_withdrawals - savings) / cpi) if self.retired: self.accumulators.lico_gap_retired.UpdateOneValue( max(0, world.LICO_SINGLE_CITY_WP * year_rec.cpi - gross_income) / cpi) if assets <= 0: self.has_been_ruined = True self.accumulators.fraction_retirement_years_ruined.UpdateOneValue( 1) else: self.accumulators.fraction_retirement_years_ruined.UpdateOneValue( 0) self.accumulators.fraction_retirement_years_below_ympe.UpdateOneValue( 1 if assets < ympe else 0) self.accumulators.fraction_retirement_years_below_twice_ympe.UpdateOneValue( 1 if assets < 2 * ympe else 0) if gross_income < world.LICO_SINGLE_CITY_WP * year_rec.cpi: self.has_experienced_income_under_lico = True self.accumulators.fraction_retirement_years_below_lico.UpdateOneValue( 1) else: self.accumulators.fraction_retirement_years_below_lico.UpdateOneValue( 0) self.accumulators.retirement_taxes.UpdateOneValue( year_rec.taxes_payable / cpi) if cpp > 0: self.accumulators.positive_cpp_benefits.UpdateOneValue(cpp / cpi) else: # Working period self.accumulators.lico_gap_working.UpdateOneValue( max(0, world.LICO_SINGLE_CITY_WP * year_rec.cpi - gross_income) / cpi) self.accumulators.earnings_working.UpdateOneValue(earnings / cpi) self.accumulators.working_annual_ei_cpp_deductions.UpdateOneValue( (year_rec.cpp_contribution + year_rec.ei_premium) / cpi) self.accumulators.working_taxes.UpdateOneValue( year_rec.taxes_payable / cpi) if earnings > 0: self.positive_earnings_years += 1 self.accumulators.fraction_earnings_saved.UpdateOneValue( savings / earnings) if ei_benefits > 0: self.ei_years += 1 self.accumulators.positive_ei_benefits.UpdateOneValue( ei_benefits / cpi) if self.age >= world.MAXIMUM_RETIREMENT_AGE: if gis > 0: self.gis_years += 1 self.has_received_gis = True self.accumulators.fraction_retirement_years_receiving_gis.UpdateOneValue( 1) self.accumulators.positive_gis_benefits.UpdateOneValue(gis / cpi) else: self.accumulators.fraction_retirement_years_receiving_gis.UpdateOneValue( 0) self.accumulators.benefits_gis.UpdateOneValue(gis / cpi) if not self.basic_only: self.net_government_revenue += (year_rec.taxes_payable + year_rec.sales_taxes - gis - oas) / year_rec.cpi self.accumulators.period_earnings.UpdateOneValue( earnings / cpi, period) self.accumulators.period_cpp_benefits.UpdateOneValue( cpp / cpi, period) self.accumulators.period_oas_benefits.UpdateOneValue( oas / cpi, period) self.accumulators.period_taxable_gains.UpdateOneValue( year_rec.taxable_capital_gains / cpi, period) self.accumulators.period_gis_benefits.UpdateOneValue( gis / cpi, period) self.accumulators.period_social_benefits_repaid.UpdateOneValue( year_rec.total_social_benefit_repayment / cpi, period) self.accumulators.period_rrsp_withdrawals.UpdateOneValue( rrsp_withdrawals / cpi, period) self.accumulators.period_tfsa_withdrawals.UpdateOneValue( tfsa_withdrawals / cpi, period) self.accumulators.period_nonreg_withdrawals.UpdateOneValue( nonreg_withdrawals / cpi, period) self.accumulators.period_cpp_contributions.UpdateOneValue( year_rec.cpp_contribution / cpi, period) self.accumulators.period_ei_premiums.UpdateOneValue( year_rec.ei_premium / cpi, period) self.accumulators.period_taxable_income.UpdateOneValue( year_rec.taxable_income / cpi, period) self.accumulators.period_income_tax.UpdateOneValue( year_rec.taxes_payable / cpi, period) self.accumulators.period_sales_tax.UpdateOneValue( year_rec.sales_taxes / cpi, period) self.accumulators.period_rrsp_savings.UpdateOneValue( rrsp_deposits / cpi, period) self.accumulators.period_tfsa_savings.UpdateOneValue( tfsa_deposits / cpi, period) self.accumulators.period_nonreg_savings.UpdateOneValue( nonreg_deposits / cpi, period) self.accumulators.period_fund_growth.UpdateOneValue( sum(rec.growth_amount for rec in year_rec.growth_records) / cpi, period) self.accumulators.persons_alive_by_age.UpdateOneValue(1, self.age) self.accumulators.gross_earnings_by_age.UpdateOneValue( earnings / cpi, self.age) self.accumulators.income_tax_by_age.UpdateOneValue( year_rec.taxes_payable / cpi, self.age) self.accumulators.sales_tax_by_age.UpdateOneValue( year_rec.sales_taxes / cpi, self.age) self.accumulators.ei_premium_by_age.UpdateOneValue( year_rec.ei_premium / cpi, self.age) self.accumulators.cpp_contributions_by_age.UpdateOneValue( year_rec.cpp_contribution / cpi, self.age) self.accumulators.ei_benefits_by_age.UpdateOneValue( ei_benefits / cpi, self.age) self.accumulators.cpp_benefits_by_age.UpdateOneValue( cpp / cpi, self.age) self.accumulators.oas_benefits_by_age.UpdateOneValue( oas / cpi, self.age) self.accumulators.gis_benefits_by_age.UpdateOneValue( gis / cpi, self.age) self.accumulators.savings_by_age.UpdateOneValue( savings / cpi, self.age) self.accumulators.rrsp_withdrawals_by_age.UpdateOneValue( rrsp_withdrawals / cpi, self.age) self.accumulators.tfsa_withdrawals_by_age.UpdateOneValue( tfsa_withdrawals / cpi, self.age) self.accumulators.nonreg_withdrawals_by_age.UpdateOneValue( nonreg_withdrawals / cpi, self.age) self.accumulators.rrsp_assets_by_age.UpdateOneValue( sum(fund.amount for fund in self.funds.values() if fund.fund_type == funds.FUND_TYPE_RRSP) / cpi, self.age) self.accumulators.bridging_assets_by_age.UpdateOneValue( sum(fund.amount for fund in self.funds.values() if fund.fund_type == funds.FUND_TYPE_BRIDGING) / cpi, self.age) self.accumulators.tfsa_assets_by_age.UpdateOneValue( sum(fund.amount for fund in self.funds.values() if fund.fund_type == funds.FUND_TYPE_TFSA) / cpi, self.age) self.accumulators.nonreg_assets_by_age.UpdateOneValue( sum(fund.amount for fund in self.funds.values() if fund.fund_type == funds.FUND_TYPE_NONREG) / cpi, self.age) if self.retired: self.accumulators.cd_withdrawals_by_age.UpdateOneValue( year_rec.cd_drawdown_amount / cpi, self.age) self.accumulators.ced_withdrawals_by_age.UpdateOneValue( year_rec.ced_drawdown_amount / cpi, self.age) self.accumulators.cd_requested_by_age.UpdateOneValue( year_rec.cd_drawdown_request / cpi, self.age) self.accumulators.ced_requested_by_age.UpdateOneValue( year_rec.ced_drawdown_request / cpi, self.age) self.accumulators.rrsp_ced_assets_by_age.UpdateOneValue( self.funds["ced_rrsp"].amount / cpi, self.age) self.accumulators.tfsa_ced_assets_by_age.UpdateOneValue( self.funds["ced_tfsa"].amount / cpi, self.age) self.accumulators.nonreg_ced_assets_by_age.UpdateOneValue( self.funds["ced_nonreg"].amount / cpi, self.age) self.accumulators.rrsp_cd_assets_by_age.UpdateOneValue( self.funds["cd_rrsp"].amount / cpi, self.age) self.accumulators.tfsa_cd_assets_by_age.UpdateOneValue( self.funds["cd_tfsa"].amount / cpi, self.age) self.accumulators.nonreg_cd_assets_by_age.UpdateOneValue( self.funds["cd_nonreg"].amount / cpi, self.age) self.accumulators.ced_ruined_by_age.UpdateOneValue( 1 if sum(fund.amount for name, fund in self.funds.items() if name.startswith("ced")) == 0 else 0, self.age) self.accumulators.cd_ruined_by_age.UpdateOneValue( 1 if sum(fund.amount for name, fund in self.funds.items() if name.startswith("cd")) == 0 else 0, self.age) self.age += 1 self.year += 1
def MeddleWithCash(self, year_rec): """This performs all operations on subject's cash pile""" cash = 0 # Get money from incomes. GIS is excluded and done after withdrawals for income in self.incomes[:-1]: amount, taxable, year_rec = income.GiveMeMoney(year_rec) cash += amount # Update RRSP room earnings = sum(receipt.amount for receipt in year_rec.incomes if receipt.income_type == incomes.INCOME_TYPE_EARNINGS) self.rrsp_room += min( earnings * world.RRSP_ACCRUAL_FRACTION, utils.Indexed(world.RRSP_LIMIT, year_rec.year, 1 + world.PARGE) * year_rec.cpi) year_rec.rrsp_room = self.rrsp_room # Do withdrawals if self.retired: # Bridging if "bridging" in self.funds and self.age < world.CPP_EXPECTED_RETIREMENT_AGE: bridging_withdrawal_amount = self.bridging_withdrawal_table[ self.age] * self.funds["bridging"].amount withdrawn, gains, year_rec = self.funds["bridging"].Withdraw( bridging_withdrawal_amount, year_rec) cash += withdrawn self.total_retirement_withdrawals += withdrawn / year_rec.cpi # CD drawdown strategy proportions = (self.strategy.drawdown_preferred_rrsp_fraction, self.strategy.drawdown_preferred_tfsa_fraction, 1) fund_chain = [ self.funds["cd_rrsp"], self.funds["cd_tfsa"], self.funds["cd_nonreg"] ] year_rec.cd_drawdown_request = self.cd_drawdown_amount * year_rec.cpi withdrawn, gains, year_rec = funds.ChainedWithdraw( year_rec.cd_drawdown_request, fund_chain, proportions, year_rec) cash += withdrawn year_rec.cd_drawdown_amount = withdrawn self.total_retirement_withdrawals += withdrawn / year_rec.cpi # CED drawdown_strategy fund_chain = [ self.funds["ced_rrsp"], self.funds["ced_tfsa"], self.funds["ced_nonreg"] ] year_rec.ced_drawdown_request = sum( f.amount for f in fund_chain) * world.CED_PROPORTION[self.age] withdrawn, gains, year_rec = funds.ChainedWithdraw( year_rec.ced_drawdown_request, fund_chain, proportions, year_rec) cash += withdrawn year_rec.ced_drawdown_amount = withdrawn self.total_retirement_withdrawals += withdrawn / year_rec.cpi else: target_cash = utils.Indexed( world.YMPE, year_rec.year, 1 + world.PARGE ) * year_rec.cpi * self.strategy.savings_threshold * world.EARNINGS_YMPE_FRACTION if cash < target_cash: # Attempt to withdraw difference from savings amount_to_withdraw = target_cash - cash proportions = ( self.strategy.working_period_drawdown_tfsa_fraction, self.strategy.working_period_drawdown_nonreg_fraction, 1) fund_chain = [ self.funds["wp_tfsa"], self.funds["wp_nonreg"], self.funds["wp_rrsp"] ] withdrawn, gains, year_rec = funds.ChainedWithdraw( amount_to_withdraw, fund_chain, proportions, year_rec) cash += withdrawn else: # Save earnings_to_save = max(earnings - target_cash, 0) * self.strategy.savings_rate proportions = (self.strategy.savings_rrsp_fraction, self.strategy.savings_tfsa_fraction, 1) fund_chain = [ self.funds["wp_rrsp"], self.funds["wp_tfsa"], self.funds["wp_nonreg"] ] deposited, year_rec = funds.ChainedDeposit( earnings_to_save, fund_chain, proportions, year_rec) cash -= deposited if deposited > 0: self.positive_savings_years += 1 # Update funds for fund in self.funds.values(): fund.Update(year_rec) # update the Person's view of RRSP and TFSA room self.tfsa_room = year_rec.tfsa_room self.rrsp_room = year_rec.rrsp_room # Calculate EI premium and CPP contributions year_rec = self.CalcPayrollDeductions(year_rec) # Now we try to get money from GIS because year_rec is populated with the needed values. income = self.incomes[-1] # GIS is last in this list amount, taxable, year_rec = income.GiveMeMoney(year_rec) cash += amount # Pay income taxes year_rec.taxes_payable = self.CalcIncomeTax(year_rec) cash -= year_rec.taxes_payable # Update incomes for income in self.incomes: income.AnnualUpdate(year_rec) # Pay sales tax non_hst_consumption = min(cash, world.SALES_TAX_EXEMPTION) hst_consumption = cash - non_hst_consumption year_rec.consumption = hst_consumption / ( 1 + world.HST_RATE) + non_hst_consumption year_rec.sales_taxes = hst_consumption * world.HST_RATE return year_rec
def CalcIncomeTax(self, year_rec): """Calculates the amount of income tax to be paid""" # Calculate Total Income income_sum = sum(receipt.amount for receipt in year_rec.incomes) rrsp_withdrawal_sum = sum( receipt.amount for receipt in year_rec.withdrawals if receipt.fund_type in (funds.FUND_TYPE_RRSP, funds.FUND_TYPE_BRIDGING)) capital_gains = (sum(receipt.gains for receipt in year_rec.withdrawals) + sum(receipt.gross_gain for receipt in year_rec.tax_receipts)) if capital_gains > 0: taxable_capital_gains = capital_gains * world.CG_INCLUSION_RATE else: self.capital_loss_carry_forward += -capital_gains taxable_capital_gains = 0 year_rec.taxable_capital_gains = taxable_capital_gains cpp_death_benefit = world.CPP_DEATH_BENEFIT if year_rec.is_dead else 0 total_income = income_sum + rrsp_withdrawal_sum + taxable_capital_gains + cpp_death_benefit # Calculate Net Income before adjustments rrsp_contribution_sum = sum( receipt.amount for receipt in year_rec.deposits if receipt.fund_type == funds.FUND_TYPE_RRSP) net_income_before_adjustments = max( total_income - rrsp_contribution_sum, 0) # Employment Insurance Social Benefits Repayment ei_benefits = sum(receipt.amount for receipt in year_rec.incomes if receipt.income_type == incomes.INCOME_TYPE_EI) ei_base_amount = utils.Indexed( world.EI_MAX_INSURABLE_EARNINGS, year_rec.year, 1 + world.PARGE) * world.EI_REPAYMENT_BASE_FRACTION * year_rec.cpi ei_benefit_repayment = min( max(0, net_income_before_adjustments - ei_base_amount), ei_benefits) * world.EI_REPAYMENT_REDUCTION_RATE # Old Age Security and Net Federal Supplements Repayment oas_plus_gis = sum(receipt.amount for receipt in year_rec.incomes if receipt.income_type in (incomes.INCOME_TYPE_OAS, incomes.INCOME_TYPE_GIS)) prospective_social_benefit_repayment = max( 0, max(0, net_income_before_adjustments - ei_benefit_repayment) - world.SBR_BASE_AMOUNT * year_rec.cpi) * world.SBR_REDUCTION_RATE oas_and_gis_repayment = min(oas_plus_gis, prospective_social_benefit_repayment) # Total Social Benefit Repayment total_social_benefit_repayment = ei_benefit_repayment + oas_and_gis_repayment year_rec.total_social_benefit_repayment = total_social_benefit_repayment # Other Payments Deduction gis_income = sum(receipt.amount for receipt in year_rec.incomes if receipt.income_type == incomes.INCOME_TYPE_GIS) oas_income = sum(receipt.amount for receipt in year_rec.incomes if receipt.income_type == incomes.INCOME_TYPE_OAS) try: oas_benefit_repaid = oas_and_gis_repayment * oas_income / ( gis_income + oas_income) except ZeroDivisionError: oas_benefit_repaid = 0 net_federal_supplements_deduction = gis_income - ( total_social_benefit_repayment - (ei_benefit_repayment + oas_benefit_repaid)) # Net Income net_income = net_income_before_adjustments - total_social_benefit_repayment # Taxable Income applied_capital_loss_amount = min( taxable_capital_gains, self.capital_loss_carry_forward * world.CG_INCLUSION_RATE) taxable_income = max( 0, net_income - (net_federal_supplements_deduction + applied_capital_loss_amount)) year_rec.taxable_income = taxable_income # Age amount age_amount_reduction = max( 0, net_income - world.AGE_AMOUNT_EXEMPTION * year_rec.cpi) * world.AGE_AMOUNT_REDUCTION_RATE age_amount = max( 0, world.AGE_AMOUNT_MAXIMUM * year_rec.cpi - age_amount_reduction) # Federal non-refundable tax credits if year_rec.is_dead: federal_non_refundable_credits = 0 else: federal_non_refundable_credits = ( world.BASIC_PERSONAL_AMOUNT * year_rec.cpi + age_amount + year_rec.cpp_contribution + year_rec.ei_premium) * world.NON_REFUNDABLE_CREDIT_RATE # Federal tax on taxable income # Tax schedule is in real terms, so we need to convert to/from nominal values net_federal_tax = max( 0, world.FEDERAL_TAX_SCHEDULE[taxable_income / year_rec.cpi] * year_rec.cpi - federal_non_refundable_credits) # Tax payable tax_payable = net_federal_tax + total_social_benefit_repayment + net_federal_tax * world.PROVINCIAL_TAX_FRACTION return tax_payable