owlplanner 2025.11.5__tar.gz → 2025.12.3__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 (121) hide show
  1. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/PKG-INFO +23 -15
  2. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/README.md +17 -10
  3. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/RELEASE_NOTES.md +17 -1
  4. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/docker/README.md +1 -1
  5. owlplanner-2025.12.3/docker/build.cmd +7 -0
  6. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/examples/case_drawdowncalc-comparison-1.toml +2 -1
  7. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/examples/case_jack+jill.toml +2 -1
  8. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/examples/case_joe.toml +2 -1
  9. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/examples/case_john+sally.toml +2 -1
  10. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/examples/case_jon+jane.toml +2 -1
  11. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/examples/case_kim+sam-bequest.toml +2 -1
  12. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/examples/case_kim+sam-spending.toml +2 -1
  13. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/notebooks/john+sally.ipynb +2 -2
  14. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/notebooks/kim+sam.ipynb +2 -2
  15. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/notebooks/template.ipynb +4 -4
  16. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/notebooks/tutorial_1.ipynb +5 -5
  17. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/notebooks/tutorial_2.ipynb +2 -2
  18. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/notebooks/tutorial_3.ipynb +2 -2
  19. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/pyproject.toml +9 -5
  20. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/src/owlplanner/config.py +11 -8
  21. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/src/owlplanner/plan.py +77 -33
  22. owlplanner-2025.12.3/src/owlplanner/socialsecurity.py +89 -0
  23. owlplanner-2025.12.3/src/owlplanner/version.py +1 -0
  24. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/tests/test_regressions.py +16 -6
  25. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/tests/test_repro.py +14 -13
  26. owlplanner-2025.12.3/tests/test_socsec.py +72 -0
  27. owlplanner-2025.12.3/ui/AI +48 -0
  28. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/ui/Create_Case.py +16 -4
  29. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/ui/Documentation.py +84 -42
  30. owlplanner-2025.12.3/ui/Fixed_Income.py +92 -0
  31. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/ui/Quick_Start.py +13 -12
  32. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/ui/Wages_and_Contributions.py +2 -2
  33. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/ui/owlbridge.py +10 -5
  34. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/ui/sskeys.py +5 -2
  35. owlplanner-2025.11.5/src/owlplanner/version.py +0 -1
  36. owlplanner-2025.11.5/ui/Fixed_Income.py +0 -68
  37. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/.devcontainer/devcontainer.json +0 -0
  38. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/.flake8 +0 -0
  39. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/.gitattributes +0 -0
  40. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/.github/workflows/github-actions-runtests.yml +0 -0
  41. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/.gitignore +0 -0
  42. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/.streamlit/config.toml +0 -0
  43. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/.streamlit/fullconfig.toml +0 -0
  44. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/INSTALL.md +0 -0
  45. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/LICENSE +0 -0
  46. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/USER_GUIDE.md +0 -0
  47. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/docker/Dockerfile.bare +0 -0
  48. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/docker/Dockerfile.static +0 -0
  49. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/docker/buildentrypoint.sh +0 -0
  50. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/docker/docker-compose.yml +0 -0
  51. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/docker/runentrypoint.sh +0 -0
  52. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/docs/images/AD-taxDef.png +0 -0
  53. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/docs/images/AD-taxFree.png +0 -0
  54. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/docs/images/AD-taxable.png +0 -0
  55. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/docs/images/Hist_Bequest.png +0 -0
  56. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/docs/images/Hist_Spending.png +0 -0
  57. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/docs/images/MC-tutorial2a.png +0 -0
  58. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/docs/images/MC-tutorial2b.png +0 -0
  59. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/docs/images/OwlUI.png +0 -0
  60. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/docs/images/allocations.png +0 -0
  61. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/docs/images/owl.png +0 -0
  62. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/docs/images/piecewiseConstant.png +0 -0
  63. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/docs/images/profile.png +0 -0
  64. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/docs/images/ratesCorrelations.png +0 -0
  65. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/docs/images/ratesPlot.png +0 -0
  66. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/docs/images/savingsPlot.png +0 -0
  67. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/docs/images/sourcesPlot.png +0 -0
  68. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/docs/images/spendingPlot.png +0 -0
  69. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/docs/images/taxIncomePlot.png +0 -0
  70. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/docs/images/taxesPlot.png +0 -0
  71. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/docs/owl.pdf +0 -0
  72. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/docs/owl.tex +0 -0
  73. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/examples/jack+jill.xlsx +0 -0
  74. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/examples/joe.xlsx +0 -0
  75. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/examples/john+sally.xlsx +0 -0
  76. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/examples/jon+jane.xlsx +0 -0
  77. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/examples/kim+sam.xlsx +0 -0
  78. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/examples/template.xlsx +0 -0
  79. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/owlplanner.cmd +0 -0
  80. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/owlplanner.sh +0 -0
  81. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/pytest.ini +0 -0
  82. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/requirements.txt +0 -0
  83. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/src/owlplanner/__init__.py +0 -0
  84. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/src/owlplanner/abcapi.py +0 -0
  85. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/src/owlplanner/data/__init__.py +0 -0
  86. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/src/owlplanner/data/rates.csv +0 -0
  87. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/src/owlplanner/mylogging.py +0 -0
  88. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/src/owlplanner/plotting/__init__.py +0 -0
  89. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/src/owlplanner/plotting/base.py +0 -0
  90. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/src/owlplanner/plotting/factory.py +0 -0
  91. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/src/owlplanner/plotting/matplotlib_backend.py +0 -0
  92. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/src/owlplanner/plotting/plotly_backend.py +0 -0
  93. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/src/owlplanner/progress.py +0 -0
  94. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/src/owlplanner/rates.py +0 -0
  95. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/src/owlplanner/tax2025.py +0 -0
  96. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/src/owlplanner/tax2026.py +0 -0
  97. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/src/owlplanner/timelists.py +0 -0
  98. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/src/owlplanner/utils.py +0 -0
  99. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/tests/test_logger.py +0 -0
  100. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/tests/test_toml_cases.py +0 -0
  101. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/tests/test_ui_asset_allocation.py +0 -0
  102. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/tests/test_ui_compare_summaries.py +0 -0
  103. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/tests/test_ui_sskeys.py +0 -0
  104. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/tests/test_units.py +0 -0
  105. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/ui/About_Owl.py +0 -0
  106. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/ui/Asset_Allocation.py +0 -0
  107. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/ui/Current_Assets.py +0 -0
  108. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/ui/Graphs.py +0 -0
  109. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/ui/Historical_Range.py +0 -0
  110. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/ui/Logs.py +0 -0
  111. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/ui/Monte_Carlo.py +0 -0
  112. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/ui/Optimization_Parameters.py +0 -0
  113. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/ui/Output_Files.py +0 -0
  114. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/ui/README.md +0 -0
  115. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/ui/Rates_Selection.py +0 -0
  116. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/ui/Settings.py +0 -0
  117. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/ui/Worksheets.py +0 -0
  118. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/ui/__init__.py +0 -0
  119. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/ui/main.py +0 -0
  120. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/ui/progress.py +0 -0
  121. {owlplanner-2025.11.5 → owlplanner-2025.12.3}/ui/tomlexamples.py +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: owlplanner
