From eedf13bf434c19e92d9f2641643db44dea63851b Mon Sep 17 00:00:00 2001 From: Scott Gasch Date: Tue, 14 Jan 2020 11:44:16 -0800 Subject: [PATCH] Modify the roth conversion logic and simplify the code around that. Fix a bug in accounts.py with Roth accounts not having a roth balance. --- accounts.py | 48 +++++++++++++++++++----------------------------- retire.py | 34 ++++++++++++++++++++-------------- 2 files changed, 39 insertions(+), 43 deletions(-) diff --git a/accounts.py b/accounts.py index 7867d21..81ab871 100644 --- a/accounts.py +++ b/accounts.py @@ -101,11 +101,11 @@ class age_restricted_tax_deferred_account(account): self.total_roth_withdrawals += roth_part print "## Satisfying %s from %s with Roth money." % (utils.format_money(roth_part), self.name) taxes.record_roth_income(roth_part) - - self.pretax -= pretax_part - self.total_pretax_withdrawals += pretax_part - print "## Satisfying %s from %s with pre-tax money." % (utils.format_money(pretax_part), self.name) - taxes.record_ordinary_income(pretax_part) + if pretax_part > 0: + self.pretax -= pretax_part + self.total_pretax_withdrawals += pretax_part + print "## Satisfying %s from %s with pre-tax money." % (utils.format_money(pretax_part), self.name) + taxes.record_ordinary_income(pretax_part) def deposit(self, amount): self.pretax_part += amount @@ -237,27 +237,17 @@ class age_restricted_tax_deferred_account(account): def has_roth(self): return True - def do_roth_conversion(self, amount): - if amount <= 0: - return 0 - if self.pretax >= amount: - self.roth += amount - self.pretax -= amount - self.total_roth_conversions += amount - print "## Executed pre-tax --> Roth conversion of %s in %s" % ( - utils.format_money(amount), - self.get_name()) - return amount - elif self.pretax > 0: - actual_amount = self.pretax - self.roth += actual_amount - self.pretax = 0 - self.total_roth_conversions += actual_amount - print "## Executed pre-tax --> Roth conversion of %s in %s" % ( - utils.format_money(actual_amount), - self.get_name()) - return actual_amount - return 0 + def do_roth_conversion(self, amount, taxes): + if amount <= 0: return 0 + amount = min(amount, self.pretax) + self.roth += amount + self.pretax -= amount + self.total_roth_conversions += amount + taxes.record_ordinary_income(amount) + print "## Executed pre-tax --> Roth conversion of %s in %s" % ( + utils.format_money(amount), + self.get_name()) + return amount def dump_final_report(self): super(age_restricted_tax_deferred_account, self).dump_final_report() @@ -286,7 +276,7 @@ class age_restricted_roth_account(age_restricted_tax_deferred_account): free. It keeps this pile separate from the pretax pile and tries to estimate taxes.""" age_restricted_tax_deferred_account.__init__( - self, total_balance, 0, name, owner) + self, total_balance, total_balance, name, owner) # Override def has_rmd(self): @@ -297,7 +287,7 @@ class age_restricted_roth_account(age_restricted_tax_deferred_account): raise Exception("This account has no RMDs") # Override - def do_roth_conversion(self, amount): + def do_roth_conversion(self, amount, taxes): return 0 class brokerage_account(account): @@ -406,7 +396,7 @@ class brokerage_account(account): def has_roth(self): return False - def do_roth_conversion(self, amount): + def do_roth_conversion(self, amount, taxes): return 0 def dump_final_report(self): diff --git a/retire.py b/retire.py index 423e254..5fd5b12 100755 --- a/retire.py +++ b/retire.py @@ -143,9 +143,12 @@ class simulation(object): money_converted = 0 for x in self.accounts: if x.has_roth(): - amount = x.do_roth_conversion(desired_conversion_amount) + amount = x.do_roth_conversion(desired_conversion_amount, taxes) money_converted += amount desired_conversion_amount -= amount + if money_converted > 0: + return True + return False def dump_report_header(self): self.params.dump() @@ -225,25 +228,28 @@ class simulation(object): total_income += money_needed money_needed = 0 - # Maybe do some opportunistic Roth conversions. - taxes_due = taxes.approximate_taxes( - self.params.get_federal_standard_deduction(), - self.params.get_federal_ordinary_income_tax_brackets(), - self.params.get_federal_dividends_and_long_term_gains_income_tax_brackets()) - total_income = taxes.get_total_income() - tax_rate = float(taxes_due) / float(total_income) - - if tax_rate <= 0.14 and self.year <= 2035: - self.do_opportunistic_roth_conversions(taxes) - - # These conversions will affect taxes due. Recompute - # them now. + look_for_conversions = True + while True: + # Maybe do some opportunistic Roth conversions. taxes_due = taxes.approximate_taxes( self.params.get_federal_standard_deduction(), self.params.get_federal_ordinary_income_tax_brackets(), self.params.get_federal_dividends_and_long_term_gains_income_tax_brackets()) total_income = taxes.get_total_income() tax_rate = float(taxes_due) / float(total_income) + print "INCOME: %s, TAXES: %s\n" % (utils.format_money(total_income), utils.format_money(taxes_due)) + + if (look_for_conversions and + tax_rate <= 0.14 and + taxes_due < 20000 and + self.year <= 2035): + + look_for_conversions = self.do_opportunistic_roth_conversions(taxes) + # because these conversions affect taxes, spin + # once more in the loop and recompute taxes + # due and tax rate. + else: + break # Pay taxes_due by going to find more money. This is a # bit hacky since withdrawing more money to cover taxes -- 2.47.1