From: Scott Gasch Date: Sun, 19 Jan 2020 16:47:41 +0000 (-0800) Subject: Creates a parameters subclass that chooses random runs of years from X-Git-Url: https://wannabe.guru.org/gitweb/?a=commitdiff_plain;h=6615df6ed9352700798a62a3c1fa5f0235a4a44d;p=retire.git Creates a parameters subclass that chooses random runs of years from US market history to replay when it reports inflation, social security and investment returns back to the main driver program. --- diff --git a/parameters.py b/parameters.py index 989d526..1d6443c 100644 --- a/parameters.py +++ b/parameters.py @@ -2,8 +2,43 @@ import constants import utils from tax_brackets import tax_brackets from money import money +from numpy import random 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.""" @@ -61,7 +96,7 @@ class parameters(object): 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 @@ -69,31 +104,31 @@ class parameters(object): 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 - 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 - 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 - 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" @@ -106,7 +141,7 @@ class parameters(object): 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 @@ -114,15 +149,15 @@ class parameters(object): 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 - 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 @@ -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." + +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 + diff --git a/retire.py b/retire.py index fa86bda..578869b 100755 --- a/retire.py +++ b/retire.py @@ -4,7 +4,7 @@ import sys 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 @@ -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.params.report_year(self.year) # 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 -params = parameters().with_default_values() +#params = mutable_default_parameters() +params = mutable_dynamic_historical_parameters() accounts = secrets.accounts s = simulation(params, accounts) s.run() diff --git a/tax_collector.py b/tax_collector.py index 455d8b5..7680764 100644 --- a/tax_collector.py +++ b/tax_collector.py @@ -30,22 +30,22 @@ class tax_collector(object): 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): - 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): - 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): - 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 diff --git a/utils.py b/utils.py index 97b9819..eddefdb 100644 --- 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)) + +def convert_rate_to_multiplier(rate): + return 1.0 + (rate / 100)