Adding comments and asserts.
authorScott Gasch <[email protected]>
Wed, 15 Jan 2020 01:26:14 +0000 (17:26 -0800)
committerScott Gasch <[email protected]>
Wed, 15 Jan 2020 01:26:14 +0000 (17:26 -0800)
parameters.py
retire.py
tax_brackets.py
tax_collector.py
utils.py

index 899545b235c9424c2b10a08acffcb289bf639e8e..a8eb839e8e406131713fd91d33fb3a8f193391e1 100644 (file)
@@ -60,6 +60,7 @@ class parameters(object):
         return self
 
     def with_initial_annual_expenses(self, expenses):
+        assert expenses >= 0, "You can't have negative expenses"
         self.initial_annual_expenses = expenses
         return self
 
@@ -91,7 +92,9 @@ class parameters(object):
                                                       person,
                                                       age,
                                                       amount):
+        assert age >= 60 and age <= 70, "age should be between 60 and 70"
         self.social_security_age[person] = age
+        assert amount >= 0, "Social security won't pay negative dollars"
         self.initial_social_security_dollars[person] = amount
         return self
 
@@ -102,6 +105,7 @@ class parameters(object):
         return self.initial_social_security_dollars[person]
 
     def with_federal_standard_deduction(self, deduction):
+        assert deduction >= 0, "Standard deduction should be non-negative"
         self.federal_standard_deduction_dollars = deduction
         return self
 
index 270c608e094d4a19d76a958e3708563839baf4b4..a0b28cd2036fdd0115aa23bc96a6d65da535dc8a 100755 (executable)
--- a/retire.py
+++ b/retire.py
@@ -141,107 +141,107 @@ class simulation(object):
         adjusted_lynn_annual_social_security_dollars = (
             self.params.get_initial_social_security_benefit(constants.LYNN))
 
-#        try:
-        for self.year in xrange(2020, 2080):
-            self.scott_age = self.year - 1974
-            self.lynn_age = self.year - 1964
-            self.alex_age = self.year - 2005
-
-            # Computed money needed this year based on inflated budget.
-            money_needed = adjusted_annual_expenses
-
-            # When Alex is in college, we need $50K more per year.
-            if self.alex_age > 18 and self.alex_age <= 22:
-                money_needed += 50000
-
-            self.dump_annual_header(money_needed)
-
-            # Now, figure out how to find money to pay for this year
-            # and how much of it is taxable.
-            total_income = 0
-
-            # When we reach a certain age we have to take RMDs from
-            # some accounts.  Handle that here.
-            rmds = self.do_rmd_withdrawals(taxes)
-            if rmds > 0:
-                print "## Satisfied %s of RMDs from age-restricted accounts." % utils.format_money(rmds)
-                total_income += rmds
-                money_needed -= rmds
-
-            # When we reach a certain age we are eligible for SS
-            # payments.
-            ss = self.get_social_security(adjusted_scott_annual_social_security_dollars,
-                                          adjusted_lynn_annual_social_security_dollars,
-                                          taxes)
-            if ss > 0:
-                print "## Social security paid %s" % utils.format_money(ss)
-                total_income += ss
-                money_needed -= ss
-
-            # If we still need money, try to go find it.
-            if money_needed > 0:
-                self.go_find_money(money_needed, taxes)
-                total_income += money_needed
-                money_needed = 0
-
-            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.
+        try:
+            for self.year in xrange(2020, 2080):
+                self.scott_age = self.year - 1974
+                self.lynn_age = self.year - 1964
+                self.alex_age = self.year - 2005
+
+                # Computed money needed this year based on inflated budget.
+                money_needed = adjusted_annual_expenses
+
+                # When Alex is in college, we need $50K more per year.
+                if self.alex_age > 18 and self.alex_age <= 22:
+                    money_needed += 50000
+
+                self.dump_annual_header(money_needed)
+
+                # Now, figure out how to find money to pay for this year
+                # and how much of it is taxable.
+                total_income = 0
+
+                # When we reach a certain age we have to take RMDs from
+                # some accounts.  Handle that here.
+                rmds = self.do_rmd_withdrawals(taxes)
+                if rmds > 0:
+                    print "## Satisfied %s of RMDs from age-restricted accounts." % utils.format_money(rmds)
+                    total_income += rmds
+                    money_needed -= rmds
+
+                # When we reach a certain age we are eligible for SS
+                # payments.
+                ss = self.get_social_security(adjusted_scott_annual_social_security_dollars,
+                                              adjusted_lynn_annual_social_security_dollars,
+                                              taxes)
+                if ss > 0:
+                    print "## Social security paid %s" % utils.format_money(ss)
+                    total_income += ss
+                    money_needed -= ss
+
+                # If we still need money, try to go find it.
+                if money_needed > 0:
+                    self.go_find_money(money_needed, taxes)
+                    total_income += money_needed
+                    money_needed = 0
+
+                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
+                # will, in turn, cause taxable income.  But I think taxes
+                # are low enough and this simulation is rough enough that
+                # we can ignore this.
+                if taxes_due > 0:
+                    print "## Estimated federal tax due: %s (this year's tax rate=%s)" % (
+                        utils.format_money(taxes_due),
+                        utils.format_rate(tax_rate))
+                    self.go_find_money(taxes_due, taxes)
                 else:
