import constants
import utils
+from money import *
class account(object):
"""This defines an account base class which inherits from object, the
tries to estimate taxes."""
assert total_balance >= 0, "Initial balance must be >= 0"
assert roth_amount_subset <= total_balance, "Roth subset too high!"
- self.roth = roth_amount_subset
- self.pretax = total_balance - roth_amount_subset
- self.total_roth_withdrawals = 0
- self.total_pretax_withdrawals = 0
- self.total_investment_gains = 0
- self.total_roth_conversions = 0
+ self.roth = money(roth_amount_subset)
+ self.pretax = total_balance - money(roth_amount_subset)
+ self.total_roth_withdrawals = money(0)
+ self.total_pretax_withdrawals = money(0)
+ self.total_investment_gains = money(0)
+ self.total_roth_conversions = money(0)
self.initial_balance = self.get_balance()
assert self.initial_balance >= 0, "Bad initial balance"
balance = self.get_balance()
if balance < amount: raise Exception("Insufficient funds")
- ratio = float(self.roth) / float(balance)
+ ratio = self.roth / balance
roth_part = amount * ratio
pretax_part = amount - roth_part
if roth_part > 0:
self.roth -= roth_part
self.total_roth_withdrawals += roth_part
- print "## Satisfying %s from %s with Roth money." % (utils.format_money(roth_part), self.name)
+ print "## Satisfying %s from %s with Roth money." % (roth_part,
+ self.name)
taxes.record_roth_income(roth_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)
+ print "## Satisfying %s from %s with pre-tax money." % (pretax_part,
+ self.name)
taxes.record_ordinary_income(pretax_part)
def deposit(self, 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())
+ amount, self.get_name())
return amount
def dump_final_report(self):
super(age_restricted_tax_deferred_account, self).dump_final_report()
print " {:<50}: {:>14}".format("Initial balance",
- utils.format_money(self.initial_balance))
+ self.initial_balance)
print " {:<50}: {:>14}".format("Ending balance",
- utils.format_money(self.get_balance()))
+ self.get_balance())
print " {:<50}: {:>14}".format("Total investment gains",
- utils.format_money(self.total_investment_gains))
+ self.total_investment_gains)
print " {:<50}: {:>14}".format("Total Roth withdrawals",
- utils.format_money(self.total_roth_withdrawals))
+ self.total_roth_withdrawals)
print " {:<50}: {:>14}".format("Total pre-tax withdrawals",
- utils.format_money(self.total_pretax_withdrawals))
+ self.total_pretax_withdrawals)
print " {:<50}: {:>14}".format("Total pre-tax converted to Roth",
- utils.format_money(self.total_roth_conversions))
+ self.total_roth_conversions)
class age_restricted_roth_account(age_restricted_tax_deferred_account):
"""This is an object to represent a Roth account like a Roth IRA. All
are taxed at 15% and as ordinary income, respectively."""
assert total_balance >= 0, "Initial balance must be >= 0"
assert cost_basis <= total_balance, "Bad initial cost basis"
- self.cost_basis = float(cost_basis)
- self.short_term_gain = 0.0
- self.long_term_gain = float(total_balance - cost_basis)
- self.total_cost_basis_withdrawals = 0
- self.total_long_term_gain_withdrawals = 0
- self.total_short_term_gain_withdrawals = 0
- self.total_investment_gains = 0
+ self.cost_basis = money(cost_basis)
+ self.short_term_gain = money(0)
+ self.long_term_gain = money(total_balance - cost_basis)
+ self.total_cost_basis_withdrawals = money(0)
+ self.total_long_term_gain_withdrawals = money(0)
+ self.total_short_term_gain_withdrawals = money(0)
+ self.total_investment_gains = money(0)
self.initial_balance = self.get_balance()
# Call the base class' c'tor as well to set up the name.
if self.cost_basis >= invested_capital_part:
self.cost_basis -= invested_capital_part
self.total_cost_basis_withdrawals += invested_capital_part
- print "## Satisfying %s from %s as cost basis." % (utils.format_money(invested_capital_part), self.name)
+ print "## Satisfying %s from %s as cost basis." % (invested_capital_part,
+ self.name)
self.withdraw_from_gains(gains_part, taxes)
else:
self.withdraw_from_gains(amount, taxes)
self.long_term_gain -= amount
self.total_long_term_gain_withdrawals += amount
print "## Satisfying %s from %s as long term gains." % (
- utils.format_money(amount),
- self.name)
+ amount, self.name)
taxes.record_dividend_or_long_term_gain(amount)
return
else:
print "## Satisfying %s from %s as long term gains (exhausting all current long term gains in account)." % (
- utils.format_money(self.long_term_gain),
- self.name)
+ self.long_term_gain, self.name)
amount -= self.long_term_gain
self.total_long_term_gain_withdrawals += self.long_term_gain
taxes.record_dividend_or_long_term_gain(self.long_term_gain)
self.short_term_gain -= amount
self.total_short_term_gain_withdrawals += amount
print "## Satisfying %s from %s as short term gains." % (
- utils.format_money(amount),
- self.name)
+ amount, self.name)
taxes.record_short_term_gain(amount)
def deposit(self, amount):
def dump_final_report(self):
super(brokerage_account, self).dump_final_report()
print " {:<50}: {:>14}".format("Initial balance",
- utils.format_money(self.initial_balance))
+ self.initial_balance)
print " {:<50}: {:>14}".format("Ending balance",
- utils.format_money(self.get_balance()))
+ self.get_balance())
print " {:<50}: {:>14}".format("Total investment gains",
- utils.format_money(self.total_investment_gains))
+ self.total_investment_gains)
print " {:<50}: {:>14}".format("Total cost basis withdrawals",
- utils.format_money(self.total_cost_basis_withdrawals))
+ self.total_cost_basis_withdrawals)
print " {:<50}: {:>14}".format("Total long term gain withdrawals",
- utils.format_money(self.total_long_term_gain_withdrawals))
+ self.total_long_term_gain_withdrawals)
print " {:<50}: {:>14}".format("Total short term gain withdrawals",
- utils.format_money(self.total_short_term_gain_withdrawals))
+ self.total_short_term_gain_withdrawals)
+from money import money
# Consts
DEFAULT = 0
return (owner >= DEFAULT and owner <= LYNN)
PESSIMISTIC_FEDERAL_INCOME_TAX_BRACKETS = [
- [ 612351, 0.50 ],
- [ 408201, 0.45 ],
- [ 321451, 0.35 ],
- [ 168401, 0.25 ],
- [ 78951, 0.22 ],
- [ 19401, 0.15 ],
- [ 1, 0.12 ]
+ [ money(612351), 0.50 ],
+ [ money(408201), 0.45 ],
+ [ money(321451), 0.35 ],
+ [ money(168401), 0.25 ],
+ [ money( 78951), 0.22 ],
+ [ money( 19401), 0.15 ],
+ [ money( 1), 0.12 ]
]
CURRENT_FEDERAL_INCOME_TAX_BRACKETS = [
- [ 612351, 0.37 ],
- [ 408201, 0.35 ],
- [ 321451, 0.32 ],
- [ 168401, 0.24 ],
- [ 78951, 0.22 ],
- [ 19401, 0.12 ],
- [ 1, 0.10 ]
+ [ money(612351), 0.37 ],
+ [ money(408201), 0.35 ],
+ [ money(321451), 0.32 ],
+ [ money(168401), 0.24 ],
+ [ money( 78951), 0.22 ],
+ [ money( 19401), 0.12 ],
+ [ money( 1), 0.10 ]
]
CURRENT_LONG_TERM_GAIN_FEDERAL_TAX_BRACKETS = [
- [ 488851, 0.20 ],
- [ 78751, 0.15 ],
- [ 1, 0.00 ]
+ [ money(488851), 0.20 ],
+ [ money( 78751), 0.15 ],
+ [ money( 1), 0.00 ]
]
PESSIMISTIC_LONG_TERM_GAIN_FEDERAL_TAX_BRACKETS = (
PESSIMISTIC_FEDERAL_INCOME_TAX_BRACKETS)
class money(object):
def __init__(self, amount="0"):
try:
+ if isinstance(amount, money):
+ amount = amount.amount
self.amount = decimal.Decimal(amount)
except decimal.InvalidOperation:
raise ValueError("amount value could not be converted to "
"Decimal(): '{}'".format(amount))
- @property
def amount(self):
return self.amount
def __hash__(self):
- return hash(self._amount)
+ return hash(self.amount)
def __repr__(self):
- return "{}".format(self._amount)
+ return "{}".format(self.amount)
def __str__(self):
return self.__unicode__().encode('utf-8')
def __unicode__(self):
- return u"${:,.2f}".format(self._amount)
+ return u"${:,.2f}".format(self.amount)
def __lt__(self, other):
if not isinstance(other, money):
- raise InvalidOperandType(other, '<')
+ other = money(other)
return self.amount < other.amount
def __le__(self, other):
if not isinstance(other, money):
- raise InvalidOperandType(other, '<=')
- return self._amount <= other.amount
+ other = money(other)
+ return self.amount <= other.amount
def __eq__(self, other):
if isinstance(other, money):
- return self._amount == other.amount
+ return self.amount == other.amount
return False
def __ne__(self, other):
def __gt__(self, other):
if not isinstance(other, money):
- raise InvalidOperandType(other, '>')
- return self._amount > other.amount
+ other = money(other)
+ return self.amount > other.amount
def __ge__(self, other):
if not isinstance(other, money):
- raise InvalidOperandType(other, '>=')
- return self._amount >= other.amount
+ other = money(other)
+ return self.amount >= other.amount
def __add__(self, other):
if not isinstance(other, money):
- raise InvalidOperandType(other, '+')
+ other = money(other)
other = other.amount
amount = self.amount + other
return self.__class__(amount)
def __sub__(self, other):
if not isinstance(other, money):
- raise InvalidOperandType(other, '-')
+ other = money(other)
other = other.amount
- amount = self._amount - other
+ amount = self.amount - other
return self.__class__(amount)
def __rsub__(self, other):
def __mul__(self, other):
if isinstance(other, money):
- raise TypeError("multiplication is unsupported between "
- "two money objects")
- amount = self._amount * other
+ other = other.amount()
+ amount = self.amount * decimal.Decimal(other)
return self.__class__(amount)
def __rmul__(self, other):
else:
if other == 0:
raise ZeroDivisionError()
- amount = self.amount / other
+ amount = self.amount / decimal.Decimal(other)
return self.__class__(amount)
def __floordiv__(self, other):
if isinstance(other, money):
if other.amount == 0:
raise ZeroDivisionError()
- return self._amount // other.amount
+ return self.amount // other.amount
else:
if other == 0:
raise ZeroDivisionError()
if isinstance(other, money):
if other.amount == 0:
raise ZeroDivisionError()
- return divmod(self._amount, other.amount)
+ return divmod(self.amount, other.amount)
else:
if other == 0:
raise ZeroDivisionError()
- whole, remainder = divmod(self._amount, other)
+ whole, remainder = divmod(self.amount, other)
return (self.__class__(whole),
self.__class__(remainder))
return self.__class__(-self.amount)
def __pos__(self):
- return self.__class__(+self._amount)
+ return self.__class__(+self.amount)
def __abs__(self):
return self.__class__(abs(self.amount))
import constants
import utils
from tax_brackets import tax_brackets
+from money import money
class parameters(object):
"""A container to hold the initial states of several simulation
def with_default_values(self):
# Annual expenses in USD at the start of the simulation. This
# will be adjusted upwards with inflation_multiplier every year.
- self.initial_annual_expenses = 144300
+ self.initial_annual_expenses = money(144300)
# The average US inflation rate during the simulation. The
# Fed's target rate is 2.0% as of 2020. The long term observed
# age 67 - $30,000 age 67 - $30,420
# age 70 - $37,200 age 70 - $37,728
#
- # X SCOTT LYNN
- self.social_security_age = [ 0, 62, 62 ]
- self.initial_social_security_dollars = [ 0, 21000, 21000 ]
+ # X SCOTT LYNN
+ self.social_security_age = [0, 62, 62 ]
+ self.initial_social_security_dollars = [0, money(21000), money(21000) ]
# Tax details... the standard deduction amount and tax
# brackets for ordinary income and long term capital gains.
def dump(self):
print "SIMULATION PARAMETERS"
print " {:<50}: {:>14}".format("Initial year annual expenses",
- utils.format_money(self.initial_annual_expenses))
+ self.initial_annual_expenses)
print " {:<50}: {:>14}".format("Annual inflation rate",
utils.format_rate(self.inflation_multiplier))
print " {:<50}: {:>14}".format("Average annual investment return rate",
print " {:<50}: {:>14}".format("Age at which Lynn takes social security",
self.social_security_age[constants.LYNN])
print " {:<50}: {:>14}".format("Lynn's first year social security benefit",
- utils.format_money(self.initial_social_security_dollars[constants.LYNN]))
+ self.initial_social_security_dollars[constants.LYNN])
print " {:<50}: {:>14}".format("Age at which Scott takes social security",
self.social_security_age[constants.SCOTT])
print " {:<50}: {:>14}".format("Scott's first year social security benefit",
- utils.format_money(self.initial_social_security_dollars[constants.SCOTT]))
+ self.initial_social_security_dollars[constants.SCOTT])
print " Federal tax brackets ["
self.federal_ordinary_income_tax_brackets.dump()
print " ]"
from tax_brackets import tax_brackets
from tax_collector import tax_collector
import utils
-import secrets
+import real_secrets
+from money import money
class simulation(object):
def __init__(self, parameters, accounts):
"""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 = 0
+ 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)
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" % (utils.format_money(amount_to_withdraw), x.get_name())
+ 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
(x.belongs_to_scott() and self.scott_age > 60))):
amount_to_withdraw = min(amount_needed, x.get_balance())
- print "## Withdrawing %s from %s" % (utils.format_money(amount_to_withdraw), x.get_name())
+ 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
(x.belongs_to_scott() and self.scott_age > 60))):
amount_to_withdraw = min(amount_needed, x.get_balance())
- print "## Withdrawing %s from %s" % (utils.format_money(amount_to_withdraw), x.get_name())
+ 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!" % utils.format_money(amount_needed))
+ 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,
"""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 = 0
+ 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)
def dump_annual_header(self, money_needed):
print "\nYear: %d, estimated annual expenses %s ---------------\n" % (
self.year,
- utils.format_money(money_needed))
+ money_needed)
print "Scott is %d, Lynn is %d and Alex is %d.\n" % (
self.scott_age, self.lynn_age, self.alex_age)
total = 0
for x in self.accounts:
total += x.get_balance()
- print "{:<50}: {:>14}".format(x.get_name(),
- utils.format_money(x.get_balance()))
- print "{:<50}: {:>14}\n".format("TOTAL", utils.format_money(total))
+ 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:"
self.alex_age = self.year - 2005
# Computed money needed this year based on inflated budget.
- money_needed = adjusted_annual_expenses
+ 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:
# Now, figure out how to find money to pay for this year
# and how much of it is taxable.
- total_income = 0
+ 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." % utils.format_money(rmds)
+ print "## Satisfied %s of RMDs from age-restricted accounts." % rmds
total_income += rmds
money_needed -= rmds
adjusted_lynn_annual_social_security_dollars,
taxes)
if ss > 0:
- print "## Social security paid %s" % utils.format_money(ss)
+ print "## Social security paid %s" % ss
total_income += ss
money_needed -= ss
money_needed = 0
look_for_conversions = True
- tax_limit = 25000
+ tax_limit = money(25000)
while True:
# Maybe do some opportunistic Roth conversions.
taxes_due = taxes.approximate_taxes(
# 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))
+ taxes_due, utils.format_rate(tax_rate))
self.go_find_money(taxes_due, taxes)
else:
print "## No federal taxes due this year!"
# main
params = parameters().with_default_values()
-accounts = secrets.accounts
+accounts = real_secrets.accounts
s = simulation(params, accounts)
s.run()
import utils
+from money import money
class tax_brackets:
"""A class to represent tax brackets and some operations on them."""
def compute_taxes_for_income(self, income):
"""Compute the tax bill for income given our brackets."""
- taxes_due = 0
+ taxes_due = money(0)
while income > 1:
(threshold, rate) = self.get_bracket_for_income(income)
taxes_due += (income - threshold) * rate
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]))
+ print "{:<20} -> {:<3}".format(x[0], utils.format_rate(x[1]))
import utils
+from money import money
class tax_collector(object):
def __init__(self):
# These four accumulate and then clear every year (i.e. every call
# to record_taxes_paid_and_reset_for_next_year)
- self.ordinary_income = 0
- self.short_term_gains = 0
- self.dividends_and_long_term_gains = 0
- self.roth_income = 0
+ self.ordinary_income = money(0)
+ self.short_term_gains = money(0)
+ self.dividends_and_long_term_gains = money(0)
+ self.roth_income = money(0)
# The rest of these aggregate and are used as part of the final stats.
- self.total_tax_bill = 0
- self.total_ordinary_income = 0
- self.total_short_term_gains = 0
- self.total_dividends_and_long_term_gains = 0
- self.total_roth_income = 0
- self.total_aggregate_income = 0
+ self.total_tax_bill = money(0)
+ self.total_ordinary_income = money(0)
+ self.total_short_term_gains = money(0)
+ self.total_dividends_and_long_term_gains = money(0)
+ self.total_roth_income = money(0)
+ self.total_aggregate_income = money(0)
def record_taxes_paid_and_reset_for_next_year(self,
taxes_paid):
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
+ self.ordinary_income = money(0)
+ self.short_term_gains = money(0)
+ self.dividends_and_long_term_gains = money(0)
+ self.roth_income = money(0)
def record_ordinary_income(self, amount):
assert amount >= 0, "Income should be non-negative"
ordinary_income_tax_brackets,
dividends_and_long_term_gains_brackets):
assert standard_deduction >= 0, "Standard deduction should be non-negative"
- taxes_due = 0
+ taxes_due = money(0)
# Handle ordinary income:
ordinary_income = (self.ordinary_income +
self.short_term_gains -
standard_deduction)
if ordinary_income < 0:
- ordinary_income = 0
+ ordinary_income = money(0)
taxes_due += ordinary_income_tax_brackets.compute_taxes_for_income(
ordinary_income)
def dump_final_report(self):
print " Taxes and income:"
print " {:<50}: {:>14}".format("Total aggregate income",
- utils.format_money(self.total_aggregate_income))
+ self.total_aggregate_income)
print " ...{:<47}: {:>14}".format("Ordinary income",
- utils.format_money(self.total_ordinary_income))
+ self.total_ordinary_income)
print " ...{:<47}: {:>14}".format("Income from short term gains",
- utils.format_money(self.total_short_term_gains))
+ self.total_short_term_gains)
print " ...{:<47}: {:>14}".format("Income from dividends and long term gains",
- utils.format_money(self.total_dividends_and_long_term_gains))
+ self.total_dividends_and_long_term_gains)
print " ...{:<47}: {:>14}".format("Roth income",
- utils.format_money(self.total_roth_income))
+ self.total_roth_income)
print " {:<50}: {:>14}".format("Total taxes paid",
- utils.format_money(self.total_tax_bill))
+ self.total_tax_bill)
overall_tax_rate = float(self.total_tax_bill) / float(self.total_aggregate_income)
print " {:<50}: {:>14}".format("Effective tax rate",
utils.format_rate(overall_tax_rate))
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: