owlplanner 2025.2.14__tar.gz → 2025.2.19__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/.github/workflows/github-actions-runtests.yml +1 -1
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/INSTALL.md +23 -11
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/PKG-INFO +28 -24
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/README.md +27 -23
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/USER_GUIDE.md +6 -2
- owlplanner-2025.2.19/docker/Dockerfile +34 -0
- owlplanner-2025.2.19/docker/README.md +74 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/docker/docker-compose.yml +1 -3
- owlplanner-2025.2.19/docker/fastentrypoint.sh +9 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/examples/case_jack+jill.toml +1 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/examples/case_joe.toml +1 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/examples/case_john+sally.toml +1 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/examples/case_kim+sam-bequest.toml +1 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/examples/case_kim+sam-spending.toml +1 -0
- owlplanner-2025.2.19/owlplanner.cmd +20 -0
- owlplanner-2025.2.19/owlplanner.sh +9 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/pyproject.toml +1 -1
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/src/owlplanner/config.py +2 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/src/owlplanner/plan.py +16 -5
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/src/owlplanner/tax2025.py +63 -43
- owlplanner-2025.2.19/src/owlplanner/version.py +1 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/tests/test_repro.py +11 -11
- owlplanner-2025.2.19/ttt.py +20 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/ui/About_Owl.py +5 -1
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/ui/Create_Case.py +1 -1
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/ui/Rates_Selection.py +7 -2
- owlplanner-2025.2.19/ui/main+fonts.py +55 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/ui/owlbridge.py +4 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/ui/requirements.txt +1 -1
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/ui/sskeys.py +1 -0
- owlplanner-2025.2.19/ui/style.css +8 -0
- owlplanner-2025.2.14/docker/Dockerfile +0 -30
- owlplanner-2025.2.14/docker/README.md +0 -69
- owlplanner-2025.2.14/docker/entrypoint.sh +0 -35
- owlplanner-2025.2.14/owlplanner.cmd +0 -8
- owlplanner-2025.2.14/owlplanner.sh +0 -10
- owlplanner-2025.2.14/src/owlplanner/version.py +0 -1
- owlplanner-2025.2.14/test.py +0 -18
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/.devcontainer/devcontainer.json +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/.flake8 +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/.gitattributes +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/.gitignore +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/LICENSE +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/docs/images/AD-taxDef.png +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/docs/images/AD-taxFree.png +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/docs/images/AD-taxable.png +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/docs/images/Hist_Bequest.png +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/docs/images/Hist_Spending.png +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/docs/images/MC-tutorial2a.png +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/docs/images/MC-tutorial2b.png +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/docs/images/OwlUI.png +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/docs/images/allocations.png +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/docs/images/owl.png +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/docs/images/profile.png +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/docs/images/ratesCorrelations.png +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/docs/images/ratesPlot.png +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/docs/images/savingsPlot.png +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/docs/images/sourcesPlot.png +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/docs/images/spendingPlot.png +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/docs/images/taxIncomePlot.png +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/docs/images/taxesPlot.png +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/docs/owl.pdf +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/docs/owl.tex +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/examples/jack+jill.xlsx +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/examples/joe.xlsx +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/examples/john+sally.xlsx +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/examples/template.xlsx +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/notebooks/john+sally.ipynb +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/notebooks/kim+sam.ipynb +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/notebooks/template.ipynb +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/notebooks/tutorial_1.ipynb +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/notebooks/tutorial_2.ipynb +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/notebooks/tutorial_3.ipynb +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/requirements.txt +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/src/owlplanner/__init__.py +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/src/owlplanner/abcapi.py +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/src/owlplanner/data/__init__.py +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/src/owlplanner/data/rates.csv +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/src/owlplanner/logging.py +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/src/owlplanner/progress.py +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/src/owlplanner/rates.py +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/src/owlplanner/timelists.py +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/src/owlplanner/utils.py +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/tests/test_logger.py +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/tests/test_regressions.py +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/tests/test_toml_cases.py +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/tests/test_units.py +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/ui/Asset_Allocation.py +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/ui/Assets.py +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/ui/Documentation.py +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/ui/Fixed_Income.py +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/ui/Graphs.py +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/ui/Historical_Range.py +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/ui/Logs.py +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/ui/Monte_Carlo.py +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/ui/Optimization_Parameters.py +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/ui/Output_Files.py +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/ui/Quick_Start.py +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/ui/README.md +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/ui/Settings.py +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/ui/Wages_And_Contributions.py +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/ui/Worksheets.py +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/ui/main.py +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/ui/plots.py +0 -0
- {owlplanner-2025.2.14 → owlplanner-2025.2.19}/ui/progress.py +0 -0
|
@@ -1,18 +1,21 @@
|
|
|
1
|
-
|
|
1
|
+
# Owl
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## A retirement exploration tool based on linear programming
|
|
4
|
+
|
|
5
|
+
<img align=right src="https://raw.github.com/mdlacasse/Owl/main/docs/images/owl.png" width="250">
|
|
6
|
+
|
|
7
|
+
------------------------------------------------------------------------------------
|
|
8
|
+
### About
|
|
9
|
+
This document is aimed at software developers desiring to install the Owl source code
|
|
10
|
+
and run it locally on their computer.
|
|
11
|
+
|
|
12
|
+
For end-users, we suggest accessing Owl from the [Streamlit Community Server](http://owlplanner.streamlit.app)
|
|
13
|
+
or, if one prefers to have everything on their own computer,
|
|
14
|
+
to install and run a Docker image as described in these [instructions](docker/README.md).
|
|
4
15
|
|
|
5
16
|
### Requirements
|
|
6
|
-
These instructions are for installing the Python source code for Owl and run it on your computer.
|
|
7
17
|
You will need Python and `pip` installed on your computer for that purpose.
|
|
8
18
|
|
|
9
|
-
### Installation steps for end-users
|
|
10
|
-
You can install the Owl package directly from the [Python Package Index](http://pypi.org).
|
|
11
|
-
The following command will install the current version of owlplanner and all its dependencies:
|
|
12
|
-
```shell
|
|
13
|
-
pip install -r ui/requirements.txt
|
|
14
|
-
```
|
|
15
|
-
|
|
16
19
|
### Installation steps for developers
|
|
17
20
|
These instructions are command-line instructions.
|
|
18
21
|
You will need the latest version of Owl from GitHub.
|
|
@@ -27,12 +30,13 @@ python -m build
|
|
|
27
30
|
pip install -e .
|
|
28
31
|
```
|
|
29
32
|
The -e instructs Python to load the live version in the current directory tree.
|
|
33
|
+
|
|
30
34
|
### Running the streamlit frontend
|
|
31
35
|
Running the Owl user interface locally from Windows:
|
|
32
36
|
```shell
|
|
33
37
|
./owlplanner.cmd
|
|
34
38
|
```
|
|
35
|
-
|
|
39
|
+
Running the Owl user interface locally from Linux or MacOS:
|
|
36
40
|
```shell
|
|
37
41
|
./owlplanner.sh
|
|
38
42
|
```
|
|
@@ -50,3 +54,11 @@ python -m build
|
|
|
50
54
|
twine upload --repository [repo] dist/*
|
|
51
55
|
```
|
|
52
56
|
where [repo] is *testpypi* or *pypi* depending on the type of release.
|
|
57
|
+
|
|
58
|
+
### Installation steps for Python package only
|
|
59
|
+
You can install the Owl package directly from the [Python Package Index](http://pypi.org).
|
|
60
|
+
The following command will install the current version of owlplanner and all its dependencies:
|
|
61
|
+
```shell
|
|
62
|
+
pip install -r ui/requirements.txt
|
|
63
|
+
```
|
|
64
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: owlplanner
|
|
3
|
-
Version: 2025.2.
|
|
3
|
+
Version: 2025.2.19
|
|
4
4
|
Summary: Owl: Retirement planner with great wisdom
|
|
5
5
|
Project-URL: HomePage, https://github.com/mdlacasse/owl
|
|
6
6
|
Project-URL: Repository, https://github.com/mdlacasse/owl
|
|
@@ -708,7 +708,7 @@ Description-Content-Type: text/markdown
|
|
|
708
708
|
|
|
709
709
|
<img align=right src="https://raw.github.com/mdlacasse/Owl/main/docs/images/owl.png" width="250">
|
|
710
710
|
|
|
711
|
-
|
|
711
|
+
-------------------------------------------------------------------------------------
|
|
712
712
|
|
|
713
713
|
### TL;DR
|
|
714
714
|
Owl is a planning tool that uses a linear programming optimization algorithm to provide guidance on retirement decisions. There are a few ways to run Owl.
|
|
@@ -719,10 +719,10 @@ Owl is a planning tool that uses a linear programming optimization algorithm to
|
|
|
719
719
|
Follow these [instructions](docker/README.md) for this option.
|
|
720
720
|
|
|
721
721
|
- Run locally on your computer using Python code and libraries.
|
|
722
|
-
Follow
|
|
723
|
-
|
|
724
|
-
-----
|
|
722
|
+
Follow these [instructions](INSTALL.md) to install Owl from the source code and run it on your computer.
|
|
725
723
|
|
|
724
|
+
-------------------------------------------------------------------------------------
|
|
725
|
+
## Overview
|
|
726
726
|
This package is a retirement modeling framework for exploring the sensitivity of retirement financial decisions.
|
|
727
727
|
Strictly speaking, it is not a planning tool, but more an environment for exploring *what if* scenarios.
|
|
728
728
|
It provides different realizations of a financial strategy through the rigorous
|
|
@@ -789,7 +789,7 @@ Other asset classes can easily be added, but would add complexity while only pro
|
|
|
789
789
|
Historical data used are from
|
|
790
790
|
[Aswath Damodaran](https://pages.stern.nyu.edu/~adamodar/) at the Stern School of Business.
|
|
791
791
|
Asset allocations are selected for the duration of the plan, and these can glide linearly
|
|
792
|
-
or along a configurable s-curve
|
|
792
|
+
or along a configurable s-curve over the lifespan of the individual.
|
|
793
793
|
|
|
794
794
|
Spending profiles are adjusted for inflation, and so are all other indexable quantities. Proflies can be
|
|
795
795
|
flat or follow a *smile* curve which is also adjustable through two simple parameters.
|
|
@@ -800,10 +800,10 @@ the statistical characteristics (means and covariance matrix) of
|
|
|
800
800
|
a selected historical year range. Pure *stochastic* rates can also be generated
|
|
801
801
|
if the user provides means, volatility (expressed as standard deviation), and optionally
|
|
802
802
|
the correlations between the different assets return rates provided as a matrix, or a list of
|
|
803
|
-
the off-diagonal elements (see
|
|
803
|
+
the off-diagonal elements (see documentation for details).
|
|
804
804
|
Average rates calculated over a historical data period can also be chosen.
|
|
805
805
|
|
|
806
|
-
Monte Carlo simulations capabilities are included
|
|
806
|
+
Monte Carlo simulations capabilities are included and provide a probability of success and a histogram of
|
|
807
807
|
outcomes. These simulations can be used for either determining the probability distribution of the
|
|
808
808
|
maximum net spending amount under
|
|
809
809
|
the constraint of a desired bequest, or the probability distribution of the maximum
|
|
@@ -812,33 +812,36 @@ simulators, Owl uses an optimization algorithm for every new scenario, which res
|
|
|
812
812
|
calculations being performed. As a result, the number of cases to be considered should be kept
|
|
813
813
|
to a reasonable number. For a few hundred cases, a few minutes of calculations can provide very good estimates
|
|
814
814
|
and reliable probability distributions.
|
|
815
|
-
|
|
815
|
+
|
|
816
|
+
Optimizing each solution is more representative than event-base simulators
|
|
817
|
+
in the sense that optimal solutions
|
|
816
818
|
will naturally adjust to the return scenarios being considered.
|
|
817
819
|
This is more realistic as retirees would certainly re-evaluate
|
|
818
820
|
their expectations under severe market drops or gains.
|
|
819
|
-
This optimal approach provides a net benefit over event-based
|
|
821
|
+
This optimal approach provides a net benefit over event-based simulators,
|
|
820
822
|
which maintain a distribution strategy either fixed, or within guardrails for capturing the
|
|
821
|
-
|
|
823
|
+
retirees' reactions to the market.
|
|
822
824
|
|
|
823
|
-
Basic input parameters
|
|
825
|
+
Basic input parameters can be entered through the user interface
|
|
826
|
+
while optional additional time series can be read from
|
|
824
827
|
an Excel spreadsheet that contains future wages, contributions
|
|
825
828
|
to savings accounts, and planned *big-ticket items* such as the purchase of a lake house,
|
|
826
829
|
the sale of a boat, large gifts, or inheritance.
|
|
827
830
|
|
|
828
831
|
Three types of savings accounts are considered: taxable, tax-deferred, and tax-exempt,
|
|
829
832
|
which are all tracked separately for married individuals. Asset transition to the surviving spouse
|
|
830
|
-
is done according to beneficiary fractions for each account
|
|
833
|
+
is done according to beneficiary fractions for each type of savings account.
|
|
831
834
|
Tax status covers married filing jointly and single, depending on the number of individuals reported.
|
|
832
835
|
|
|
833
|
-
Medicare and IRMAA calculations are performed through a self-consistent loop on cash flow constraints.
|
|
834
|
-
values are simple projections of current values with the assumed inflation rates.
|
|
836
|
+
Medicare and IRMAA calculations are performed through a self-consistent loop on cash flow constraints.
|
|
837
|
+
Future values are simple projections of current values with the assumed inflation rates.
|
|
835
838
|
|
|
836
839
|
### Limitations
|
|
837
840
|
Owl is work in progress. At the current time:
|
|
838
841
|
- Only the US federal income tax is considered (and minimized through the optimization algorithm).
|
|
839
842
|
Head of household filing status has not been added but can easily be.
|
|
840
843
|
- Required minimum distributions are calculated, but tables for spouses more than 10 years apart are not included.
|
|
841
|
-
|
|
844
|
+
These cases are detected and will generate an error message.
|
|
842
845
|
- Social security rule for surviving spouse assumes that benefits were taken at full retirement age.
|
|
843
846
|
- Current version has no optimization of asset allocations between individuals and/or types of savings accounts.
|
|
844
847
|
If there is interest, that could be added in the future.
|
|
@@ -847,12 +850,12 @@ If there is interest, that could be added in the future.
|
|
|
847
850
|
This means that the Medicare premiums are calculated after an initial solution is generated,
|
|
848
851
|
and then a new solution is re-generated with these premiums as a constraint.
|
|
849
852
|
In some situations, when the income (MAGI) is near an IRMAA bracket, oscillatory solutions can arise.
|
|
850
|
-
|
|
851
|
-
While the solutions generated are very close to one another, Owl will pick the smallest one
|
|
853
|
+
While the solutions generated are very close to one another, Owl will pick the smallest solution
|
|
852
854
|
for being conservative.
|
|
853
|
-
- Part D is not included in the IRMAA calculations. Being considerably more,
|
|
854
|
-
|
|
855
|
-
|
|
855
|
+
- Part D is not included in the IRMAA calculations. Being considerably more significant,
|
|
856
|
+
only Part B is taken into account.
|
|
857
|
+
- Future tax brackets are pure speculations derived from the little we know now and projected to the next 30 years.
|
|
858
|
+
Your guesses are as good as mine.
|
|
856
859
|
|
|
857
860
|
The solution from an optimization algorithm has only two states: feasible and infeasible.
|
|
858
861
|
Therefore, unlike event-driven simulators that can tell you that your distribution strategy runs
|
|
@@ -867,7 +870,7 @@ assets to support, even with no estate being left.
|
|
|
867
870
|
|
|
868
871
|
- Documentation for the app user interface is available from the interface itself.
|
|
869
872
|
- Installation guide and software requirements can be found [here](INSTALL.md).
|
|
870
|
-
- User guide for the underlying
|
|
873
|
+
- User guide for the underlying Python package as used in a Jupyter notebook can be found [here](USER_GUIDE.md).
|
|
871
874
|
|
|
872
875
|
---------------------------------------------------------------------
|
|
873
876
|
|
|
@@ -876,13 +879,14 @@ assets to support, even with no estate being left.
|
|
|
876
879
|
- Image from [freepik](https://freepik.com)
|
|
877
880
|
- Optimization solver from [HiGHS](https://highs.dev)
|
|
878
881
|
- Streamlit Community Cloud [Streamlit](https://streamlit.io)
|
|
879
|
-
-
|
|
882
|
+
- Contributors: Josh (noimjosh@gmail.com) for Docker image code
|
|
880
883
|
|
|
881
884
|
---------------------------------------------------------------------
|
|
882
885
|
|
|
883
886
|
Copyright © 2024 - Martin-D. Lacasse
|
|
884
887
|
|
|
885
|
-
Disclaimers: I am not a financial planner. You make your own decisions.
|
|
888
|
+
Disclaimers: I am not a financial planner. You make your own decisions.
|
|
889
|
+
This program comes with no guarantee. Use at your own risk.
|
|
886
890
|
|
|
887
891
|
--------------------------------------------------------
|
|
888
892
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
<img align=right src="https://raw.github.com/mdlacasse/Owl/main/docs/images/owl.png" width="250">
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
-------------------------------------------------------------------------------------
|
|
9
9
|
|
|
10
10
|
### TL;DR
|
|
11
11
|
Owl is a planning tool that uses a linear programming optimization algorithm to provide guidance on retirement decisions. There are a few ways to run Owl.
|
|
@@ -16,10 +16,10 @@ Owl is a planning tool that uses a linear programming optimization algorithm to
|
|
|
16
16
|
Follow these [instructions](docker/README.md) for this option.
|
|
17
17
|
|
|
18
18
|
- Run locally on your computer using Python code and libraries.
|
|
19
|
-
Follow
|
|
20
|
-
|
|
21
|
-
-----
|
|
19
|
+
Follow these [instructions](INSTALL.md) to install Owl from the source code and run it on your computer.
|
|
22
20
|
|
|
21
|
+
-------------------------------------------------------------------------------------
|
|
22
|
+
## Overview
|
|
23
23
|
This package is a retirement modeling framework for exploring the sensitivity of retirement financial decisions.
|
|
24
24
|
Strictly speaking, it is not a planning tool, but more an environment for exploring *what if* scenarios.
|
|
25
25
|
It provides different realizations of a financial strategy through the rigorous
|
|
@@ -86,7 +86,7 @@ Other asset classes can easily be added, but would add complexity while only pro
|
|
|
86
86
|
Historical data used are from
|
|
87
87
|
[Aswath Damodaran](https://pages.stern.nyu.edu/~adamodar/) at the Stern School of Business.
|
|
88
88
|
Asset allocations are selected for the duration of the plan, and these can glide linearly
|
|
89
|
-
or along a configurable s-curve
|
|
89
|
+
or along a configurable s-curve over the lifespan of the individual.
|
|
90
90
|
|
|
91
91
|
Spending profiles are adjusted for inflation, and so are all other indexable quantities. Proflies can be
|
|
92
92
|
flat or follow a *smile* curve which is also adjustable through two simple parameters.
|
|
@@ -97,10 +97,10 @@ the statistical characteristics (means and covariance matrix) of
|
|
|
97
97
|
a selected historical year range. Pure *stochastic* rates can also be generated
|
|
98
98
|
if the user provides means, volatility (expressed as standard deviation), and optionally
|
|
99
99
|
the correlations between the different assets return rates provided as a matrix, or a list of
|
|
100
|
-
the off-diagonal elements (see
|
|
100
|
+
the off-diagonal elements (see documentation for details).
|
|
101
101
|
Average rates calculated over a historical data period can also be chosen.
|
|
102
102
|
|
|
103
|
-
Monte Carlo simulations capabilities are included
|
|
103
|
+
Monte Carlo simulations capabilities are included and provide a probability of success and a histogram of
|
|
104
104
|
outcomes. These simulations can be used for either determining the probability distribution of the
|
|
105
105
|
maximum net spending amount under
|
|
106
106
|
the constraint of a desired bequest, or the probability distribution of the maximum
|
|
@@ -109,33 +109,36 @@ simulators, Owl uses an optimization algorithm for every new scenario, which res
|
|
|
109
109
|
calculations being performed. As a result, the number of cases to be considered should be kept
|
|
110
110
|
to a reasonable number. For a few hundred cases, a few minutes of calculations can provide very good estimates
|
|
111
111
|
and reliable probability distributions.
|
|
112
|
-
|
|
112
|
+
|
|
113
|
+
Optimizing each solution is more representative than event-base simulators
|
|
114
|
+
in the sense that optimal solutions
|
|
113
115
|
will naturally adjust to the return scenarios being considered.
|
|
114
116
|
This is more realistic as retirees would certainly re-evaluate
|
|
115
117
|
their expectations under severe market drops or gains.
|
|
116
|
-
This optimal approach provides a net benefit over event-based
|
|
118
|
+
This optimal approach provides a net benefit over event-based simulators,
|
|
117
119
|
which maintain a distribution strategy either fixed, or within guardrails for capturing the
|
|
118
|
-
|
|
120
|
+
retirees' reactions to the market.
|
|
119
121
|
|
|
120
|
-
Basic input parameters
|
|
122
|
+
Basic input parameters can be entered through the user interface
|
|
123
|
+
while optional additional time series can be read from
|
|
121
124
|
an Excel spreadsheet that contains future wages, contributions
|
|
122
125
|
to savings accounts, and planned *big-ticket items* such as the purchase of a lake house,
|
|
123
126
|
the sale of a boat, large gifts, or inheritance.
|
|
124
127
|
|
|
125
128
|
Three types of savings accounts are considered: taxable, tax-deferred, and tax-exempt,
|
|
126
129
|
which are all tracked separately for married individuals. Asset transition to the surviving spouse
|
|
127
|
-
is done according to beneficiary fractions for each account
|
|
130
|
+
is done according to beneficiary fractions for each type of savings account.
|
|
128
131
|
Tax status covers married filing jointly and single, depending on the number of individuals reported.
|
|
129
132
|
|
|
130
|
-
Medicare and IRMAA calculations are performed through a self-consistent loop on cash flow constraints.
|
|
131
|
-
values are simple projections of current values with the assumed inflation rates.
|
|
133
|
+
Medicare and IRMAA calculations are performed through a self-consistent loop on cash flow constraints.
|
|
134
|
+
Future values are simple projections of current values with the assumed inflation rates.
|
|
132
135
|
|
|
133
136
|
### Limitations
|
|
134
137
|
Owl is work in progress. At the current time:
|
|
135
138
|
- Only the US federal income tax is considered (and minimized through the optimization algorithm).
|
|
136
139
|
Head of household filing status has not been added but can easily be.
|
|
137
140
|
- Required minimum distributions are calculated, but tables for spouses more than 10 years apart are not included.
|
|
138
|
-
|
|
141
|
+
These cases are detected and will generate an error message.
|
|
139
142
|
- Social security rule for surviving spouse assumes that benefits were taken at full retirement age.
|
|
140
143
|
- Current version has no optimization of asset allocations between individuals and/or types of savings accounts.
|
|
141
144
|
If there is interest, that could be added in the future.
|
|
@@ -144,12 +147,12 @@ If there is interest, that could be added in the future.
|
|
|
144
147
|
This means that the Medicare premiums are calculated after an initial solution is generated,
|
|
145
148
|
and then a new solution is re-generated with these premiums as a constraint.
|
|
146
149
|
In some situations, when the income (MAGI) is near an IRMAA bracket, oscillatory solutions can arise.
|
|
147
|
-
|
|
148
|
-
While the solutions generated are very close to one another, Owl will pick the smallest one
|
|
150
|
+
While the solutions generated are very close to one another, Owl will pick the smallest solution
|
|
149
151
|
for being conservative.
|
|
150
|
-
- Part D is not included in the IRMAA calculations. Being considerably more,
|
|
151
|
-
|
|
152
|
-
|
|
152
|
+
- Part D is not included in the IRMAA calculations. Being considerably more significant,
|
|
153
|
+
only Part B is taken into account.
|
|
154
|
+
- Future tax brackets are pure speculations derived from the little we know now and projected to the next 30 years.
|
|
155
|
+
Your guesses are as good as mine.
|
|
153
156
|
|
|
154
157
|
The solution from an optimization algorithm has only two states: feasible and infeasible.
|
|
155
158
|
Therefore, unlike event-driven simulators that can tell you that your distribution strategy runs
|
|
@@ -164,7 +167,7 @@ assets to support, even with no estate being left.
|
|
|
164
167
|
|
|
165
168
|
- Documentation for the app user interface is available from the interface itself.
|
|
166
169
|
- Installation guide and software requirements can be found [here](INSTALL.md).
|
|
167
|
-
- User guide for the underlying
|
|
170
|
+
- User guide for the underlying Python package as used in a Jupyter notebook can be found [here](USER_GUIDE.md).
|
|
168
171
|
|
|
169
172
|
---------------------------------------------------------------------
|
|
170
173
|
|
|
@@ -173,13 +176,14 @@ assets to support, even with no estate being left.
|
|
|
173
176
|
- Image from [freepik](https://freepik.com)
|
|
174
177
|
- Optimization solver from [HiGHS](https://highs.dev)
|
|
175
178
|
- Streamlit Community Cloud [Streamlit](https://streamlit.io)
|
|
176
|
-
-
|
|
179
|
+
- Contributors: Josh (noimjosh@gmail.com) for Docker image code
|
|
177
180
|
|
|
178
181
|
---------------------------------------------------------------------
|
|
179
182
|
|
|
180
183
|
Copyright © 2024 - Martin-D. Lacasse
|
|
181
184
|
|
|
182
|
-
Disclaimers: I am not a financial planner. You make your own decisions.
|
|
185
|
+
Disclaimers: I am not a financial planner. You make your own decisions.
|
|
186
|
+
This program comes with no guarantee. Use at your own risk.
|
|
183
187
|
|
|
184
188
|
--------------------------------------------------------
|
|
185
189
|
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
# Owl
|
|
1
|
+
# Owl
|
|
3
2
|
|
|
4
3
|
## A retirement exploration tool based on linear programming
|
|
5
4
|
|
|
6
5
|
<img align=right src="https://raw.github.com/mdlacasse/Owl/main/docs/images/owl.png" width="250">
|
|
7
6
|
|
|
7
|
+
------------------------------------------------------------------------------------
|
|
8
|
+
### About
|
|
9
|
+
This document describes the underlying Owl package and how to use it within
|
|
10
|
+
Python scripts or in Jupyter notebooks.
|
|
11
|
+
|
|
8
12
|
-----------------------------------------------------------------------
|
|
9
13
|
## An example of Owl's functionality
|
|
10
14
|
With about 10 lines of Python code, one can generate a full case study.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# docker/Dockerfile
|
|
2
|
+
|
|
3
|
+
FROM python:3.13-slim
|
|
4
|
+
|
|
5
|
+
WORKDIR /app
|
|
6
|
+
|
|
7
|
+
RUN apt-get update; \
|
|
8
|
+
apt-get upgrade; \
|
|
9
|
+
apt-get install -y --no-install-recommends \
|
|
10
|
+
curl \
|
|
11
|
+
git \
|
|
12
|
+
; \
|
|
13
|
+
rm -rf /var/lib/apt/lists/*
|
|
14
|
+
|
|
15
|
+
COPY fastentrypoint.sh /usr/bin/entrypoint.sh
|
|
16
|
+
|
|
17
|
+
RUN chmod 555 /usr/bin/entrypoint.sh
|
|
18
|
+
|
|
19
|
+
# Build in the container for faster starts.
|
|
20
|
+
RUN python -m pip install --no-cache-dir --upgrade pip
|
|
21
|
+
|
|
22
|
+
RUN pip install --no-cache-dir build
|
|
23
|
+
|
|
24
|
+
RUN git clone --depth 1 https://github.com/mdlacasse/Owl.git owl
|
|
25
|
+
|
|
26
|
+
RUN cd /app/owl && python -m build
|
|
27
|
+
|
|
28
|
+
RUN cd /app/owl && pip install --no-cache-dir .
|
|
29
|
+
|
|
30
|
+
EXPOSE 8501
|
|
31
|
+
|
|
32
|
+
HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health
|
|
33
|
+
|
|
34
|
+
ENTRYPOINT ["/bin/bash", "/usr/bin/entrypoint.sh"]
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Owl
|
|
2
|
+
|
|
3
|
+
## A retirement exploration tool based on linear programming
|
|
4
|
+
|
|
5
|
+
<img align=right src="https://raw.github.com/mdlacasse/Owl/main/docs/images/owl.png" width="250">
|
|
6
|
+
|
|
7
|
+
------------------------------------------------------------------------------------
|
|
8
|
+
### About
|
|
9
|
+
This document describes how to run Owl using a Docker container.
|
|
10
|
+
|
|
11
|
+
------------------------------------------------------------------------------------
|
|
12
|
+
### Run Owl without the source code
|
|
13
|
+
Using this approach only requires downloading the Docker image from
|
|
14
|
+
the [Docker Hub](http://hub.docker.com) and having the [Docker](http://docker.com)
|
|
15
|
+
application installed on your computer.
|
|
16
|
+
|
|
17
|
+
Downloading the Docker image from the command line:
|
|
18
|
+
```
|
|
19
|
+
docker pull owlplanner/owldocker
|
|
20
|
+
```
|
|
21
|
+
Then the container can be started (and stopped) from the command line:
|
|
22
|
+
```
|
|
23
|
+
docker run -p 8501:8501 --rm owlplanner/owldocker
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Just point your browser to http://localhost:8501 to access the Owl user interface.
|
|
27
|
+
Owl will run locally and safely through a container on your computer.
|
|
28
|
+
|
|
29
|
+
One can also use the Docker Desktop graphical user interface for performing the same steps.
|
|
30
|
+
The image *owlplanner/owldocker* can be searched for and downloaded in the
|
|
31
|
+
*Docker Hub* section of Docker Desktop. Then, on the *Images* section,
|
|
32
|
+
click on the run icon for the image, and use host port 8501 to map to container port 8501.
|
|
33
|
+
|
|
34
|
+
------------------------------------------------------------------------------------
|
|
35
|
+
### Building the docker image
|
|
36
|
+
This approach requires cloning the Owl package from GitHub,
|
|
37
|
+
and having both Python and Docker installed on your computer.
|
|
38
|
+
|
|
39
|
+
##### Docker image
|
|
40
|
+
First build the Docker image from the `docker` directory:
|
|
41
|
+
```shell
|
|
42
|
+
cd docker
|
|
43
|
+
docker build --no-cache -t owldocker .
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
#### Running the container
|
|
47
|
+
The container can be run directly from the command line,
|
|
48
|
+
with the desired port mapping.
|
|
49
|
+
|
|
50
|
+
```shell
|
|
51
|
+
docker run -p 8501:8501 --rm owldocker
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
#### Running with docker-compose
|
|
55
|
+
Alternatively, the container can be started using `docker-compose` as follows
|
|
56
|
+
```shell
|
|
57
|
+
docker-compose up
|
|
58
|
+
```
|
|
59
|
+
The compose file maps the host-side port to the container-side port.
|
|
60
|
+
```yml
|
|
61
|
+
services:
|
|
62
|
+
owl:
|
|
63
|
+
image: owldocker
|
|
64
|
+
restart: always
|
|
65
|
+
ports:
|
|
66
|
+
- 8501:8501
|
|
67
|
+
networks: {}
|
|
68
|
+
```
|
|
69
|
+
As before, just point your browser to http://localhost:8501 to access the Owl user interface.
|
|
70
|
+
|
|
71
|
+
------------------------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
#### Credits
|
|
74
|
+
Josh (noimjosh@gmail.com)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#/bin/bash
|
|
2
|
+
|
|
3
|
+
echo "Owl is now running locally: Point your browser to http://localhost:8501"
|
|
4
|
+
if type -P streamlit >& /dev/null; then
|
|
5
|
+
streamlit run /app/owl/ui/main.py --server.port=8501 --server.address=0.0.0.0 --browser.gatherUsageStats=false
|
|
6
|
+
else
|
|
7
|
+
python3 -m streamlit run /app/owl/ui/main.py --server.port=8501 --server.address=0.0.0.0 --browser.gatherUsageStats=false
|
|
8
|
+
fi
|
|
9
|
+
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
@ECHO OFF
|
|
2
|
+
:: Change to suit your specific installation of Python
|
|
3
|
+
set root=C:\Users\%username%\Anaconda3
|
|
4
|
+
call %root%\Scripts\activate.bat
|
|
5
|
+
|
|
6
|
+
cd ui
|
|
7
|
+
where /q streamlit
|
|
8
|
+
if ERRORLEVEL 1 (
|
|
9
|
+
where /q python3
|
|
10
|
+
if ERRORLEVEL 1 (
|
|
11
|
+
echo Application cannot be started.
|
|
12
|
+
exit /B
|
|
13
|
+
) else (
|
|
14
|
+
echo Hit Ctrl-C to terminate the server.
|
|
15
|
+
call python3 -m streamlit run main.py --browser.gatherUsageStats=false --browser.serverAddress=localhost
|
|
16
|
+
)
|
|
17
|
+
) else (
|
|
18
|
+
echo Hit Ctrl-C to terminate the Streamlit server.
|
|
19
|
+
call streamlit run main.py --browser.gatherUsageStats=false --browser.serverAddress=localhost
|
|
20
|
+
)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
echo Hit Ctrl-C to terminate the server.
|
|
4
|
+
cd ui
|
|
5
|
+
if type -P streamlit; then
|
|
6
|
+
streamlit run main.py --browser.gatherUsageStats=false --browser.serverAddress=localhost
|
|
7
|
+
else
|
|
8
|
+
python3 -m streamlit run main.py --browser.gatherUsageStats=false --browser.serverAddress=localhost
|
|
9
|
+
fi
|
|
@@ -64,6 +64,7 @@ def saveConfig(plan, file, mylog):
|
|
|
64
64
|
"Heirs rate on tax-deferred estate": float(100 * plan.nu),
|
|
65
65
|
"Long-term capital gain tax rate": float(100 * plan.psi),
|
|
66
66
|
"Dividend tax rate": float(100 * plan.mu),
|
|
67
|
+
"TCJA expiration year": plan.yTCJA,
|
|
67
68
|
"Method": plan.rateMethod,
|
|
68
69
|
}
|
|
69
70
|
if plan.rateMethod in ["user", "stochastic"]:
|
|
@@ -226,6 +227,7 @@ def readConfig(file, *, verbose=True, logstreams=None, readContributions=True):
|
|
|
226
227
|
p.setDividendRate(float(diconf["Rates Selection"]["Dividend tax rate"]))
|
|
227
228
|
p.setLongTermCapitalTaxRate(float(diconf["Rates Selection"]["Long-term capital gain tax rate"]))
|
|
228
229
|
p.setHeirsTaxRate(float(diconf["Rates Selection"]["Heirs rate on tax-deferred estate"]))
|
|
230
|
+
p.yTCJA = int(diconf["Rates Selection"]["TCJA expiration year"])
|
|
229
231
|
|
|
230
232
|
frm = None
|
|
231
233
|
to = None
|