Creates a parameters subclass that chooses random runs of years from
authorScott Gasch <[email protected]>
Sun, 19 Jan 2020 16:47:41 +0000 (08:47 -0800)
committerScott Gasch <[email protected]>
Sun, 19 Jan 2020 16:47:41 +0000 (08:47 -0800)
US market history to replay when it reports inflation, social security
and investment returns back to the main driver program.

parameters.py
retire.py
tax_collector.py
utils.py

index 989d5265468cce99ea9f64589d4ba003cfbbd390..1d6443c40250d5b8ad76c397ce60160a042f873a 100644 (file)
@@ -2,8 +2,43 @@ import constants
 import utils
 from tax_brackets import tax_brackets
 from money import money
 import utils
 from tax_brackets import tax_brackets
 from money import money
+from numpy import random
 
 class parameters(object):
 
 class parameters(object):
+    def get_initial_annual_expenses(self):
+        pass
+
+    def get_average_inflation_multiplier(self):
+        pass
+
+    def get_average_social_security_multiplier(self):
+        pass
+
+    def get_average_investment_return_multiplier(self):
+        pass
+
+    def get_initial_social_security_age(self, person):
+        pass
+
+    def get_initial_social_security_benefit(self, person):
+        pass
+
+    def get_federal_standard_deduction(self):
+        pass
+
+    def get_federal_ordinary_income_tax_brackets(self):
+        pass
+
+    def get_federal_dividends_and_long_term_gains_income_tax_brackets(self):
+        pass
+
+    def dump(self):
+        pass
+
+    def report_year(self, year):
+        pass
+
+class mutable_default_parameters(parameters):
     """A container to hold the initial states of several simulation
        parameters.  Play with them as you see fit and see what happens."""
 
     """A container to hold the initial states of several simulation
        parameters.  Play with them as you see fit and see what happens."""
 
@@ -61,7 +96,7 @@ class parameters(object):
             constants.CURRENT_LONG_TERM_GAIN_FEDERAL_TAX_BRACKETS)
         return self
 
             constants.CURRENT_LONG_TERM_GAIN_FEDERAL_TAX_BRACKETS)
         return self
 
-    def with_initial_annual_expenses(self, expenses):
+    def set_initial_annual_expenses(self, expenses):
         assert expenses >= 0, "You can't have negative expenses"
         self.initial_annual_expenses = expenses
         return self
         assert expenses >= 0, "You can't have negative expenses"
         self.initial_annual_expenses = expenses
         return self
@@ -69,31 +104,31 @@ class parameters(object):
     def get_initial_annual_expenses(self):
         return self.initial_annual_expenses
 
     def get_initial_annual_expenses(self):
         return self.initial_annual_expenses
 
-    def with_average_inflation_multiplier(self, multiplier):
+    def set_average_inflation_multiplier(self, multiplier):
         self.inflation_multiplier = multiplier
         return self
 
     def get_average_inflation_multiplier(self):
         return self.inflation_multiplier
 
         self.inflation_multiplier = multiplier
         return self
 
     def get_average_inflation_multiplier(self):
         return self.inflation_multiplier
 
-    def with_average_social_security_multiplier(self, multiplier):
+    def set_average_social_security_multiplier(self, multiplier):
         self.social_security_multiplier = multiplier
         return self
 
     def get_average_social_security_multiplier(self):
         return self.social_security_multiplier
 
         self.social_security_multiplier = multiplier
         return self
 
     def get_average_social_security_multiplier(self):
         return self.social_security_multiplier
 
-    def with_average_investment_return_multiplier(self, multiplier):
+    def set_average_investment_return_multiplier(self, multiplier):
         self.investment_multiplier = multiplier
         return self
 
     def get_average_investment_return_multiplier(self):
         return self.investment_multiplier
 
         self.investment_multiplier = multiplier
         return self
 
     def get_average_investment_return_multiplier(self):
         return self.investment_multiplier
 
