owlplanner 2025.3.13__tar.gz → 2025.3.15__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/PKG-INFO +1 -1
  2. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/docs/owl.pdf +0 -0
  3. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/docs/owl.tex +25 -6
  4. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/pyproject.toml +1 -1
  5. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/src/owlplanner/plan.py +19 -3
  6. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/src/owlplanner/utils.py +9 -2
  7. owlplanner-2025.3.15/src/owlplanner/version.py +1 -0
  8. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/tests/test_regressions.py +27 -27
  9. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ui/Rates_Selection.py +2 -1
  10. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ui/requirements.txt +1 -1
  11. owlplanner-2025.3.13/src/owlplanner/version.py +0 -1
  12. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/.devcontainer/devcontainer.json +0 -0
  13. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/.flake8 +0 -0
  14. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/.gitattributes +0 -0
  15. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/.github/workflows/github-actions-runtests.yml +0 -0
  16. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/.gitignore +0 -0
  17. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/INSTALL.md +0 -0
  18. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/LICENSE +0 -0
  19. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/Papers/FE00006821-Class-VI-Injection-Permit--Salient-Features-and-Regulatory-Challenges_Final.pdf +0 -0
  20. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/Papers/Kou-OptionPricingDouble-2004.pdf +0 -0
  21. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/Papers/Multi-Period Mean Expected-Shortfall Strategies Cut Your Losses and Ride Your Gains .pdf +0 -0
  22. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/Papers/Optimal Asset Allocation for Retirement Saving Deterministic Vs. Time Consistent Adaptive Strategies.pdf +0 -0
  23. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/Papers/Rule-based_strategies_for_dynamic_life_cycle_inves.pdf +0 -0
  24. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/Papers/s10436-006-0062-y.pdf +0 -0
  25. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/README.md +0 -0
  26. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/USER_GUIDE.md +0 -0
  27. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/docker/Dockerfile +0 -0
  28. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/docker/README.md +0 -0
  29. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/docker/docker-compose.yml +0 -0
  30. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/docker/fastentrypoint.sh +0 -0
  31. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/docs/images/AD-taxDef.png +0 -0
  32. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/docs/images/AD-taxFree.png +0 -0
  33. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/docs/images/AD-taxable.png +0 -0
  34. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/docs/images/Hist_Bequest.png +0 -0
  35. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/docs/images/Hist_Spending.png +0 -0
  36. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/docs/images/MC-tutorial2a.png +0 -0
  37. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/docs/images/MC-tutorial2b.png +0 -0
  38. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/docs/images/OwlUI.png +0 -0
  39. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/docs/images/allocations.png +0 -0
  40. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/docs/images/owl.png +0 -0
  41. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/docs/images/profile.png +0 -0
  42. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/docs/images/ratesCorrelations.png +0 -0
  43. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/docs/images/ratesPlot.png +0 -0
  44. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/docs/images/savingsPlot.png +0 -0
  45. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/docs/images/sourcesPlot.png +0 -0
  46. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/docs/images/spendingPlot.png +0 -0
  47. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/docs/images/taxIncomePlot.png +0 -0
  48. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/docs/images/taxesPlot.png +0 -0
  49. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/examples/case_jack+jill.toml +0 -0
  50. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/examples/case_joe.toml +0 -0
  51. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/examples/case_john+sally.toml +0 -0
  52. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/examples/case_jon+jane.toml +0 -0
  53. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/examples/case_kim+sam-bequest.toml +0 -0
  54. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/examples/case_kim+sam-spending.toml +0 -0
  55. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/examples/jack+jill.xlsx +0 -0
  56. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/examples/joe.xlsx +0 -0
  57. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/examples/john+sally.xlsx +0 -0
  58. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/examples/jon+jane.xlsx +0 -0
  59. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/examples/template.xlsx +0 -0
  60. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/notebooks/john+sally.ipynb +0 -0
  61. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/notebooks/kim+sam.ipynb +0 -0
  62. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/notebooks/template.ipynb +0 -0
  63. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/notebooks/tutorial_1.ipynb +0 -0
  64. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/notebooks/tutorial_2.ipynb +0 -0
  65. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/notebooks/tutorial_3.ipynb +0 -0
  66. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/owlplanner.cmd +0 -0
  67. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/owlplanner.sh +0 -0
  68. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/requirements.txt +0 -0
  69. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/src/owlplanner/__init__.py +0 -0
  70. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/src/owlplanner/abcapi.py +0 -0
  71. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/src/owlplanner/config.py +0 -0
  72. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/src/owlplanner/data/__init__.py +0 -0
  73. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/src/owlplanner/data/rates.csv +0 -0
  74. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/src/owlplanner/logging.py +0 -0
  75. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/src/owlplanner/progress.py +0 -0
  76. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/src/owlplanner/rates.py +0 -0
  77. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/src/owlplanner/tax2025.py +0 -0
  78. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/src/owlplanner/timelists.py +0 -0
  79. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/tests/test_logger.py +0 -0
  80. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/tests/test_repro.py +0 -0
  81. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/tests/test_toml_cases.py +0 -0
  82. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/tests/test_units.py +0 -0
  83. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ttt.py +0 -0
  84. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ui/About_Owl.py +0 -0
  85. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ui/Asset_Allocation.py +0 -0
  86. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ui/Create_Case.py +0 -0
  87. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ui/Current_Assets.py +0 -0
  88. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ui/Documentation.py +0 -0
  89. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ui/Fixed_Income.py +0 -0
  90. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ui/Graphs.py +0 -0
  91. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ui/Historical_Range.py +0 -0
  92. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ui/Logs.py +0 -0
  93. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ui/Monte_Carlo.py +0 -0
  94. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ui/Optimization_Parameters.py +0 -0
  95. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ui/Output_Files.py +0 -0
  96. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ui/Quick_Start.py +0 -0
  97. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ui/README.md +0 -0
  98. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ui/Settings.py +0 -0
  99. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ui/Wages_And_Contributions.py +0 -0
  100. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ui/Worksheets.py +0 -0
  101. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ui/main+fonts.py +0 -0
  102. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ui/main.py +0 -0
  103. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ui/owlbridge.py +0 -0
  104. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ui/plots.py +0 -0
  105. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ui/progress.py +0 -0
  106. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ui/sskeys.py +0 -0
  107. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ui/style.css +0 -0
  108. {owlplanner-2025.3.13 → owlplanner-2025.3.15}/ui/tomlexamples.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: owlplanner
