owlplanner 2025.2.15__tar.gz → 2025.2.20__tar.gz
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-2025.2.15 → owlplanner-2025.2.20}/PKG-INFO +1 -1
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/docs/owl.pdf +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/docs/owl.tex +15 -13
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/pyproject.toml +1 -1
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/src/owlplanner/plan.py +6 -5
- owlplanner-2025.2.20/src/owlplanner/version.py +1 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/tests/test_repro.py +11 -11
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/ui/Quick_Start.py +3 -2
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/ui/Rates_Selection.py +7 -6
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/ui/requirements.txt +1 -1
- owlplanner-2025.2.15/src/owlplanner/version.py +0 -1
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/.devcontainer/devcontainer.json +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/.flake8 +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/.gitattributes +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/.github/workflows/github-actions-runtests.yml +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/.gitignore +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/INSTALL.md +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/LICENSE +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/README.md +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/USER_GUIDE.md +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/docker/Dockerfile +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/docker/README.md +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/docker/docker-compose.yml +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/docker/fastentrypoint.sh +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/docs/images/AD-taxDef.png +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/docs/images/AD-taxFree.png +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/docs/images/AD-taxable.png +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/docs/images/Hist_Bequest.png +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/docs/images/Hist_Spending.png +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/docs/images/MC-tutorial2a.png +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/docs/images/MC-tutorial2b.png +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/docs/images/OwlUI.png +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/docs/images/allocations.png +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/docs/images/owl.png +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/docs/images/profile.png +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/docs/images/ratesCorrelations.png +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/docs/images/ratesPlot.png +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/docs/images/savingsPlot.png +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/docs/images/sourcesPlot.png +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/docs/images/spendingPlot.png +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/docs/images/taxIncomePlot.png +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/docs/images/taxesPlot.png +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/examples/case_jack+jill.toml +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/examples/case_joe.toml +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/examples/case_john+sally.toml +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/examples/case_kim+sam-bequest.toml +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/examples/case_kim+sam-spending.toml +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/examples/jack+jill.xlsx +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/examples/joe.xlsx +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/examples/john+sally.xlsx +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/examples/template.xlsx +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/notebooks/john+sally.ipynb +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/notebooks/kim+sam.ipynb +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/notebooks/template.ipynb +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/notebooks/tutorial_1.ipynb +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/notebooks/tutorial_2.ipynb +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/notebooks/tutorial_3.ipynb +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/owlplanner.cmd +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/owlplanner.sh +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/requirements.txt +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/src/owlplanner/__init__.py +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/src/owlplanner/abcapi.py +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/src/owlplanner/config.py +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/src/owlplanner/data/__init__.py +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/src/owlplanner/data/rates.csv +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/src/owlplanner/logging.py +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/src/owlplanner/progress.py +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/src/owlplanner/rates.py +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/src/owlplanner/tax2025.py +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/src/owlplanner/timelists.py +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/src/owlplanner/utils.py +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/tests/test_logger.py +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/tests/test_regressions.py +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/tests/test_toml_cases.py +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/tests/test_units.py +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/ttt.py +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/ui/About_Owl.py +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/ui/Asset_Allocation.py +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/ui/Assets.py +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/ui/Create_Case.py +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/ui/Documentation.py +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/ui/Fixed_Income.py +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/ui/Graphs.py +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/ui/Historical_Range.py +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/ui/Logs.py +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/ui/Monte_Carlo.py +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/ui/Optimization_Parameters.py +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/ui/Output_Files.py +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/ui/README.md +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/ui/Settings.py +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/ui/Wages_And_Contributions.py +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/ui/Worksheets.py +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/ui/main+fonts.py +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/ui/main.py +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/ui/owlbridge.py +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/ui/plots.py +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/ui/progress.py +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/ui/sskeys.py +0 -0
- {owlplanner-2025.2.15 → owlplanner-2025.2.20}/ui/style.css +0 -0
|
Binary file
|
|
@@ -160,7 +160,8 @@ Parameter values are either set by the user, historical data, or by the tax code
|
|
|
160
160
|
\gamma_n = \prod_{n' = 0}^{n-1} (1 + \tau_{3n'}),
|
|
161
161
|
\end{equation}
|
|
162
162
|
with $\gamma_0 := 1$, and where $n'$ is a dummy index.
|
|
163
|
-
As the time span of interest goes from the first year to the
|
|
163
|
+
As the time span of interest goes from the first year to the beginning
|
|
164
|
+
of the year following the last year,
|
|
164
165
|
variable $\gamma_n$ will have $N_n + 1$ elements.
|
|
165
166
|
Parameters indexed for inflation will be indicated by a bar on top as in $\bar{\sigma}_n$.
|
|
166
167
|
\item [$\sigma_n$]
|
|
@@ -218,7 +219,7 @@ Parameter values are either set by the user, historical data, or by the tax code
|
|
|
218
219
|
brackets are adjusted accordingly.
|
|
219
220
|
\item [$\theta_{tn}$]
|
|
220
221
|
Tax rate for tax bracket $t$ in year $n$. Using $N_t$ time series allows to adjust income
|
|
221
|
-
tax rates in foreseeable future.
|
|
222
|
+
tax rates in the foreseeable future.
|
|
222
223
|
For example, in 2024 the rates (in decimal) are .10, .12, .22, .24, .32, .35, and .37.
|
|
223
224
|
It is speculated that the rates will revert back to 2017 rates in 2026 with
|
|
224
225
|
.10, .15, .25, .28, .33, .35, and .396. See Eq.~(\ref{Eq:IncTax0}) for its use.
|
|
@@ -286,8 +287,8 @@ or an s-curve as in
|
|
|
286
287
|
(e.g., sell a house, inheritance) or negative (e.g., buy a house, large gifts).
|
|
287
288
|
\item [$\pi_{in}$]
|
|
288
289
|
Sum of pension benefits for individual $i$ in year $n$. These amounts are typically
|
|
289
|
-
specified along with the ages at which these benefits begin.
|
|
290
|
-
|
|
290
|
+
specified along with the ages at which these benefits begin. Pensions
|
|
291
|
+
can optionally be indexed for inflation.
|
|
291
292
|
\item [$\zeta_{in}$]
|
|
292
293
|
Social security benefits for individual $i$ in year $n$. Starting age and the passing
|
|
293
294
|
of one individual for spouses will determine the time series. $\bar{\zeta}_{in}$ is
|
|
@@ -298,7 +299,8 @@ or an s-curve as in
|
|
|
298
299
|
for the heirs expressed in today's dollars. See parameter $\nu$ for the heirs tax rate.
|
|
299
300
|
\item [$\kappa_{ijn}$]
|
|
300
301
|
Sum of contributions to savings account $j$ made by individual $i$ during year $n$.
|
|
301
|
-
We assume that contributions are made at half-year to
|
|
302
|
+
We assume that contributions are made at half-year to better represent periodic
|
|
303
|
+
contributions made throughout the year.
|
|
302
304
|
In practice, a contribution
|
|
303
305
|
amount $\kappa_{ijn}$ is specified in which case the contribution to each asset
|
|
304
306
|
class is
|
|
@@ -309,7 +311,7 @@ or an s-curve as in
|
|
|
309
311
|
Sum of wages obtained by individual $i$ during year $n$.
|
|
310
312
|
Do not confuse wages $\omega$ with withdrawals $w$.
|
|
311
313
|
\item [$\mu$]
|
|
312
|
-
Dividend return rate in taxable accounts. Average is little above 2\% for S\&P 500.
|
|
314
|
+
Dividend return rate for equities in taxable accounts. Average is little above 2\% for S\&P 500.
|
|
313
315
|
\item [$\nu$]
|
|
314
316
|
Heirs income tax rate to be applied on the tax-deferred portion of the estate. This is not an estate tax
|
|
315
317
|
but rather the federal income marginal tax rate for the heirs.
|
|
@@ -359,8 +361,8 @@ or an s-curve as in
|
|
|
359
361
|
\label{Eq:IRMAA}
|
|
360
362
|
\mathcal{M}_n = \sum_{iq} z_{iqn} \bar{C}_{qn}.
|
|
361
363
|
\end{equation}
|
|
362
|
-
If the plan
|
|
363
|
-
|
|
364
|
+
If the plan needs data from 1 or 2 years ago as Medicare has already started or will in the next years,
|
|
365
|
+
values for years before current year need to be provided.
|
|
364
366
|
|
|
365
367
|
While this approach has been implemented and tested, the robustness of the {\em big M} approach
|
|
366
368
|
is not guaranteed. Moreover, the introduction of a large number ($5\times N_i\times N_m$,
|
|
@@ -409,7 +411,7 @@ All intermediate variables are in uppercase letters.
|
|
|
409
411
|
|
|
410
412
|
\item [$Q_n$]
|
|
411
413
|
Qualified dividends and long-term capital gains obtained in year $n$.
|
|
412
|
-
They only involve
|
|
414
|
+
They only involve dividends occurring in the taxable savings accounts $(j=0)$ that
|
|
413
415
|
were obtained from equities $(k=0)$, or sales of stocks due to withdrawals
|
|
414
416
|
from taxable savings accounts.
|
|
415
417
|
For simplicity, we assume that all equity sales only generate long-term capital gains and
|
|
@@ -422,10 +424,10 @@ All intermediate variables are in uppercase letters.
|
|
|
422
424
|
A formulation where only a fraction of dividends are qualified can easily be
|
|
423
425
|
implemented with the addition of another parameter.
|
|
424
426
|
Notice that we are using return rates from the previous year.
|
|
425
|
-
The first terms on the right-hand side represent
|
|
426
|
-
|
|
427
|
-
half the yearly contributions. The
|
|
428
|
-
|
|
427
|
+
The first terms on the right-hand side represent dividends generated by
|
|
428
|
+
equities $(k=0)$ in the $(j=0)$ taxable savings account plus
|
|
429
|
+
half the yearly contributions. The second term account for withdrawals $w$
|
|
430
|
+
of equities assumed to have been purchased a year ago.
|
|
429
431
|
It does not account for losses, but a market drop
|
|
430
432
|
would most likely result in stock purchase rather than sale.
|
|
431
433
|
For withdrawals, we make the assumption of
|
|
@@ -306,10 +306,8 @@ class Plan(object):
|
|
|
306
306
|
endyear = thisyear + self.horizons[i] - 1
|
|
307
307
|
self.mylog.vprint(f"{self.inames[i]:>14}: life horizon from {thisyear} -> {endyear}.")
|
|
308
308
|
|
|
309
|
-
# Prepare
|
|
309
|
+
# Prepare RMD time series.
|
|
310
310
|
self.rho_in = tx.rho_in(self.yobs, self.N_n)
|
|
311
|
-
self.sigma_n, self.theta_tn, self.Delta_tn = tx.taxParams(self.yobs, self.i_d, self.n_d,
|
|
312
|
-
self.N_n, self.yTCJA)
|
|
313
311
|
|
|
314
312
|
# If none was given, default is to begin plan on today's date.
|
|
315
313
|
self._setStartingDate(startDate)
|
|
@@ -452,6 +450,7 @@ class Plan(object):
|
|
|
452
450
|
self.mylog.vprint(f"Setting TCJA expiration year to {yTCJA}.")
|
|
453
451
|
self.yTCJA = yTCJA
|
|
454
452
|
self.caseStatus = "modified"
|
|
453
|
+
self._adjustedParameters = False
|
|
455
454
|
|
|
456
455
|
def setLongTermCapitalTaxRate(self, psi):
|
|
457
456
|
"""
|
|
@@ -990,11 +989,13 @@ class Plan(object):
|
|
|
990
989
|
|
|
991
990
|
if not self._adjustedParameters:
|
|
992
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]
|
|
993
995
|
self.DeltaBar_tn = self.Delta_tn * self.gamma_n[:-1]
|
|
994
996
|
self.zetaBar_in = self.zeta_in * self.gamma_n[:-1]
|
|
995
|
-
self.sigmaBar_n = self.sigma_n * self.gamma_n[:-1]
|
|
996
997
|
self.xiBar_n = self.xi_n * self.gamma_n[:-1]
|
|
997
|
-
self.piBar_in = self.pi_in
|
|
998
|
+
self.piBar_in = np.array(self.pi_in)
|
|
998
999
|
for i in range(self.N_i):
|
|
999
1000
|
if self.pensionIndexed[i]:
|
|
1000
1001
|
self.piBar_in[i] *= self.gamma_n[:-1]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2025.02.20"
|
|
@@ -36,7 +36,7 @@ def test_case1():
|
|
|
36
36
|
p = createJackAndJillPlan('case1')
|
|
37
37
|
p.setRates('historical', 1969)
|
|
38
38
|
p.solve('maxSpending', options={'maxRothConversion': 100, 'bequest': 500})
|
|
39
|
-
assert p.basis == pytest.approx(
|
|
39
|
+
assert p.basis == pytest.approx(81978.0, abs=0.5)
|
|
40
40
|
assert p.bequest == pytest.approx(500000, abs=0.5)
|
|
41
41
|
|
|
42
42
|
|
|
@@ -45,7 +45,7 @@ def test_case2():
|
|
|
45
45
|
p.setRates('historical', 1969)
|
|
46
46
|
p.solve('maxBequest', options={'maxRothConversion': 100, 'netSpending': 80})
|
|
47
47
|
assert p.basis == pytest.approx(80000, abs=0.5)
|
|
48
|
-
assert p.bequest == pytest.approx(
|
|
48
|
+
assert p.bequest == pytest.approx(595407.5, abs=0.5)
|
|
49
49
|
|
|
50
50
|
|
|
51
51
|
def test_config1():
|
|
@@ -54,7 +54,7 @@ def test_config1():
|
|
|
54
54
|
p.setRates('historical', 1969)
|
|
55
55
|
p.solve('maxBequest', options={'maxRothConversion': 100, 'netSpending': 80})
|
|
56
56
|
assert p.basis == pytest.approx(80000, abs=0.5)
|
|
57
|
-
assert p.bequest == pytest.approx(
|
|
57
|
+
assert p.bequest == pytest.approx(595407.5, abs=0.5)
|
|
58
58
|
p.saveConfig()
|
|
59
59
|
base_filename = 'case_' + name
|
|
60
60
|
full_filename = 'case_' + name + '.toml'
|
|
@@ -62,11 +62,11 @@ def test_config1():
|
|
|
62
62
|
p2 = owl.readConfig(base_filename)
|
|
63
63
|
p2.solve('maxBequest', options={'maxRothConversion': 100, 'netSpending': 80})
|
|
64
64
|
assert p2.basis == pytest.approx(80000, abs=0.5)
|
|
65
|
-
assert p2.bequest == pytest.approx(
|
|
65
|
+
assert p2.bequest == pytest.approx(595407.5, abs=0.5)
|
|
66
66
|
p3 = owl.readConfig(full_filename)
|
|
67
67
|
p3.solve('maxBequest', options={'maxRothConversion': 100, 'netSpending': 80})
|
|
68
68
|
assert p3.basis == pytest.approx(80000, abs=0.5)
|
|
69
|
-
assert p3.bequest == pytest.approx(
|
|
69
|
+
assert p3.bequest == pytest.approx(595407.5, abs=0.5)
|
|
70
70
|
os.remove(full_filename)
|
|
71
71
|
|
|
72
72
|
|
|
@@ -76,14 +76,14 @@ def test_config2():
|
|
|
76
76
|
p.setRates('historical', 1969)
|
|
77
77
|
p.solve('maxBequest', options={'maxRothConversion': 100, 'netSpending': 80})
|
|
78
78
|
assert p.basis == pytest.approx(80000, abs=0.5)
|
|
79
|
-
assert p.bequest == pytest.approx(
|
|
79
|
+
assert p.bequest == pytest.approx(595407.5, abs=0.5)
|
|
80
80
|
iostring = StringIO()
|
|
81
81
|
p.saveConfig(iostring)
|
|
82
82
|
# print('iostream:', iostream.getvalue())
|
|
83
83
|
p2 = owl.readConfig(iostring)
|
|
84
84
|
p2.solve('maxBequest', options={'maxRothConversion': 100, 'netSpending': 80})
|
|
85
85
|
assert p2.basis == pytest.approx(80000, abs=0.5)
|
|
86
|
-
assert p2.bequest == pytest.approx(
|
|
86
|
+
assert p2.bequest == pytest.approx(595407.5, abs=0.5)
|
|
87
87
|
|
|
88
88
|
|
|
89
89
|
def test_clone1():
|
|
@@ -92,12 +92,12 @@ def test_clone1():
|
|
|
92
92
|
p.setRates('historical', 1969)
|
|
93
93
|
p.solve('maxBequest', options={'maxRothConversion': 100, 'netSpending': 80})
|
|
94
94
|
assert p.basis == pytest.approx(80000, abs=0.5)
|
|
95
|
-
assert p.bequest == pytest.approx(
|
|
95
|
+
assert p.bequest == pytest.approx(595407.5, abs=0.5)
|
|
96
96
|
name2 = 'testclone2'
|
|
97
97
|
p2 = owl.clone(p, name2)
|
|
98
98
|
p2.solve('maxBequest', options={'maxRothConversion': 100, 'netSpending': 80})
|
|
99
99
|
assert p2.basis == pytest.approx(80000, abs=0.5)
|
|
100
|
-
assert p2.bequest == pytest.approx(
|
|
100
|
+
assert p2.bequest == pytest.approx(595407.5, abs=0.5)
|
|
101
101
|
|
|
102
102
|
|
|
103
103
|
def test_clone2():
|
|
@@ -105,10 +105,10 @@ def test_clone2():
|
|
|
105
105
|
p = createJackAndJillPlan(name)
|
|
106
106
|
p.setRates('historical', 1969)
|
|
107
107
|
p.solve('maxSpending', options={'maxRothConversion': 100, 'bequest': 0})
|
|
108
|
-
assert p.basis == pytest.approx(
|
|
108
|
+
assert p.basis == pytest.approx(92317.5, abs=0.5)
|
|
109
109
|
assert p.bequest == pytest.approx(0, abs=0.5)
|
|
110
110
|
name2 = 'testclone2'
|
|
111
111
|
p2 = owl.clone(p, name2)
|
|
112
112
|
p2.solve('maxSpending', options={'maxRothConversion': 100, 'bequest': 0})
|
|
113
|
-
assert p2.basis == pytest.approx(
|
|
113
|
+
assert p2.basis == pytest.approx(92317.5, abs=0.5)
|
|
114
114
|
assert p2.bequest == pytest.approx(0, abs=0.5)
|
|
@@ -24,8 +24,9 @@ This file is in *toml* format which is editable with a simple text editor.
|
|
|
24
24
|
time table with anticipated wages, future contributions
|
|
25
25
|
to savings accounts, and anticipated big-ticket items, which can be either expenses or income.
|
|
26
26
|
This file is in Excel or LibreOffice format, and has one tab per individual in the plan.
|
|
27
|
+
If no file is provided, values will default to zero, but these values can be edited in the app.
|
|
27
28
|
|
|
28
|
-
With these two files, a scenario can be created and solved
|
|
29
|
+
With these two files, a scenario can be created and solved with only a few steps. We will use the case
|
|
29
30
|
of Jack and Jill provided here as an example:
|
|
30
31
|
1) Download these two files from the GitHub repository
|
|
31
32
|
(right-click on the link and select `Save link as...`):
|
|
@@ -46,7 +47,7 @@ experiment with different parameters.
|
|
|
46
47
|
|
|
47
48
|
For creating your own cases, you can start
|
|
48
49
|
from scratch by selecting `New Case...` in the selection box while on the **Create Case** page,
|
|
49
|
-
and fill in the information needed on each page
|
|
50
|
+
and fill in the information needed on each page in the `Case Setup` section.
|
|
50
51
|
Once a case has been fully parameterized and successfully optimized,
|
|
51
52
|
its parameters can be saved by using the `Download case file...` button on the `Output Files` page.
|
|
52
53
|
|
|
@@ -210,23 +210,24 @@ else:
|
|
|
210
210
|
|
|
211
211
|
st.divider()
|
|
212
212
|
st.write("### Other rates")
|
|
213
|
-
col1, col2 = st.columns(
|
|
213
|
+
col1, col2, col3 = st.columns(3, gap="large", vertical_alignment="top")
|
|
214
214
|
with col1:
|
|
215
215
|
kz.initKey("divRate", 2)
|
|
216
216
|
helpmsg = "Average annual (qualified) dividend return rate on stock portfolio for income tax purposes."
|
|
217
217
|
ret = kz.getNum("Dividend rate (%)", "divRate", max_value=100.0, format="%.2f", help=helpmsg, step=1.0)
|
|
218
218
|
|
|
219
219
|
st.write("#### Income taxes")
|
|
220
|
-
col1, col2 = st.columns(
|
|
220
|
+
col1, col2, col3 = st.columns(3, gap="large", vertical_alignment="top")
|
|
221
221
|
with col1:
|
|
222
222
|
kz.initKey("gainTx", 15)
|
|
223
223
|
ret = kz.getNum("Long-term capital gains tax rate (%)", "gainTx", max_value=100.0, step=1.0)
|
|
224
224
|
|
|
225
|
-
kz.initKey("yTCJA", 2026)
|
|
226
|
-
helpmsg = "Year at which the Tax Cut And Job Act tax rates are speculated to expire."
|
|
227
|
-
ret = kz.getIntNum("TCJA expiration year", "yTCJA", help=helpmsg)
|
|
228
|
-
|
|
229
225
|
with col2:
|
|
230
226
|
kz.initKey("heirsTx", 30)
|
|
231
227
|
helpmsg = "Marginal tax rate that heirs would have to pay on inherited tax-deferred balance."
|
|
232
228
|
ret = kz.getNum("Heirs marginal tax rate (%)", "heirsTx", max_value=100.0, help=helpmsg, step=1.0)
|
|
229
|
+
|
|
230
|
+
with col3:
|
|
231
|
+
kz.initKey("yTCJA", 2026)
|
|
232
|
+
helpmsg = "Year at which the Tax Cut And Job Act tax rates are speculated to expire."
|
|
233
|
+
ret = kz.getIntNum("TCJA expiration year", "yTCJA", help=helpmsg)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "2025.02.15"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|