-    def with_initial_social_security_age_and_benefits(self,
-                                                      person,
-                                                      age,
-                                                      amount):
+    def set_initial_social_security_age_and_benefits(self,
+                                                     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"
         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"
@@ -106,7 +141,7 @@ class parameters(object):
     def get_initial_social_security_benefit(self, person):
         return self.initial_social_security_dollars[person]
 
     def get_initial_social_security_benefit(self, person):
         return self.initial_social_security_dollars[person]
 
-    def with_federal_standard_deduction(self, deduction):
+    def set_federal_standard_deduction(self, deduction):
         assert deduction >= 0, "Standard deduction should be non-negative"
         self.federal_standard_deduction_dollars = deduction
         return self
         assert deduction >= 0, "Standard deduction should be non-negative"
         self.federal_standard_deduction_dollars = deduction
         return self
@@ -114,15 +149,15 @@ class parameters(object):
     def get_federal_standard_deduction(self):
         return self.federal_standard_deduction_dollars
 
     def get_federal_standard_deduction(self):
         return self.federal_standard_deduction_dollars
 
-    def with_federal_ordinary_income_tax_brackets(self, brackets):
+    def set_federal_ordinary_income_tax_brackets(self, brackets):
         self.federal_ordinary_income_tax_brackets = brackets
         return self
 
     def get_federal_ordinary_income_tax_brackets(self):
         return self.federal_ordinary_income_tax_brackets
 
         self.federal_ordinary_income_tax_brackets = brackets
         return self
 
     def get_federal_ordinary_income_tax_brackets(self):
         return self.federal_ordinary_income_tax_brackets
 
-    def with_federal_dividends_and_long_term_gains_income_tax_brackets(self,
-                                                                       brackets):
+    def set_federal_dividends_and_long_term_gains_income_tax_brackets(self,
+                                                                      brackets):
         self.federal_dividends_and_long_term_gains_brackets = brackets;
         return self
 
         self.federal_dividends_and_long_term_gains_brackets = brackets;
         return self
 
@@ -155,3 +190,143 @@ class parameters(object):
         self.federal_dividends_and_long_term_gains_brackets.dump()
         print "  ]"
         print "  We assume Roth money continues to be available tax-free."
         self.federal_dividends_and_long_term_gains_brackets.dump()
         print "  ]"
         print "  We assume Roth money continues to be available tax-free."
+
+class mutable_dynamic_historical_parameters(mutable_default_parameters):
+    def __init__(self):
+        self.selection_index = None
+        self.selection_duration = 0
+        self.historical_tuples = [
+           # year  stock  infl  bond
+            (2020,  3.14,  1.90,  1.54),
+            (2019, 31.49,  1.70,  2.15),
+            (2018, -4.38,  2.40,  2.33),
+            (2017, 21.83,  2.10,  1.19),
+            (2016, 11.96,  1.30,  0.61),
+            (2015,  1.38,  0.10,  0.32),
+            (2014, 13.69,  1.60,  0.12),
+            (2013, 32.39,  1.50,  0.13),
+            (2012, 16.00,  2.10,  0.17),
+            (2011,  2.11,  3.20,  0.18),
+            (2010, 15.06,  1.60,  0.32),
+            (2009, 26.46, -0.40,  0.47),
+            (2008,-37.00,  3.80,  1.83),
+            (2007,  5.49,  2.80,  4.53),
+            (2006, 15.79,  3.20,  4.94),
+            (2005,  4.91,  3.40,  3.62),
+            (2004, 10.88,  2.70,  1.89),
+            (2003, 28.68,  2.30,  1.24),
+            (2002,-22.10,  1.60,  2.00),
+            (2001,-11.89,  2.80,  3.49),
+            (2000, -9.10,  3.40,  6.11),
+            (1999, 21.04,  2.20,  5.08),
+            (1998, 28.58,  1.50,  5.05),
+            (1997, 33.36,  2.30,  5.63),
+            (1996, 22.96,  3.00,  5.52),
+            (1995, 37.58,  2.80,  5.94),
+            (1994,  1.32,  2.60,  5.32),
+            (1993, 10.08,  3.00,  3.43),
+            (1992,  7.62,  3.00,  3.89),
+            (1991, 30.47,  4.20,  5.86),
+            (1990, -3.10,  5.40,  7.89),
+            (1989, 31.69,  4.82,  8.54),
+            (1988, 16.61,  4.14,  7.65),
+            (1987, 15.25,  3.65,  6.77),
+            (1986, 18.67,  0.86,  6.45),
+            (1985, 31.73,  3.56,  8.42),
+            (1984,  6.27,  4.32, 10.91),
+            (1983, 22.56,  3.21,  9.58),
+            (1982, 21.55,  6.16, 12.27),
+            (1981, -4.91, 10.32, 14.80),
+            (1980, 32.42, 13.50, 12.00),
+            (1979, 18.44, 11.35, 10.65),
+            (1978,  6.65,  7.59,  7.00),
+            (1977, -7.18,  6.50,  6.08),
+            (1976, 23.84,  5.76,  5.88),
+            (1975, 37.20,  9.13,  6.78),
+            (1974,-26.47, 11.04,  8.20),
+            (1973,-14.66,  6.22,  7.32),
+            (1972, 18.90,  3.21,  4.95),
+            (1971, 14.31,  4.38,  4.89),
+            (1970, 14.01,  5.72,  6.90),
+            (1969, -8.50,  5.46,  7.12),
+            (1968, 11.06,  4.19,  5.69),
+            (1967, 23.98,  3.09,  4.88),
+            (1966,-10.06,  2.86,  5.20),
+            (1965, 12.45,  1.61,  4.15),
+            (1964, 16.48,  1.31,  3.85),
+            (1963, 22.80,  1.32,  3.36),
+            (1962, -8.73,  1.00,  2.90),
+            (1961, 26.89,  1.01,  2.60),
+            (1960,  0.47,  1.72,  3.24),
+            (1959, 11.96,  0.69,  3.83),
+            (1958, 43.36,  2.85,  3.5),
+            (1957,-10.78,  3.31,  3.6),
+            (1956,  6.56,  1.49,  2.9),
+            (1955, 31.56, -0.37,  2.5),
+            (1954, 52.62,  0.75,  2.37),
+            (1953, -0.99,  0.75,  2.71),
+            (1952, 18.37,  1.92,  2.19),
+            (1951, 24.02,  7.88,  2.00),
+            (1950, 31.71,  1.26,  1.98),
+            (1949, 18.79, -1.24,  2.21),
+            (1948,  5.50,  8.07,  2.40),
+            (1947,  5.71, 14.36,  2.01),
+            (1946, -8.07,  8.33,  1.64),
+            (1945, 36.44,  2.27,  1.67),
+            (1944, 19.75,  1.73,  1.86),
+            (1943, 25.90,  6.13,  2.05),
+            (1942, 20.34, 10.88,  2.36),
+            (1941,-11.59,  5.00,  2.10),
+            (1940, -9.78,  0.72,  2.76),
+            (1939, -0.41, -1.42,  2.76),
+            (1938, 31.12, -2.08,  2.94),
+            (1937,-35.03,  3.60,  2.76),
+            (1936, 33.92,  1.46,  3.39),
+            (1935, 47.67,  2.24,  2.76),
+            (1934, -1.44,  3.08,  2.76),
+            (1933, 53.99, -5.11,  4.71),
+            (1932, -8.19, -9.87,  4.71),
+            (1931,-43.34, -8.90,  3.99),
+            (1930,-24.90, -2.34,  4.71),
+            (1929, -8.42,  0.00,  4.27) ]
+        mutable_default_parameters.__init__(self)
+
+    def report_year(self, year):
+        if self.selection_index is None:
+            self.selection_index = random.randint(0,
+                                                  len(self.historical_tuples) - 1)
+            self.selection_duration = 1
+
+        t = self.historical_tuples[self.selection_index]
+        sim_year = t[0]
+        stock_return = t[1]
+        inflation = t[2]
+        bond_return = t[3]
+        print
+        print "## REPLAY YEAR %d" % sim_year
+        inflation_multiplier = utils.convert_rate_to_multiplier(inflation)
+        self.set_average_inflation_multiplier(inflation_multiplier)
+        print "## INFLATION is %s (%f)" % (
+            utils.format_rate(inflation_multiplier),
+            inflation_multiplier)
+        ss_multiplier = inflation_multiplier
+        if ss_multiplier >= 1.0:
+            ss_multiplier -= 1.0
+            ss_multiplier *= 0.6666
+            ss_multiplier += 1.0
+        self.set_average_social_security_multiplier(ss_multiplier)
+        print "## SS is %s (%f)" % (
+            utils.format_rate(self.get_average_social_security_multiplier()),
+            ss_multiplier)
+
+        # Assumes 50/50 Stocks/Bonds portfolio
+        our_return = (stock_return * 0.5 +
+                      bond_return * 0.5)
+        self.set_average_investment_return_multiplier(utils.convert_rate_to_multiplier(our_return))
+        print "## 50/50 INVESTMENTS RETURN is %s" % utils.format_rate(self.get_average_investment_return_multiplier())
+        self.selection_duration += 1
+        self.selection_index -= 1
+        if self.selection_index < 0 or random.randint(1, 100) < 20:
+            self.selection_index = None
+            self.selection_duration = None
+
index fa86bdaaf2e641f38303960a40069c4fb1e98b8b..578869b402e55b04ccfec4e1e7e1e72b20cf9265 100755 (executable)
--- a/retire.py
+++ b/retire.py
@@ -4,7 +4,7 @@ import sys
 
 from accounts import *
 import constants
 
 from accounts import *
 import constants
-from parameters import parameters
+from parameters import *
 from tax_brackets import tax_brackets
 from tax_collector import tax_collector
 import utils
 from tax_brackets import tax_brackets
 from tax_collector import tax_collector
 import utils
@@ -152,6 +152,7 @@ class simulation(object):
                 self.scott_age = self.year - 1974
                 self.lynn_age = self.year - 1964
                 self.alex_age = self.year - 2005
                 self.scott_age = self.year - 1974
                 self.lynn_age = self.year - 1964
                 self.alex_age = self.year - 2005
+                self.params.report_year(self.year)
 
                 # Computed money needed this year based on inflated budget.
                 money_needed = money(adjusted_annual_expenses)
 
                 # Computed money needed this year based on inflated budget.
                 money_needed = money(adjusted_annual_expenses)
@@ -251,7 +252,8 @@ class simulation(object):
             self.dump_final_report(taxes)
 
 # main
             self.dump_final_report(taxes)
 
 # main
-params = parameters().with_default_values()
+#params = mutable_default_parameters()
+params = mutable_dynamic_historical_parameters()
 accounts = secrets.accounts
 s = simulation(params, accounts)
 s.run()
 accounts = secrets.accounts
 s = simulation(params, accounts)
 s.run()
index 455d8b5eceff893e02c5cd6277a034079ccaa85f..76807644a2b25c1d370cd9bfc062a4089e7a60d4 100644 (file)
@@ -30,22 +30,22 @@ class tax_collector(object):
         self.roth_income = money(0)
 
     def record_ordinary_income(self, amount):
         self.roth_income = money(0)
 
     def record_ordinary_income(self, amount):
-        assert amount >= 0, "Income should be non-negative"
+        assert amount >= 0, "Income should be non-negative; got %s" % amount
         self.ordinary_income += amount
         self.total_ordinary_income += amount
 
     def record_short_term_gain(self, amount):
         self.ordinary_income += amount
         self.total_ordinary_income += amount
 
     def record_short_term_gain(self, amount):
-        assert amount >= 0, "Income should be non-negative"
+        assert amount >= 0, "Income should be non-negative; got %s" % amount
         self.short_term_gains += amount
         self.total_short_term_gains += amount
 
     def record_dividend_or_long_term_gain(self, amount):
         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"
+        assert amount >= 0, "Income should be non-negative; got %s" % amount
         self.dividends_and_long_term_gains += amount
         self.total_dividends_and_long_term_gains += amount
 
     def record_roth_income(self, amount):
         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"
+        assert amount >= 0, "Income should be non-negative; got %s" % amount
         self.roth_income += amount
         self.total_roth_income += amount
 
         self.roth_income += amount
         self.total_roth_income += amount
 
index 97b98199c1ac2786714d8a2f6f32a178e337fed7..eddefdb7d93ab7427ecbbf212fb38037bd9425f2 100644 (file)
--- a/utils.py
+++ b/utils.py
@@ -12,3 +12,6 @@ def format_rate(rate):
         return format_rate(rate - 1.0)
     else:
         return "{:<}%".format(round(rate * 100, 3))
         return format_rate(rate - 1.0)
     else:
         return "{:<}%".format(round(rate * 100, 3))
+
+def convert_rate_to_multiplier(rate):
+    return 1.0 + (rate / 100)