3
- Version: 2025.3.13
3
+ Version: 2025.3.15
4
4
  Summary: Owl: Retirement planner with great wisdom
5
5
  Project-URL: HomePage, https://github.com/mdlacasse/owl
6
6
  Project-URL: Repository, https://github.com/mdlacasse/owl
@@ -87,11 +87,11 @@ index name as a subscript, e.g., $N_i$ for index $i$.
87
87
  More asset classes could be considered at the cost of increasing
88
88
  the complexity of the problem while not generating much more insights.
89
89
  \item [$n$]
90
- Year being modeled. Period being modeled runs from the beginning of year 0 to
90
+ Index of year being modeled. Period being modeled runs from the beginning of year 0 to
91
91
  the end of year $N_n-1$, and therefore $N_n + 1$ years are considered.
92
92
  Year $N_n$ is the first year following the passing of all
93
93
  individuals in the plan. The time period for all decision variables is annual.
94
- For spouses, the end of year $n_d-1$ is the year in which the first individual passes while
94
+ For spouses, the end of year $n_d-1$ is when the first individual is assumed to pass while
95
95
  the survivor will decease at the end of year $N_n-1$ of the plan.
96
96
  \item [$t$]
97
97
  Federal income tax bracket. $t$ goes from 0 to $N_t - 1$, from low to high.
@@ -435,6 +435,24 @@ All intermediate variables are in uppercase letters.
435
435
  the taxable savings account is being depleted slowly. An implementation keeping track
436
436
  of stock purchases and sales is beyond the goal of providing a guide for retirement decisions.
437
437
 
438
+ \item [$P_n$]
439
+ Amount of 10\% early withdrawal penalty in year $n$,
440
+ \begin{equation}
441
+ \label{Eq:PenTax0}
442
+ P_n = 0.10 \sum_{i, j\neq0} (1 - \mathcal{H}(n - n_{i,59})) w_{ijn}.
443
+ \end{equation}
444
+ Here, $H(n - n_{i, 59})$ is a Heavyside step function which is 0 or 1, depending on the sign of
445
+ its argument:
446
+ \begin{equation}
447
+ \mathcal{H}(x) :=
448
+ \begin{cases}
449
+ 0 & x < 0 \\
450
+ 1 & x \geq 0.
451
+ \end{cases}
452
+ \end{equation}
453
+ The variable $n_{i, 59}$ is the year index when individual $i$ turns 59,
454
+ or 0 if already 59 years old or older.
455
+
438
456
  \item [$T_n$]
