3 from typing import List, Optional, Tuple
5 from type.money import Money
6 from type.rate import Rate
7 import string_utils as su
10 PESSIMISTIC_FEDERAL_INCOME_TAX_BRACKETS = [
11 (Money(612351), Rate(0.70)),
12 (Money(408201), Rate(0.55)),
13 (Money(321451), Rate(0.45)),
14 (Money(168401), Rate(0.35)),
15 (Money( 78951), Rate(0.26)),
16 (Money( 19401), Rate(0.18)),
17 (Money( 1), Rate(0.13)),
20 CURRENT_FEDERAL_INCOME_TAX_BRACKETS = [
21 (Money(612351), Rate(0.37)),
22 (Money(408201), Rate(0.35)),
23 (Money(321451), Rate(0.32)),
24 (Money(168401), Rate(0.24)),
25 (Money( 78951), Rate(0.22)),
26 (Money( 19401), Rate(0.12)),
27 (Money( 1), Rate(0.10)),
30 CURRENT_LONG_TERM_GAIN_FEDERAL_TAX_BRACKETS = [
31 (Money(488851), Rate(0.20)),
32 (Money( 78751), Rate(0.15)),
33 (Money( 1), Rate(0.00)),
36 PESSIMISTIC_LONG_TERM_GAIN_FEDERAL_TAX_BRACKETS = (
37 PESSIMISTIC_FEDERAL_INCOME_TAX_BRACKETS)
40 federal_standard_deduction_dollars = Money(24800) # Federal tax standard deduction
41 federal_ordinary_income_tax_brackets = PESSIMISTIC_FEDERAL_INCOME_TAX_BRACKETS
42 federal_dividends_and_long_term_gains_brackets = CURRENT_LONG_TERM_GAIN_FEDERAL_TAX_BRACKETS
45 class TaxCollector(object):
48 ordinary_income_brackets,
49 dividends_and_long_term_gains_brackets,
51 self.ordinary_income_brackets = ordinary_income_brackets
52 self.dividends_and_long_term_gains_brackets = dividends_and_long_term_gains_brackets
54 # These four accumulate and then clear every year (i.e. every call
55 # to record_taxes_paid_and_reset_for_next_year)
56 self.ordinary_income = Money(0)
57 self.short_term_gains = Money(0)
58 self.dividends_and_long_term_gains = Money(0)
59 self.roth_income = Money(0)
61 # The rest of these aggregate and are used as part of the final stats.
62 self.total_tax_bill = Money(0)
63 self.total_ordinary_income = Money(0)
64 self.total_short_term_gains = Money(0)
65 self.total_dividends_and_long_term_gains = Money(0)
66 self.total_roth_income = Money(0)
67 self.total_aggregate_income = Money(0)
68 self.max_annual_income = None
69 self.max_annual_income_year = None
71 def record_taxes_paid_and_reset_for_next_year(self, current_year: int):
72 self.total_tax_bill += self.approximate_annual_taxes()
73 income = self.get_total_annual_income()
74 if self.max_annual_income is None or income > self.max_annual_income:
75 self.max_annual_income = income
76 self.max_annual_income_year = current_year
77 self.total_aggregate_income += income
78 self.ordinary_income = Money(0)
79 self.short_term_gains = Money(0)
80 self.dividends_and_long_term_gains = Money(0)
81 self.roth_income = Money(0)
83 def record_ordinary_income(self, amount: Money):
84 self.ordinary_income += amount
85 self.total_ordinary_income += amount
87 def record_short_term_gain(self, amount: Money):
88 self.short_term_gains += amount
89 self.total_short_term_gains += amount
91 def record_dividend_or_long_term_gain(self, amount: Money):
92 self.dividends_and_long_term_gains += amount
93 self.total_dividends_and_long_term_gains += amount
95 def record_roth_income(self, amount: Money):
96 self.roth_income += amount
97 self.total_roth_income += amount
99 def approximate_annual_taxes(self):
102 # Handle ordinary income:
103 ordinary_income = (self.ordinary_income +
104 self.short_term_gains -
105 federal_standard_deduction_dollars)
106 if ordinary_income < 0:
108 taxes_due += self.compute_taxes_on_income_using_brackets(
110 self.ordinary_income_brackets
113 # Handle dividends and long term gains:
114 taxes_due += self.compute_taxes_on_income_using_brackets(
115 self.dividends_and_long_term_gains,
116 self.dividends_and_long_term_gains_brackets
119 # Assume Roth money is still available tax free in the future.
122 def compute_taxes_on_income_using_brackets(
125 brackets: List[Tuple[Money, Rate]]
128 while income > Money(1.0):
131 for bracket in brackets:
132 # Bracket is a tuple of (monetary_threshold,
133 # tax_rate). So bracket[0] is a dollar amount and
134 # bracket[1] is a tax rate. e.g. ( 250000, 0.20 )
135 # indicates that every dollar you earn over 250K is
137 if (income > bracket[0] and rate < bracket[1]):
138 threshold = bracket[0]
140 taxed_amount = income - threshold
141 taxes_due += taxed_amount * rate
145 def how_many_more_dollars_can_we_earn_without_changing_tax_rate(
147 ) -> Optional[Money]:
148 """Return number of ordinary income dollars we can make without
149 changing this year's tax rate. Note: this may return None
150 which actually indicates something more like 'infinite since
151 you're already at the top tax bracket.'"""
154 income = self.ordinary_income
156 # Kinda hacky but if there's been no ordinary income this year
157 # this method was saying "oh, well, you can earn one dollar
158 # before your tax rate goes from zero to something non-zero."
159 # This is correct but not useful -- this method is used to
160 # opportunistically shuffle pretax money into Roth. So let's
161 # assume we've earned at least one dollar of ordinary income
162 # this year and work from there.
163 if income <= Money(0):
166 # Walk the income tax brackets and look for the one just above
167 # the one this year's income is activating.
168 for bracket in self.ordinary_income_brackets:
169 if (bracket[0] > income and bracket[1] < rate):
170 threshold = bracket[0]
173 # If we found the bracket just above here, use it to compute a
175 if threshold is not None:
176 assert threshold > income
177 delta = threshold - income
181 def get_total_annual_income(self) -> Money:
183 self.ordinary_income +
184 self.short_term_gains +
185 self.dividends_and_long_term_gains +
189 def approximate_annual_tax_rate(self) -> Rate:
190 total_income = self.get_total_annual_income()
191 total_taxes = self.approximate_annual_taxes()
193 multiplier=float(total_taxes) / float(total_income)
197 with su.SprintfStdout() as ret:
198 print("Taxes and Income:")
199 print(" %-50s: %18s" % ("Total aggregate income",
200 self.total_aggregate_income))
201 print(" ...%-47s: %18s" % ("Ordinary income",
202 self.total_ordinary_income))
203 print(" ...%-47s: %18s" % ("Income from short term gains",
204 self.total_short_term_gains))
205 print(" ...%-47s: %18s" % ("Income from dividends and long term gains",
206 self.total_dividends_and_long_term_gains))
207 print(" ...%-47s: %18s" % ("Roth income",
208 self.total_roth_income))
209 print(" %-50s: %18s (%s)" % ("Maximum annual income",
210 self.max_annual_income,
211 f'in {self.max_annual_income_year}'))
212 print(" %-50s: %18s" % ("Total taxes paid",
213 self.total_tax_bill))
214 overall_tax_rate = Rate(
215 float(self.total_tax_bill) / float(self.total_aggregate_income)
217 print(" %-50s: %18s" % ("Effective tax rate",
218 overall_tax_rate), end='')