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.
Files changed (105) hide show
  1. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/.github/workflows/github-actions-runtests.yml +1 -1
  2. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/INSTALL.md +23 -11
  3. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/PKG-INFO +28 -24
  4. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/README.md +27 -23
  5. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/USER_GUIDE.md +6 -2
  6. owlplanner-2025.2.19/docker/Dockerfile +34 -0
  7. owlplanner-2025.2.19/docker/README.md +74 -0
  8. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/docker/docker-compose.yml +1 -3
  9. owlplanner-2025.2.19/docker/fastentrypoint.sh +9 -0
  10. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/examples/case_jack+jill.toml +1 -0
  11. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/examples/case_joe.toml +1 -0
  12. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/examples/case_john+sally.toml +1 -0
  13. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/examples/case_kim+sam-bequest.toml +1 -0
  14. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/examples/case_kim+sam-spending.toml +1 -0
  15. owlplanner-2025.2.19/owlplanner.cmd +20 -0
  16. owlplanner-2025.2.19/owlplanner.sh +9 -0
  17. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/pyproject.toml +1 -1
  18. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/src/owlplanner/config.py +2 -0
  19. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/src/owlplanner/plan.py +16 -5
  20. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/src/owlplanner/tax2025.py +63 -43
  21. owlplanner-2025.2.19/src/owlplanner/version.py +1 -0
  22. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/tests/test_repro.py +11 -11
  23. owlplanner-2025.2.19/ttt.py +20 -0
  24. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/ui/About_Owl.py +5 -1
  25. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/ui/Create_Case.py +1 -1
  26. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/ui/Rates_Selection.py +7 -2
  27. owlplanner-2025.2.19/ui/main+fonts.py +55 -0
  28. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/ui/owlbridge.py +4 -0
  29. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/ui/requirements.txt +1 -1
  30. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/ui/sskeys.py +1 -0
  31. owlplanner-2025.2.19/ui/style.css +8 -0
  32. owlplanner-2025.2.14/docker/Dockerfile +0 -30
  33. owlplanner-2025.2.14/docker/README.md +0 -69
  34. owlplanner-2025.2.14/docker/entrypoint.sh +0 -35
  35. owlplanner-2025.2.14/owlplanner.cmd +0 -8
  36. owlplanner-2025.2.14/owlplanner.sh +0 -10
  37. owlplanner-2025.2.14/src/owlplanner/version.py +0 -1
  38. owlplanner-2025.2.14/test.py +0 -18
  39. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/.devcontainer/devcontainer.json +0 -0
  40. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/.flake8 +0 -0
  41. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/.gitattributes +0 -0
  42. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/.gitignore +0 -0
  43. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/LICENSE +0 -0
  44. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/docs/images/AD-taxDef.png +0 -0
  45. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/docs/images/AD-taxFree.png +0 -0
  46. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/docs/images/AD-taxable.png +0 -0
  47. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/docs/images/Hist_Bequest.png +0 -0
  48. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/docs/images/Hist_Spending.png +0 -0
  49. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/docs/images/MC-tutorial2a.png +0 -0
  50. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/docs/images/MC-tutorial2b.png +0 -0
  51. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/docs/images/OwlUI.png +0 -0
  52. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/docs/images/allocations.png +0 -0
  53. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/docs/images/owl.png +0 -0
  54. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/docs/images/profile.png +0 -0
  55. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/docs/images/ratesCorrelations.png +0 -0
  56. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/docs/images/ratesPlot.png +0 -0
  57. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/docs/images/savingsPlot.png +0 -0
  58. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/docs/images/sourcesPlot.png +0 -0
  59. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/docs/images/spendingPlot.png +0 -0
  60. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/docs/images/taxIncomePlot.png +0 -0
  61. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/docs/images/taxesPlot.png +0 -0
  62. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/docs/owl.pdf +0 -0
  63. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/docs/owl.tex +0 -0
  64. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/examples/jack+jill.xlsx +0 -0
  65. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/examples/joe.xlsx +0 -0
  66. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/examples/john+sally.xlsx +0 -0
  67. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/examples/template.xlsx +0 -0
  68. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/notebooks/john+sally.ipynb +0 -0
  69. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/notebooks/kim+sam.ipynb +0 -0
  70. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/notebooks/template.ipynb +0 -0
  71. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/notebooks/tutorial_1.ipynb +0 -0
  72. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/notebooks/tutorial_2.ipynb +0 -0
  73. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/notebooks/tutorial_3.ipynb +0 -0
  74. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/requirements.txt +0 -0
  75. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/src/owlplanner/__init__.py +0 -0
  76. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/src/owlplanner/abcapi.py +0 -0
  77. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/src/owlplanner/data/__init__.py +0 -0
  78. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/src/owlplanner/data/rates.csv +0 -0
  79. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/src/owlplanner/logging.py +0 -0
  80. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/src/owlplanner/progress.py +0 -0
  81. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/src/owlplanner/rates.py +0 -0
  82. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/src/owlplanner/timelists.py +0 -0
  83. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/src/owlplanner/utils.py +0 -0
  84. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/tests/test_logger.py +0 -0
  85. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/tests/test_regressions.py +0 -0
  86. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/tests/test_toml_cases.py +0 -0
  87. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/tests/test_units.py +0 -0
  88. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/ui/Asset_Allocation.py +0 -0
  89. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/ui/Assets.py +0 -0
  90. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/ui/Documentation.py +0 -0
  91. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/ui/Fixed_Income.py +0 -0
  92. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/ui/Graphs.py +0 -0
  93. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/ui/Historical_Range.py +0 -0
  94. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/ui/Logs.py +0 -0
  95. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/ui/Monte_Carlo.py +0 -0
  96. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/ui/Optimization_Parameters.py +0 -0
  97. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/ui/Output_Files.py +0 -0
  98. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/ui/Quick_Start.py +0 -0
  99. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/ui/README.md +0 -0
  100. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/ui/Settings.py +0 -0
  101. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/ui/Wages_And_Contributions.py +0 -0
  102. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/ui/Worksheets.py +0 -0
  103. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/ui/main.py +0 -0
  104. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/ui/plots.py +0 -0
  105. {owlplanner-2025.2.14 → owlplanner-2025.2.19}/ui/progress.py +0 -0