439
457
  Amount of income tax paid on taxable ordinary income $G_n$ in year $n$.
440
458
  This is the taxes paid on ordinary income expressed as the sum of the amounts
@@ -453,8 +471,9 @@ All intermediate variables are in uppercase letters.
453
471
  filling in the lower brackets first when optimizing. In that case
454
472
  \begin{equation}
455
473
  \label{Eq:IncTax1}
456
- T_n = \sum_t \bar{F}_{tn} \theta_{tn}.
474
+ T_n = \sum_t \bar{F}_{tn} \theta_{tn}
457
475
  \end{equation}
476
+
458
477
  \item [$U_n$]
459
478
  Amount of income tax paid on long-term capital gains and qualified dividends in year $n$,
460
479
  \begin{equation}
@@ -688,7 +707,7 @@ add the market returns to the savings balances.
688
707
  \begin{eqnarray}
689
708
  g_n = \sum_i [\omega_{in} + \bar{\zeta}_{in} + \pi_{in} ]
690
709
  + \sum_{ij} w_{ijn} + \sum_i \Lambda^\pm_{in} - s_{n}
691
- - T_n - U_n - \mathcal{M}^\ell_n.
710
+ - P_n - T_n - U_n - \mathcal{M}^\ell_n.
692
711
  \end{eqnarray}
693
712
  When both spouses are alive, surplus $s_n$ gets deposited in the taxable accounts
694
713
  according to variable $\eta$ as described in Eq.~(\ref{Eq:eta}),
@@ -702,7 +721,7 @@ add the market returns to the savings balances.
702
721
  Replacing intermediate variables and bringing all variables to the left-hand side, we get
703
722
  \begin{eqnarray}
704
723
  \label{Eq:C4}
705
- g_n - \sum_{ij} w_{ijn} + s_{n}
724
+ g_n - \sum_{ij} w_{ijn} + 0.1 \sum_{i,j\neq0} (1-\mathcal{H}(n - n_{i, 59})) w_{ijn} + s_n
706
725
  + \sum_t \bar{F}_{tn} \theta_{t n} &&\nonumber \\
707
726
  + \psi\alpha_{i00n} \sum_{i} \left[\mu(b_{i0n} - w_{i0n} + d_{in})
708
727
  + w_{i0n}\max(0, \tau_{0n-1})\right]
@@ -1010,7 +1029,7 @@ For the equality constraint on net spending expressed in Eq.~(\ref{Eq:C4}),
1010
1029
  we add $N_n$ more rows to $A_ey = v$ as
1011
1030
  \begin{eqnarray}
1012
1031
  A_e[J_2(n), q_g(n)] &=& 1, \nonumber \\
1013
- A_e[J_2(n), q_w(i, j ,n)] &=& -1 + \delta(j, 0)\psi\alpha_{i00n}(\max(0, \tau_{0n-1}) - \mu), \nonumber \\
1032
+ A_e[J_2(n), q_w(i, j ,n)] &=& -1 + .1(1-\delta(j, 0))(1-\mathcal{H}(n-n_{i, 59})) + \delta(j, 0)\psi\alpha_{i00n}(\max(0, \tau_{0n-1}) - \mu), \nonumber \\
1014
1033
  A_e[J_2(n), q_d(i, n)] &=& 1 + \psi\mu\alpha_{i00n}, \nonumber \\
1015
1034
  A_e[J_2(n), q_F(t, n)] &=& \theta_{t n}, \nonumber \\
1016
1035
  A_e[J_2(n), q_b(i, 0, n)] &=& \psi\mu\alpha_{i00n}, \nonumber \\
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "owlplanner"
7
- version = "2025.03.13"
7
+ version = "2025.03.15"
8
8
  authors = [
9
9
  { name="Martin-D. Lacasse", email="martin.d.lacasse@gmail.com" },
10
10
  ]
@@ -266,6 +266,9 @@ class Plan(object):
266
266
  # self.horizons = [yobs[i] + expectancy[i] - thisyear + 1 for i in range(self.N_i)]
267
267
  self.N_n = np.max(self.horizons)
268
268
  self.year_n = np.linspace(thisyear, thisyear + self.N_n - 1, self.N_n, dtype=np.int32)