-                    break
-
-            # Pay taxes_due by going to find more money.  This is a
-            # bit hacky since withdrawing more money to cover taxes
-            # will, in turn, cause taxable income.  But I think taxes
-            # are low enough and this simulation is rough enough that
-            # we can ignore this.
-            if taxes_due > 0:
-                print "## Estimated federal tax due: %s (this year's tax rate=%s)" % (
-                    utils.format_money(taxes_due),
-                    utils.format_rate(tax_rate))
-                self.go_find_money(taxes_due, taxes)
-            else:
-                print "## No federal taxes due this year!"
-            taxes.record_taxes_paid_and_reset_for_next_year(taxes_due)
-
-            # Inflation and appreciation:
-            #   * Cost of living increases
-            #   * Social security benefits increase
-            #   * Tax brackets are adjusted for inflation
-            inflation_multiplier = self.params.get_average_inflation_multiplier()
-            adjusted_annual_expenses *= inflation_multiplier
-            for x in self.accounts:
-                x.appreciate(self.params.get_average_investment_return_multiplier())
-            if self.scott_age >= self.params.get_initial_social_security_age(constants.SCOTT):
-                adjusted_scott_annual_social_security_dollars *= self.params.get_average_social_security_multiplier()
-            if self.lynn_age >= self.params.get_initial_social_security_age(constants.LYNN):
-                adjusted_lynn_annual_social_security_dollars *= self.params.get_average_social_security_multiplier()
-
-            self.params.get_federal_ordinary_income_tax_brackets().adjust_with_multiplier(inflation_multiplier)
-            self.params.get_federal_dividends_and_long_term_gains_income_tax_brackets().adjust_with_multiplier(inflation_multiplier)
-#        except:
-#            print "Ran out of money!!!"
-#            pass
-
-#        finally:
-        self.dump_final_report(taxes)
+                    print "## No federal taxes due this year!"
+                taxes.record_taxes_paid_and_reset_for_next_year(taxes_due)
+
+                # Inflation and appreciation:
+                #   * Cost of living increases
+                #   * Social security benefits increase
+                #   * Tax brackets are adjusted for inflation
+                inflation_multiplier = self.params.get_average_inflation_multiplier()
+                adjusted_annual_expenses *= inflation_multiplier
+                for x in self.accounts:
+                    x.appreciate(self.params.get_average_investment_return_multiplier())
+                if self.scott_age >= self.params.get_initial_social_security_age(constants.SCOTT):
+                    adjusted_scott_annual_social_security_dollars *= self.params.get_average_social_security_multiplier()
+                if self.lynn_age >= self.params.get_initial_social_security_age(constants.LYNN):
+                    adjusted_lynn_annual_social_security_dollars *= self.params.get_average_social_security_multiplier()
+
+                self.params.get_federal_ordinary_income_tax_brackets().adjust_with_multiplier(inflation_multiplier)
+                self.params.get_federal_dividends_and_long_term_gains_income_tax_brackets().adjust_with_multiplier(inflation_multiplier)
+        except:
+            print "Ran out of money!!!"
+            pass
+
+        finally:
+            self.dump_final_report(taxes)
 
 # main
 params = parameters().with_default_values()
index 29b007992295e3307477db25c4b91906a0e833c6..c64cf2b842086b51bc1259c25852997c31663960 100644 (file)
@@ -13,10 +13,12 @@ class tax_brackets:
             (threshold, rate) = self.get_bracket_for_income(income)
             taxes_due += (income - threshold) * rate
             income = threshold