3
- Version: 2025.11.5
4
- Summary: Owl: Retirement planner with great wisdom
3
+ Version: 2025.12.3
4
+ Summary: Owl - Optimal Wealth Lab: 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
7
7
  Project-URL: Issues, https://github.com/mdlacasse/owl/issues
@@ -684,19 +684,20 @@ License: GNU GENERAL PUBLIC LICENSE
684
684
  Public License instead of this License. But first, please read
685
685
  <https://www.gnu.org/licenses/why-not-lgpl.html>.
686
686
  License-File: LICENSE
687
- Classifier: Development Status :: 4 - Beta
687
+ Classifier: Development Status :: 5 - Production/Stable
688
688
  Classifier: Intended Audience :: End Users/Desktop
689
689
  Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
690
690
  Classifier: Operating System :: OS Independent
691
691
  Classifier: Programming Language :: Python :: 3
692
692
  Classifier: Topic :: Office/Business :: Financial :: Investment
693
- Requires-Python: >=3.8
693
+ Requires-Python: >=3.10
694
+ Requires-Dist: highspy
694
695
  Requires-Dist: matplotlib
695
696
  Requires-Dist: numpy
696
697
  Requires-Dist: odfpy
697
698
  Requires-Dist: openpyxl
698
699
  Requires-Dist: pandas
699
- Requires-Dist: plotly
700
+ Requires-Dist: plotly>=6.3
700
701
  Requires-Dist: pulp
701
702
  Requires-Dist: scipy
702
703
  Requires-Dist: seaborn
@@ -721,16 +722,16 @@ Users can select varying return rates to perform historical back testing,
721
722
  stochastic rates for performing Monte Carlo analyses,
722
723
  or fixed rates either derived from historical averages, or set by the user.
723
724
 
724
- There are a few ways to run Owl:
725
+ There are three ways to run Owl:
725
726
 