269
+ # Year in the plan (if any) where individuals turn 59. For 10% withdrawal penalty.
270
+ self.n59 = 59 - thisyear + self.yobs
271
+ self.n59[self.n59 < 0] = 0
269
272
  # Handle passing of one spouse before the other.
270
273
  if self.N_i == 2 and np.min(self.horizons) != np.max(self.horizons):
271
274
  self.n_d = np.min(self.horizons)
@@ -1281,8 +1284,9 @@ class Plan(object):
1281
1284
  # Minus capital gains on taxable withdrawals using last year's rate if >=0.
1282
1285
  # Plus taxable account withdrawals, and all other withdrawals.
1283
1286
  row.addElem(_q3(Cw, i, 0, n, Ni, Nj, Nn), fac * (tau_0prev[n] - self.mu) - 1)
1284
- row.addElem(_q3(Cw, i, 1, n, Ni, Nj, Nn), -1)
1285
- row.addElem(_q3(Cw, i, 2, n, Ni, Nj, Nn), -1)
1287
+ penalty = 0.1 if n < self.n59[i] else 0
1288
+ row.addElem(_q3(Cw, i, 1, n, Ni, Nj, Nn), -1 + penalty)
1289
+ row.addElem(_q3(Cw, i, 2, n, Ni, Nj, Nn), -1 + penalty)
1286
1290
  row.addElem(_q2(Cd, i, n, Ni, Nn), fac * self.mu)
1287
1291
 
1288
1292
  # Minus tax on ordinary income, T_n.
@@ -1970,6 +1974,12 @@ class Plan(object):
1970
1974
  self.G_n = np.sum(self.F_tn, axis=0)
1971
1975
  self.T_tn = self.F_tn * self.theta_tn
1972
1976
  self.T_n = np.sum(self.T_tn, axis=0)
1977
+ self.P_n = np.zeros(Nn)
1978
+ # Add early withdrawal penalty if any.
1979
+ for i in range(Ni):
1980
+ self.P_n[0:self.n59[i]] += 0.1*(self.w_ijn[i, 1, 0:self.n59[i]] + self.w_ijn[i, 2, 0:self.n59[i]])
1981
+
1982
+ self.T_n += self.P_n
1973
1983
 
1974
1984
  tau_0 = np.array(self.tau_kn[0, :])
1975
1985
  tau_0[tau_0 < 0] = 0
@@ -2112,6 +2122,11 @@ class Plan(object):
2112
2122
  dic[f"-- Subtotal in tax bracket {tname}"] = f"{u.d(taxPaidNow)}"
2113
2123
  dic[f"-- [Subtotal in tax bracket {tname}]"] = f"{u.d(taxPaid)}"
2114
2124
 
2125
+ penaltyPaid = np.sum(self.P_n, axis=0)
2126
+ penaltyPaidNow = np.sum(self.P_n / self.gamma_n[:-1], axis=0)
2127
+ dic["-- Subtotal in early withdrawal penalty"] = f"{u.d(penaltyPaidNow)}"
2128
+ dic["-- [Subtotal in early withdrawal penalty]"] = f"{u.d(penaltyPaid)}"
2129
+
2115
2130
  taxPaid = np.sum(self.U_n, axis=0)
2116
2131
  taxPaidNow = np.sum(self.U_n / self.gamma_n[:-1], axis=0)
2117
2132
  dic["Total tax paid on gains and dividends"] = f"{u.d(taxPaidNow)}"
@@ -2507,6 +2522,7 @@ class Plan(object):
2507
2522
 
2508
2523
  title = self._name + "\nRaw Income Sources"
2509
2524
  stypes = self.sources_in.keys()
2525
+ # stypes = [item for item in stypes if "RothX" not in item]
2510
2526
 
2511
2527
  if tag != "":
2512
2528
  title += " - " + tag
@@ -2517,7 +2533,7 @@ class Plan(object):
2517
2533
  else:
2518
2534
  yformat = "\\$k (" + str(self.year_n[0]) + "\\$)"
2519
2535
  sources_in = {}
2520
- for key in self.sources_in:
2536
+ for key in stypes:
2521
2537
  sources_in[key] = self.sources_in[key] / self.gamma_n[:-1]
2522
2538
 