@@ -11,7 +11,7 @@ jobs:
11
11
  strategy:
12
12
  fail-fast: false
13
13
  matrix:
14
- python-version: ["3.9", "3.10", "3.12"]
14
+ python-version: ["3.9", "3.10", "3.12", "3.13"]
15
15
 
16
16
  steps:
17
17
  - uses: actions/checkout@v4
@@ -1,18 +1,21 @@
1
- ## Installation steps
1
+ # Owl
2
2
 
3
- ### To install and run a Docker image, please see these [instructions](docker/README.md).
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
- From Linux or MacOS:
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.14
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 there [instructions](INSTALL.md) to install Owl from the source code and run it on your computer.
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 from now to the last year of the plan.
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 the notebook tutorial for details).
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 and provide a probability of success and a histogram of
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
- Optimizing each solution is more representative in the sense that optimal solutions
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 simulations,
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
- retirees' reactions to the market.
823
+ retirees' reactions to the market.
822
824
 
823
- Basic input parameters are given through function calls while optional additional time series can be read from
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 type.
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. Future
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
- An error message will be generated for these cases.
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
- Owl will detect these cases and inform the user.
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, only Part B is taken into account.
854
- - Future tax brackets are pure speculations derived from the little we know now and projected to the next 30 years. Your guesses are as good as mine.
855
- Having a knob to adjust future rates might be an interesting feature to add for measuring the impact on Roth conversions.
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 library to be used in a Jupyter notebook can be found [here](USER_GUIDE.md).
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
- - Other contributors: Josh (noimjosh@gmail.com) for Docker image code
882
+ - Contributors: Josh (noimjosh@gmail.com) for Docker image code
880
883
 
881
884
  ---------------------------------------------------------------------
882
885
 
883
886
  Copyright &copy; 2024 - Martin-D. Lacasse
884
887
 
