owlplanner 2025.3.12__tar.gz → 2025.3.14__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.12 → owlplanner-2025.3.14}/.devcontainer/devcontainer.json +32 -32
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/PKG-INFO +1 -1
- owlplanner-2025.3.14/Papers/FE00006821-Class-VI-Injection-Permit--Salient-Features-and-Regulatory-Challenges_Final.pdf +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/docs/owl.tex +12 -2
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/pyproject.toml +1 -1
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/src/owlplanner/plan.py +40 -20
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/src/owlplanner/utils.py +9 -2
- owlplanner-2025.3.14/src/owlplanner/version.py +1 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/tests/test_regressions.py +27 -27
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/ui/Documentation.py +7 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/ui/Output_Files.py +1 -1
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/ui/Rates_Selection.py +3 -2
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/ui/requirements.txt +1 -1
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/ui/sskeys.py +0 -1
- owlplanner-2025.3.12/src/owlplanner/version.py +0 -1
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/.flake8 +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/.gitattributes +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/.github/workflows/github-actions-runtests.yml +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/.gitignore +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/INSTALL.md +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/LICENSE +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/Papers/Kou-OptionPricingDouble-2004.pdf +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/Papers/Multi-Period Mean Expected-Shortfall Strategies Cut Your Losses and Ride Your Gains .pdf +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/Papers/Optimal Asset Allocation for Retirement Saving Deterministic Vs. Time Consistent Adaptive Strategies.pdf +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/Papers/Rule-based_strategies_for_dynamic_life_cycle_inves.pdf +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/Papers/s10436-006-0062-y.pdf +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/README.md +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/USER_GUIDE.md +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/docker/Dockerfile +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/docker/README.md +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/docker/docker-compose.yml +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/docker/fastentrypoint.sh +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/docs/images/AD-taxDef.png +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/docs/images/AD-taxFree.png +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/docs/images/AD-taxable.png +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/docs/images/Hist_Bequest.png +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/docs/images/Hist_Spending.png +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/docs/images/MC-tutorial2a.png +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/docs/images/MC-tutorial2b.png +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/docs/images/OwlUI.png +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/docs/images/allocations.png +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/docs/images/owl.png +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/docs/images/profile.png +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/docs/images/ratesCorrelations.png +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/docs/images/ratesPlot.png +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/docs/images/savingsPlot.png +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/docs/images/sourcesPlot.png +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/docs/images/spendingPlot.png +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/docs/images/taxIncomePlot.png +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/docs/images/taxesPlot.png +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/docs/owl.pdf +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/examples/case_jack+jill.toml +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/examples/case_joe.toml +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/examples/case_john+sally.toml +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/examples/case_jon+jane.toml +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/examples/case_kim+sam-bequest.toml +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/examples/case_kim+sam-spending.toml +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/examples/jack+jill.xlsx +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/examples/joe.xlsx +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/examples/john+sally.xlsx +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/examples/jon+jane.xlsx +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/examples/template.xlsx +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/notebooks/john+sally.ipynb +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/notebooks/kim+sam.ipynb +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/notebooks/template.ipynb +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/notebooks/tutorial_1.ipynb +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/notebooks/tutorial_2.ipynb +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/notebooks/tutorial_3.ipynb +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/owlplanner.cmd +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/owlplanner.sh +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/requirements.txt +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/src/owlplanner/__init__.py +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/src/owlplanner/abcapi.py +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/src/owlplanner/config.py +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/src/owlplanner/data/__init__.py +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/src/owlplanner/data/rates.csv +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/src/owlplanner/logging.py +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/src/owlplanner/progress.py +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/src/owlplanner/rates.py +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/src/owlplanner/tax2025.py +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/src/owlplanner/timelists.py +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/tests/test_logger.py +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/tests/test_repro.py +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/tests/test_toml_cases.py +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/tests/test_units.py +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/ttt.py +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/ui/About_Owl.py +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/ui/Asset_Allocation.py +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/ui/Create_Case.py +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/ui/Current_Assets.py +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/ui/Fixed_Income.py +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/ui/Graphs.py +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/ui/Historical_Range.py +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/ui/Logs.py +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/ui/Monte_Carlo.py +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/ui/Optimization_Parameters.py +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/ui/Quick_Start.py +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/ui/README.md +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/ui/Settings.py +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/ui/Wages_And_Contributions.py +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/ui/Worksheets.py +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/ui/main+fonts.py +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/ui/main.py +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/ui/owlbridge.py +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/ui/plots.py +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/ui/progress.py +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/ui/style.css +0 -0
- {owlplanner-2025.3.12 → owlplanner-2025.3.14}/ui/tomlexamples.py +0 -0
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "Python 3",
|
|
3
|
-
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
|
|
4
|
-
"image": "mcr.microsoft.com/devcontainers/python:1-3.11-bullseye",
|
|
5
|
-
"customizations": {
|
|
6
|
-
"codespaces": {
|
|
7
|
-
"openFiles": [
|
|
8
|
-
"README.md",
|
|
9
|
-
"ui/main.py"
|
|
10
|
-
]
|
|
11
|
-
},
|
|
12
|
-
"vscode": {
|
|
13
|
-
"settings": {},
|
|
14
|
-
"extensions": [
|
|
15
|
-
"ms-python.python",
|
|
16
|
-
"ms-python.vscode-pylance"
|
|
17
|
-
]
|
|
18
|
-
}
|
|
19
|
-
},
|
|
20
|
-
"updateContentCommand": "[ -f packages.txt ] && sudo apt update && sudo apt upgrade -y && sudo xargs apt install -y <packages.txt; [ -f requirements.txt ] && pip3 install --user -r requirements.txt; pip3 install --user streamlit; echo '✅ Packages installed and Requirements met'",
|
|
21
|
-
"postAttachCommand": {
|
|
22
|
-
"server": "streamlit run ui/main.py --server.enableCORS false --server.enableXsrfProtection false"
|
|
23
|
-
},
|
|
24
|
-
"portsAttributes": {
|
|
25
|
-
"8501": {
|
|
26
|
-
"label": "Application",
|
|
27
|
-
"onAutoForward": "openPreview"
|
|
28
|
-
}
|
|
29
|
-
},
|
|
30
|
-
"forwardPorts": [
|
|
31
|
-
8501
|
|
32
|
-
]
|
|
1
|
+
{
|
|
2
|
+
"name": "Python 3",
|
|
3
|
+
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
|
|
4
|
+
"image": "mcr.microsoft.com/devcontainers/python:1-3.11-bullseye",
|
|
5
|
+
"customizations": {
|
|
6
|
+
"codespaces": {
|
|
7
|
+
"openFiles": [
|
|
8
|
+
"README.md",
|
|
9
|
+
"ui/main.py"
|
|
10
|
+
]
|
|
11
|
+
},
|
|
12
|
+
"vscode": {
|
|
13
|
+
"settings": {},
|
|
14
|
+
"extensions": [
|
|
15
|
+
"ms-python.python",
|
|
16
|
+
"ms-python.vscode-pylance"
|
|
17
|
+
]
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"updateContentCommand": "[ -f packages.txt ] && sudo apt update && sudo apt upgrade -y && sudo xargs apt install -y <packages.txt; [ -f requirements.txt ] && pip3 install --user -r requirements.txt; pip3 install --user streamlit; echo '✅ Packages installed and Requirements met'",
|
|
21
|
+
"postAttachCommand": {
|
|
22
|
+
"server": "streamlit run ui/main.py --server.enableCORS false --server.enableXsrfProtection false"
|
|
23
|
+
},
|
|
24
|
+
"portsAttributes": {
|
|
25
|
+
"8501": {
|
|
26
|
+
"label": "Application",
|
|
27
|
+
"onAutoForward": "openPreview"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"forwardPorts": [
|
|
31
|
+
8501
|
|
32
|
+
]
|
|
33
33
|
}
|
|
Binary file
|
|
@@ -91,7 +91,7 @@ index name as a subscript, e.g., $N_i$ for index $i$.
|
|
|
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.
|
|
@@ -441,8 +441,18 @@ All intermediate variables are in uppercase letters.
|
|
|
441
441
|
paid in each tax bracket as
|
|
442
442
|
\begin{equation}
|
|
443
443
|
\label{Eq:IncTax0}
|
|
444
|
-
T_n = \sum_t f_{tn}\bar{\Delta}_{tn}\theta_{tn}.
|
|
444
|
+
T_n = \sum_t f_{tn}\bar{\Delta}_{tn}\theta_{tn} + 0.10 \sum_i (1 - \mathcal{H}(n - n_{i,60})) w_{i1n},
|
|
445
445
|
\end{equation}
|
|
446
|
+
where $H(n - n_{i, 60})$ is a Heavyside step function which is 0 or 1, depending on the sign of
|
|
447
|
+
its argument:
|
|
448
|
+
\[
|
|
449
|
+
\mathcal{H}(x) \def
|
|
450
|
+
\begin{cases}
|
|
451
|
+
0 & x < 0 \\
|
|
452
|
+
1 & x \geq 0. \\
|
|
453
|
+
\end{cases}
|
|
454
|
+
\]
|
|
455
|
+
Here, $n_{i, 60}$ is the year when individual $i$ will turn 60.
|
|
446
456
|
Notice that $G_n$ is also defined by Eq.~(\ref{Eq:Tx1}), and that optimal
|
|
447
457
|
values of $f_{tn}$ have to
|
|
448
458
|
minimize $T_n$ when either the bequest or the desired net spending are maximized.
|
|
@@ -34,7 +34,7 @@ from owlplanner import logging
|
|
|
34
34
|
from owlplanner import progress
|
|
35
35
|
|
|
36
36
|
|
|
37
|
-
# This makes all graphs
|
|
37
|
+
# This makes all graphs to have the same height.
|
|
38
38
|
plt.rcParams.update({'figure.autolayout': True})
|
|
39
39
|
|
|
40
40
|
|
|
@@ -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)
|
|
@@ -1289,6 +1292,11 @@ class Plan(object):
|
|
|
1289
1292
|
for t in range(Nt):
|
|
1290
1293
|
row.addElem(_q2(CF, t, n, Nt, Nn), self.theta_tn[t, n])
|
|
1291
1294
|
|
|
1295
|
+
# Minus 10% penalty on early withdrawals.
|
|
1296
|
+
if n < self.n59[i]:
|
|
1297
|
+
row.addElem(_q3(Cw, i, 1, n, Ni, Nj, Nn), 0.1)
|
|
1298
|
+
row.addElem(_q3(Cw, i, 2, n, Ni, Nj, Nn), 0.1)
|
|
1299
|
+
|
|
1292
1300
|
A.addRow(row, rhs, rhs)
|
|
1293
1301
|
|
|
1294
1302
|
# Impose income profile.
|
|
@@ -1970,6 +1978,12 @@ class Plan(object):
|
|
|
1970
1978
|
self.G_n = np.sum(self.F_tn, axis=0)
|
|
1971
1979
|
self.T_tn = self.F_tn * self.theta_tn
|
|
1972
1980
|
self.T_n = np.sum(self.T_tn, axis=0)
|
|
1981
|
+
self.penalty_n = np.zeros(Nn)
|
|
1982
|
+
# Add early withdrawal penalties if any.
|
|
1983
|
+
for i in range(Ni):
|
|
1984
|
+
self.penalty_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]])
|
|
1985
|
+
|
|
1986
|
+
self.T_n += self.penalty_n
|
|
1973
1987
|
|
|
1974
1988
|
tau_0 = np.array(self.tau_kn[0, :])
|
|
1975
1989
|
tau_0[tau_0 < 0] = 0
|
|
@@ -2094,33 +2108,38 @@ class Plan(object):
|
|
|
2094
2108
|
totIncome = np.sum(self.g_n, axis=0)
|
|
2095
2109
|
totIncomeNow = np.sum(self.g_n / self.gamma_n[:-1], axis=0)
|
|
2096
2110
|
dic["Total net spending"] = f"{u.d(totIncomeNow)}"
|
|
2097
|
-
dic["
|
|
2111
|
+
dic["[Total net spending]"] = f"{u.d(totIncome)}"
|
|
2098
2112
|
|
|
2099
2113
|
totRoth = np.sum(self.x_in, axis=(0, 1))
|
|
2100
2114
|
totRothNow = np.sum(np.sum(self.x_in, axis=0) / self.gamma_n[:-1], axis=0)
|
|
2101
2115
|
dic["Total Roth conversions"] = f"{u.d(totRothNow)}"
|
|
2102
|
-
dic["
|
|
2116
|
+
dic["[Total Roth conversions]"] = f"{u.d(totRoth)}"
|
|
2103
2117
|
|
|
2104
2118
|
taxPaid = np.sum(self.T_n, axis=0)
|
|
2105
2119
|
taxPaidNow = np.sum(self.T_n / self.gamma_n[:-1], axis=0)
|
|
2106
2120
|
dic["Total income tax paid on ordinary income"] = f"{u.d(taxPaidNow)}"
|
|
2107
|
-
dic["
|
|
2121
|
+
dic["[Total income tax paid on ordinary income]"] = f"{u.d(taxPaid)}"
|
|
2108
2122
|
for t in range(self.N_t):
|
|
2109
2123
|
taxPaid = np.sum(self.T_tn[t], axis=0)
|
|
2110
2124
|
taxPaidNow = np.sum(self.T_tn[t] / self.gamma_n[:-1], axis=0)
|
|
2111
2125
|
tname = tx.taxBracketNames[t]
|
|
2112
2126
|
dic[f"-- Subtotal in tax bracket {tname}"] = f"{u.d(taxPaidNow)}"
|
|
2113
|
-
dic[f"
|
|
2127
|
+
dic[f"-- [Subtotal in tax bracket {tname}]"] = f"{u.d(taxPaid)}"
|
|
2128
|
+
|
|
2129
|
+
penaltyPaid = np.sum(self.penalty_n, axis=0)
|
|
2130
|
+
penaltyPaidNow = np.sum(self.penalty_n / self.gamma_n[:-1], axis=0)
|
|
2131
|
+
dic["-- Subtotal in early withdrawal penalty"] = f"{u.d(penaltyPaidNow)}"
|
|
2132
|
+
dic["-- [Subtotal in early withdrawal penalty]"] = f"{u.d(penaltyPaid)}"
|
|
2114
2133
|
|
|
2115
2134
|
taxPaid = np.sum(self.U_n, axis=0)
|
|
2116
2135
|
taxPaidNow = np.sum(self.U_n / self.gamma_n[:-1], axis=0)
|
|
2117
2136
|
dic["Total tax paid on gains and dividends"] = f"{u.d(taxPaidNow)}"
|
|
2118
|
-
dic["
|
|
2137
|
+
dic["[Total tax paid on gains and dividends]"] = f"{u.d(taxPaid)}"
|
|
2119
2138
|
|
|
2120
2139
|
taxPaid = np.sum(self.M_n, axis=0)
|
|
2121
2140
|
taxPaidNow = np.sum(self.M_n / self.gamma_n[:-1], axis=0)
|
|
2122
2141
|
dic["Total Medicare premiums paid"] = f"{u.d(taxPaidNow)}"
|
|
2123
|
-
dic["
|
|
2142
|
+
dic["[Total Medicare premiums paid]"] = f"{u.d(taxPaid)}"
|
|
2124
2143
|
|
|
2125
2144
|
if self.N_i == 2 and self.n_d < self.N_n:
|
|
2126
2145
|
p_j = self.partialEstate_j * (1 - self.phi_j)
|
|
@@ -2135,24 +2154,24 @@ class Plan(object):
|
|
|
2135
2154
|
iname_s = self.inames[self.i_s]
|
|
2136
2155
|
iname_d = self.inames[self.i_d]
|
|
2137
2156
|
dic[f"Sum of spousal transfer to {iname_s} in year {ynx}"] = (f"{u.d(totSpousalNow)}")
|
|
2138
|
-
dic[f"
|
|
2157
|
+
dic[f"[Sum of spousal transfer to {iname_s} in year {ynx}]"] = (
|
|
2139
2158
|
f"{u.d(totSpousal)}")
|
|
2140
|
-
dic[f"-- Spousal transfer to {iname_s} in year {ynx} - taxable
|
|
2159
|
+
dic[f"-- [Spousal transfer to {iname_s} in year {ynx} - taxable]"] = (
|
|
2141
2160
|
f"{u.d(q_j[0])}")
|
|
2142
|
-
dic[f"-- Spousal transfer to {iname_s} in year {ynx} - tax-def
|
|
2161
|
+
dic[f"-- [Spousal transfer to {iname_s} in year {ynx} - tax-def]"] = (
|
|
2143
2162
|
f"{u.d(q_j[1])}")
|
|
2144
|
-
dic[f"-- Spousal transfer to {iname_s} in year {ynx} - tax-free
|
|
2163
|
+
dic[f"-- [Spousal transfer to {iname_s} in year {ynx} - tax-free]"] = (
|
|
2145
2164
|
f"{u.d(q_j[2])}")
|
|
2146
2165
|
|
|
2147
2166
|
dic[f"Sum of post-tax non-spousal bequests from {iname_d} in year {ynx}"] = (
|
|
2148
2167
|
f"{u.d(totOthersNow)}")
|
|
2149
|
-
dic[f"
|
|
2168
|
+
dic[f"[Sum of post-tax non-spousal bequests from {iname_d} in year {ynx}]"] = (
|
|
2150
2169
|
f"{u.d(totOthers)}")
|
|
2151
|
-
dic[f"-- Post-tax non-spousal bequests from {iname_d} in year {ynx} - taxable
|
|
2170
|
+
dic[f"-- [Post-tax non-spousal bequests from {iname_d} in year {ynx} - taxable]"] = (
|
|
2152
2171
|
f"{u.d(p_j[0])}")
|
|
2153
|
-
dic[f"-- Post-tax non-spousal bequests from {iname_d} in year {ynx} - tax-def
|
|
2172
|
+
dic[f"-- [Post-tax non-spousal bequests from {iname_d} in year {ynx} - tax-def]"] = (
|
|
2154
2173
|
f"{u.d(p_j[1])}")
|
|
2155
|
-
dic[f"-- Post-tax non-spousal bequests from {iname_d} in year {ynx} - tax-free
|
|
2174
|
+
dic[f"-- [Post-tax non-spousal bequests from {iname_d} in year {ynx} - tax-free]"] = (
|
|
2156
2175
|
f"{u.d(p_j[2])}")
|
|
2157
2176
|
|
|
2158
2177
|
estate = np.sum(self.b_ijn[:, :, self.N_n], axis=0)
|
|
@@ -2161,10 +2180,10 @@ class Plan(object):
|
|
|
2161
2180
|
totEstate = np.sum(estate)
|
|
2162
2181
|
totEstateNow = totEstate / self.gamma_n[-1]
|
|
2163
2182
|
dic[f"Total estate value at the end of {lastyear}"] = (f"{u.d(totEstateNow)}")
|
|
2164
|
-
dic[f"
|
|
2165
|
-
dic[f"-- Post-tax account value at the end of {lastyear} - taxable
|
|
2166
|
-
dic[f"-- Post-tax account value at the end of {lastyear} - tax-def
|
|
2167
|
-
dic[f"-- Post-tax account value at the end of {lastyear} - tax-free
|
|
2183
|
+
dic[f"[Total estate value at the end of {lastyear}]"] = (f"{u.d(totEstate)}")
|
|
2184
|
+
dic[f"-- [Post-tax account value at the end of {lastyear} - taxable]"] = (f"{u.d(estate[0])}")
|
|
2185
|
+
dic[f"-- [Post-tax account value at the end of {lastyear} - tax-def]"] = (f"{u.d(estate[1])}")
|
|
2186
|
+
dic[f"-- [Post-tax account value at the end of {lastyear} - tax-free]"] = (f"{u.d(estate[2])}")
|
|
2168
2187
|
|
|
2169
2188
|
dic["Plan starting date"] = str(self.startDate)
|
|
2170
2189
|
dic[f"Cumulative inflation factor from start date to end of {lastyear}"] = (f"{self.gamma_n[-1]:.2f}")
|
|
@@ -2507,6 +2526,7 @@ class Plan(object):
|
|
|
2507
2526
|
|
|
2508
2527
|
title = self._name + "\nRaw Income Sources"
|
|
2509
2528
|
stypes = self.sources_in.keys()
|
|
2529
|
+
# stypes = [item for item in stypes if "RothX" not in item]
|
|
2510
2530
|
|
|
2511
2531
|
if tag != "":
|
|
2512
2532
|
title += " - " + tag
|
|
@@ -2517,7 +2537,7 @@ class Plan(object):
|
|
|
2517
2537
|
else:
|
|
2518
2538
|
yformat = "\\$k (" + str(self.year_n[0]) + "\\$)"
|
|
2519
2539
|
sources_in = {}
|
|
2520
|
-
for key in
|
|
2540
|
+
for key in stypes:
|
|
2521
2541
|
sources_in[key] = self.sources_in[key] / self.gamma_n[:-1]
|
|
2522
2542
|
|
|
2523
2543
|
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.14"
|
|
@@ -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]])
|
|
@@ -53,6 +53,13 @@ reported as yearly values. These include wages, income, rates, social security,
|
|
|
53
53
|
Dollar values are typically entered in thousands, unless in tables, where they
|
|
54
54
|
are entered and reported in unit dollars.
|
|
55
55
|
|
|
56
|
+
If you are accessing Owl through the Chrome browser,
|
|
57
|
+
the performance manager might be configured to disable hidden or inactive tabs.
|
|
58
|
+
This will cause your Owl session to inadvertently reset, and losing the state of the calculator.
|
|
59
|
+
To avoid this, configure Chrome to keep the page active using
|
|
60
|
+
`More Tools` -> `Performance` -> `Always keep these sites active` and
|
|
61
|
+
add the site *owlplanner.streamlit.app*.
|
|
62
|
+
|
|
56
63
|
There are four sections in the user interface:
|
|
57
64
|
**Case Setup**, **Single Scenario**, **Multiple Scenarios**, and **Resources**.
|
|
58
65
|
The sections below follow the same logical order.
|
|
@@ -20,7 +20,7 @@ else:
|
|
|
20
20
|
st.write("#### Synopsis")
|
|
21
21
|
styledDf = df[1:].style.map(kz.colorBySign)
|
|
22
22
|
st.dataframe(styledDf, use_container_width=True)
|
|
23
|
-
st.caption("Values are in today's
|
|
23
|
+
st.caption("Values with [legend] are nominal, otherwise in today's \\$.")
|
|
24
24
|
st.download_button(
|
|
25
25
|
"Download synopsis", data=df[1:].to_string(), file_name=f"Synopsis_{caseName}.txt",
|
|
26
26
|
mime="text/plain;charset=UTF-8"
|
|
@@ -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"],
|
|
@@ -228,5 +229,5 @@ else:
|
|
|
228
229
|
|
|
229
230
|
with col3:
|
|
230
231
|
kz.initKey("yTCJA", 2026)
|
|
231
|
-
helpmsg = "Year at which the Tax Cut And Job Act tax rates are speculated to
|
|
232
|
+
helpmsg = "Year at which the Tax Cut And Job Act tax rates are speculated to be expired."
|
|
232
233
|
ret = kz.getIntNum("TCJA expiration year", "yTCJA", help=helpmsg)
|
|
@@ -510,7 +510,6 @@ def orangeDivider():
|
|
|
510
510
|
|
|
511
511
|
|
|
512
512
|
def titleBar(txt, choices=None):
|
|
513
|
-
# st.html(f"<div style='text-align: left;color: orange;font-style: italic;'>{currentCaseName()}</div>")
|
|
514
513
|
if choices is None:
|
|
515
514
|
choices = onlyCaseNames()
|
|
516
515
|
helpmsg = "Select an existing case."
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "2025.03.12"
|
|
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
|