2523
2539
  fig, ax = _stackPlot(
@@ -70,8 +70,8 @@ def getUnits(units) -> int:
70
70
  return fac
71
71
 
72
72
 
73
- # Could be a one-line lambda function:
74
- # krond = lambda a, b: 1 if a == b else 0
73
+ # Next two functins could be a one-line lambda functions.
74
+ # e.g., krond = lambda a, b: 1 if a == b else 0
75
75
  def krond(a, b) -> int:
76
76
  """
77
77
  Kronecker integer delta function.
@@ -79,6 +79,13 @@ def krond(a, b) -> int:
79
79
  return 1 if a == b else 0
80
80
 
81
81
 
82
+ def heavyside(x) -> int:
83
+ """
84
+ Heavyside step function.
85
+ """
86
+ return 1 if x >= 0 else 0
87
+
88
+
82
89
  def roundCents(values, decimals=2):
83
90
  """
84
91
  Round values in NumPy array down to second decimal.
@@ -0,0 +1 @@
1
+ __version__ = "2025.03.15"
@@ -89,7 +89,7 @@ def createPlan(ni, name, ny, topAge):
89
89
 
90
90
  def test_withdrawal1():
91
91
  n = 10
92
- p = createPlan(1, 'withdrawal1', n, 64)
92
+ p = createPlan(1, 'withdrawal1', n, 70)
93
93
  amount = 3.0
94
94
  p.setAccountBalances(taxable=[0], taxDeferred=[amount], taxFree=[0])
95
95
  p.setAllocationRatios('individual', generic=[[[0, 0, 0, 100], [0, 0, 0, 100]]])
@@ -102,7 +102,7 @@ def test_withdrawal1():
102
102
 
103
103
  def test_withdrawal2():
104
104
  n = 10
105
- p = createPlan(1, 'withdrawal2', n, 64)
105
+ p = createPlan(1, 'withdrawal2', n, 70)
106
106
  # Small taxable income creates an income smaller than standard deduction. Testing e_n.
107
107
  amount = 40.0
108
108
  p.setAccountBalances(taxable=[0], taxDeferred=[amount], taxFree=[0])
@@ -116,7 +116,7 @@ def test_withdrawal2():
116
116
 
117
117
  def test_withdrawal2_2():
118
118
  n = 10
119
- p = createPlan(2, 'withdrawal2_2', n, 64)
119
+ p = createPlan(2, 'withdrawal2_2', n, 70)
120
120
  # Small taxable income creates an income smaller than standard deduction. Testing e_n.
121
121
  amount = 50
122
122
  p.setAccountBalances(taxable=[0, 0], taxDeferred=[amount/2, amount/2], taxFree=[0, 0])
@@ -130,7 +130,7 @@ def test_withdrawal2_2():
130
130
 
131
131
  def test_withdrawal3():
132
132
  n = 6
133
- p = createPlan(1, 'withdrawal3', n, 64)
133
+ p = createPlan(1, 'withdrawal3', n, 70)
134
134
  amount = 60
135
135
  p.setAccountBalances(taxable=[0], taxDeferred=[0], taxFree=[amount])
136
136
  p.setAllocationRatios('individual', generic=[[[0, 0, 0, 100], [0, 0, 0, 100]]])
@@ -143,7 +143,7 @@ def test_withdrawal3():
143
143
 
144
144
  def test_withdrawal3_2():
145
145
  n = 6
146
- p = createPlan(2, 'withdrawal3', n, 64)
146
+ p = createPlan(2, 'withdrawal3', n, 70)
147
147
  amount = 60
148
148
  p.setAccountBalances(taxable=[0, 0], taxDeferred=[0, 0], taxFree=[amount/2, amount/2])
149
149
  p.setAllocationRatios('spouses', generic=[[0, 0, 0, 100], [0, 0, 0, 100]])
@@ -156,7 +156,7 @@ def test_withdrawal3_2():
156
156
 
157
157
  def test_taxfreegrowth1():
158
158
  n = 12
159
- p = createPlan(1, 'taxfreegrowth1', n, 64)
159
+ p = createPlan(1, 'taxfreegrowth1', n, 72)
160
160
  amount = 120
161
161
  p.setAccountBalances(taxable=[0], taxDeferred=[0], taxFree=[amount])
162
162
  p.setAllocationRatios('individual', generic=[[[0, 0, 100, 0], [0, 0, 100, 0]]])
@@ -170,7 +170,7 @@ def test_taxfreegrowth1():
170
170
 
171
171
  def test_taxfreegrowth1_2():
172
172
  n = 12
173
- p = createPlan(2, 'taxfreegrowth1', n, 64)
173
+ p = createPlan(2, 'taxfreegrowth1', n, 72)
174
174
  amount = 120
175
175
  p.setAccountBalances(taxable=[0, 0], taxDeferred=[0, 0], taxFree=[amount/2, amount/2])
176
176
  p.setAllocationRatios('spouses', generic=[[0, 0, 100, 0], [0, 0, 100, 0]])
@@ -184,7 +184,7 @@ def test_taxfreegrowth1_2():
184
184
 
185
185
  def test_taxfreegrowth2():
186
186
  n = 15
187
- p = createPlan(1, 'taxfreegrowth2', n, 64)
187
+ p = createPlan(1, 'taxfreegrowth2', n, 75)
188
188
  amount = 120
189
189
  p.setAccountBalances(taxable=[0], taxDeferred=[0], taxFree=[amount])
190
190
  p.setAllocationRatios('individual', generic=[[[0, 50, 50, 0], [0, 50, 50, 0]]])
@@ -198,7 +198,7 @@ def test_taxfreegrowth2():
198
198
 
199
199
  def test_taxfreegrowth2_2():
200
200
  n = 15
201
- p = createPlan(2, 'taxfreegrowth2', n, 64)
201
+ p = createPlan(2, 'taxfreegrowth2', n, 75)
202
202
  amount = 120
203
203
  p.setAccountBalances(taxable=[0, 0], taxDeferred=[0, 0], taxFree=[amount/2, amount/2])
204
204
  p.setAllocationRatios('spouses', generic=[[0, 50, 50, 0], [0, 50, 50, 0]])
@@ -212,7 +212,7 @@ def test_taxfreegrowth2_2():
212
212
 
213
213
  def test_taxfreegrowth3():
214
214
  n = 15
215
- p = createPlan(1, 'taxfreegrowth3', n, 64)
215
+ p = createPlan(1, 'taxfreegrowth3', n, 75)
216
216
  amount = 120
217
217
  p.setAccountBalances(taxable=[0], taxDeferred=[0], taxFree=[amount])
218
218
  p.setAllocationRatios('individual', generic=[[[50, 50, 0, 0], [50, 50, 0, 0]]])
@@ -226,7 +226,7 @@ def test_taxfreegrowth3():
226
226
 
227
227
  def test_taxfreegrowth3_2():
228
228
  n = 15
229
- p = createPlan(2, 'taxfreegrowth3', n, 64)
229
+ p = createPlan(2, 'taxfreegrowth3', n, 75)
230
230
  amount = 120
231
231
  p.setAccountBalances(taxable=[0, 0], taxDeferred=[0, 0], taxFree=[amount/2, amount/2])
232
232
  p.setAllocationRatios('spouses', generic=[[50, 50, 0, 0], [50, 50, 0, 0]])
@@ -240,7 +240,7 @@ def test_taxfreegrowth3_2():
240
240
 
241
241
  def test_taxfreegrowth4():
242
242
  n = 16
243
- p = createPlan(1, 'taxfreegrowth4', n, 64)
243
+ p = createPlan(1, 'taxfreegrowth4', n, 76)
244
244
  amount = 120
245
245
  p.setAccountBalances(taxable=[0], taxDeferred=[0], taxFree=[amount])
246
246
  p.setAllocationRatios('individual', generic=[[[0, 50, 50, 0], [0, 50, 50, 0]]])
@@ -255,7 +255,7 @@ def test_taxfreegrowth4():
255
255
 
256
256
  def test_taxfreegrowth4_2():
257
257
  n = 16
258
- p = createPlan(2, 'taxfreegrowth4', n, 64)
258
+ p = createPlan(2, 'taxfreegrowth4', n, 76)
259
259
  amount = 120
260
260
  p.setAccountBalances(taxable=[0, 0], taxDeferred=[0, 0], taxFree=[amount/2, amount/2])
261
261
  p.setAllocationRatios('spouses', generic=[[0, 50, 50, 0], [0, 50, 50, 0]])
@@ -270,7 +270,7 @@ def test_taxfreegrowth4_2():
270
270
 
271
271
  def test_taxfreegrowth5():
272
272
  n = 15
273
- p = createPlan(1, 'taxfreegrowth5', n, 64)
273
+ p = createPlan(1, 'taxfreegrowth5', n, 76)
274
274
  amount = 120
275
275
  p.setAccountBalances(taxable=[0], taxDeferred=[0], taxFree=[amount])
276
276
  p.setAllocationRatios('individual', generic=[[[0, 0, 100, 0], [0, 0, 100, 0]]])
@@ -284,7 +284,7 @@ def test_taxfreegrowth5():
284
284
 
285
285
  def test_taxfreegrowth5_2():
286
286
  n = 15
287
- p = createPlan(2, 'taxfreegrowth5', n, 64)
287
+ p = createPlan(2, 'taxfreegrowth5', n, 76)
288
288
  amount = 120
289
289
  p.setAccountBalances(taxable=[0, 0], taxDeferred=[0, 0], taxFree=[amount/2, amount/2])
290
290
  p.setAllocationRatios('spouses', generic=[[0, 0, 100, 0], [0, 0, 100, 0]])
@@ -298,7 +298,7 @@ def test_taxfreegrowth5_2():
298
298
 
299
299
  def test_taxfreegrowth6():
300
300
  n = 15
301
- p = createPlan(1, 'taxfreegrowth6', n, 64)
301
+ p = createPlan(1, 'taxfreegrowth6', n, 76)
302
302
  amount = 120
303
303
  p.setAccountBalances(taxable=[0], taxDeferred=[0], taxFree=[amount])
304
304
  p.setAllocationRatios('individual', generic=[[[0, 0, 0, 100], [0, 0, 0, 100]]])
@@ -312,7 +312,7 @@ def test_taxfreegrowth6():
312
312
 
313
313
  def test_taxfreegrowth6_2():
314
314
  n = 15
315
- p = createPlan(2, 'taxfreegrowth6', n, 64)
315
+ p = createPlan(2, 'taxfreegrowth6', n, 76)
316
316
  amount = 120
317
317
  p.setAccountBalances(taxable=[0, 0], taxDeferred=[0, 0], taxFree=[amount/2, amount/2])
318
318
  p.setAllocationRatios('spouses', generic=[[0, 0, 0, 100], [0, 0, 0, 100]])
@@ -326,7 +326,7 @@ def test_taxfreegrowth6_2():
326
326
 
327
327
  def test_taxfreegrowth7():
328
328
  n = 15
329
- p = createPlan(1, 'taxfreegrowth7', n, 64)
329
+ p = createPlan(1, 'taxfreegrowth7', n, 76)
330
330
  amount = 120
331
331
  p.setAccountBalances(taxable=[0], taxDeferred=[0], taxFree=[amount])
332
332
  p.setAllocationRatios('individual', generic=[[[0, 100, 0, 0], [0, 100, 0, 0]]])
@@ -340,7 +340,7 @@ def test_taxfreegrowth7():
340
340
 
341
341
  def test_taxfreegrowth7_2():
342
342
  n = 15
343
- p = createPlan(2, 'taxfreegrowth7', n, 64)
343
+ p = createPlan(2, 'taxfreegrowth7', n, 76)
344
344
  amount = 120
345
345
  p.setAccountBalances(taxable=[0, 0], taxDeferred=[0, 0], taxFree=[amount/2, amount/2])
346
346
  p.setAllocationRatios('spouses', generic=[[0, 100, 0, 0], [0, 100, 0, 0]])
@@ -354,7 +354,7 @@ def test_taxfreegrowth7_2():
354
354
 
355
355
  def test_taxfreegrowth8():
356
356
  n = 15
357
- p = createPlan(1, 'taxfreegrowth8', n, 64)
357
+ p = createPlan(1, 'taxfreegrowth8', n, 76)
358
358
  amount = 120
359
359
  p.setAccountBalances(taxable=[0], taxDeferred=[0], taxFree=[amount])
360
360
  p.setAllocationRatios('individual', generic=[[[100, 0, 0, 0], [100, 0, 0, 0]]])
@@ -368,7 +368,7 @@ def test_taxfreegrowth8():
368
368
 
369
369
  def test_taxfreegrowth8_2():
370
370
  n = 15
371
- p = createPlan(2, 'taxfreegrowth8', n, 64)
371
+ p = createPlan(2, 'taxfreegrowth8', n, 76)
372
372
  amount = 120
373
373
  p.setAccountBalances(taxable=[0, 0], taxDeferred=[0, 0], taxFree=[amount/2, amount/2])
374
374
  p.setAllocationRatios('spouses', generic=[[100, 0, 0, 0], [100, 0, 0, 0]])
@@ -382,7 +382,7 @@ def test_taxfreegrowth8_2():
382
382
 
383
383
  def test_annuity1():
384
384
  n = 12
385
- p = createPlan(1, 'annuity1', n, 64)
385
+ p = createPlan(1, 'annuity1', n, 76)
386
386
  amount = 120
387
387
  p.setAccountBalances(taxable=[0], taxDeferred=[0], taxFree=[amount])
388
388
  p.setAllocationRatios('individual', generic=[[[0, 0, 100, 0], [0, 0, 100, 0]]])
@@ -400,7 +400,7 @@ def test_annuity1():
400
400
 
401
401
  def test_annuity1_2():
402
402
  n = 12
403
- p = createPlan(2, 'annuity1', n, 64)
403
+ p = createPlan(2, 'annuity1', n, 76)
404
404
  amount = 120
405
405
  p.setAccountBalances(taxable=[0, 0], taxDeferred=[0, 0], taxFree=[amount/2, amount/2])
406
406
  p.setAllocationRatios('spouses', generic=[[0, 0, 100, 0], [0, 0, 100, 0]])
@@ -418,7 +418,7 @@ def test_annuity1_2():
418
418
 
419
419
  def test_annuity2():
420
420
  n = 18
421
- p = createPlan(1, 'annuity2', n, 64)
421
+ p = createPlan(1, 'annuity2', n, 76)
422
422
  amount = 120
423
423
  p.setAccountBalances(taxable=[0], taxDeferred=[0], taxFree=[amount])
424
424
  p.setAllocationRatios('individual', generic=[[[0, 0, 100, 0], [0, 0, 100, 0]]])
@@ -436,7 +436,7 @@ def test_annuity2():
436
436
 
437
437
  def test_annuity2_2():
438
438
  n = 18
439
- p = createPlan(2, 'annuity2', n, 64)
439
+ p = createPlan(2, 'annuity2', n, 78)
440
440
  amount = 120
441
441
  p.setAccountBalances(taxable=[0, 0], taxDeferred=[0, 0], taxFree=[amount/2, amount/2])
442
442
  p.setAllocationRatios('spouses', generic=[[0, 0, 100, 0], [0, 0, 100, 0]])
@@ -454,7 +454,7 @@ def test_annuity2_2():
454
454
 
455
455
  def test_annuity3():
456
456
  n = 30
457
- p = createPlan(1, 'annuity2', n, 64)
457
+ p = createPlan(1, 'annuity2', n, 90)
458
458
  amount = 100
459
459
  p.setAccountBalances(taxable=[0], taxDeferred=[0], taxFree=[amount])
460
460
  p.setAllocationRatios('individual', generic=[[[0, 0, 100, 0], [0, 0, 100, 0]]])
@@ -472,7 +472,7 @@ def test_annuity3():
472
472
 
473
473
  def test_annuity3_2():
474
474
  n = 30
475
- p = createPlan(2, 'annuity2', n, 64)
475
+ p = createPlan(2, 'annuity2', n, 90)
476
476
  amount = 100
477
477
  p.setAccountBalances(taxable=[0, 0], taxDeferred=[0, 0], taxFree=[amount/2, amount/2])
478
478
  p.setAllocationRatios('spouses', generic=[[0, 0, 100, 0], [0, 0, 100, 0]])
@@ -93,10 +93,11 @@ else:
93
93
 
94
94
  col1, col2, col3, col4 = st.columns(4, gap="large", vertical_alignment="top")
95
95
  with col1:
96
+ maxValue = owb.TO if kz.getKey("varyingType") == "historical" else kz.getKey("yto") - 1
96
97
  st.number_input(
97
98
  "Starting year",
98
99
  min_value=owb.FROM,
99
- max_value=kz.getKey("yto") - 1,
100
+ max_value=maxValue,
100
101
  value=kz.getKey("yfrm"),
101
102
  on_change=updateRates,
102
103
  args=["yfrm"],
@@ -7,4 +7,4 @@ scipy
7
7
  streamlit
8
8
  toml
9
9
  # --extra-index-url https://test.pypi.org/simple
10
- owlplanner >= 2025.03.13
10
+ owlplanner >= 2025.03.15
@@ -1 +0,0 @@
1
- __version__ = "2025.03.13"
File without changes
File without changes
File without changes
File without changes