-#!/usr/local/bin/python
-
-import sys
-
-from accounts import *
-import constants
-from parameters import parameters
-from tax_brackets import tax_brackets
-from tax_collector import tax_collector
-import utils
-import secrets
-from money import money
-
-class simulation(object):
- def __init__(self, parameters, accounts):
- """Initialize simulation parameters and starting account balances"""
- self.params = parameters
- self.accounts = accounts
- self.lynn_age = 0
- self.scott_age = 0
- self.alex_age = 0
- self.year = 0
-
- def do_rmd_withdrawals(self, taxes):
- """Determine if any account that has RMDs will require someone to
- take money out of it this year and, if so, how much. Then do
- the withdrawal."""
- total_withdrawn = money(0)
- for x in self.accounts:
- if x.has_rmd() and x.get_owner() == constants.SCOTT:
- rmd = x.do_rmd_withdrawal(self.scott_age, taxes)
- total_withdrawn += rmd
- elif x.has_rmd() and x.get_owner() == constants.LYNN:
- rmd = x.do_rmd_withdrawal(self.lynn_age, taxes)
- total_withdrawn += rmd
- return total_withdrawn
-
- def go_find_money(self, amount_needed, taxes):
- """Look through accounts and try to find amount using some heuristics
- about where to withdraw money first."""
-
- # Try brokerage accounts first
- for x in self.accounts:
- if not x.is_age_restricted():
- amount_to_withdraw = min(amount_needed, x.get_balance())
- print "## Withdrawing %s from %s" % (amount_to_withdraw,
- x.get_name())
- x.withdraw(amount_to_withdraw, taxes)
- amount_needed -= amount_to_withdraw
- if amount_needed <= 0: return
-
- # Next try age restircted accounts
- for x in self.accounts:
- if (x.is_age_restricted() and
- x.has_rmd() and
- ((x.belongs_to_lynn() and self.lynn_age > 60) or
- (x.belongs_to_scott() and self.scott_age > 60))):
-
- amount_to_withdraw = min(amount_needed, x.get_balance())
- print "## Withdrawing %s from %s" % (amount_to_withdraw,
- x.get_name())
- x.withdraw(amount_to_withdraw, taxes)
- amount_needed -= amount_to_withdraw
- if amount_needed <= 0: return
-
- # Last try Roth accounts
- for x in self.accounts:
- if (x.is_age_restricted() and
- x.has_roth() and
- ((x.belongs_to_lynn() and self.lynn_age > 60) or
- (x.belongs_to_scott() and self.scott_age > 60))):
-
- amount_to_withdraw = min(amount_needed, x.get_balance())
- print "## Withdrawing %s from %s" % (amount_to_withdraw,
- x.get_name())
- x.withdraw(amount_to_withdraw, taxes)
- amount_needed -= amount_to_withdraw
- if amount_needed <= 0: return
- raise Exception("Unable to find enough money this year, still need %s more!" % amount_needed)
-
- def get_social_security(self,
- scott_annual_social_security_dollars,
- lynn_annual_social_security_dollars,
- taxes):
- """Figure out if Scott and/or Lynn are taking social security at
- their present age in the simulation and, if so, how much their
- annual benefit should be."""
- total_benefit = money(0)
- if self.scott_age >= self.params.get_initial_social_security_age(constants.SCOTT):
- total_benefit += scott_annual_social_security_dollars
- taxes.record_ordinary_income(scott_annual_social_security_dollars)
- if self.lynn_age >= self.params.get_initial_social_security_age(constants.LYNN):
- total_benefit += lynn_annual_social_security_dollars
- taxes.record_ordinary_income(lynn_annual_social_security_dollars)
- return total_benefit
-
- def do_opportunistic_roth_conversions(self, taxes):
- """Roll over money from pretax 401(k)s or IRAs into Roth."""
- desired_conversion_amount = (
- taxes.how_many_more_dollars_can_we_earn_without_changing_tax_rate(
- self.params.get_federal_ordinary_income_tax_brackets()))
- if desired_conversion_amount > 0:
- money_converted = 0
- for x in self.accounts:
- if x.has_roth():
- 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()
-
- def dump_annual_header(self, money_needed):
- print "\nYear: %d, estimated annual expenses %s ---------------\n" % (
- self.year,
- money_needed)
- print "Scott is %d, Lynn is %d and Alex is %d.\n" % (
- self.scott_age, self.lynn_age, self.alex_age)
-
- # Print out how much money is in each account + overall net worth.
- total = 0
- for x in self.accounts:
- total += x.get_balance()
- print "{:<50}: {:>14}".format(x.get_name(), x.get_balance())
- print "{:<50}: {:>14}\n".format("TOTAL", total)
-
- def dump_final_report(self, taxes):
- print "\nAGGREGATE STATS FINAL REPORT:"
- for x in self.accounts:
- x.dump_final_report()
- taxes.dump_final_report()
-
- def run(self):
- """Run the simulation!"""
- self.dump_report_header()
-
- taxes = tax_collector()
- adjusted_annual_expenses = self.params.get_initial_annual_expenses()
- adjusted_scott_annual_social_security_dollars = (
- self.params.get_initial_social_security_benefit(constants.SCOTT))
- 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 = money(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 = money(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." % 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" % 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
- tax_limit = money(25000)
- 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)
-
- if (look_for_conversions and
- tax_rate <= 0.14 and
- taxes_due < tax_limit and
- self.year <= 2036):
-
- 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)" % (
- 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 Exception as e:
- print "Exception: %s" % e
- print "Ran out of money!!!"
- pass
-
- finally:
- self.dump_final_report(taxes)
-
-# main
-params = parameters().with_default_values()
-accounts = secrets.accounts
-s = simulation(params, accounts)
-s.run()
+#!/usr/bin/env python3
+
+from typing import List
+
+import bootstrap
+import config
+
+import account
+from person import Person
+import result_summarizer
+import returns_and_expenses as rai
+import simulation
+import simulation_params
+import trials
+from type.money import Money
+
+
+args = config.add_commandline_args(
+ 'Retire!',
+ 'Args that drive the retirement simulator',
+)
+args.add_argument(
+ '--num_trials',
+ '-n',
+ type=int,
+ default=1,
+ help='How many simulations to run'
+)
+args.add_argument(
+ '--verbosity',
+ '-v',
+ type=int,
+ choices=range(0, 3),
+ default=1,
+ help='How verbose should I be?',
+)
+
+
+# This defines the set of account and their initial balances.
+accounts: List[account.Account] = [
+
+ # Your accounts here....
+
+]
+
+
+def main() -> None:
+ params = simulation_params.DEFAULT_SIMULATION_PARAMS
+ params.initial_account_states = accounts
+ params.returns_and_expenses = rai.GaussianRae() #rai.HistoricalRaE()
+
+ if config.config['num_trials'] > 1:
+ with simulation.ReportColorizer():
+ print(params)
+ results = trials.run_multiple_trials(
+ params=params,
+ num_trials=config.config['num_trials']
+ )
+ print
+ result_summarizer.summarize_results(results)
+ else:
+ sim = simulation.Simulation(params)
+ results = sim.simulate(
+ simulation.Verbosity(config.config['verbosity'])
+ )
+ if not results.success:
+ print("Unsuccessful")
+ return 1
+ return 0
+
+
+if __name__ == '__main__':
+ main()