726
- - Run Owl directly on the Streamlit Community Server at
727
+ - **Streamlit Hub:** Run Owl remotely as hosted on the Streamlit Community Server at
727
728
  [owlplanner.streamlit.app](https://owlplanner.streamlit.app).
728
729
 
729
- - Run locally on your computer using a Docker image.
730
- Follow these [instructions](docker/README.md) for this option.
730
+ - **Docker Container:** Run Owl locally on your computer using a Docker image.
731
+ Follow these [instructions](docker/README.md) for using this option.
731
732
 
732
- - Run locally on your computer using Python code and libraries.
733
- Follow these [instructions](INSTALL.md) to install Owl from the source code and run it on your computer.
733
+ - **Self-hosting:** Run Owl locally on your computer using Python code and libraries.
734
+ Follow these [instructions](INSTALL.md) to install from the source code and self-host on your own computer.
734
735
 
735
736
  -------------------------------------------------------------------------------------
736
737
  ## Overview
@@ -750,13 +751,17 @@ your strategy under different market assumptions, helping to better understand y
750
751
 
751
752
  -------------------------------------------------------------------------------------
752
753
  ## Purpose and vision
753
- The goal of Owl is to create a free and open-source ecosystem that has cutting-edge optimization capabilities,
754
+ One goal of Owl is to provide a free and open-source ecosystem that has cutting-edge optimization capabilities,
754
755
  allowing for the next generation of Python-literate retirees to experiment with their own financial future
755
- while providing a codebase where they can learn and contribute. There are and were
756
+ while providing a codebase where they can learn and contribute. At the same time, an intuitive and easy-to-use
757
+ user interface based on Streamlit allows a broad set of users to benefit from the application as it only requires basic financial knowledge.
758
+
759
+ There are and were
756
760
  good retirement optimizers in the recent past, but the vast majority of them are either proprietary platforms
757
761
  collecting your data, or academic papers that share the results without really sharing the details of
758
762
  the underlying mathematical models.
759
- The algorithms in Owl rely on the open-source HiGHS linear programming solver. The complete formulation and
763
+ The algorithms in Owl rely on the open-source HiGHS linear programming solver but they have also been ported and tested on
764
+ other platforms such as Mosek and COIN-OR. The complete formulation and
760
765
  detailed description of the underlying
761
766
  mathematical model can be found [here](https://github.com/mdlacasse/Owl/blob/main/docs/owl.pdf).
762
767
 
@@ -844,13 +849,16 @@ They can also be optimized explicitly as an option, but this choice can lead to
844
849
  due to the use of the many additional binary variables required by the formulation.
845
850
  Future Medicare and IRMAA values are simple projections of current values with the assumed inflation rates.
846
851
 
852
+ Owl has a basic social security calculator that determines the actual benefits based on the individual's
853
+ primary insurance amount (PIA), full retirement age (FRA), and claiming age. Both
854
+ spousal's benefits and survivor's benefits are calculated for non-complex cases.
855
+
847
856
  ### Limitations
848
857
  Owl is work in progress. At the current time:
849
858
  - Only the US federal income tax is considered (and minimized through the optimization algorithm).
850
859
  Head of household filing status has not been added but can easily be.
851
860
  - Required minimum distributions are calculated, but tables for spouses more than 10 years apart are not included.
852
861
  These cases are detected and will generate an error message.
853
- - Social security rule for surviving spouse assumes that benefits were taken at full retirement age.
854
862
  - Current version has no optimization of asset allocations between individuals and/or types of savings accounts.
855
863
  If there is interest, that could be added in the future.
856
864
  - In the current implementation, social securiy is always taxed at 85%, assuming that your taxable income will be larger than 34 k$ (single) or 44 k$ (married filing jointly).
@@ -15,16 +15,16 @@ Users can select varying return rates to perform historical back testing,
15
15
  stochastic rates for performing Monte Carlo analyses,
16
16
  or fixed rates either derived from historical averages, or set by the user.
17
17
 
18
- There are a few ways to run Owl:
18
+ There are three ways to run Owl:
19
19
 
20
- - Run Owl directly on the Streamlit Community Server at
20
+ - **Streamlit Hub:** Run Owl remotely as hosted on the Streamlit Community Server at
21
21
  [owlplanner.streamlit.app](https://owlplanner.streamlit.app).
22
22
 
23
- - Run locally on your computer using a Docker image.
24
- Follow these [instructions](docker/README.md) for this option.
23
+ - **Docker Container:** Run Owl locally on your computer using a Docker image.
24
+ Follow these [instructions](docker/README.md) for using this option.
25
25
 
26
- - Run locally on your computer using Python code and libraries.
27
- Follow these [instructions](INSTALL.md) to install Owl from the source code and run it on your computer.
26
+ - **Self-hosting:** Run Owl locally on your computer using Python code and libraries.
27
+ Follow these [instructions](INSTALL.md) to install from the source code and self-host on your own computer.
28
28
 
29
29
  -------------------------------------------------------------------------------------
30
30
  ## Overview
@@ -44,13 +44,17 @@ your strategy under different market assumptions, helping to better understand y
44
44
 
45
45
  -------------------------------------------------------------------------------------
46
46
  ## Purpose and vision
47
- The goal of Owl is to create a free and open-source ecosystem that has cutting-edge optimization capabilities,
47
+ One goal of Owl is to provide a free and open-source ecosystem that has cutting-edge optimization capabilities,
48
48
  allowing for the next generation of Python-literate retirees to experiment with their own financial future
49
- while providing a codebase where they can learn and contribute. There are and were
49
+ while providing a codebase where they can learn and contribute. At the same time, an intuitive and easy-to-use
50
+ user interface based on Streamlit allows a broad set of users to benefit from the application as it only requires basic financial knowledge.
51
+
52
+ There are and were
50
53
  good retirement optimizers in the recent past, but the vast majority of them are either proprietary platforms
51
54
  collecting your data, or academic papers that share the results without really sharing the details of
52
55
  the underlying mathematical models.
53
- The algorithms in Owl rely on the open-source HiGHS linear programming solver. The complete formulation and
56
+ The algorithms in Owl rely on the open-source HiGHS linear programming solver but they have also been ported and tested on
57
+ other platforms such as Mosek and COIN-OR. The complete formulation and
54
58
  detailed description of the underlying
55
59
  mathematical model can be found [here](https://github.com/mdlacasse/Owl/blob/main/docs/owl.pdf).
56
60
 
@@ -138,13 +142,16 @@ They can also be optimized explicitly as an option, but this choice can lead to
138
142
  due to the use of the many additional binary variables required by the formulation.
139
143
  Future Medicare and IRMAA values are simple projections of current values with the assumed inflation rates.
140
144
 
145
+ Owl has a basic social security calculator that determines the actual benefits based on the individual's
146
+ primary insurance amount (PIA), full retirement age (FRA), and claiming age. Both
147
+ spousal's benefits and survivor's benefits are calculated for non-complex cases.
148
+
141
149
  ### Limitations
142
150
  Owl is work in progress. At the current time:
143
151
  - Only the US federal income tax is considered (and minimized through the optimization algorithm).
144
152
  Head of household filing status has not been added but can easily be.
145
153
  - Required minimum distributions are calculated, but tables for spouses more than 10 years apart are not included.
146
154
  These cases are detected and will generate an error message.
147
- - Social security rule for surviving spouse assumes that benefits were taken at full retirement age.
148
155
  - Current version has no optimization of asset allocations between individuals and/or types of savings accounts.
149
156
  If there is interest, that could be added in the future.
150
157
  - In the current implementation, social securiy is always taxed at 85%, assuming that your taxable income will be larger than 34 k$ (single) or 44 k$ (married filing jointly).
@@ -1,3 +1,20 @@
1
+ ### Version 2025.12.03
2
+ - Coded social security to use monthly PIA instead of annual amount
3
+ - Added exact routines for FRA and increase/decrease factors due to claiming age
4
+ - Added exact spousal benefits
5
+ - Adjusted documentation for social security
6
+ - Added birth month for more precise calculation on first year of social security
7
+ - Added month to age for claiming social security
8
+
9
+ ### Version 2025.11.29
10
+ - Fixed social security for survivor
11
+ - Enhanced documentation for SS amounts
12
+
13
+ ### Version 2025.11.09
14
+ - Moved development status to production/stable in pyproject
15
+ - Made version propagate everywhere needed
16
+ - Added node limit on milp to avoid Streamlit server shutdown on memory consumption
17
+
1
18
  ### Version 2025.11.05
2
19
  - Mentioning Owl as Optimal Wealth Lab
3
20
  - Port to Streamlit 1.50 which broke many widgets
@@ -5,7 +22,6 @@
5
22
  - Rework Docker to smaller Alpine image and fix docs
6
23
 
7
24
  ### Version 2025.07.01
8
-
9
25
  Added:
10
26
  - Settings option for menu position thanks to Streamlit 1.46 top and sidebar capabilities. Default is top.
11
27
  - Net Investment Income Tax calculations in self-consistent loop.
@@ -9,7 +9,7 @@
9
9
  This document describes how to run Owl using a Docker container.
10
10
 
11
11
  ------------------------------------------------------------------------------------
12
- ### Run Owl without the source code
12
+ ### Running Owl without the source code
13
13
  Using this approach only requires downloading the Docker image from
14
14
  the [Docker Hub](http://hub.docker.com) and having the [Docker](http://docker.com)
15
15
  application installed on your computer.
@@ -0,0 +1,7 @@
1
+ ::
2
+ :: A simple script to build both Docker images
3
+ ::
4
+ docker build --no-cache -f Dockerfile.bare -t owlplanner/owldocker.bare:latest .
5
+ docker push owlplanner/owldocker.bare
6
+ docker build --no-cache -f Dockerfile.static -t owlplanner/owldocker.static:latest .
7
+ docker push owlplanner/owldocker.static
@@ -5,6 +5,7 @@ Description = "This is a case involving a single individual. Case is used for co
5
5
  Status = "single"
6
6
  Names = [ "Charles",]
7
7
  "Birth year" = [ 1966,]
8
+ "Birth month" = [ 1,]
8
9
  "Life expectancy" = [ 89,]
9
10
  "Start date" = "2025-01-01"
10
11
 
@@ -20,7 +21,7 @@ Names = [ "Charles",]
20
21
  "Pension amounts" = [ 0.0,]
21
22
  "Pension ages" = [ 65,]
22
23
  "Pension indexed" = [ true,]
23
- "Social security amounts" = [ 36.0,]
24
+ "Social security PIA amounts" = [ 3000,]
24
25
  "Social security ages" = [ 70,]
25
26
 
26
27
  ["Rates Selection"]
@@ -5,6 +5,7 @@ Description = "This example aims to demonstrate some of Owl's capabilities. Jack
5
5
  Status = "married"
6
6
  Names = [ "Jack", "Jill",]
7
7
  "Birth year" = [ 1962, 1965,]
8
+ "Birth month" = [ 1, 1,]
8
9
  "Life expectancy" = [ 89, 92,]
9
10
  "Start date" = "01-01"
10
11
 
@@ -22,7 +23,7 @@ Names = [ "Jack", "Jill",]
22
23
  "Pension amounts" = [ 0.0, 10.5,]
23
24
  "Pension ages" = [ 65, 65,]
24
25
  "Pension indexed" = [ false, false,]
25
- "Social security amounts" = [ 28.4, 19.7,]
26
+ "Social security PIA amounts" = [ 2360, 1642,]
26
27
  "Social security ages" = [ 70, 62,]
27
28
 
28
29
  ["Rates Selection"]
@@ -5,6 +5,7 @@ Description = "This is an example of a case involving a single individual. Joe i
5
5
  Status = "single"
6
6
  Names = [ "Joe",]
7
7
  "Birth year" = [ 1966,]
8
+ "Birth month" = [ 1,]
8
9
  "Life expectancy" = [ 89,]
9
10
  "Start date" = "01-01"
10
11
 
@@ -20,7 +21,7 @@ Names = [ "Joe",]
20
21
  "Pension amounts" = [ 18.0,]
21
22
  "Pension ages" = [ 65,]
22
23
  "Pension indexed" = [ true,]
23
- "Social security amounts" = [ 28.4,]
24
+ "Social security PIA amounts" = [ 2360,]
24
25
  "Social security ages" = [ 67,]
25
26
 
26
27
  ["Rates Selection"]
@@ -5,6 +5,7 @@ Description = "This example reproduces the case of John and Sally, discussed by
5
5
  Status = "married"
6
6
  Names = [ "John", "Sally",]
7
7
  "Birth year" = [ 1962, 1962,]
8
+ "Birth month" = [ 1, 1,]
8
9
  "Life expectancy" = [ 92, 92,]
9
10
  "Start date" = "01-01"
10
11
 
@@ -22,7 +23,7 @@ Names = [ "John", "Sally",]
22
23
  "Pension amounts" = [ 0.0, 0.0,]
23
24
  "Pension ages" = [ 65, 65,]
24
25
  "Pension indexed" = [ false, false,]
25
- "Social security amounts" = [ 36.0, 21.6,]
26
+ "Social security PIA amounts" = [ 3000, 1800,]
26
27
  "Social security ages" = [ 67, 67,]
27
28
 
28
29
  ["Rates Selection"]
@@ -5,6 +5,7 @@ Description = "This case reproduces a similar case discussed a while back on i-o
5
5
  Status = "married"
6
6
  Names = [ "Jon", "Jane",]
7
7
  "Birth year" = [ 1965, 1968,]
8
+ "Birth month" = [ 1, 1,]
8
9
  "Life expectancy" = [ 92, 92,]
9
10
  "Start date" = "01-01"
10
11
 
@@ -22,7 +23,7 @@ Names = [ "Jon", "Jane",]
22
23
  "Pension amounts" = [ 0.0, 0.0,]
23
24
  "Pension ages" = [ 65, 65,]
24
25
  "Pension indexed" = [ false, false,]
25
- "Social security amounts" = [ 21.0, 21.0,]
26
+ "Social security PIA amounts" = [ 1750, 1750,]
26
27
  "Social security ages" = [ 65, 65,]
27
28
 
28
29
  ["Rates Selection"]
@@ -5,6 +5,7 @@ Description = "This is the case of Kim and Sam used as an example case for optim
5
5
  Status = "married"
6
6
  Names = [ "Kim", "Sam",]
7
7
  "Birth year" = [ 1966, 1967,]
8
+ "Birth month" = [ 1, 1,]
8
9
  "Life expectancy" = [ 86, 89,]
9
10
  "Start date" = "01-01"
10
11
 
@@ -22,7 +23,7 @@ Names = [ "Kim", "Sam",]
22
23
  "Pension amounts" = [ 0.0, 0.0,]
23
24
  "Pension ages" = [ 65, 65,]
24
25
  "Pension indexed" = [ false, false,]
25
- "Social security amounts" = [ 45.0, 25.0,]
26
+ "Social security PIA amounts" = [ 3750, 2083,]
26
27
  "Social security ages" = [ 70, 68,]
27
28
 
28
29
  ["Rates Selection"]
@@ -5,6 +5,7 @@ Description = "This is the case of Kim and Sam used as an example case for optim
5
5
  Status = "married"
6
6
  Names = [ "Kim", "Sam",]
7
7
  "Birth year" = [ 1966, 1967,]
8
+ "Birth month" = [ 1, 1,]
8
9
  "Life expectancy" = [ 86, 89,]
9
10
  "Start date" = "01-01"
10
11
 
@@ -22,7 +23,7 @@ Names = [ "Kim", "Sam",]
22
23
  "Pension amounts" = [ 0.0, 0.0,]
23
24
  "Pension ages" = [ 65, 65,]
24
25
  "Pension indexed" = [ false, false,]
25
- "Social security amounts" = [ 45.0, 25.0,]
26
+ "Social security PIA amounts" = [ 3750, 2083,]
26
27
  "Social security ages" = [ 70, 68,]
27
28
 
28
29
  ["Rates Selection"]
@@ -41,14 +41,14 @@
41
41
  "%%time\n",
42
42
  "import owlplanner as owl\n",
43
43
  "\n",
44
- "plan = owl.Plan(['John', 'Sally'], [1962, 1962], [92, 92], 'john+sally')\n",
44
+ "plan = owl.Plan(['John', 'Sally'], [1962, 1962], [1, 1], [92, 92], 'john+sally')\n",
45
45
  "# plan.setPlotBackend(\"plotly\")\n",
46
46
  "plan.setAccountBalances(taxable=[200, 200], taxDeferred=[750, 750], taxFree=[50, 50])\n",
47
47
  "# Unrealistic empty contributions and wages\n",
48
48
  "plan.readContributions('../examples/john+sally.xlsx')\n",
49
49
  "# plan.setInterpolationMethod('s-curve')\n",
50
50
  "plan.setAllocationRatios('individual', generic=[[[60, 40, 0, 0], [60, 40, 0, 0]], [[60, 40, 0, 0], [60, 40, 0, 0]]])\n",
51
- "plan.setSocialSecurity([36, 21.6], [67, 67])\n",
51
+ "plan.setSocialSecurity([3000, 1800], [67, 67])\n",
52
52
  "#plan.setSpendingProfile('smile')\n",
53
53
  "plan.setSpendingProfile('flat')\n",
54
54
  "plan.setRates('historical average', 1990, 2023)\n",
@@ -27,7 +27,7 @@
27
27
  "outputs": [],
28
28
  "source": [
29
29
  "import owlplanner as owl\n",
30
- "p = owl.Plan(['Kim', 'Sam'], [1966, 1967], [86, 89], 'kim+sam-spending', verbose=True)\n",
30
+ "p = owl.Plan(['Kim', 'Sam'], [1966, 1967], [1, 1], [86, 89], 'kim+sam-spending', verbose=True)\n",
31
31
  "# p.setPlotBackend(\"plotly\")"
32
32
  ]
33
33
  },
@@ -39,7 +39,7 @@
39
39
  "outputs": [],
40
40
  "source": [
41
41
  "# p.setPension([0, 0], [65, 65])\n",
42
- "p.setSocialSecurity([45, 25], [70, 68])"
42
+ "p.setSocialSecurity([3750, 2083], [70, 68])"
43
43
  ]
44
44
  },
45
45
  {
@@ -135,7 +135,7 @@
135
135
  "metadata": {},
136
136
  "outputs": [],
137
137
  "source": [
138
- "plan = owl.Plan(['Kim', 'Sam'], [YYYY, YYYY], [AA, AA], 'Kim+Sam-spending', verbose=True)"
138
+ "plan = owl.Plan(['Kim', 'Sam'], [YYYY, YYYY], [MM, MM], [AA, AA], 'Kim+Sam-spending', verbose=True)"
139
139
  ]
140
140
  },
141
141
  {
@@ -340,13 +340,13 @@
340
340
  "metadata": {},
341
341
  "source": [
342
342
  "## What about anticipated fixed income?\n",
343
- "Pension and social security are fixed income. Model here assumes that pension income is not inflation adjusted while social security benefits are (but Owl can easily be modified to account for inflation-adjusted pensions). Numbers to be provided are the predicted annual amount for each spouse and the age of the commencement of benefits.\n",
343
+ "Pension and social security are fixed income. Pension income can be adjusted for inflation as social security benefits are. Numbers to be provided are the predicted annual amount for each spouse and the age of the commencement of benefits.\n",
344
344
  "\n",
345
345
  "By default, no pension benefits are assumed. This can also be specified explicitly by entering zeros (0) as entries, as in\n",
346
346
  "\n",
347
- " plan.setPension([0, 0], [65, 65])\n",
347
+ " plan.setPension([0, 0], [65, 65], [False, False])\n",
348
348
  " \n",
349
- "For social security, one must provide the predicted annual amount(s) and the starting age(s) at which benefits are anticipated to be received. There are plenty of social security benefit estimators on the web, including the info you can get directly from your own account at the Social Security Administration (ssa.gov). Another interesting calculator can be found at www.opensocialsecurity.com. This calculator allows you to compare different scenarios regarding your commencement age through a sensitivity plot.\n"
349
+ "For social security, one must provide the predicted monthly Primary Insurance Amount(s) and the starting age(s) at which benefits are anticipated to be received. There are plenty of social security benefit estimators on the web, including the info you can get directly from your own account at the Social Security Administration (ssa.gov). Another interesting calculator can be found at www.opensocialsecurity.com. This calculator allows you to compare different scenarios regarding your commencement age through a sensitivity plot.\n"
350
350
  ]
351
351
  },
352
352
  {
@@ -133,7 +133,7 @@
133
133
  "metadata": {},
134
134
  "outputs": [],
135
135
  "source": [
136
- "plan = owl.Plan(['Jack', 'Jill'], [1962, 1965], [89, 92], 'jack+jill-spending-69')"
136
+ "plan = owl.Plan(['Jack', 'Jill'], [1962, 1965], [1, 1], [89, 92], 'jack+jill-spending-69')"
137
137
  ]
138
138
  },
139
139
  {
@@ -332,13 +332,13 @@
332
332
  "metadata": {},
333
333
  "source": [
334
334
  "## What about anticipated fixed income?\n",
335
- "Pension and social security are fixed income. Model here assumes that pension income is not inflation adjusted while social security benefits are (but Owl can easily be modified to account for inflation-adjusted pensions). Numbers to be provided are the predicted annual amount for each spouse and the age of the commencement of benefits. Values are expressed in today's dollars (as do statements from the Social Security Administration).\n",
335
+ "Pension and social security are fixed income. Pension income can be inflation adjusted as social security benefits are. Numbers to be provided are the predicted annual amount for each spouse and the age of the commencement of benefits. Values are expressed in today's dollars (as do statements from the Social Security Administration).\n",
336
336
  "\n",
337
337
  "By default, no pension benefits are assumed. This can also be specified explicitly by entering zeros (0) as entries, as in\n",
338
338
  "\n",
339
339
  " plan.setPension([0, 0], [65, 65])\n",
340
340
  " \n",
341
- "For social security, one must provide the predicted annual amount(s) and the starting age(s) at which benefits are anticipated to be received. There are plenty of social security benefit estimators on the web, including the info you can get directly from your own account at the Social Security Administration (ssa.gov). Another interesting calculator can be found at www.opensocialsecurity.com. This calculator allows you to compare different scenarios regarding your commencement age through a sensitivity plot.\n"
341
+ "For social security, one must provide the predicted monthly Primary Insurance Amount(s) and the starting age(s) at which benefits are anticipated to be received. There are plenty of social security benefit estimators on the web, including the info you can get directly from your own account at the Social Security Administration (ssa.gov). Another interesting calculator can be found at www.opensocialsecurity.com. This calculator allows you to compare different scenarios regarding your commencement age through a sensitivity plot.\n"
342
342
  ]
343
343
  },
344
344
  {
@@ -346,7 +346,7 @@
346
346
  "id": "43bd9a2a-e2bf-434f-88ac-b0a70dcdd1fd",
347
347
  "metadata": {},
348
348
  "source": [
349
- "Here, Jill has an unindexed pension of \\\\$10 k per year. Both Jack and Jill believe they have good genes and decided to take their social security benefits at age 70. The amounts provided (28k\\\\$ and 25k\\\\$) are estimation of the amounts they would receive at age 70."
349
+ "Here, Jill has an unindexed pension of \\\\$10 k per year. Both Jack and Jill believe they have good genes and decided to take their social security benefits at age 70. The amounts provided (28k\\\\$ and 25k\\\\$) are estimation of the amounts they would receive at Full Retirement Age (FRA)."
350
350
  ]
351
351
  },
352
352
  {
@@ -357,7 +357,7 @@
357
357
  "outputs": [],
358
358
  "source": [
359
359
  "plan.setPension([0, 10], [65, 65])\n",
360
- "plan.setSocialSecurity([28, 25], [70, 70])"
360
+ "plan.setSocialSecurity([2333, 2083], [70, 70])"
361
361
  ]
362
362
  },
363
363
  {
@@ -95,7 +95,7 @@
95
95
  "metadata": {},
96
96
  "outputs": [],
97
97
  "source": [
98
- "plan = owl.Plan(['Jack', 'Jill'], [1962, 1965], [89, 92], 'jack+jill-spending-MC', verbose=True)"
98
+ "plan = owl.Plan(['Jack', 'Jill'], [1962, 1965], [1, 1], [89, 92], 'jack+jill-spending-MC', verbose=True)"
99
99
  ]
100
100
  },
101
101
  {
@@ -145,7 +145,7 @@
145
145
  "outputs": [],
146
146
  "source": [
147
147
  "plan.setPension([0, 10], [65, 65])\n",
148
- "plan.setSocialSecurity([28, 25], [70, 70])"
148
+ "plan.setSocialSecurity([2333, 2083], [70, 70])"
149
149
  ]
150
150
  },
151
151
  {
@@ -95,7 +95,7 @@
95
95
  "metadata": {},
96
96
  "outputs": [],
97
97
  "source": [
98
- "plan = owl.Plan(['Jack', 'Jill'], [1962, 1965], [89, 92], 'jack+jill-tutorial3', verbose=True)"
98
+ "plan = owl.Plan(['Jack', 'Jill'], [1962, 1965], [1, 1], [89, 92], 'jack+jill-tutorial3', verbose=True)"
99
99
  ]
100
100
  },
101
101
  {
@@ -145,7 +145,7 @@
145
145
  "outputs": [],
146
146
  "source": [
147
147
  "plan.setPension([0, 10], [65, 65])\n",
148
- "plan.setSocialSecurity([28, 25], [70, 70])"
148
+ "plan.setSocialSecurity([2333, 2083], [70, 70])"
149
149
  ]
150
150
  },
151
151
  {
@@ -2,20 +2,23 @@
2
2
  requires = ["hatchling"]
3
3
  build-backend = "hatchling.build"
4
4
 
5
+ [tool.hatch.version]
6
+ path = "src/owlplanner/version.py"
7
+
5
8
  [project]
6
9
  name = "owlplanner"
7
- version = "2025.11.05"
10
+ dynamic = ["version"]
8
11
  authors = [
9
12
  { name="Martin-D. Lacasse", email="martin.d.lacasse@gmail.com" },
10
13
  ]
11
14
  maintainers = [
12
15
  { name="Martin-D. Lacasse", email="martin.d.lacasse@gmail.com" },
13
16
  ]
14
- description = "Owl: Retirement planner with great wisdom"
17
+ description = "Owl - Optimal Wealth Lab: Retirement planner with great wisdom"
15
18
  readme = "README.md"
16
- requires-python = ">=3.8"
19
+ requires-python = ">=3.10"
17
20
  classifiers = [
18
- "Development Status :: 4 - Beta",
21
+ "Development Status :: 5 - Production/Stable",
19
22
  "Programming Language :: Python :: 3",
20
23
  "Intended Audience :: End Users/Desktop",
21
24
  "Topic :: Office/Business :: Financial :: Investment",
@@ -24,12 +27,13 @@ classifiers = [
24
27
  "Programming Language :: Python :: 3",
25
28
  ]
26
29
  dependencies = [
30
+ "highspy",
27
31
  "matplotlib",
28
32
  "numpy",
29
33
  "odfpy",
30
34
  "openpyxl",
31
35
  "pandas",
32
- "plotly",
36
+ "plotly>=6.3",
33
37
  "pulp",
34
38
  "seaborn",
35
39
  "scipy",
@@ -37,6 +37,7 @@ def saveConfig(myplan, file, mylog):
37
37
  "Status": ["unknown", "single", "married"][myplan.N_i],
38
38
  "Names": myplan.inames,
39
39
  "Birth year": myplan.yobs.tolist(),
40
+ "Birth month": myplan.mobs.tolist(),
40
41
  "Life expectancy": myplan.expectancy.tolist(),
41
42
  "Start date": myplan.startDate,
42
43
  }
@@ -55,10 +56,10 @@ def saveConfig(myplan, file, mylog):
55
56
 
56
57
  # Fixed Income.
57
58
  diconf["Fixed Income"] = {
58
- "Pension amounts": (myplan.pensionAmounts / 1000).tolist(),
59
+ "Pension monthly amounts": (myplan.pensionAmounts).tolist(),
59
60
  "Pension ages": myplan.pensionAges.tolist(),
60
61
  "Pension indexed": myplan.pensionIsIndexed,
61
- "Social security amounts": (myplan.ssecAmounts / 1000).tolist(),
62
+ "Social security PIA amounts": (myplan.ssecAmounts).tolist(),
62
63
  "Social security ages": myplan.ssecAges.tolist(),
63
64
  }
64
65
 
@@ -181,11 +182,13 @@ def readConfig(file, *, verbose=True, logstreams=None, readContributions=True):
181
182
  inames = diconf["Basic Info"]["Names"]
182
183
  # status = diconf['Basic Info']['Status']
183
184
  yobs = diconf["Basic Info"]["Birth year"]
184
- expectancy = diconf["Basic Info"]["Life expectancy"]
185
185
  icount = len(yobs)
186
+ # Default to January if no month entry found.
187
+ mobs = diconf["Basic Info"].get("Birth month", [1]*icount)
188
+ expectancy = diconf["Basic Info"]["Life expectancy"]
186
189
  s = ["", "s"][icount - 1]
187
190
  mylog.vprint(f"Plan for {icount} individual{s}: {inames}.")
188
- p = plan.Plan(inames, yobs, expectancy, name, verbose=True, logstreams=logstreams)
191
+ p = plan.Plan(inames, yobs, mobs, expectancy, name, verbose=True, logstreams=logstreams)
189
192
  p._description = diconf.get("Description", "")
190
193
 
191
194
  # Assets.
@@ -217,11 +220,11 @@ def readConfig(file, *, verbose=True, logstreams=None, readContributions=True):
217
220
  mylog.vprint(f"Ignoring to read contributions file {timeListsFileName}.")
218
221
 
219
222
  # Fixed Income.
220
- ssecAmounts = np.array(diconf["Fixed Income"]["Social security amounts"], dtype=np.float32)
221
- ssecAges = np.array(diconf["Fixed Income"]["Social security ages"], dtype=np.int32)
223
+ ssecAmounts = np.array(diconf["Fixed Income"].get("Social security PIA amounts", [0]*icount), dtype=np.int32)
224
+ ssecAges = np.array(diconf["Fixed Income"]["Social security ages"])
222
225
  p.setSocialSecurity(ssecAmounts, ssecAges)
223
- pensionAmounts = np.array(diconf["Fixed Income"]["Pension amounts"], dtype=np.float32)
224
- pensionAges = np.array(diconf["Fixed Income"]["Pension ages"], dtype=np.int32)
226
+ pensionAmounts = np.array(diconf["Fixed Income"].get("Pension monthly amounts", [0]*icount), dtype=np.float32)
227
+ pensionAges = np.array(diconf["Fixed Income"]["Pension ages"])
225
228
  pensionIsIndexed = diconf["Fixed Income"]["Pension indexed"]
226
229
  p.setPension(pensionAmounts, pensionAges, pensionIsIndexed)
227
230