owlplanner 2025.3.13__tar.gz → 2025.3.15__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.3.13 → owlplanner-2025.3.15}/PKG-INFO +1 -1
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/docs/owl.pdf +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/docs/owl.tex +25 -6
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/pyproject.toml +1 -1
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/src/owlplanner/plan.py +19 -3
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/src/owlplanner/utils.py +9 -2
- owlplanner-2025.3.15/src/owlplanner/version.py +1 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/tests/test_regressions.py +27 -27
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ui/Rates_Selection.py +2 -1
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ui/requirements.txt +1 -1
- owlplanner-2025.3.13/src/owlplanner/version.py +0 -1
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/.devcontainer/devcontainer.json +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/.flake8 +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/.gitattributes +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/.github/workflows/github-actions-runtests.yml +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/.gitignore +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/INSTALL.md +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/LICENSE +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/Papers/FE00006821-Class-VI-Injection-Permit--Salient-Features-and-Regulatory-Challenges_Final.pdf +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/Papers/Kou-OptionPricingDouble-2004.pdf +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/Papers/Multi-Period Mean Expected-Shortfall Strategies Cut Your Losses and Ride Your Gains .pdf +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/Papers/Optimal Asset Allocation for Retirement Saving Deterministic Vs. Time Consistent Adaptive Strategies.pdf +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/Papers/Rule-based_strategies_for_dynamic_life_cycle_inves.pdf +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/Papers/s10436-006-0062-y.pdf +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/README.md +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/USER_GUIDE.md +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/docker/Dockerfile +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/docker/README.md +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/docker/docker-compose.yml +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/docker/fastentrypoint.sh +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/docs/images/AD-taxDef.png +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/docs/images/AD-taxFree.png +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/docs/images/AD-taxable.png +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/docs/images/Hist_Bequest.png +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/docs/images/Hist_Spending.png +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/docs/images/MC-tutorial2a.png +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/docs/images/MC-tutorial2b.png +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/docs/images/OwlUI.png +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/docs/images/allocations.png +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/docs/images/owl.png +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/docs/images/profile.png +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/docs/images/ratesCorrelations.png +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/docs/images/ratesPlot.png +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/docs/images/savingsPlot.png +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/docs/images/sourcesPlot.png +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/docs/images/spendingPlot.png +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/docs/images/taxIncomePlot.png +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/docs/images/taxesPlot.png +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/examples/case_jack+jill.toml +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/examples/case_joe.toml +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/examples/case_john+sally.toml +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/examples/case_jon+jane.toml +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/examples/case_kim+sam-bequest.toml +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/examples/case_kim+sam-spending.toml +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/examples/jack+jill.xlsx +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/examples/joe.xlsx +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/examples/john+sally.xlsx +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/examples/jon+jane.xlsx +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/examples/template.xlsx +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/notebooks/john+sally.ipynb +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/notebooks/kim+sam.ipynb +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/notebooks/template.ipynb +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/notebooks/tutorial_1.ipynb +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/notebooks/tutorial_2.ipynb +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/notebooks/tutorial_3.ipynb +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/owlplanner.cmd +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/owlplanner.sh +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/requirements.txt +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/src/owlplanner/__init__.py +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/src/owlplanner/abcapi.py +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/src/owlplanner/config.py +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/src/owlplanner/data/__init__.py +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/src/owlplanner/data/rates.csv +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/src/owlplanner/logging.py +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/src/owlplanner/progress.py +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/src/owlplanner/rates.py +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/src/owlplanner/tax2025.py +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/src/owlplanner/timelists.py +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/tests/test_logger.py +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/tests/test_repro.py +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/tests/test_toml_cases.py +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/tests/test_units.py +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ttt.py +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ui/About_Owl.py +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ui/Asset_Allocation.py +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ui/Create_Case.py +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ui/Current_Assets.py +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ui/Documentation.py +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ui/Fixed_Income.py +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ui/Graphs.py +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ui/Historical_Range.py +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ui/Logs.py +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ui/Monte_Carlo.py +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ui/Optimization_Parameters.py +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ui/Output_Files.py +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ui/Quick_Start.py +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ui/README.md +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ui/Settings.py +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ui/Wages_And_Contributions.py +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ui/Worksheets.py +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ui/main+fonts.py +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ui/main.py +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ui/owlbridge.py +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ui/plots.py +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ui/progress.py +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ui/sskeys.py +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ui/style.css +0 -0
- {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ui/tomlexamples.py +0 -0
|
Binary file
|
|
@@ -87,11 +87,11 @@ index name as a subscript, e.g., $N_i$ for index $i$.
|
|
|
87
87
|
More asset classes could be considered at the cost of increasing
|
|
88
88
|
the complexity of the problem while not generating much more insights.
|
|
89
89
|
\item [$n$]
|
|
90
|
-
|
|
90
|
+
Index of year being modeled. Period being modeled runs from the beginning of year 0 to
|
|
91
91
|
the end of year $N_n-1$, and therefore $N_n + 1$ years are considered.
|
|
92
92
|
Year $N_n$ is the first year following the passing of all
|
|
93
93
|
individuals in the plan. The time period for all decision variables is annual.
|
|
94
|
-
For spouses, the end of year $n_d-1$ is the
|
|
94
|
+
For spouses, the end of year $n_d-1$ is when the first individual is assumed to pass while
|
|
95
95
|
the survivor will decease at the end of year $N_n-1$ of the plan.
|
|
96
96
|
\item [$t$]
|
|
97
97
|
Federal income tax bracket. $t$ goes from 0 to $N_t - 1$, from low to high.
|
|
@@ -435,6 +435,24 @@ All intermediate variables are in uppercase letters.
|
|
|
435
435
|
the taxable savings account is being depleted slowly. An implementation keeping track
|
|
436
436
|
of stock purchases and sales is beyond the goal of providing a guide for retirement decisions.
|
|
437
437
|
|
|
438
|
+
\item [$P_n$]
|
|
439
|
+
Amount of 10\% early withdrawal penalty in year $n$,
|
|
440
|
+
\begin{equation}
|
|
441
|
+
\label{Eq:PenTax0}
|
|
442
|
+
P_n = 0.10 \sum_{i, j\neq0} (1 - \mathcal{H}(n - n_{i,59})) w_{ijn}.
|
|
443
|
+
\end{equation}
|
|
444
|
+
Here, $H(n - n_{i, 59})$ is a Heavyside step function which is 0 or 1, depending on the sign of
|
|
445
|
+
its argument:
|
|
446
|
+
\begin{equation}
|
|
447
|
+
\mathcal{H}(x) :=
|
|
448
|
+
\begin{cases}
|
|
449
|
+
0 & x < 0 \\
|
|
450
|
+
1 & x \geq 0.
|
|
451
|
+
\end{cases}
|
|
452
|
+
\end{equation}
|
|
453
|
+
The variable $n_{i, 59}$ is the year index when individual $i$ turns 59,
|
|
454
|
+
or 0 if already 59 years old or older.
|
|
455
|
+
|
|
438
456
|
\item [$T_n$]
|
|
439
457
|
Amount of income tax paid on taxable ordinary income $G_n$ in year $n$.
|
|
440
458
|
This is the taxes paid on ordinary income expressed as the sum of the amounts
|
|
@@ -453,8 +471,9 @@ All intermediate variables are in uppercase letters.
|
|
|
453
471
|
filling in the lower brackets first when optimizing. In that case
|
|
454
472
|
\begin{equation}
|
|
455
473
|
\label{Eq:IncTax1}
|
|
456
|
-
T_n = \sum_t \bar{F}_{tn} \theta_{tn}
|
|
474
|
+
T_n = \sum_t \bar{F}_{tn} \theta_{tn}
|
|
457
475
|
\end{equation}
|
|
476
|
+
|
|
458
477
|
\item [$U_n$]
|
|
459
478
|
Amount of income tax paid on long-term capital gains and qualified dividends in year $n$,
|
|
460
479
|
\begin{equation}
|
|
@@ -688,7 +707,7 @@ add the market returns to the savings balances.
|
|
|
688
707
|
\begin{eqnarray}
|
|
689
708
|
g_n = \sum_i [\omega_{in} + \bar{\zeta}_{in} + \pi_{in} ]
|
|
690
709
|
+ \sum_{ij} w_{ijn} + \sum_i \Lambda^\pm_{in} - s_{n}
|
|
691
|
-
- T_n - U_n - \mathcal{M}^\ell_n.
|
|
710
|
+
- P_n - T_n - U_n - \mathcal{M}^\ell_n.
|
|
692
711
|
\end{eqnarray}
|
|
693
712
|
When both spouses are alive, surplus $s_n$ gets deposited in the taxable accounts
|
|
694
713
|
according to variable $\eta$ as described in Eq.~(\ref{Eq:eta}),
|
|
@@ -702,7 +721,7 @@ add the market returns to the savings balances.
|
|
|
702
721
|
Replacing intermediate variables and bringing all variables to the left-hand side, we get
|
|
703
722
|
\begin{eqnarray}
|
|
704
723
|
\label{Eq:C4}
|
|
705
|
-
g_n - \sum_{ij} w_{ijn} +
|
|
724
|
+
g_n - \sum_{ij} w_{ijn} + 0.1 \sum_{i,j\neq0} (1-\mathcal{H}(n - n_{i, 59})) w_{ijn} + s_n
|
|
706
725
|
+ \sum_t \bar{F}_{tn} \theta_{t n} &&\nonumber \\
|
|
707
726
|
+ \psi\alpha_{i00n} \sum_{i} \left[\mu(b_{i0n} - w_{i0n} + d_{in})
|
|
708
727
|
+ w_{i0n}\max(0, \tau_{0n-1})\right]
|
|
@@ -1010,7 +1029,7 @@ For the equality constraint on net spending expressed in Eq.~(\ref{Eq:C4}),
|
|
|
1010
1029
|
we add $N_n$ more rows to $A_ey = v$ as
|
|
1011
1030
|
\begin{eqnarray}
|
|
1012
1031
|
A_e[J_2(n), q_g(n)] &=& 1, \nonumber \\
|
|
1013
|
-
A_e[J_2(n), q_w(i, j ,n)] &=& -1 + \delta(j, 0)\psi\alpha_{i00n}(\max(0, \tau_{0n-1}) - \mu), \nonumber \\
|
|
1032
|
+
A_e[J_2(n), q_w(i, j ,n)] &=& -1 + .1(1-\delta(j, 0))(1-\mathcal{H}(n-n_{i, 59})) + \delta(j, 0)\psi\alpha_{i00n}(\max(0, \tau_{0n-1}) - \mu), \nonumber \\
|
|
1014
1033
|
A_e[J_2(n), q_d(i, n)] &=& 1 + \psi\mu\alpha_{i00n}, \nonumber \\
|
|
1015
1034
|
A_e[J_2(n), q_F(t, n)] &=& \theta_{t n}, \nonumber \\
|
|
1016
1035
|
A_e[J_2(n), q_b(i, 0, n)] &=& \psi\mu\alpha_{i00n}, \nonumber \\
|
|
@@ -266,6 +266,9 @@ class Plan(object):
|
|
|
266
266
|
# self.horizons = [yobs[i] + expectancy[i] - thisyear + 1 for i in range(self.N_i)]
|
|
267
267
|
self.N_n = np.max(self.horizons)
|
|
268
268
|
self.year_n = np.linspace(thisyear, thisyear + self.N_n - 1, self.N_n, dtype=np.int32)
|
|
269
|
+
# Year in the plan (if any) where individuals turn 59. For 10% withdrawal penalty.
|
|
270
|
+
self.n59 = 59 - thisyear + self.yobs
|
|
271
|
+
self.n59[self.n59 < 0] = 0
|
|
269
272
|
# Handle passing of one spouse before the other.
|
|
270
273
|
if self.N_i == 2 and np.min(self.horizons) != np.max(self.horizons):
|
|
271
274
|
self.n_d = np.min(self.horizons)
|
|
@@ -1281,8 +1284,9 @@ class Plan(object):
|
|
|
1281
1284
|
# Minus capital gains on taxable withdrawals using last year's rate if >=0.
|
|
1282
1285
|
# Plus taxable account withdrawals, and all other withdrawals.
|
|
1283
1286
|
row.addElem(_q3(Cw, i, 0, n, Ni, Nj, Nn), fac * (tau_0prev[n] - self.mu) - 1)
|
|
1284
|
-
|
|
1285
|
-
row.addElem(_q3(Cw, i,
|
|
1287
|
+
penalty = 0.1 if n < self.n59[i] else 0
|
|
1288
|
+
row.addElem(_q3(Cw, i, 1, n, Ni, Nj, Nn), -1 + penalty)
|
|
1289
|
+
row.addElem(_q3(Cw, i, 2, n, Ni, Nj, Nn), -1 + penalty)
|
|
1286
1290
|
row.addElem(_q2(Cd, i, n, Ni, Nn), fac * self.mu)
|
|
1287
1291
|
|
|
1288
1292
|
# Minus tax on ordinary income, T_n.
|
|
@@ -1970,6 +1974,12 @@ class Plan(object):
|
|
|
1970
1974
|
self.G_n = np.sum(self.F_tn, axis=0)
|
|
1971
1975
|
self.T_tn = self.F_tn * self.theta_tn
|
|
1972
1976
|
self.T_n = np.sum(self.T_tn, axis=0)
|
|
1977
|
+
self.P_n = np.zeros(Nn)
|
|
1978
|
+
# Add early withdrawal penalty if any.
|
|
1979
|
+
for i in range(Ni):
|
|
1980
|
+
self.P_n[0:self.n59[i]] += 0.1*(self.w_ijn[i, 1, 0:self.n59[i]] + self.w_ijn[i, 2, 0:self.n59[i]])
|
|
1981
|
+
|
|
1982
|
+
self.T_n += self.P_n
|
|
1973
1983
|
|
|
1974
1984
|
tau_0 = np.array(self.tau_kn[0, :])
|
|
1975
1985
|
tau_0[tau_0 < 0] = 0
|
|
@@ -2112,6 +2122,11 @@ class Plan(object):
|
|
|
2112
2122
|
dic[f"-- Subtotal in tax bracket {tname}"] = f"{u.d(taxPaidNow)}"
|
|
2113
2123
|
dic[f"-- [Subtotal in tax bracket {tname}]"] = f"{u.d(taxPaid)}"
|
|
2114
2124
|
|
|
2125
|
+
penaltyPaid = np.sum(self.P_n, axis=0)
|
|
2126
|
+
penaltyPaidNow = np.sum(self.P_n / self.gamma_n[:-1], axis=0)
|
|
2127
|
+
dic["-- Subtotal in early withdrawal penalty"] = f"{u.d(penaltyPaidNow)}"
|
|
2128
|
+
dic["-- [Subtotal in early withdrawal penalty]"] = f"{u.d(penaltyPaid)}"
|
|
2129
|
+
|
|
2115
2130
|
taxPaid = np.sum(self.U_n, axis=0)
|
|
2116
2131
|
taxPaidNow = np.sum(self.U_n / self.gamma_n[:-1], axis=0)
|
|
2117
2132
|
dic["Total tax paid on gains and dividends"] = f"{u.d(taxPaidNow)}"
|
|
@@ -2507,6 +2522,7 @@ class Plan(object):
|
|
|
2507
2522
|
|
|
2508
2523
|
title = self._name + "\nRaw Income Sources"
|
|
2509
2524
|
stypes = self.sources_in.keys()
|
|
2525
|
+
# stypes = [item for item in stypes if "RothX" not in item]
|
|
2510
2526
|
|
|
2511
2527
|
if tag != "":
|
|
2512
2528
|
title += " - " + tag
|
|
@@ -2517,7 +2533,7 @@ class Plan(object):
|
|
|
2517
2533
|
else:
|
|
2518
2534
|
yformat = "\\$k (" + str(self.year_n[0]) + "\\$)"
|
|
2519
2535
|
sources_in = {}
|
|
2520
|
-
for key in
|
|
2536
|
+
for key in stypes:
|
|
2521
2537
|
sources_in[key] = self.sources_in[key] / self.gamma_n[:-1]
|
|
2522
2538
|
|
|
2523
2539
|
fig, ax = _stackPlot(
|
|
@@ -70,8 +70,8 @@ def getUnits(units) -> int:
|
|
|
70
70
|
return fac
|
|
71
71
|
|
|
72
72
|
|
|
73
|
-
#
|
|
74
|
-
# krond = lambda a, b: 1 if a == b else 0
|
|
73
|
+
# Next two functins could be a one-line lambda functions.
|
|
74
|
+
# e.g., krond = lambda a, b: 1 if a == b else 0
|
|
75
75
|
def krond(a, b) -> int:
|
|
76
76
|
"""
|
|
77
77
|
Kronecker integer delta function.
|
|
@@ -79,6 +79,13 @@ def krond(a, b) -> int:
|
|
|
79
79
|
return 1 if a == b else 0
|
|
80
80
|
|
|
81
81
|
|
|
82
|
+
def heavyside(x) -> int:
|
|
83
|
+
"""
|
|
84
|
+
Heavyside step function.
|
|
85
|
+
"""
|
|
86
|
+
return 1 if x >= 0 else 0
|
|
87
|
+
|
|
88
|
+
|
|
82
89
|
def roundCents(values, decimals=2):
|
|
83
90
|
"""
|
|
84
91
|
Round values in NumPy array down to second decimal.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2025.03.15"
|
|
@@ -89,7 +89,7 @@ def createPlan(ni, name, ny, topAge):
|
|
|
89
89
|
|
|
90
90
|
def test_withdrawal1():
|
|
91
91
|
n = 10
|
|
92
|
-
p = createPlan(1, 'withdrawal1', n,
|
|
92
|
+
p = createPlan(1, 'withdrawal1', n, 70)
|
|
93
93
|
amount = 3.0
|
|
94
94
|
p.setAccountBalances(taxable=[0], taxDeferred=[amount], taxFree=[0])
|
|
95
95
|
p.setAllocationRatios('individual', generic=[[[0, 0, 0, 100], [0, 0, 0, 100]]])
|
|
@@ -102,7 +102,7 @@ def test_withdrawal1():
|
|
|
102
102
|
|
|
103
103
|
def test_withdrawal2():
|
|
104
104
|
n = 10
|
|
105
|
-
p = createPlan(1, 'withdrawal2', n,
|
|
105
|
+
p = createPlan(1, 'withdrawal2', n, 70)
|
|
106
106
|
# Small taxable income creates an income smaller than standard deduction. Testing e_n.
|
|
107
107
|
amount = 40.0
|
|
108
108
|
p.setAccountBalances(taxable=[0], taxDeferred=[amount], taxFree=[0])
|
|
@@ -116,7 +116,7 @@ def test_withdrawal2():
|
|
|
116
116
|
|
|
117
117
|
def test_withdrawal2_2():
|
|
118
118
|
n = 10
|
|
119
|
-
p = createPlan(2, 'withdrawal2_2', n,
|
|
119
|
+
p = createPlan(2, 'withdrawal2_2', n, 70)
|
|
120
120
|
# Small taxable income creates an income smaller than standard deduction. Testing e_n.
|
|
121
121
|
amount = 50
|
|
122
122
|
p.setAccountBalances(taxable=[0, 0], taxDeferred=[amount/2, amount/2], taxFree=[0, 0])
|
|
@@ -130,7 +130,7 @@ def test_withdrawal2_2():
|
|
|
130
130
|
|
|
131
131
|
def test_withdrawal3():
|
|
132
132
|
n = 6
|
|
133
|
-
p = createPlan(1, 'withdrawal3', n,
|
|
133
|
+
p = createPlan(1, 'withdrawal3', n, 70)
|
|
134
134
|
amount = 60
|
|
135
135
|
p.setAccountBalances(taxable=[0], taxDeferred=[0], taxFree=[amount])
|
|
136
136
|
p.setAllocationRatios('individual', generic=[[[0, 0, 0, 100], [0, 0, 0, 100]]])
|
|
@@ -143,7 +143,7 @@ def test_withdrawal3():
|
|
|
143
143
|
|
|
144
144
|
def test_withdrawal3_2():
|
|
145
145
|
n = 6
|
|
146
|
-
p = createPlan(2, 'withdrawal3', n,
|
|
146
|
+
p = createPlan(2, 'withdrawal3', n, 70)
|
|
147
147
|
amount = 60
|
|
148
148
|
p.setAccountBalances(taxable=[0, 0], taxDeferred=[0, 0], taxFree=[amount/2, amount/2])
|
|
149
149
|
p.setAllocationRatios('spouses', generic=[[0, 0, 0, 100], [0, 0, 0, 100]])
|
|
@@ -156,7 +156,7 @@ def test_withdrawal3_2():
|
|
|
156
156
|
|
|
157
157
|
def test_taxfreegrowth1():
|
|
158
158
|
n = 12
|
|
159
|
-
p = createPlan(1, 'taxfreegrowth1', n,
|
|
159
|
+
p = createPlan(1, 'taxfreegrowth1', n, 72)
|
|
160
160
|
amount = 120
|
|
161
161
|
p.setAccountBalances(taxable=[0], taxDeferred=[0], taxFree=[amount])
|
|
162
162
|
p.setAllocationRatios('individual', generic=[[[0, 0, 100, 0], [0, 0, 100, 0]]])
|
|
@@ -170,7 +170,7 @@ def test_taxfreegrowth1():
|
|
|
170
170
|
|
|
171
171
|
def test_taxfreegrowth1_2():
|
|
172
172
|
n = 12
|
|
173
|
-
p = createPlan(2, 'taxfreegrowth1', n,
|
|
173
|
+
p = createPlan(2, 'taxfreegrowth1', n, 72)
|
|
174
174
|
amount = 120
|
|
175
175
|
p.setAccountBalances(taxable=[0, 0], taxDeferred=[0, 0], taxFree=[amount/2, amount/2])
|
|
176
176
|
p.setAllocationRatios('spouses', generic=[[0, 0, 100, 0], [0, 0, 100, 0]])
|
|
@@ -184,7 +184,7 @@ def test_taxfreegrowth1_2():
|
|
|
184
184
|
|
|
185
185
|
def test_taxfreegrowth2():
|
|
186
186
|
n = 15
|
|
187
|
-
p = createPlan(1, 'taxfreegrowth2', n,
|
|
187
|
+
p = createPlan(1, 'taxfreegrowth2', n, 75)
|
|
188
188
|
amount = 120
|
|
189
189
|
p.setAccountBalances(taxable=[0], taxDeferred=[0], taxFree=[amount])
|
|
190
190
|
p.setAllocationRatios('individual', generic=[[[0, 50, 50, 0], [0, 50, 50, 0]]])
|
|
@@ -198,7 +198,7 @@ def test_taxfreegrowth2():
|
|
|
198
198
|
|
|
199
199
|
def test_taxfreegrowth2_2():
|
|
200
200
|
n = 15
|
|
201
|
-
p = createPlan(2, 'taxfreegrowth2', n,
|
|
201
|
+
p = createPlan(2, 'taxfreegrowth2', n, 75)
|
|
202
202
|
amount = 120
|
|
203
203
|
p.setAccountBalances(taxable=[0, 0], taxDeferred=[0, 0], taxFree=[amount/2, amount/2])
|
|
204
204
|
p.setAllocationRatios('spouses', generic=[[0, 50, 50, 0], [0, 50, 50, 0]])
|
|
@@ -212,7 +212,7 @@ def test_taxfreegrowth2_2():
|
|
|
212
212
|
|
|
213
213
|
def test_taxfreegrowth3():
|
|
214
214
|
n = 15
|
|
215
|
-
p = createPlan(1, 'taxfreegrowth3', n,
|
|
215
|
+
p = createPlan(1, 'taxfreegrowth3', n, 75)
|
|
216
216
|
amount = 120
|
|
217
217
|
p.setAccountBalances(taxable=[0], taxDeferred=[0], taxFree=[amount])
|
|
218
218
|
p.setAllocationRatios('individual', generic=[[[50, 50, 0, 0], [50, 50, 0, 0]]])
|
|
@@ -226,7 +226,7 @@ def test_taxfreegrowth3():
|
|
|
226
226
|
|
|
227
227
|
def test_taxfreegrowth3_2():
|
|
228
228
|
n = 15
|
|
229
|
-
p = createPlan(2, 'taxfreegrowth3', n,
|
|
229
|
+
p = createPlan(2, 'taxfreegrowth3', n, 75)
|
|
230
230
|
amount = 120
|
|
231
231
|
p.setAccountBalances(taxable=[0, 0], taxDeferred=[0, 0], taxFree=[amount/2, amount/2])
|
|
232
232
|
p.setAllocationRatios('spouses', generic=[[50, 50, 0, 0], [50, 50, 0, 0]])
|
|
@@ -240,7 +240,7 @@ def test_taxfreegrowth3_2():
|
|
|
240
240
|
|
|
241
241
|
def test_taxfreegrowth4():
|
|
242
242
|
n = 16
|
|
243
|
-
p = createPlan(1, 'taxfreegrowth4', n,
|
|
243
|
+
p = createPlan(1, 'taxfreegrowth4', n, 76)
|
|
244
244
|
amount = 120
|
|
245
245
|
p.setAccountBalances(taxable=[0], taxDeferred=[0], taxFree=[amount])
|
|
246
246
|
p.setAllocationRatios('individual', generic=[[[0, 50, 50, 0], [0, 50, 50, 0]]])
|
|
@@ -255,7 +255,7 @@ def test_taxfreegrowth4():
|
|
|
255
255
|
|
|
256
256
|
def test_taxfreegrowth4_2():
|
|
257
257
|
n = 16
|
|
258
|
-
p = createPlan(2, 'taxfreegrowth4', n,
|
|
258
|
+
p = createPlan(2, 'taxfreegrowth4', n, 76)
|
|
259
259
|
amount = 120
|
|
260
260
|
p.setAccountBalances(taxable=[0, 0], taxDeferred=[0, 0], taxFree=[amount/2, amount/2])
|
|
261
261
|
p.setAllocationRatios('spouses', generic=[[0, 50, 50, 0], [0, 50, 50, 0]])
|
|
@@ -270,7 +270,7 @@ def test_taxfreegrowth4_2():
|
|
|
270
270
|
|
|
271
271
|
def test_taxfreegrowth5():
|
|
272
272
|
n = 15
|
|
273
|
-
p = createPlan(1, 'taxfreegrowth5', n,
|
|
273
|
+
p = createPlan(1, 'taxfreegrowth5', n, 76)
|
|
274
274
|
amount = 120
|
|
275
275
|
p.setAccountBalances(taxable=[0], taxDeferred=[0], taxFree=[amount])
|
|
276
276
|
p.setAllocationRatios('individual', generic=[[[0, 0, 100, 0], [0, 0, 100, 0]]])
|
|
@@ -284,7 +284,7 @@ def test_taxfreegrowth5():
|
|
|
284
284
|
|
|
285
285
|
def test_taxfreegrowth5_2():
|
|
286
286
|
n = 15
|
|
287
|
-
p = createPlan(2, 'taxfreegrowth5', n,
|
|
287
|
+
p = createPlan(2, 'taxfreegrowth5', n, 76)
|
|
288
288
|
amount = 120
|
|
289
289
|
p.setAccountBalances(taxable=[0, 0], taxDeferred=[0, 0], taxFree=[amount/2, amount/2])
|
|
290
290
|
p.setAllocationRatios('spouses', generic=[[0, 0, 100, 0], [0, 0, 100, 0]])
|
|
@@ -298,7 +298,7 @@ def test_taxfreegrowth5_2():
|
|
|
298
298
|
|
|
299
299
|
def test_taxfreegrowth6():
|
|
300
300
|
n = 15
|
|
301
|
-
p = createPlan(1, 'taxfreegrowth6', n,
|
|
301
|
+
p = createPlan(1, 'taxfreegrowth6', n, 76)
|
|
302
302
|
amount = 120
|
|
303
303
|
p.setAccountBalances(taxable=[0], taxDeferred=[0], taxFree=[amount])
|
|
304
304
|
p.setAllocationRatios('individual', generic=[[[0, 0, 0, 100], [0, 0, 0, 100]]])
|
|
@@ -312,7 +312,7 @@ def test_taxfreegrowth6():
|
|
|
312
312
|
|
|
313
313
|
def test_taxfreegrowth6_2():
|
|
314
314
|
n = 15
|
|
315
|
-
p = createPlan(2, 'taxfreegrowth6', n,
|
|
315
|
+
p = createPlan(2, 'taxfreegrowth6', n, 76)
|
|
316
316
|
amount = 120
|
|
317
317
|
p.setAccountBalances(taxable=[0, 0], taxDeferred=[0, 0], taxFree=[amount/2, amount/2])
|
|
318
318
|
p.setAllocationRatios('spouses', generic=[[0, 0, 0, 100], [0, 0, 0, 100]])
|
|
@@ -326,7 +326,7 @@ def test_taxfreegrowth6_2():
|
|
|
326
326
|
|
|
327
327
|
def test_taxfreegrowth7():
|
|
328
328
|
n = 15
|
|
329
|
-
p = createPlan(1, 'taxfreegrowth7', n,
|
|
329
|
+
p = createPlan(1, 'taxfreegrowth7', n, 76)
|
|
330
330
|
amount = 120
|
|
331
331
|
p.setAccountBalances(taxable=[0], taxDeferred=[0], taxFree=[amount])
|
|
332
332
|
p.setAllocationRatios('individual', generic=[[[0, 100, 0, 0], [0, 100, 0, 0]]])
|
|
@@ -340,7 +340,7 @@ def test_taxfreegrowth7():
|
|
|
340
340
|
|
|
341
341
|
def test_taxfreegrowth7_2():
|
|
342
342
|
n = 15
|
|
343
|
-
p = createPlan(2, 'taxfreegrowth7', n,
|
|
343
|
+
p = createPlan(2, 'taxfreegrowth7', n, 76)
|
|
344
344
|
amount = 120
|
|
345
345
|
p.setAccountBalances(taxable=[0, 0], taxDeferred=[0, 0], taxFree=[amount/2, amount/2])
|
|
346
346
|
p.setAllocationRatios('spouses', generic=[[0, 100, 0, 0], [0, 100, 0, 0]])
|
|
@@ -354,7 +354,7 @@ def test_taxfreegrowth7_2():
|
|
|
354
354
|
|
|
355
355
|
def test_taxfreegrowth8():
|
|
356
356
|
n = 15
|
|
357
|
-
p = createPlan(1, 'taxfreegrowth8', n,
|
|
357
|
+
p = createPlan(1, 'taxfreegrowth8', n, 76)
|
|
358
358
|
amount = 120
|
|
359
359
|
p.setAccountBalances(taxable=[0], taxDeferred=[0], taxFree=[amount])
|
|
360
360
|
p.setAllocationRatios('individual', generic=[[[100, 0, 0, 0], [100, 0, 0, 0]]])
|
|
@@ -368,7 +368,7 @@ def test_taxfreegrowth8():
|
|
|
368
368
|
|
|
369
369
|
def test_taxfreegrowth8_2():
|
|
370
370
|
n = 15
|
|
371
|
-
p = createPlan(2, 'taxfreegrowth8', n,
|
|
371
|
+
p = createPlan(2, 'taxfreegrowth8', n, 76)
|
|
372
372
|
amount = 120
|
|
373
373
|
p.setAccountBalances(taxable=[0, 0], taxDeferred=[0, 0], taxFree=[amount/2, amount/2])
|
|
374
374
|
p.setAllocationRatios('spouses', generic=[[100, 0, 0, 0], [100, 0, 0, 0]])
|
|
@@ -382,7 +382,7 @@ def test_taxfreegrowth8_2():
|
|
|
382
382
|
|
|
383
383
|
def test_annuity1():
|
|
384
384
|
n = 12
|
|
385
|
-
p = createPlan(1, 'annuity1', n,
|
|
385
|
+
p = createPlan(1, 'annuity1', n, 76)
|
|
386
386
|
amount = 120
|
|
387
387
|
p.setAccountBalances(taxable=[0], taxDeferred=[0], taxFree=[amount])
|
|
388
388
|
p.setAllocationRatios('individual', generic=[[[0, 0, 100, 0], [0, 0, 100, 0]]])
|
|
@@ -400,7 +400,7 @@ def test_annuity1():
|
|
|
400
400
|
|
|
401
401
|
def test_annuity1_2():
|
|
402
402
|
n = 12
|
|
403
|
-
p = createPlan(2, 'annuity1', n,
|
|
403
|
+
p = createPlan(2, 'annuity1', n, 76)
|
|
404
404
|
amount = 120
|
|
405
405
|
p.setAccountBalances(taxable=[0, 0], taxDeferred=[0, 0], taxFree=[amount/2, amount/2])
|
|
406
406
|
p.setAllocationRatios('spouses', generic=[[0, 0, 100, 0], [0, 0, 100, 0]])
|
|
@@ -418,7 +418,7 @@ def test_annuity1_2():
|
|
|
418
418
|
|
|
419
419
|
def test_annuity2():
|
|
420
420
|
n = 18
|
|
421
|
-
p = createPlan(1, 'annuity2', n,
|
|
421
|
+
p = createPlan(1, 'annuity2', n, 76)
|
|
422
422
|
amount = 120
|
|
423
423
|
p.setAccountBalances(taxable=[0], taxDeferred=[0], taxFree=[amount])
|
|
424
424
|
p.setAllocationRatios('individual', generic=[[[0, 0, 100, 0], [0, 0, 100, 0]]])
|
|
@@ -436,7 +436,7 @@ def test_annuity2():
|
|
|
436
436
|
|
|
437
437
|
def test_annuity2_2():
|
|
438
438
|
n = 18
|
|
439
|
-
p = createPlan(2, 'annuity2', n,
|
|
439
|
+
p = createPlan(2, 'annuity2', n, 78)
|
|
440
440
|
amount = 120
|
|
441
441
|
p.setAccountBalances(taxable=[0, 0], taxDeferred=[0, 0], taxFree=[amount/2, amount/2])
|
|
442
442
|
p.setAllocationRatios('spouses', generic=[[0, 0, 100, 0], [0, 0, 100, 0]])
|
|
@@ -454,7 +454,7 @@ def test_annuity2_2():
|
|
|
454
454
|
|
|
455
455
|
def test_annuity3():
|
|
456
456
|
n = 30
|
|
457
|
-
p = createPlan(1, 'annuity2', n,
|
|
457
|
+
p = createPlan(1, 'annuity2', n, 90)
|
|
458
458
|
amount = 100
|
|
459
459
|
p.setAccountBalances(taxable=[0], taxDeferred=[0], taxFree=[amount])
|
|
460
460
|
p.setAllocationRatios('individual', generic=[[[0, 0, 100, 0], [0, 0, 100, 0]]])
|
|
@@ -472,7 +472,7 @@ def test_annuity3():
|
|
|
472
472
|
|
|
473
473
|
def test_annuity3_2():
|
|
474
474
|
n = 30
|
|
475
|
-
p = createPlan(2, 'annuity2', n,
|
|
475
|
+
p = createPlan(2, 'annuity2', n, 90)
|
|
476
476
|
amount = 100
|
|
477
477
|
p.setAccountBalances(taxable=[0, 0], taxDeferred=[0, 0], taxFree=[amount/2, amount/2])
|
|
478
478
|
p.setAllocationRatios('spouses', generic=[[0, 0, 100, 0], [0, 0, 100, 0]])
|
|
@@ -93,10 +93,11 @@ else:
|
|
|
93
93
|
|
|
94
94
|
col1, col2, col3, col4 = st.columns(4, gap="large", vertical_alignment="top")
|
|
95
95
|
with col1:
|
|
96
|
+
maxValue = owb.TO if kz.getKey("varyingType") == "historical" else kz.getKey("yto") - 1
|
|
96
97
|
st.number_input(
|
|
97
98
|
"Starting year",
|
|
98
99
|
min_value=owb.FROM,
|
|
99
|
-
max_value=
|
|
100
|
+
max_value=maxValue,
|
|
100
101
|
value=kz.getKey("yfrm"),
|
|
101
102
|
on_change=updateRates,
|
|
102
103
|
args=["yfrm"],
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "2025.03.13"
|
|
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
|
|
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
|