+        assert taxes_due >= 0, "Somehow computed negative tax bill?!"
         return taxes_due
 
     def get_bracket_for_income(self, income):
         """Return the bracket that the last dollar of income was in."""
+        assert income > 0, "Income should be >0 to be taxed"
         rate = 0.0
         threshold = 0
         for bracket in self.brackets:
@@ -32,6 +34,7 @@ class tax_brackets:
 
     def get_bracket_above_income(self, income):
         """Return the next bracket above the one activated by income."""
+        assert income > 0, "Income should be >0 to be taxed"
         rate = 1.0
         threshold = None
 
@@ -48,12 +51,14 @@ class tax_brackets:
 
     def get_bracket_below_income(self, income):
         """Return the next bracket below the one activated by income."""
+        assert income > 0, "Income should be >0 to be taxed"
         bracket = self.get_bracket_for_income(income)
         income = bracket[0]
         return self.get_bracket_for_income(income)
 
     def get_effective_tax_rate_for_income(self, income):
         """Compute and return the effective tax rate for an income."""
+        if income <= 0: return 0
         tax_bill = self.compute_taxes_for_income(income)
         return float(tax_bill) / income
 
@@ -64,6 +69,7 @@ class tax_brackets:
                 bracket[0] *= multiplier
 
     def dump(self):
+        """Print out the tax brackets we're using in here."""
         for x in self.brackets:
             print "{:<20} -> {:<3}".format(utils.format_money(x[0]),
                                            utils.format_rate(x[1]))
index 09dc3c786bc8445ebcda581e0e033eb5588ed450..248c944c1a8fc771befc357201540aa5ac0abafb 100644 (file)
@@ -19,26 +19,32 @@ class tax_collector(object):
 
     def record_taxes_paid_and_reset_for_next_year(self,
                                                   taxes_paid):
+        assert taxes_paid >= 0, "You can't pay negative taxes"
         self.total_tax_bill += taxes_paid
         self.total_aggregate_income += self.get_total_income()
+        assert self.total_aggregate_income >= 0, "Accumulator should be >= 0"
         self.ordinary_income = 0
         self.short_term_gains = 0
         self.dividends_and_long_term_gains = 0
         self.roth_income = 0
 
     def record_ordinary_income(self, amount):
+        assert amount >= 0, "Income should be non-negative"
         self.ordinary_income += amount
         self.total_ordinary_income += amount
 
     def record_short_term_gain(self, amount):
+        assert amount >= 0, "Income should be non-negative"
         self.short_term_gains += amount
         self.total_short_term_gains += amount
 
     def record_dividend_or_long_term_gain(self, amount):
+        assert amount >= 0, "Income should be non-negative"
         self.dividends_and_long_term_gains += amount
         self.total_dividends_and_long_term_gains += amount
 
     def record_roth_income(self, amount):
+        assert amount >= 0, "Income should be non-negative"
         self.roth_income += amount
         self.total_roth_income += amount
 
@@ -46,6 +52,7 @@ class tax_collector(object):
                           standard_deduction,
                           ordinary_income_tax_brackets,
                           dividends_and_long_term_gains_brackets):
+        assert standard_deduction >= 0, "Standard deduction should be non-negative"
         taxes_due = 0
 
         # Handle ordinary income:
@@ -62,6 +69,7 @@ class tax_collector(object):
             self.dividends_and_long_term_gains)
 
         # Assume Roth money is still available tax free in the future.
+        assert taxes_due >= 0, "Computed negative taxes?!"
         return taxes_due
 
     def how_many_more_dollars_can_we_earn_without_changing_tax_rate(
index a9cd2f094d62b2ecd04a50630de762c24109860d..ce664204666e749bb1343fa86aa945a28004fbc5 100644 (file)
--- a/utils.py
+++ b/utils.py
@@ -1,13 +1,17 @@
 
 # Global helper functions
 def truncate(n, decimals=2):
+    """Truncate a float to a particular number of decimals."""
+    assert decimals > 0 and decimals < 10, "Decimals is weird"
     multiplier = 10 ** decimals
     return int(n * multiplier) / multiplier
 
 def format_money(number):
+    """Format a monetary amount with a $ and comma thousands separators."""
     return ("${:,}".format(truncate(number)))
 
 def format_rate(rate):
+    """Format a multiplier nee rate to look nice."""
     if rate >= 1.0:
         return format_rate(rate - 1.0)
     else: