owlplanner 2025.8.1__tar.gz → 2025.9.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.8.1 → owlplanner-2025.9.15}/PKG-INFO +2 -2
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/README.md +1 -1
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/examples/case_drawdowncalc-comparison-1.toml +2 -1
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/pyproject.toml +1 -1
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/requirements.txt +1 -1
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/src/owlplanner/config.py +4 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/src/owlplanner/plan.py +9 -10
- owlplanner-2025.9.15/src/owlplanner/version.py +1 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/ui/Documentation.py +10 -14
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/ui/Optimization_Parameters.py +15 -10
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/ui/Output_Files.py +1 -1
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/ui/owlbridge.py +4 -4
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/ui/sskeys.py +1 -1
- owlplanner-2025.8.1/examples/joe.xlsx +0 -0
- owlplanner-2025.8.1/examples/john+sally.xlsx +0 -0
- owlplanner-2025.8.1/examples/jon+jane.xlsx +0 -0
- owlplanner-2025.8.1/examples/kim+sam.xlsx +0 -0
- owlplanner-2025.8.1/examples/template.xlsx +0 -0
- owlplanner-2025.8.1/examples.new/case_drawdowncalc-comparison-1.toml +0 -56
- owlplanner-2025.8.1/examples.new/case_jack+jill.toml +0 -57
- owlplanner-2025.8.1/examples.new/case_joe.toml +0 -54
- owlplanner-2025.8.1/examples.new/case_john+sally.toml +0 -53
- owlplanner-2025.8.1/examples.new/case_jon+jane.toml +0 -56
- owlplanner-2025.8.1/examples.new/case_kim+sam-bequest.toml +0 -56
- owlplanner-2025.8.1/examples.new/case_kim+sam-spending.toml +0 -56
- owlplanner-2025.8.1/examples.new/jack+jill.xlsx +0 -0
- owlplanner-2025.8.1/mediopt/owl.pdf +0 -0
- owlplanner-2025.8.1/mediopt/owl.tex +0 -1524
- owlplanner-2025.8.1/mediopt/owl.tex.merged +0 -1524
- owlplanner-2025.8.1/mediopt/piecewiseConstant.png +0 -0
- owlplanner-2025.8.1/mediopt/plan.py +0 -3077
- owlplanner-2025.8.1/mediopt/tax2025.py +0 -294
- owlplanner-2025.8.1/src/owlplanner/version.py +0 -1
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/.devcontainer/devcontainer.json +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/.flake8 +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/.gitattributes +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/.github/workflows/github-actions-runtests.yml +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/.gitignore +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/.streamlit/config.toml +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/.streamlit/fullconfig.toml +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/INSTALL.md +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/LICENSE +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/RELEASE_NOTES.md +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/USER_GUIDE.md +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/docker/Dockerfile.build +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/docker/Dockerfile.run +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/docker/README.md +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/docker/buildentrypoint.sh +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/docker/docker-compose.yml +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/docker/runentrypoint.sh +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/docs/images/AD-taxDef.png +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/docs/images/AD-taxFree.png +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/docs/images/AD-taxable.png +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/docs/images/Hist_Bequest.png +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/docs/images/Hist_Spending.png +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/docs/images/MC-tutorial2a.png +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/docs/images/MC-tutorial2b.png +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/docs/images/OwlUI.png +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/docs/images/allocations.png +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/docs/images/owl.png +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/docs/images/piecewiseConstant.png +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/docs/images/profile.png +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/docs/images/ratesCorrelations.png +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/docs/images/ratesPlot.png +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/docs/images/savingsPlot.png +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/docs/images/sourcesPlot.png +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/docs/images/spendingPlot.png +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/docs/images/taxIncomePlot.png +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/docs/images/taxesPlot.png +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/docs/owl.pdf +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/docs/owl.tex +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/examples/case_jack+jill.toml +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/examples/case_joe.toml +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/examples/case_john+sally.toml +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/examples/case_jon+jane.toml +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/examples/case_kim+sam-bequest.toml +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/examples/case_kim+sam-spending.toml +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/examples/jack+jill.xlsx +0 -0
- {owlplanner-2025.8.1/examples.new → owlplanner-2025.9.15/examples}/joe.xlsx +0 -0
- {owlplanner-2025.8.1/examples.new → owlplanner-2025.9.15/examples}/john+sally.xlsx +0 -0
- {owlplanner-2025.8.1/examples.new → owlplanner-2025.9.15/examples}/jon+jane.xlsx +0 -0
- {owlplanner-2025.8.1/examples.new → owlplanner-2025.9.15/examples}/kim+sam.xlsx +0 -0
- {owlplanner-2025.8.1/examples.new → owlplanner-2025.9.15/examples}/template.xlsx +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/notebooks/john+sally.ipynb +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/notebooks/kim+sam.ipynb +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/notebooks/template.ipynb +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/notebooks/tutorial_1.ipynb +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/notebooks/tutorial_2.ipynb +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/notebooks/tutorial_3.ipynb +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/owlplanner.cmd +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/owlplanner.sh +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/pytest.ini +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/src/owlplanner/__init__.py +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/src/owlplanner/abcapi.py +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/src/owlplanner/data/__init__.py +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/src/owlplanner/data/rates.csv +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/src/owlplanner/mylogging.py +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/src/owlplanner/plotting/__init__.py +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/src/owlplanner/plotting/base.py +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/src/owlplanner/plotting/factory.py +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/src/owlplanner/plotting/matplotlib_backend.py +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/src/owlplanner/plotting/plotly_backend.py +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/src/owlplanner/progress.py +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/src/owlplanner/rates.py +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/src/owlplanner/tax2025.py +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/src/owlplanner/timelists.py +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/src/owlplanner/utils.py +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/tests/test_logger.py +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/tests/test_regressions.py +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/tests/test_repro.py +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/tests/test_toml_cases.py +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/tests/test_ui_asset_allocation.py +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/tests/test_ui_compare_summaries.py +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/tests/test_ui_sskeys.py +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/tests/test_units.py +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/ui/About_Owl.py +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/ui/Asset_Allocation.py +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/ui/Create_Case.py +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/ui/Current_Assets.py +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/ui/Fixed_Income.py +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/ui/Graphs.py +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/ui/Historical_Range.py +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/ui/Logs.py +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/ui/Monte_Carlo.py +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/ui/Quick_Start.py +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/ui/README.md +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/ui/Rates_Selection.py +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/ui/Settings.py +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/ui/Wages_and_Contributions.py +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/ui/Worksheets.py +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/ui/__init__.py +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/ui/main.py +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/ui/progress.py +0 -0
- {owlplanner-2025.8.1 → owlplanner-2025.9.15}/ui/tomlexamples.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: owlplanner
|
|
3
|
-
Version: 2025.
|
|
3
|
+
Version: 2025.9.15
|
|
4
4
|
Summary: Owl: Retirement planner with great wisdom
|
|
5
5
|
Project-URL: HomePage, https://github.com/mdlacasse/owl
|
|
6
6
|
Project-URL: Repository, https://github.com/mdlacasse/owl
|
|
@@ -744,7 +744,7 @@ Look at *Basic capabilities* below for more detail.
|
|
|
744
744
|
One can certainly have a savings plan, but due to the volatility of financial investments,
|
|
745
745
|
it is impossible to have a certain asset earnings plan. This does not mean one cannot make decisions.
|
|
746
746
|
These decisions need to be guided with an understanding of the sensitivity of the parameters.
|
|
747
|
-
This is exactly where this tool fits
|
|
747
|
+
This is exactly where this tool fits in. Given your savings capabilities and spending desires,
|
|
748
748
|
it can generate different future realizations of
|
|
749
749
|
your strategy under different market assumptions, helping to better understand your financial situation.
|
|
750
750
|
|
|
@@ -38,7 +38,7 @@ Look at *Basic capabilities* below for more detail.
|
|
|
38
38
|
One can certainly have a savings plan, but due to the volatility of financial investments,
|
|
39
39
|
it is impossible to have a certain asset earnings plan. This does not mean one cannot make decisions.
|
|
40
40
|
These decisions need to be guided with an understanding of the sensitivity of the parameters.
|
|
41
|
-
This is exactly where this tool fits
|
|
41
|
+
This is exactly where this tool fits in. Given your savings capabilities and spending desires,
|
|
42
42
|
it can generate different future realizations of
|
|
43
43
|
your strategy under different market assumptions, helping to better understand your financial situation.
|
|
44
44
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"Plan Name" = "drawdowncalc-comparison-1"
|
|
2
|
-
Description = "This is a case involving a single individual. Case is used for comparing with @hugcity's DrawdownCalc. For max bequest, it should yield $90,
|
|
2
|
+
Description = "This is a case involving a single individual. Case is used for comparing with @hugcity's DrawdownCalc. For max bequest, it should yield $90,882, while an $80k net spending should leave $678,625 as a bequest."
|
|
3
3
|
|
|
4
4
|
["Basic Info"]
|
|
5
5
|
Status = "single"
|
|
@@ -51,6 +51,7 @@ startRothConversions = 2025
|
|
|
51
51
|
withSCLoop = false
|
|
52
52
|
solver = "HiGHS"
|
|
53
53
|
spendingSlack = 0
|
|
54
|
+
withMedicare = "None"
|
|
54
55
|
|
|
55
56
|
[Results]
|
|
56
57
|
"Default plots" = "today"
|
|
@@ -296,6 +296,10 @@ def readConfig(file, *, verbose=True, logstreams=None, readContributions=True):
|
|
|
296
296
|
# Solver Options.
|
|
297
297
|
p.solverOptions = diconf["Solver Options"]
|
|
298
298
|
|
|
299
|
+
# Address legacy case files.
|
|
300
|
+
if diconf["Solver Options"].get("withMedicare", None) is True:
|
|
301
|
+
p.solverOptions["withMedicare"] = "loop"
|
|
302
|
+
|
|
299
303
|
# Check consistency of noRothConversions.
|
|
300
304
|
name = p.solverOptions.get("noRothConversions", "None")
|
|
301
305
|
if name != "None" and name not in p.inames:
|
|
@@ -1021,7 +1021,7 @@ class Plan(object):
|
|
|
1021
1021
|
Refer to companion document for explanations.
|
|
1022
1022
|
All binary variables must be lumped at the end of the vector.
|
|
1023
1023
|
"""
|
|
1024
|
-
medi = options.get("
|
|
1024
|
+
medi = options.get("withMedicare", "loop") == "optimize"
|
|
1025
1025
|
|
|
1026
1026
|
# Stack all variables in a single block vector with all binary variables at the end.
|
|
1027
1027
|
C = {}
|
|
@@ -1386,7 +1386,7 @@ class Plan(object):
|
|
|
1386
1386
|
self.B.setRange(_q3(self.C["zx"], i, n, 1, self.N_i, self.N_n, self.N_zx), 0, 0)
|
|
1387
1387
|
|
|
1388
1388
|
def _configure_Medicare_binary_variables(self, options):
|
|
1389
|
-
if
|
|
1389
|
+
if options.get("withMedicare", "loop") != "optimize":
|
|
1390
1390
|
return
|
|
1391
1391
|
|
|
1392
1392
|
bigM = options.get("bigM", 5e6)
|
|
@@ -1444,7 +1444,7 @@ class Plan(object):
|
|
|
1444
1444
|
self.A.addRow(row2, -np.inf, rhs2)
|
|
1445
1445
|
|
|
1446
1446
|
def _add_Medicare_costs(self, options):
|
|
1447
|
-
if
|
|
1447
|
+
if options.get("withMedicare", "loop") != "optimize":
|
|
1448
1448
|
return
|
|
1449
1449
|
|
|
1450
1450
|
for n in range(self.nm):
|
|
@@ -1627,7 +1627,7 @@ class Plan(object):
|
|
|
1627
1627
|
"netSpending",
|
|
1628
1628
|
"noRothConversions",
|
|
1629
1629
|
"oppCostX",
|
|
1630
|
-
"
|
|
1630
|
+
"withMedicare",
|
|
1631
1631
|
"previousMAGIs",
|
|
1632
1632
|
"solver",
|
|
1633
1633
|
"spendingSlack",
|
|
@@ -1635,7 +1635,6 @@ class Plan(object):
|
|
|
1635
1635
|
"units",
|
|
1636
1636
|
"xorConstraints",
|
|
1637
1637
|
"withSCLoop",
|
|
1638
|
-
"withMedicare", # Ignore keyword.
|
|
1639
1638
|
]
|
|
1640
1639
|
# We might modify options if required.
|
|
1641
1640
|
options = {} if options is None else options
|
|
@@ -1713,7 +1712,7 @@ class Plan(object):
|
|
|
1713
1712
|
"""
|
|
1714
1713
|
Self-consistent loop, regardless of solver.
|
|
1715
1714
|
"""
|
|
1716
|
-
|
|
1715
|
+
includeMedicare = options.get("withMedicare", "loop") == "loop"
|
|
1717
1716
|
withSCLoop = options.get("withSCLoop", True)
|
|
1718
1717
|
|
|
1719
1718
|
if objective == "maxSpending":
|
|
@@ -1724,7 +1723,7 @@ class Plan(object):
|
|
|
1724
1723
|
it = 0
|
|
1725
1724
|
old_x = np.zeros(self.nvars)
|
|
1726
1725
|
old_objfns = [np.inf]
|
|
1727
|
-
self._computeNLstuff(None,
|
|
1726
|
+
self._computeNLstuff(None, includeMedicare)
|
|
1728
1727
|
while True:
|
|
1729
1728
|
objfn, xx, solverSuccess, solverMsg = solverMethod(objective, options)
|
|
1730
1729
|
|
|
@@ -1735,7 +1734,7 @@ class Plan(object):
|
|
|
1735
1734
|
if not withSCLoop:
|
|
1736
1735
|
break
|
|
1737
1736
|
|
|
1738
|
-
self._computeNLstuff(xx,
|
|
1737
|
+
self._computeNLstuff(xx, includeMedicare)
|
|
1739
1738
|
|
|
1740
1739
|
delta = xx - old_x
|
|
1741
1740
|
absSolDiff = np.sum(np.abs(delta), axis=0)/100
|
|
@@ -1955,7 +1954,7 @@ class Plan(object):
|
|
|
1955
1954
|
|
|
1956
1955
|
return J_n
|
|
1957
1956
|
|
|
1958
|
-
def _computeNLstuff(self, x,
|
|
1957
|
+
def _computeNLstuff(self, x, includeMedicare):
|
|
1959
1958
|
"""
|
|
1960
1959
|
Compute MAGI, Medicare costs, long-term capital gain tax rate, and
|
|
1961
1960
|
net investment income tax (NIIT).
|
|
@@ -1972,7 +1971,7 @@ class Plan(object):
|
|
|
1972
1971
|
self.J_n = self._computeNIIT(self.MAGI_n, self.I_n, self.Q_n)
|
|
1973
1972
|
self.psi_n = tx.capitalGainTaxRate(self.N_i, self.MAGI_n, self.gamma_n[:-1], self.n_d, self.N_n)
|
|
1974
1973
|
# Compute Medicare through self-consistent loop.
|
|
1975
|
-
if
|
|
1974
|
+
if includeMedicare:
|
|
1976
1975
|
self.M_n = tx.mediCosts(self.yobs, self.horizons, self.MAGI_n, self.prevMAGI, self.gamma_n[:-1], self.N_n)
|
|
1977
1976
|
|
|
1978
1977
|
return None
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2025.09.15"
|
|
@@ -400,27 +400,23 @@ no Roth conversions will be allowed before the year specified.
|
|
|
400
400
|
A self-consistent loop is used to compute values that are difficult
|
|
401
401
|
to integrate into a linear program. These includes the net investment income tax (NIIT),
|
|
402
402
|
the rate on capital gains (0, 15, or 20%), the phase out of the additional exemption for seniors,
|
|
403
|
-
and the Medicare and IRMAA premiums.
|
|
403
|
+
and potentially the Medicare and IRMAA premiums.
|
|
404
404
|
Turning off the self-consistent loop will default all these values to zero.
|
|
405
|
+
|
|
405
406
|
An additional setting allows to turn off mutually exclusive operations,
|
|
406
407
|
such as Roth conversions and withdrawals from the tax-free account.
|
|
407
|
-
Surprinsingly, dropping these constraints can lead to slightly different optimal points.
|
|
408
|
-
|
|
409
|
-
Mutually exclusive constraints can be imposed between Roth conversions and
|
|
410
|
-
withdrawals from tax-free accounts. Similarly, withdrawals from taxable
|
|
411
|
-
and tax-free accounts should not exist if there is a surplus.
|
|
412
408
|
Enabling these mutually exclusive constraints avoids both these situations.
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
different between cases with the constraints on or off for reasons that escape me.
|
|
409
|
+
Surprinsingly, dropping these constraints can lead to slightly different optimal points
|
|
410
|
+
for reasons that escape me.
|
|
416
411
|
|
|
417
412
|
Medicare premiums start automatically in the year each individual reach age 65.
|
|
418
|
-
Calculations of Medicare and IRMAA can
|
|
419
|
-
the self-consistent loop.
|
|
420
|
-
Optimizing (as opposed to calculating) Medicare and IRMAA
|
|
413
|
+
Calculations of Medicare and IRMAA can be turned off, calculated through
|
|
414
|
+
the self-consistent loop, or be integrated into the optimization.
|
|
415
|
+
Optimizing (as opposed to calculating) Medicare and IRMAA can sometimes be much slower given
|
|
421
416
|
the additional number of binary variables involved. This option should be used
|
|
422
|
-
for a single case, and compared with results obtained from
|
|
423
|
-
Medicare optimization should not be used when running
|
|
417
|
+
for a single case, and compared with results obtained from self-consistent calculations.
|
|
418
|
+
Medicare optimization should not be used when running multiple scenarios such
|
|
419
|
+
as in Monte Carlo simulations.
|
|
424
420
|
|
|
425
421
|
If the current age of any individual in the plan makes them eligible
|
|
426
422
|
for Medicare within the next two years,
|
|
@@ -11,6 +11,8 @@ kz.initKey("survivor", 60)
|
|
|
11
11
|
kz.initKey("smileDip", 15)
|
|
12
12
|
kz.initKey("smileIncrease", 12)
|
|
13
13
|
kz.initKey("smileDelay", 0)
|
|
14
|
+
mediChoices = ["None", "loop", "optimize"]
|
|
15
|
+
kz.initKey("withMedicare", mediChoices[1])
|
|
14
16
|
|
|
15
17
|
|
|
16
18
|
def initProfile():
|
|
@@ -78,8 +80,8 @@ else:
|
|
|
78
80
|
col1, col2 = st.columns(2, gap="large", vertical_alignment="top")
|
|
79
81
|
with col1:
|
|
80
82
|
helpmsg = ("Option to use a self-consistent loop to adjust additional values such as the net"
|
|
81
|
-
" investment income tax (NIIT), and
|
|
82
|
-
"
|
|
83
|
+
" investment income tax (NIIT), and capital gain tax rates."
|
|
84
|
+
" If selected below, this loop will also compute Medicare and IRMAA.")
|
|
83
85
|
ret = kz.getToggle("Self-consistent loop calculations", "withSCLoop", help=helpmsg)
|
|
84
86
|
with col2:
|
|
85
87
|
helpmsg = ("Enable mutually exclusive constraints between surplus deposits,"
|
|
@@ -89,16 +91,19 @@ else:
|
|
|
89
91
|
|
|
90
92
|
st.divider()
|
|
91
93
|
st.write("#### :orange[Medicare]")
|
|
92
|
-
kz.initKey("optimizeMedicare", False)
|
|
93
94
|
col1, col2 = st.columns(2, gap="large", vertical_alignment="top")
|
|
94
|
-
with col2:
|
|
95
|
-
st.markdown("##### :material/warning: Slow convergence - maybe time for a coffee :coffee:?")
|
|
96
|
-
helpmsg = ("Optimize for Medicare and IRMAA."
|
|
97
|
-
" Due to the binary variables involved, this requires a few minutes of computation."
|
|
98
|
-
" Be patient.")
|
|
99
|
-
ret = kz.getToggle("Optimize Medicare and IRMAA", "optimizeMedicare", help=helpmsg)
|
|
100
95
|
with col1:
|
|
101
|
-
|
|
96
|
+
helpmsg = ("How to compute Medicare and IRMAA premiums:"
|
|
97
|
+
" ignore, use self-consistent loop, or use additional variables in optimization.")
|
|
98
|
+
ret = kz.getRadio("Medicare and IRMAA calculations", mediChoices, "withMedicare", help=helpmsg)
|
|
99
|
+
if ret == "optimize":
|
|
100
|
+
st.markdown(":material/warning: Medicare optimization can sometimes have slow convergence -"
|
|
101
|
+
" time for :coffee: ?")
|
|
102
|
+
elif ret == "loop" and not kz.getKey("withSCLoop"):
|
|
103
|
+
st.markdown(":material/warning: Medicare set to 'loop' while self-consistent loop is off.")
|
|
104
|
+
with col2:
|
|
105
|
+
medi = kz.getKey("withMedicare")
|
|
106
|
+
if medi == "optimize" or (medi == "loop" and kz.getKey("withSCLoop")):
|
|
102
107
|
helpmsg = "MAGI in nominal $k for current and previous years."
|
|
103
108
|
years = owb.backYearsMAGI()
|
|
104
109
|
for ii in range(2):
|
|
@@ -23,7 +23,7 @@ else:
|
|
|
23
23
|
"This table provides a summary of the current case and"
|
|
24
24
|
" compares it with other similar cases that ran successfully.")
|
|
25
25
|
styledDf = df[1:].style.map(kz.colorBySign)
|
|
26
|
-
st.dataframe(styledDf,
|
|
26
|
+
st.dataframe(styledDf, width="stretch")
|
|
27
27
|
st.caption("Values with [legend] are nominal, otherwise in today's \\$. "
|
|
28
28
|
"Lines starting with » indicate itemized subtotals.")
|
|
29
29
|
col1, col2 = st.columns(2, gap="large")
|
|
@@ -559,7 +559,7 @@ def showWorkbook(plan):
|
|
|
559
559
|
colfor[col] = st.column_config.NumberColumn(None, format="%.3f")
|
|
560
560
|
|
|
561
561
|
st.write(f"#### :orange[{name}]")
|
|
562
|
-
st.dataframe(df.astype(str),
|
|
562
|
+
st.dataframe(df.astype(str), width="stretch", column_config=colfor, hide_index=True)
|
|
563
563
|
|
|
564
564
|
if dollars:
|
|
565
565
|
st.caption("Values are in nominal $, rounded to the nearest dollar.")
|
|
@@ -705,7 +705,7 @@ def genDic(plan):
|
|
|
705
705
|
|
|
706
706
|
optionKeys = list(plan.solverOptions)
|
|
707
707
|
optList = ["netSpending", "maxRothConversion", "noRothConversions",
|
|
708
|
-
"startRothConversions", "
|
|
708
|
+
"startRothConversions", "withMedicare", "bequest", "solver",
|
|
709
709
|
"spendingSlack", "oppCostX", "xorConstraints", "withSCLoop",]
|
|
710
710
|
for key in optList:
|
|
711
711
|
if key in optionKeys:
|
|
@@ -779,9 +779,9 @@ def renderPlot(fig, col=None):
|
|
|
779
779
|
# Check if it's a plotly figure.
|
|
780
780
|
if hasattr(fig, 'to_dict'): # plotly figures have to_dict method.
|
|
781
781
|
if col:
|
|
782
|
-
col.plotly_chart(fig,
|
|
782
|
+
col.plotly_chart(fig, width="stretch")
|
|
783
783
|
else:
|
|
784
|
-
st.plotly_chart(fig,
|
|
784
|
+
st.plotly_chart(fig, width="stretch")
|
|
785
785
|
else: # matplotlib figure.
|
|
786
786
|
if col:
|
|
787
787
|
col.pyplot(fig)
|
|
@@ -369,7 +369,7 @@ def getSolveParameters():
|
|
|
369
369
|
|
|
370
370
|
options = {}
|
|
371
371
|
optList = ["netSpending", "maxRothConversion", "noRothConversions",
|
|
372
|
-
"startRothConversions", "
|
|
372
|
+
"startRothConversions", "withMedicare", "bequest", "solver",
|
|
373
373
|
"spendingSlack", "oppCostX", "xorConstraints", "withSCLoop",]
|
|
374
374
|
for opt in optList:
|
|
375
375
|
val = getKey(opt)
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
"Plan Name" = "drawdowncalc-comparison-1"
|
|
2
|
-
Description = "This is a case involving a single individual. Case is used for comparing with @hugcity's DrawdownCalc. For max bequest, it should yield $90,792, while an $80k net spending should leave $673,156 as a bequest."
|
|
3
|
-
|
|
4
|
-
["Basic Info"]
|
|
5
|
-
Status = "single"
|
|
6
|
-
Names = [ "Charles",]
|
|
7
|
-
"Birth year" = [ 1966,]
|
|
8
|
-
"Life expectancy" = [ 89,]
|
|
9
|
-
"Start date" = "2025-01-01"
|
|
10
|
-
|
|
11
|
-
[Assets]
|
|
12
|
-
"taxable savings balances" = [ 250.0,]
|
|
13
|
-
"tax-deferred savings balances" = [ 600.0,]
|
|
14
|
-
"tax-free savings balances" = [ 600.0,]
|
|
15
|
-
|
|
16
|
-
["Wages and Contributions"]
|
|
17
|
-
"Contributions file name" = "edited values"
|
|
18
|
-
|
|
19
|
-
["Fixed Income"]
|
|
20
|
-
"Pension amounts" = [ 0.0,]
|
|
21
|
-
"Pension ages" = [ 65,]
|
|
22
|
-
"Pension indexed" = [ true,]
|
|
23
|
-
"Social security amounts" = [ 36.0,]
|
|
24
|
-
"Social security ages" = [ 70,]
|
|
25
|
-
|
|
26
|
-
["Rates Selection"]
|
|
27
|
-
"Heirs rate on tax-deferred estate" = 0.0
|
|
28
|
-
"Dividend rate" = 0.0
|
|
29
|
-
"TCJA expiration year" = 2099
|
|
30
|
-
Method = "user"
|
|
31
|
-
Values = [ 6.5, 0.0, 0.0, 2.8,]
|
|
32
|
-
From = 1928
|
|
33
|
-
To = 2024
|
|
34
|
-
|
|
35
|
-
["Asset Allocation"]
|
|
36
|
-
"Interpolation method" = "s-curve"
|
|
37
|
-
"Interpolation center" = 15.0
|
|
38
|
-
"Interpolation width" = 5.0
|
|
39
|
-
Type = "individual"
|
|
40
|
-
generic = [ [ [ 100, 0, 0, 0,], [ 100, 0, 0, 0,],],]
|
|
41
|
-
|
|
42
|
-
["Optimization Parameters"]
|
|
43
|
-
"Spending profile" = "flat"
|
|
44
|
-
"Surviving spouse spending percent" = 60
|
|
45
|
-
Objective = "maxBequest"
|
|
46
|
-
|
|
47
|
-
["Solver Options"]
|
|
48
|
-
netSpending = 80.0
|
|
49
|
-
maxRothConversion = 50
|
|
50
|
-
startRothConversions = 2025
|
|
51
|
-
withMedicare = false
|
|
52
|
-
solver = "HiGHS"
|
|
53
|
-
spendingSlack = 0
|
|
54
|
-
|
|
55
|
-
[Results]
|
|
56
|
-
"Default plots" = "today"
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
"Plan Name" = "jack+jill"
|
|
2
|
-
Description = "This example aims to demonstrate some of Owl's capabilities. Jack and Jill are a married couple a few years from retirement. A wages and contributions file called 'jack+jill.xlsx' is associated with this case. This case uses the historical rate sequence of 1969 as a test case for guiding spending amounts from a near worst-case historical scenario. This case also demonstrates that the optimal strategy for Roth conversions does not necessarily involve surfing a tax bracket. \nA good exercise for learning Owl's capabilities is to duplicate this case and compare two scenarios: one with optimized Roth conversions and one without. Another possible exercise could involve comparing a historical retirement in 1969 vs. one taken in 1966. Or anything else you can think of..."
|
|
3
|
-
|
|
4
|
-
["Basic Info"]
|
|
5
|
-
Status = "married"
|
|
6
|
-
Names = [ "Jack", "Jill",]
|
|
7
|
-
"Birth year" = [ 1962, 1965,]
|
|
8
|
-
"Life expectancy" = [ 89, 92,]
|
|
9
|
-
"Start date" = "01-01"
|
|
10
|
-
|
|
11
|
-
[Assets]
|
|
12
|
-
"taxable savings balances" = [ 90.5, 60.2,]
|
|
13
|
-
"tax-deferred savings balances" = [ 600.2, 150.0,]
|
|
14
|
-
"tax-free savings balances" = [ 70.6, 40.8,]
|
|
15
|
-
"Beneficiary fractions" = [ 1, 1, 1,]
|
|
16
|
-
"Spousal surplus deposit fraction" = 0.0
|
|
17
|
-
|
|
18
|
-
["Wages and Contributions"]
|
|
19
|
-
"Contributions file name" = "jack+jill.xlsx"
|
|
20
|
-
|
|
21
|
-
["Fixed Income"]
|
|
22
|
-
"Pension amounts" = [ 0.0, 10.5,]
|
|
23
|
-
"Pension ages" = [ 65, 65,]
|
|
24
|
-
"Pension indexed" = [ false, false,]
|
|
25
|
-
"Social security amounts" = [ 28.4, 19.7,]
|
|
26
|
-
"Social security ages" = [ 70, 62,]
|
|
27
|
-
|
|
28
|
-
["Rates Selection"]
|
|
29
|
-
"Heirs rate on tax-deferred estate" = 30.0
|
|
30
|
-
"Dividend rate" = 1.8
|
|
31
|
-
"TCJA expiration year" = 2026
|
|
32
|
-
Method = "historical"
|
|
33
|
-
From = 1969
|
|
34
|
-
To = 2002
|
|
35
|
-
|
|
36
|
-
["Asset Allocation"]
|
|
37
|
-
"Interpolation method" = "s-curve"
|
|
38
|
-
"Interpolation center" = 15
|
|
39
|
-
"Interpolation width" = 5
|
|
40
|
-
Type = "individual"
|
|
41
|
-
generic = [ [ [ 60, 40, 0, 0,], [ 70, 30, 0, 0,],], [ [ 60, 40, 0, 0,], [ 80, 0, 10, 10,],],]
|
|
42
|
-
|
|
43
|
-
["Optimization Parameters"]
|
|
44
|
-
"Spending profile" = "smile"
|
|
45
|
-
"Smile dip" = 15
|
|
46
|
-
"Smile increase" = 12
|
|
47
|
-
"Smile delay" = 0
|
|
48
|
-
"Surviving spouse spending percent" = 60
|
|
49
|
-
Objective = "maxSpending"
|
|
50
|
-
|
|
51
|
-
["Solver Options"]
|
|
52
|
-
maxRothConversion = 100
|
|
53
|
-
bequest = 500
|
|
54
|
-
noRothConversions = "Jill"
|
|
55
|
-
|
|
56
|
-
[Results]
|
|
57
|
-
"Default plots" = "today"
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
"Plan Name" = "joe"
|
|
2
|
-
Description = "This is an example of a case involving a single individual. Joe is single and will retire in a few years. His wages and contributions are contained in the 'joe.xlsx' spreadsheet."
|
|
3
|
-
|
|
4
|
-
["Basic Info"]
|
|
5
|
-
Status = "single"
|
|
6
|
-
Names = [ "Joe",]
|
|
7
|
-
"Birth year" = [ 1966,]
|
|
8
|
-
"Life expectancy" = [ 89,]
|
|
9
|
-
"Start date" = "01-01"
|
|
10
|
-
|
|
11
|
-
[Assets]
|
|
12
|
-
"taxable savings balances" = [ 338.5,]
|
|
13
|
-
"tax-deferred savings balances" = [ 650.2,]
|
|
14
|
-
"tax-free savings balances" = [ 60.6,]
|
|
15
|
-
|
|
16
|
-
["Wages and Contributions"]
|
|
17
|
-
"Contributions file name" = "joe.xlsx"
|
|
18
|
-
|
|
19
|
-
["Fixed Income"]
|
|
20
|
-
"Pension amounts" = [ 18.0,]
|
|
21
|
-
"Pension ages" = [ 65,]
|
|
22
|
-
"Pension indexed" = [ true,]
|
|
23
|
-
"Social security amounts" = [ 28.4,]
|
|
24
|
-
"Social security ages" = [ 67,]
|
|
25
|
-
|
|
26
|
-
["Rates Selection"]
|
|
27
|
-
"Heirs rate on tax-deferred estate" = 30.0
|
|
28
|
-
"Dividend rate" = 1.8
|
|
29
|
-
"TCJA expiration year" = 2026
|
|
30
|
-
Method = "historical average"
|
|
31
|
-
From = 1969
|
|
32
|
-
To = 2002
|
|
33
|
-
|
|
34
|
-
["Asset Allocation"]
|
|
35
|
-
"Interpolation method" = "s-curve"
|
|
36
|
-
"Interpolation center" = 15
|
|
37
|
-
"Interpolation width" = 5
|
|
38
|
-
Type = "individual"
|
|
39
|
-
generic = [ [ [ 60, 40, 0, 0,], [ 70, 30, 0, 0,],],]
|
|
40
|
-
|
|
41
|
-
["Optimization Parameters"]
|
|
42
|
-
"Spending profile" = "smile"
|
|
43
|
-
"Smile dip" = 15
|
|
44
|
-
"Smile increase" = 12
|
|
45
|
-
"Smile delay" = 0
|
|
46
|
-
"Surviving spouse spending percent" = 60
|
|
47
|
-
Objective = "maxSpending"
|
|
48
|
-
|
|
49
|
-
["Solver Options"]
|
|
50
|
-
maxRothConversion = 50
|
|
51
|
-
bequest = 300
|
|
52
|
-
|
|
53
|
-
[Results]
|
|
54
|
-
"Default plots" = "nominal"
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
"Plan Name" = "john+sally"
|
|
2
|
-
Description = "This example reproduces the case of John and Sally, discussed by Eric Sajdak. This case can be used to compare the heuristic strategy of surfing a tax bracket for performing Roth conversions to a solution optimized by linear programming. The former is a good approach when one assumes fixed rates. When rates are varying, and the market drops, an optimized solution shows that it is sometime advantageous to convert above the target tax bracket. File 'john+sally.xlsx' contains wages and contributions associated with this case. Run this case with different rates to see the effects on Roth conversions."
|
|
3
|
-
|
|
4
|
-
["Basic Info"]
|
|
5
|
-
Status = "married"
|
|
6
|
-
Names = [ "John", "Sally",]
|
|
7
|
-
"Birth year" = [ 1962, 1962,]
|
|
8
|
-
"Life expectancy" = [ 92, 92,]
|
|
9
|
-
"Start date" = "01-01"
|
|
10
|
-
|
|
11
|
-
[Assets]
|
|
12
|
-
"taxable savings balances" = [ 200.0, 200.0,]
|
|
13
|
-
"tax-deferred savings balances" = [ 750.0, 750.0,]
|
|
14
|
-
"tax-free savings balances" = [ 50.0, 50.0,]
|
|
15
|
-
"Beneficiary fractions" = [ 1, 1, 1,]
|
|
16
|
-
"Spousal surplus deposit fraction" = 0.5
|
|
17
|
-
|
|
18
|
-
["Wages and Contributions"]
|
|
19
|
-
"Contributions file name" = "john+sally.xlsx"
|
|
20
|
-
|
|
21
|
-
["Fixed Income"]
|
|
22
|
-
"Pension amounts" = [ 0.0, 0.0,]
|
|
23
|
-
"Pension ages" = [ 65, 65,]
|
|
24
|
-
"Pension indexed" = [ false, false,]
|
|
25
|
-
"Social security amounts" = [ 36.0, 21.6,]
|
|
26
|
-
"Social security ages" = [ 67, 67,]
|
|
27
|
-
|
|
28
|
-
["Rates Selection"]
|
|
29
|
-
"Heirs rate on tax-deferred estate" = 30.0
|
|
30
|
-
"Dividend rate" = 1.8
|
|
31
|
-
"TCJA expiration year" = 2026
|
|
32
|
-
Method = "historical average"
|
|
33
|
-
From = 1990
|
|
34
|
-
To = 2023
|
|
35
|
-
|
|
36
|
-
["Asset Allocation"]
|
|
37
|
-
"Interpolation method" = "linear"
|
|
38
|
-
"Interpolation center" = 15
|
|
39
|
-
"Interpolation width" = 5
|
|
40
|
-
Type = "individual"
|
|
41
|
-
generic = [ [ [ 60, 40, 0, 0,], [ 60, 40, 0, 0,],], [ [ 60, 40, 0, 0,], [ 60, 40, 0, 0,],],]
|
|
42
|
-
|
|
43
|
-
["Optimization Parameters"]
|
|
44
|
-
"Spending profile" = "flat"
|
|
45
|
-
"Surviving spouse spending percent" = 60
|
|
46
|
-
Objective = "maxBequest"
|
|
47
|
-
|
|
48
|
-
["Solver Options"]
|
|
49
|
-
maxRothConversion = 200
|
|
50
|
-
netSpending = 100
|
|
51
|
-
|
|
52
|
-
[Results]
|
|
53
|
-
"Default plots" = "today"
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
"Plan Name" = "Jon+Jane"
|
|
2
|
-
Description = "This case reproduces a similar case discussed a while back on i-orp. It involves Jon and Jane close to retirement, and assumes a base case of optimistic returns of 10% with an inflation of 3.5%. A wages and contributions file called 'jon+jane.xlsx' is associated with this case."
|
|
3
|
-
|
|
4
|
-
["Basic Info"]
|
|
5
|
-
Status = "married"
|
|
6
|
-
Names = [ "Jon", "Jane",]
|
|
7
|
-
"Birth year" = [ 1965, 1968,]
|
|
8
|
-
"Life expectancy" = [ 92, 92,]
|
|
9
|
-
"Start date" = "01-01"
|
|
10
|
-
|
|
11
|
-
[Assets]
|
|
12
|
-
"taxable savings balances" = [ 50.0, 0.0,]
|
|
13
|
-
"tax-deferred savings balances" = [ 900.0, 500.0,]
|
|
14
|
-
"tax-free savings balances" = [ 0.0, 0.0,]
|
|
15
|
-
"Beneficiary fractions" = [ 1.0, 1.0, 1.0,]
|
|
16
|
-
"Spousal surplus deposit fraction" = 0.0
|
|
17
|
-
|
|
18
|
-
["Wages and Contributions"]
|
|
19
|
-
"Contributions file name" = "jon+jane.xslx"
|
|
20
|
-
|
|
21
|
-
["Fixed Income"]
|
|
22
|
-
"Pension amounts" = [ 0.0, 0.0,]
|
|
23
|
-
"Pension ages" = [ 65, 65,]
|
|
24
|
-
"Pension indexed" = [ false, false,]
|
|
25
|
-
"Social security amounts" = [ 21.0, 21.0,]
|
|
26
|
-
"Social security ages" = [ 65, 65,]
|
|
27
|
-
|
|
28
|
-
["Rates Selection"]
|
|
29
|
-
"Heirs rate on tax-deferred estate" = 30.0
|
|
30
|
-
"Dividend rate" = 1.8
|
|
31
|
-
"TCJA expiration year" = 2026
|
|
32
|
-
Method = "user"
|
|
33
|
-
Values = [ 10.0, 6.0, 5.0, 3.5000000000000004,]
|
|
34
|
-
From = 1928
|
|
35
|
-
To = 2024
|
|
36
|
-
|
|
37
|
-
["Asset Allocation"]
|
|
38
|
-
"Interpolation method" = "linear"
|
|
39
|
-
"Interpolation center" = 15.0
|
|
40
|
-
"Interpolation width" = 5.0
|
|
41
|
-
Type = "individual"
|
|
42
|
-
generic = [ [ [ 100, 0, 0, 0,], [ 100, 0, 0, 0,],], [ [ 100, 0, 0, 0,], [ 100, 0, 0, 0,],],]
|
|
43
|
-
|
|
44
|
-
["Optimization Parameters"]
|
|
45
|
-
"Spending profile" = "flat"
|
|
46
|
-
"Surviving spouse spending percent" = 60
|
|
47
|
-
Objective = "maxSpending"
|
|
48
|
-
|
|
49
|
-
["Solver Options"]
|
|
50
|
-
maxRothConversion = 0.0
|
|
51
|
-
noRothConversions = "None"
|
|
52
|
-
withMedicare = true
|
|
53
|
-
bequest = 30.0
|
|
54
|
-
|
|
55
|
-
[Results]
|
|
56
|
-
"Default plots" = "today"
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
"Plan Name" = "kim+sam-bequest"
|
|
2
|
-
Description = "This is the case of Kim and Sam used as an example case for optimizing bequest and Roth conversions. No wages and contributions file is associated with this case."
|
|
3
|
-
|
|
4
|
-
["Basic Info"]
|
|
5
|
-
Status = "married"
|
|
6
|
-
Names = [ "Kim", "Sam",]
|
|
7
|
-
"Birth year" = [ 1966, 1967,]
|
|
8
|
-
"Life expectancy" = [ 86, 89,]
|
|
9
|
-
"Start date" = "01-01"
|
|
10
|
-
|
|
11
|
-
[Assets]
|
|
12
|
-
"taxable savings balances" = [ 1000.0, 0.0,]
|
|
13
|
-
"tax-deferred savings balances" = [ 2000.0, 800.0,]
|
|
14
|
-
"tax-free savings balances" = [ 500.0, 25.0,]
|
|
15
|
-
"Beneficiary fractions" = [ 1, 1, 1,]
|
|
16
|
-
"Spousal surplus deposit fraction" = 0.5
|
|
17
|
-
|
|
18
|
-
["Wages and Contributions"]
|
|
19
|
-
"Contributions file name" = "kim+sam.xlsx"
|
|
20
|
-
|
|
21
|
-
["Fixed Income"]
|
|
22
|
-
"Pension amounts" = [ 0.0, 0.0,]
|
|
23
|
-
"Pension ages" = [ 65, 65,]
|
|
24
|
-
"Pension indexed" = [ false, false,]
|
|
25
|
-
"Social security amounts" = [ 45.0, 25.0,]
|
|
26
|
-
"Social security ages" = [ 70, 68,]
|
|
27
|
-
|
|
28
|
-
["Rates Selection"]
|
|
29
|
-
"Heirs rate on tax-deferred estate" = 33.0
|
|
30
|
-
"Dividend rate" = 1.8
|
|
31
|
-
"TCJA expiration year" = 2026
|
|
32
|
-
Method = "conservative"
|
|
33
|
-
From = 1922
|
|
34
|
-
To = 2023
|
|
35
|
-
|
|
36
|
-
["Asset Allocation"]
|
|
37
|
-
"Interpolation method" = "s-curve"
|
|
38
|
-
"Interpolation center" = 15
|
|
39
|
-
"Interpolation width" = 5
|
|
40
|
-
Type = "individual"
|
|
41
|
-
generic = [ [ [ 60, 40, 0, 0,], [ 70, 30, 0, 0,],], [ [ 60, 40, 0, 0,], [ 70, 30, 0, 0,],],]
|
|
42
|
-
|
|
43
|
-
["Optimization Parameters"]
|
|
44
|
-
"Spending profile" = "smile"
|
|
45
|
-
"Smile dip" = 15
|
|
46
|
-
"Smile increase" = 12
|
|
47
|
-
"Smile delay" = 0
|
|
48
|
-
"Surviving spouse spending percent" = 60
|
|
49
|
-
Objective = "maxBequest"
|
|
50
|
-
|
|
51
|
-
["Solver Options"]
|
|
52
|
-
maxRothConversion = 300
|
|
53
|
-
netSpending = 180
|
|
54
|
-
|
|
55
|
-
[Results]
|
|
56
|
-
"Default plots" = "today"
|