owlplanner 2025.2.14__py3-none-any.whl → 2025.2.19__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- owlplanner/config.py +2 -0
- owlplanner/plan.py +16 -5
- owlplanner/tax2025.py +63 -43
- owlplanner/version.py +1 -1
- {owlplanner-2025.2.14.dist-info → owlplanner-2025.2.19.dist-info}/METADATA +28 -24
- {owlplanner-2025.2.14.dist-info → owlplanner-2025.2.19.dist-info}/RECORD +8 -8
- {owlplanner-2025.2.14.dist-info → owlplanner-2025.2.19.dist-info}/WHEEL +0 -0
- {owlplanner-2025.2.14.dist-info → owlplanner-2025.2.19.dist-info}/licenses/LICENSE +0 -0
owlplanner/config.py
CHANGED
|
@@ -64,6 +64,7 @@ def saveConfig(plan, file, mylog):
|
|
|
64
64
|
"Heirs rate on tax-deferred estate": float(100 * plan.nu),
|
|
65
65
|
"Long-term capital gain tax rate": float(100 * plan.psi),
|
|
66
66
|
"Dividend tax rate": float(100 * plan.mu),
|
|
67
|
+
"TCJA expiration year": plan.yTCJA,
|
|
67
68
|
"Method": plan.rateMethod,
|
|
68
69
|
}
|
|
69
70
|
if plan.rateMethod in ["user", "stochastic"]:
|
|
@@ -226,6 +227,7 @@ def readConfig(file, *, verbose=True, logstreams=None, readContributions=True):
|
|
|
226
227
|
p.setDividendRate(float(diconf["Rates Selection"]["Dividend tax rate"]))
|
|
227
228
|
p.setLongTermCapitalTaxRate(float(diconf["Rates Selection"]["Long-term capital gain tax rate"]))
|
|
228
229
|
p.setHeirsTaxRate(float(diconf["Rates Selection"]["Heirs rate on tax-deferred estate"]))
|
|
230
|
+
p.yTCJA = int(diconf["Rates Selection"]["TCJA expiration year"])
|
|
229
231
|
|
|
230
232
|
frm = None
|
|
231
233
|
to = None
|
owlplanner/plan.py
CHANGED
|
@@ -249,7 +249,8 @@ class Plan(object):
|
|
|
249
249
|
assert inames[0] != "" or (self.N_i == 2 and inames[1] == ""), "Name for each individual must be provided."
|
|
250
250
|
|
|
251
251
|
self.filingStatus = ["single", "married"][self.N_i - 1]
|
|
252
|
-
|
|
252
|
+
# Default year TCJA is speculated to expire.
|
|
253
|
+
self.yTCJA = 2026
|
|
253
254
|
self.inames = inames
|
|
254
255
|
self.yobs = np.array(yobs, dtype=np.int32)
|
|
255
256
|
self.expectancy = np.array(expectancy, dtype=np.int32)
|
|
@@ -305,9 +306,8 @@ class Plan(object):
|
|
|
305
306
|
endyear = thisyear + self.horizons[i] - 1
|
|
306
307
|
self.mylog.vprint(f"{self.inames[i]:>14}: life horizon from {thisyear} -> {endyear}.")
|
|
307
308
|
|
|
308
|
-
# Prepare
|
|
309
|
+
# Prepare RMD time series.
|
|
309
310
|
self.rho_in = tx.rho_in(self.yobs, self.N_n)
|
|
310
|
-
self.sigma_n, self.theta_tn, self.Delta_tn = tx.taxParams(self.yobs, self.i_d, self.n_d, self.N_n)
|
|
311
311
|
|
|
312
312
|
# If none was given, default is to begin plan on today's date.
|
|
313
313
|
self._setStartingDate(startDate)
|
|
@@ -443,6 +443,15 @@ class Plan(object):
|
|
|
443
443
|
self.mu = mu
|
|
444
444
|
self.caseStatus = "modified"
|
|
445
445
|
|
|
446
|
+
def setExpirationYearTCJA(self, yTCJA):
|
|
447
|
+
"""
|
|
448
|
+
Set year at which TCJA is speculated to expire.
|
|
449
|
+
"""
|
|
450
|
+
self.mylog.vprint(f"Setting TCJA expiration year to {yTCJA}.")
|
|
451
|
+
self.yTCJA = yTCJA
|
|
452
|
+
self.caseStatus = "modified"
|
|
453
|
+
self._adjustedParameters = False
|
|
454
|
+
|
|
446
455
|
def setLongTermCapitalTaxRate(self, psi):
|
|
447
456
|
"""
|
|
448
457
|
Set long-term income tax rate. Rate is in percent. Default 15%.
|
|
@@ -980,9 +989,11 @@ class Plan(object):
|
|
|
980
989
|
|
|
981
990
|
if not self._adjustedParameters:
|
|
982
991
|
self.mylog.vprint("Adjusting parameters for inflation.")
|
|
992
|
+
self.sigma_n, self.theta_tn, self.Delta_tn = tx.taxParams(self.yobs, self.i_d, self.n_d,
|
|
993
|
+
self.N_n, self.yTCJA)
|
|
994
|
+
self.sigmaBar_n = self.sigma_n * self.gamma_n[:-1]
|
|
983
995
|
self.DeltaBar_tn = self.Delta_tn * self.gamma_n[:-1]
|
|
984
996
|
self.zetaBar_in = self.zeta_in * self.gamma_n[:-1]
|
|
985
|
-
self.sigmaBar_n = self.sigma_n * self.gamma_n[:-1]
|
|
986
997
|
self.xiBar_n = self.xi_n * self.gamma_n[:-1]
|
|
987
998
|
self.piBar_in = self.pi_in
|
|
988
999
|
for i in range(self.N_i):
|
|
@@ -2572,7 +2583,7 @@ class Plan(object):
|
|
|
2572
2583
|
|
|
2573
2584
|
fig, ax = _lineIncomePlot(self.year_n, series, style, title, yformat)
|
|
2574
2585
|
|
|
2575
|
-
data = tx.taxBrackets(self.N_i, self.n_d, self.N_n)
|
|
2586
|
+
data = tx.taxBrackets(self.N_i, self.n_d, self.N_n, self.yTCJA)
|
|
2576
2587
|
for key in data:
|
|
2577
2588
|
data_adj = data[key] * infladjust
|
|
2578
2589
|
ax.plot(self.year_n, data_adj, label=key, ls=":")
|
owlplanner/tax2025.py
CHANGED
|
@@ -24,49 +24,62 @@ from datetime import date
|
|
|
24
24
|
|
|
25
25
|
taxBracketNames = ["10%", "12/15%", "22/25%", "24/28%", "32/33%", "35%", "37/40%"]
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
rates_TCJA = np.array([0.10, 0.12, 0.22, 0.24, 0.32, 0.35, 0.370])
|
|
28
|
+
rates_nonTCJA = np.array([0.10, 0.15, 0.25, 0.28, 0.33, 0.35, 0.396])
|
|
29
29
|
|
|
30
|
+
###############################################################################
|
|
31
|
+
# Start of section where rates need to be actualized every year.
|
|
32
|
+
###############################################################################
|
|
30
33
|
# Single [0] and married filing jointly [1].
|
|
31
|
-
|
|
34
|
+
|
|
35
|
+
# These are current.
|
|
36
|
+
taxBrackets_TCJA = np.array(
|
|
32
37
|
[
|
|
33
38
|
[11925, 48475, 103350, 197300, 250525, 626350, 9999999],
|
|
34
39
|
[23850, 96950, 206700, 394600, 501050, 751700, 9999999],
|
|
35
40
|
]
|
|
36
41
|
)
|
|
37
42
|
|
|
38
|
-
|
|
43
|
+
irmaaBrackets = np.array(
|
|
39
44
|
[
|
|
40
45
|
[0, 106000, 133000, 167000, 200000, 500000],
|
|
41
46
|
[0, 212000, 266000, 334000, 400000, 750000],
|
|
42
47
|
]
|
|
43
48
|
)
|
|
44
49
|
|
|
45
|
-
#
|
|
50
|
+
# Index [0] stores the standard Medicare part B premium.
|
|
46
51
|
# Following values are incremental IRMAA part B monthly fees.
|
|
47
|
-
|
|
48
|
-
# irmaaFees_2024 = 12 * np.array([174.70, 69.90, 104.80, 104.80, 104.80, 35.00])
|
|
49
|
-
irmaaFees_2025 = 12 * np.array([185.00, 74.00, 111.00, 110.90, 111.00, 37.00])
|
|
52
|
+
irmaaFees = 12 * np.array([185.00, 74.00, 111.00, 110.90, 111.00, 37.00])
|
|
50
53
|
|
|
51
|
-
#
|
|
54
|
+
# Make projection for non-TCJA using 2017 to current year.
|
|
52
55
|
# taxBrackets_2017 = np.array(
|
|
53
56
|
# [ [9325, 37950, 91900, 191650, 416700, 418400, 9999999],
|
|
54
|
-
# [18650, 75900, 153100, 233350, 416700,
|
|
57
|
+
# [18650, 75900, 153100, 233350, 416700, 470700, 9999999],
|
|
55
58
|
# ])
|
|
56
|
-
|
|
57
|
-
|
|
59
|
+
#
|
|
60
|
+
# stdDeduction_2017 = [6350, 12700]
|
|
61
|
+
#
|
|
62
|
+
# For 2025, I used a 30.5% adjustment from 2017, rounded to closest 50.
|
|
63
|
+
#
|
|
64
|
+
# These are speculated.
|
|
65
|
+
taxBrackets_nonTCJA = np.array(
|
|
58
66
|
[
|
|
59
|
-
[
|
|
60
|
-
[
|
|
67
|
+
[12150, 49550, 119950, 250200, 544000, 546200, 9999999],
|
|
68
|
+
[24350, 99100, 199850, 304600, 543950, 614450, 9999999],
|
|
61
69
|
]
|
|
62
70
|
)
|
|
63
71
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
72
|
+
# These are current.
|
|
73
|
+
stdDeduction_TCJA = np.array([15000, 30000])
|
|
74
|
+
# These are speculated.
|
|
75
|
+
stdDeduction_nonTCJA = np.array([8300, 16600])
|
|
67
76
|
|
|
77
|
+
# These are current.
|
|
78
|
+
extra65Deduction = np.array([2000, 1600])
|
|
68
79
|
|
|
69
|
-
|
|
80
|
+
###############################################################################
|
|
81
|
+
# End of section where rates need to be actualized every year.
|
|
82
|
+
###############################################################################
|
|
70
83
|
|
|
71
84
|
|
|
72
85
|
def mediCosts(yobs, horizons, magi, prevmagi, gamma_n, Nn):
|
|
@@ -80,21 +93,25 @@ def mediCosts(yobs, horizons, magi, prevmagi, gamma_n, Nn):
|
|
|
80
93
|
for i in range(Ni):
|
|
81
94
|
if thisyear + n - yobs[i] >= 65 and n < horizons[i]:
|
|
82
95
|
# Start with the (indexed) basic Medicare part B premium.
|
|
83
|
-
costs[n] += gamma_n[n] *
|
|
96
|
+
costs[n] += gamma_n[n] * irmaaFees[0]
|
|
84
97
|
if n < 2:
|
|
85
98
|
mymagi = prevmagi[n]
|
|
86
99
|
else:
|
|
87
100
|
mymagi = magi[n - 2]
|
|
88
101
|
for q in range(1, 6):
|
|
89
|
-
if mymagi > gamma_n[n] *
|
|
90
|
-
costs[n] += gamma_n[n] *
|
|
102
|
+
if mymagi > gamma_n[n] * irmaaBrackets[Ni - 1][q]:
|
|
103
|
+
costs[n] += gamma_n[n] * irmaaFees[q]
|
|
91
104
|
|
|
92
105
|
return costs
|
|
93
106
|
|
|
94
107
|
|
|
95
|
-
def taxParams(yobs, i_d, n_d, N_n):
|
|
108
|
+
def taxParams(yobs, i_d, n_d, N_n, y_TCJA=2026):
|
|
96
109
|
"""
|
|
97
|
-
|
|
110
|
+
Input is year of birth, index of shortest-lived individual,
|
|
111
|
+
lifespan of shortest-lived individual, total number of years
|
|
112
|
+
in the plan, and the year that TCJA might expire.
|
|
113
|
+
|
|
114
|
+
It returns 3 time series:
|
|
98
115
|
1) Standard deductions at year n (sigma_n).
|
|
99
116
|
2) Tax rate in year n (theta_tn)
|
|
100
117
|
3) Delta from top to bottom of tax brackets (Delta_tn)
|
|
@@ -102,12 +119,12 @@ def taxParams(yobs, i_d, n_d, N_n):
|
|
|
102
119
|
Returned values are not indexed for inflation.
|
|
103
120
|
"""
|
|
104
121
|
# Compute the deltas in-place between brackets, starting from the end.
|
|
105
|
-
|
|
106
|
-
|
|
122
|
+
deltaBrackets_TCJA = np.array(taxBrackets_TCJA)
|
|
123
|
+
deltaBrackets_nonTCJA = np.array(taxBrackets_nonTCJA)
|
|
107
124
|
for t in range(6, 0, -1):
|
|
108
125
|
for i in range(2):
|
|
109
|
-
|
|
110
|
-
|
|
126
|
+
deltaBrackets_TCJA[i, t] -= deltaBrackets_TCJA[i, t - 1]
|
|
127
|
+
deltaBrackets_nonTCJA[i, t] -= deltaBrackets_nonTCJA[i, t - 1]
|
|
111
128
|
|
|
112
129
|
# Prepare the 3 arrays to return - use transpose for easy slicing.
|
|
113
130
|
sigma = np.zeros((N_n))
|
|
@@ -124,23 +141,23 @@ def taxParams(yobs, i_d, n_d, N_n):
|
|
|
124
141
|
souls.remove(i_d)
|
|
125
142
|
filingStatus -= 1
|
|
126
143
|
|
|
127
|
-
if thisyear + n <
|
|
128
|
-
sigma[n] =
|
|
129
|
-
Delta[n, :] =
|
|
144
|
+
if thisyear + n < y_TCJA:
|
|
145
|
+
sigma[n] = stdDeduction_TCJA[filingStatus]
|
|
146
|
+
Delta[n, :] = deltaBrackets_TCJA[filingStatus, :]
|
|
130
147
|
else:
|
|
131
|
-
sigma[n] =
|
|
132
|
-
Delta[n, :] =
|
|
148
|
+
sigma[n] = stdDeduction_nonTCJA[filingStatus]
|
|
149
|
+
Delta[n, :] = deltaBrackets_nonTCJA[filingStatus, :]
|
|
133
150
|
|
|
134
151
|
# Add 65+ additional exemption(s).
|
|
135
152
|
for i in souls:
|
|
136
153
|
if thisyear + n - yobs[i] >= 65:
|
|
137
|
-
sigma[n] +=
|
|
154
|
+
sigma[n] += extra65Deduction[filingStatus]
|
|
138
155
|
|
|
139
156
|
# Fill in future tax rates for year n.
|
|
140
|
-
if thisyear + n <
|
|
141
|
-
theta[n, :] =
|
|
157
|
+
if thisyear + n < y_TCJA:
|
|
158
|
+
theta[n, :] = rates_TCJA[:]
|
|
142
159
|
else:
|
|
143
|
-
theta[n, :] =
|
|
160
|
+
theta[n, :] = rates_nonTCJA[:]
|
|
144
161
|
|
|
145
162
|
Delta = Delta.transpose()
|
|
146
163
|
theta = theta.transpose()
|
|
@@ -149,23 +166,26 @@ def taxParams(yobs, i_d, n_d, N_n):
|
|
|
149
166
|
return sigma, theta, Delta
|
|
150
167
|
|
|
151
168
|
|
|
152
|
-
def taxBrackets(N_i, n_d, N_n):
|
|
169
|
+
def taxBrackets(N_i, n_d, N_n, y_TCJA):
|
|
153
170
|
"""
|
|
154
171
|
Return dictionary containing future tax brackets
|
|
155
172
|
unadjusted for inflation for plotting.
|
|
156
173
|
"""
|
|
157
174
|
assert 0 < N_i and N_i <= 2, f"Cannot process {N_i} individuals."
|
|
158
|
-
# This 1 is the number of years left in TCJA from 2025.
|
|
159
|
-
ytc = 1
|
|
160
|
-
status = N_i - 1
|
|
161
175
|
n_d = min(n_d, N_n)
|
|
176
|
+
status = N_i - 1
|
|
177
|
+
|
|
178
|
+
# Number of years left in TCJA from this year.
|
|
179
|
+
thisyear = date.today().year
|
|
180
|
+
ytc = y_TCJA - thisyear
|
|
162
181
|
|
|
163
182
|
data = {}
|
|
164
183
|
for t in range(len(taxBracketNames) - 1):
|
|
165
184
|
array = np.zeros(N_n)
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
185
|
+
for n in range(N_n):
|
|
186
|
+
stat = status if n < n_d else 0
|
|
187
|
+
array[n] = taxBrackets_TCJA[stat][t] if n < ytc else taxBrackets_nonTCJA[stat][t]
|
|
188
|
+
|
|
169
189
|
data[taxBracketNames[t]] = array
|
|
170
190
|
|
|
171
191
|
return data
|
owlplanner/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "2025.02.
|
|
1
|
+
__version__ = "2025.02.19"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: owlplanner
|
|
3
|
-
Version: 2025.2.
|
|
3
|
+
Version: 2025.2.19
|
|
4
4
|
Summary: Owl: Retirement planner with great wisdom
|
|
5
5
|
Project-URL: HomePage, https://github.com/mdlacasse/owl
|
|
6
6
|
Project-URL: Repository, https://github.com/mdlacasse/owl
|
|
@@ -708,7 +708,7 @@ Description-Content-Type: text/markdown
|
|
|
708
708
|
|
|
709
709
|
<img align=right src="https://raw.github.com/mdlacasse/Owl/main/docs/images/owl.png" width="250">
|
|
710
710
|
|
|
711
|
-
|
|
711
|
+
-------------------------------------------------------------------------------------
|
|
712
712
|
|
|
713
713
|
### TL;DR
|
|
714
714
|
Owl is a planning tool that uses a linear programming optimization algorithm to provide guidance on retirement decisions. There are a few ways to run Owl.
|
|
@@ -719,10 +719,10 @@ Owl is a planning tool that uses a linear programming optimization algorithm to
|
|
|
719
719
|
Follow these [instructions](docker/README.md) for this option.
|
|
720
720
|
|
|
721
721
|
- Run locally on your computer using Python code and libraries.
|
|
722
|
-
Follow
|
|
723
|
-
|
|
724
|
-
-----
|
|
722
|
+
Follow these [instructions](INSTALL.md) to install Owl from the source code and run it on your computer.
|
|
725
723
|
|
|
724
|
+
-------------------------------------------------------------------------------------
|
|
725
|
+
## Overview
|
|
726
726
|
This package is a retirement modeling framework for exploring the sensitivity of retirement financial decisions.
|
|
727
727
|
Strictly speaking, it is not a planning tool, but more an environment for exploring *what if* scenarios.
|
|
728
728
|
It provides different realizations of a financial strategy through the rigorous
|
|
@@ -789,7 +789,7 @@ Other asset classes can easily be added, but would add complexity while only pro
|
|
|
789
789
|
Historical data used are from
|
|
790
790
|
[Aswath Damodaran](https://pages.stern.nyu.edu/~adamodar/) at the Stern School of Business.
|
|
791
791
|
Asset allocations are selected for the duration of the plan, and these can glide linearly
|
|
792
|
-
or along a configurable s-curve
|
|
792
|
+
or along a configurable s-curve over the lifespan of the individual.
|
|
793
793
|
|
|
794
794
|
Spending profiles are adjusted for inflation, and so are all other indexable quantities. Proflies can be
|
|
795
795
|
flat or follow a *smile* curve which is also adjustable through two simple parameters.
|
|
@@ -800,10 +800,10 @@ the statistical characteristics (means and covariance matrix) of
|
|
|
800
800
|
a selected historical year range. Pure *stochastic* rates can also be generated
|
|
801
801
|
if the user provides means, volatility (expressed as standard deviation), and optionally
|
|
802
802
|
the correlations between the different assets return rates provided as a matrix, or a list of
|
|
803
|
-
the off-diagonal elements (see
|
|
803
|
+
the off-diagonal elements (see documentation for details).
|
|
804
804
|
Average rates calculated over a historical data period can also be chosen.
|
|
805
805
|
|
|
806
|
-
Monte Carlo simulations capabilities are included
|
|
806
|
+
Monte Carlo simulations capabilities are included and provide a probability of success and a histogram of
|
|
807
807
|
outcomes. These simulations can be used for either determining the probability distribution of the
|
|
808
808
|
maximum net spending amount under
|
|
809
809
|
the constraint of a desired bequest, or the probability distribution of the maximum
|
|
@@ -812,33 +812,36 @@ simulators, Owl uses an optimization algorithm for every new scenario, which res
|
|
|
812
812
|
calculations being performed. As a result, the number of cases to be considered should be kept
|
|
813
813
|
to a reasonable number. For a few hundred cases, a few minutes of calculations can provide very good estimates
|
|
814
814
|
and reliable probability distributions.
|
|
815
|
-
|
|
815
|
+
|
|
816
|
+
Optimizing each solution is more representative than event-base simulators
|
|
817
|
+
in the sense that optimal solutions
|
|
816
818
|
will naturally adjust to the return scenarios being considered.
|
|
817
819
|
This is more realistic as retirees would certainly re-evaluate
|
|
818
820
|
their expectations under severe market drops or gains.
|
|
819
|
-
This optimal approach provides a net benefit over event-based
|
|
821
|
+
This optimal approach provides a net benefit over event-based simulators,
|
|
820
822
|
which maintain a distribution strategy either fixed, or within guardrails for capturing the
|
|
821
|
-
|
|
823
|
+
retirees' reactions to the market.
|
|
822
824
|
|
|
823
|
-
Basic input parameters
|
|
825
|
+
Basic input parameters can be entered through the user interface
|
|
826
|
+
while optional additional time series can be read from
|
|
824
827
|
an Excel spreadsheet that contains future wages, contributions
|
|
825
828
|
to savings accounts, and planned *big-ticket items* such as the purchase of a lake house,
|
|
826
829
|
the sale of a boat, large gifts, or inheritance.
|
|
827
830
|
|
|
828
831
|
Three types of savings accounts are considered: taxable, tax-deferred, and tax-exempt,
|
|
829
832
|
which are all tracked separately for married individuals. Asset transition to the surviving spouse
|
|
830
|
-
is done according to beneficiary fractions for each account
|
|
833
|
+
is done according to beneficiary fractions for each type of savings account.
|
|
831
834
|
Tax status covers married filing jointly and single, depending on the number of individuals reported.
|
|
832
835
|
|
|
833
|
-
Medicare and IRMAA calculations are performed through a self-consistent loop on cash flow constraints.
|
|
834
|
-
values are simple projections of current values with the assumed inflation rates.
|
|
836
|
+
Medicare and IRMAA calculations are performed through a self-consistent loop on cash flow constraints.
|
|
837
|
+
Future values are simple projections of current values with the assumed inflation rates.
|
|
835
838
|
|
|
836
839
|
### Limitations
|
|
837
840
|
Owl is work in progress. At the current time:
|
|
838
841
|
- Only the US federal income tax is considered (and minimized through the optimization algorithm).
|
|
839
842
|
Head of household filing status has not been added but can easily be.
|
|
840
843
|
- Required minimum distributions are calculated, but tables for spouses more than 10 years apart are not included.
|
|
841
|
-
|
|
844
|
+
These cases are detected and will generate an error message.
|
|
842
845
|
- Social security rule for surviving spouse assumes that benefits were taken at full retirement age.
|
|
843
846
|
- Current version has no optimization of asset allocations between individuals and/or types of savings accounts.
|
|
844
847
|
If there is interest, that could be added in the future.
|
|
@@ -847,12 +850,12 @@ If there is interest, that could be added in the future.
|
|
|
847
850
|
This means that the Medicare premiums are calculated after an initial solution is generated,
|
|
848
851
|
and then a new solution is re-generated with these premiums as a constraint.
|
|
849
852
|
In some situations, when the income (MAGI) is near an IRMAA bracket, oscillatory solutions can arise.
|
|
850
|
-
|
|
851
|
-
While the solutions generated are very close to one another, Owl will pick the smallest one
|
|
853
|
+
While the solutions generated are very close to one another, Owl will pick the smallest solution
|
|
852
854
|
for being conservative.
|
|
853
|
-
- Part D is not included in the IRMAA calculations. Being considerably more,
|
|
854
|
-
|
|
855
|
-
|
|
855
|
+
- Part D is not included in the IRMAA calculations. Being considerably more significant,
|
|
856
|
+
only Part B is taken into account.
|
|
857
|
+
- Future tax brackets are pure speculations derived from the little we know now and projected to the next 30 years.
|
|
858
|
+
Your guesses are as good as mine.
|
|
856
859
|
|
|
857
860
|
The solution from an optimization algorithm has only two states: feasible and infeasible.
|
|
858
861
|
Therefore, unlike event-driven simulators that can tell you that your distribution strategy runs
|
|
@@ -867,7 +870,7 @@ assets to support, even with no estate being left.
|
|
|
867
870
|
|
|
868
871
|
- Documentation for the app user interface is available from the interface itself.
|
|
869
872
|
- Installation guide and software requirements can be found [here](INSTALL.md).
|
|
870
|
-
- User guide for the underlying
|
|
873
|
+
- User guide for the underlying Python package as used in a Jupyter notebook can be found [here](USER_GUIDE.md).
|
|
871
874
|
|
|
872
875
|
---------------------------------------------------------------------
|
|
873
876
|
|
|
@@ -876,13 +879,14 @@ assets to support, even with no estate being left.
|
|
|
876
879
|
- Image from [freepik](https://freepik.com)
|
|
877
880
|
- Optimization solver from [HiGHS](https://highs.dev)
|
|
878
881
|
- Streamlit Community Cloud [Streamlit](https://streamlit.io)
|
|
879
|
-
-
|
|
882
|
+
- Contributors: Josh (noimjosh@gmail.com) for Docker image code
|
|
880
883
|
|
|
881
884
|
---------------------------------------------------------------------
|
|
882
885
|
|
|
883
886
|
Copyright © 2024 - Martin-D. Lacasse
|
|
884
887
|
|
|
885
|
-
Disclaimers: I am not a financial planner. You make your own decisions.
|
|
888
|
+
Disclaimers: I am not a financial planner. You make your own decisions.
|
|
889
|
+
This program comes with no guarantee. Use at your own risk.
|
|
886
890
|
|
|
887
891
|
--------------------------------------------------------
|
|
888
892
|
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
owlplanner/__init__.py,sha256=QqrdT0Qks20osBTg7h0vJHAxpP9lL7DA99xb0nYbtw4,254
|
|
2
2
|
owlplanner/abcapi.py,sha256=LbzW_KcNy0IeHp42MUHwGu_H67B2h_e1_vu-c2ACTkQ,6646
|
|
3
|
-
owlplanner/config.py,sha256=
|
|
3
|
+
owlplanner/config.py,sha256=Yzi_Xivd_EFfuHklIoQ-LNqKCxF2ruc8p-Il_HVgEaw,11817
|
|
4
4
|
owlplanner/logging.py,sha256=tYMw04O-XYSzjTj36fmKJGLcE1VkK6k6oJNeqtKXzuc,2530
|
|
5
|
-
owlplanner/plan.py,sha256=
|
|
5
|
+
owlplanner/plan.py,sha256=FH16r5-VgpmNIntAcV4oxpDwORxWRmXqYpDIAmdUgwU,113561
|
|
6
6
|
owlplanner/progress.py,sha256=8jlCvvtgDI89zXVNMBg1-lnEyhpPvKQS2X5oAIpoOVQ,384
|
|
7
7
|
owlplanner/rates.py,sha256=TN407qU4n-bac1oymkQ_n2QKEPwFQxy6JZVGwgIkLQU,15585
|
|
8
|
-
owlplanner/tax2025.py,sha256=
|
|
8
|
+
owlplanner/tax2025.py,sha256=B-A5eU3wxdcAaxRCbT3qI-JEKoD_ZeNbg_86XhNdQEI,7745
|
|
9
9
|
owlplanner/timelists.py,sha256=tYieZU67FT6TCcQQis36JaXGI7dT6NqD7RvdEjgJL4M,4026
|
|
10
10
|
owlplanner/utils.py,sha256=HM70W60qB41zfnbl2LltNwAuLYHyy5XYbwnbNcaa6FE,2351
|
|
11
|
-
owlplanner/version.py,sha256=
|
|
11
|
+
owlplanner/version.py,sha256=Hzg91vFrm20nzeHuT9pblaGfwSjRgv3vebQPbImE-iw,28
|
|
12
12
|
owlplanner/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
13
|
owlplanner/data/rates.csv,sha256=6fxg56BVVORrj9wJlUGFdGXKvOX5r7CSca8uhUbbuIU,3734
|
|
14
|
-
owlplanner-2025.2.
|
|
15
|
-
owlplanner-2025.2.
|
|
16
|
-
owlplanner-2025.2.
|
|
17
|
-
owlplanner-2025.2.
|
|
14
|
+
owlplanner-2025.2.19.dist-info/METADATA,sha256=P2d1EnFOkSlwiiz0FUNO2nAHXu54IPj8-kPLU1-4IZo,53506
|
|
15
|
+
owlplanner-2025.2.19.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
16
|
+
owlplanner-2025.2.19.dist-info/licenses/LICENSE,sha256=IwGE9guuL-ryRPEKi6wFPI_zOhg7zDZbTYuHbSt_SAk,35823
|
|
17
|
+
owlplanner-2025.2.19.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|