885
- Disclaimers: I am not a financial planner. You make your own decisions. This program comes with no guarantee. Use at your own risk.
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 there [instructions](INSTALL.md) to install Owl from the source code and run it on your computer.
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 from now to the last year of the plan.
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 the notebook tutorial for details).
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 and provide a probability of success and a histogram of
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
- Optimizing each solution is more representative in the sense that optimal solutions
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 simulations,
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
- retirees' reactions to the market.
120
+ retirees' reactions to the market.
119
121
 
120
- Basic input parameters are given through function calls while optional additional time series can be read from
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 type.
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. Future
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
- An error message will be generated for these cases.
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
- Owl will detect these cases and inform the user.
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, only Part B is taken into account.
151
- - Future tax brackets are pure speculations derived from the little we know now and projected to the next 30 years. Your guesses are as good as mine.
152
- Having a knob to adjust future rates might be an interesting feature to add for measuring the impact on Roth conversions.
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 library to be used in a Jupyter notebook can be found [here](USER_GUIDE.md).
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
- - Other contributors: Josh (noimjosh@gmail.com) for Docker image code
179
+ - Contributors: Josh (noimjosh@gmail.com) for Docker image code
177
180
 
178
181
  ---------------------------------------------------------------------
179
182
 
180
183
  Copyright &copy; 2024 - Martin-D. Lacasse
181
184
 
182
- Disclaimers: I am not a financial planner. You make your own decisions. This program comes with no guarantee. Use at your own risk.
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)
@@ -1,9 +1,7 @@
1
1
  services:
2
2
  owl:
3
- image: owldocker
3
+ image: owlplanner/owldocker
4
4
  restart: always
5
5
  ports:
6
6
  - 8501:8501
7
- volumes:
8
- - /tmp/owl:/app
9
7
  networks: {}
@@ -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
+
@@ -28,6 +28,7 @@ Names = [ "Jack", "Jill",]
28
28
  "Heirs rate on tax-deferred estate" = 30.0
29
29
  "Long-term capital gain tax rate" = 15.0
30
30
  "Dividend tax rate" = 2.0
31
+ "TCJA expiration year" = 2026
31
32
  Method = "historical"
32
33
  From = 1969
33
34
  To = 2002
@@ -26,6 +26,7 @@ Names = [ "Joe",]
26
26
  "Heirs rate on tax-deferred estate" = 30.0
27
27
  "Long-term capital gain tax rate" = 15.0
28
28
  "Dividend tax rate" = 2.0
29
+ "TCJA expiration year" = 2026
29
30
  Method = "historical average"
30
31
  From = 1969
31
32
  To = 2002
@@ -28,6 +28,7 @@ Names = [ "John", "Sally",]
28
28
  "Heirs rate on tax-deferred estate" = 30.0
29
29
  "Long-term capital gain tax rate" = 15.0
30
30
  "Dividend tax rate" = 2.0
31
+ "TCJA expiration year" = 2026
31
32
  Method = "historical average"
32
33
  From = 1990
33
34
  To = 2023
@@ -28,6 +28,7 @@ Names = [ "Kim", "Sam",]
28
28
  "Heirs rate on tax-deferred estate" = 33.0
29
29
  "Long-term capital gain tax rate" = 15.0
30
30
  "Dividend tax rate" = 2.0
31
+ "TCJA expiration year" = 2026
31
32
  Method = "conservative"
32
33
  From = 1922
33
34
  To = 2023
@@ -28,6 +28,7 @@ Names = [ "Kim", "Sam",]
28
28
  "Heirs rate on tax-deferred estate" = 33.0
29
29
  "Long-term capital gain tax rate" = 15.0
30
30
  "Dividend tax rate" = 2.0
31
+ "TCJA expiration year" = 2026
31
32
  Method = "conservative"
32
33
  From = 1922
33
34
  To = 2023
@@ -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
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "owlplanner"
7
- version = "2025.02.14"
7
+ version = "2025.02.19"
8
8
  authors = [
9
9
  { name="Martin-D. Lacasse", email="martin.d.lacasse@gmail.com" },
10
10
  ]
@@ -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