owlplanner 2025.2.25__py3-none-any.whl → 2025.3.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- owlplanner/plan.py +53 -26
- owlplanner/version.py +1 -1
- {owlplanner-2025.2.25.dist-info → owlplanner-2025.3.7.dist-info}/METADATA +9 -3
- {owlplanner-2025.2.25.dist-info → owlplanner-2025.3.7.dist-info}/RECORD +6 -6
- {owlplanner-2025.2.25.dist-info → owlplanner-2025.3.7.dist-info}/WHEEL +0 -0
- {owlplanner-2025.2.25.dist-info → owlplanner-2025.3.7.dist-info}/licenses/LICENSE +0 -0
owlplanner/plan.py
CHANGED
|
@@ -1968,8 +1968,8 @@ class Plan(object):
|
|
|
1968
1968
|
self.dist_in = self.w_ijn[:, 1, :] - self.rmd_in
|
|
1969
1969
|
self.dist_in[self.dist_in < 0] = 0
|
|
1970
1970
|
self.G_n = np.sum(self.F_tn, axis=0)
|
|
1971
|
-
T_tn = self.F_tn * self.theta_tn
|
|
1972
|
-
self.T_n = np.sum(T_tn, axis=0)
|
|
1971
|
+
self.T_tn = self.F_tn * self.theta_tn
|
|
1972
|
+
self.T_n = np.sum(self.T_tn, axis=0)
|
|
1973
1973
|
|
|
1974
1974
|
tau_0 = np.array(self.tau_kn[0, :])
|
|
1975
1975
|
tau_0[tau_0 < 0] = 0
|
|
@@ -2062,6 +2062,12 @@ class Plan(object):
|
|
|
2062
2062
|
|
|
2063
2063
|
return mylist
|
|
2064
2064
|
|
|
2065
|
+
def summaryDf(self):
|
|
2066
|
+
"""
|
|
2067
|
+
Return summary as a dataframe.
|
|
2068
|
+
"""
|
|
2069
|
+
return pd.DataFrame(self.summaryDic(), index=[self._name])
|
|
2070
|
+
|
|
2065
2071
|
def summaryString(self):
|
|
2066
2072
|
"""
|
|
2067
2073
|
Return summary as a string.
|
|
@@ -2081,29 +2087,40 @@ class Plan(object):
|
|
|
2081
2087
|
dic = {}
|
|
2082
2088
|
# Results
|
|
2083
2089
|
dic["Plan name"] = self._name
|
|
2084
|
-
dic[
|
|
2085
|
-
dic[f"Net
|
|
2090
|
+
dic["Net yearly spending basis"] = u.d(self.g_n[0] / self.xi_n[0])
|
|
2091
|
+
dic[f"Net spending for year {now}"] = u.d(self.g_n[0] / self.yearFracLeft)
|
|
2086
2092
|
dic[f"Net spending remaining in year {now}"] = u.d(self.g_n[0])
|
|
2087
2093
|
|
|
2088
2094
|
totIncome = np.sum(self.g_n, axis=0)
|
|
2089
2095
|
totIncomeNow = np.sum(self.g_n / self.gamma_n[:-1], axis=0)
|
|
2090
|
-
dic[
|
|
2096
|
+
dic["Total net spending"] = f"{u.d(totIncomeNow)}"
|
|
2097
|
+
dic["- Total net spending (nominal)"] = f"{u.d(totIncome)}"
|
|
2091
2098
|
|
|
2092
2099
|
totRoth = np.sum(self.x_in, axis=(0, 1))
|
|
2093
2100
|
totRothNow = np.sum(np.sum(self.x_in, axis=0) / self.gamma_n[:-1], axis=0)
|
|
2094
|
-
dic[
|
|
2101
|
+
dic["Total Roth conversions"] = f"{u.d(totRothNow)}"
|
|
2102
|
+
dic["- Total Roth conversions (nominal)"] = f"{u.d(totRoth)}"
|
|
2095
2103
|
|
|
2096
2104
|
taxPaid = np.sum(self.T_n, axis=0)
|
|
2097
2105
|
taxPaidNow = np.sum(self.T_n / self.gamma_n[:-1], axis=0)
|
|
2098
|
-
dic[
|
|
2106
|
+
dic["Total income tax paid on ordinary income"] = f"{u.d(taxPaidNow)}"
|
|
2107
|
+
dic["- Total income tax paid on ordinary income (nominal)"] = f"{u.d(taxPaid)}"
|
|
2108
|
+
for t in range(self.N_t):
|
|
2109
|
+
taxPaid = np.sum(self.T_tn[t], axis=0)
|
|
2110
|
+
taxPaidNow = np.sum(self.T_tn[t] / self.gamma_n[:-1], axis=0)
|
|
2111
|
+
tname = tx.taxBracketNames[t]
|
|
2112
|
+
dic[f"-- Subtotal in tax bracket {tname}"] = f"{u.d(taxPaidNow)}"
|
|
2113
|
+
dic[f"--- Subtotal in tax bracket {tname} (nominal)"] = f"{u.d(taxPaid)}"
|
|
2099
2114
|
|
|
2100
2115
|
taxPaid = np.sum(self.U_n, axis=0)
|
|
2101
2116
|
taxPaidNow = np.sum(self.U_n / self.gamma_n[:-1], axis=0)
|
|
2102
|
-
dic[
|
|
2117
|
+
dic["Total tax paid on gains and dividends"] = f"{u.d(taxPaidNow)}"
|
|
2118
|
+
dic["- Total tax paid on gains and dividends (nominal)"] = f"{u.d(taxPaid)}"
|
|
2103
2119
|
|
|
2104
2120
|
taxPaid = np.sum(self.M_n, axis=0)
|
|
2105
2121
|
taxPaidNow = np.sum(self.M_n / self.gamma_n[:-1], axis=0)
|
|
2106
|
-
dic[
|
|
2122
|
+
dic["Total Medicare premiums paid"] = f"{u.d(taxPaidNow)}"
|
|
2123
|
+
dic["- Total Medicare premiums paid (nominal)"] = f"{u.d(taxPaid)}"
|
|
2107
2124
|
|
|
2108
2125
|
if self.N_i == 2 and self.n_d < self.N_n:
|
|
2109
2126
|
p_j = self.partialEstate_j * (1 - self.phi_j)
|
|
@@ -2117,30 +2134,40 @@ class Plan(object):
|
|
|
2117
2134
|
totSpousalNow = totSpousal / self.gamma_n[nx + 1]
|
|
2118
2135
|
iname_s = self.inames[self.i_s]
|
|
2119
2136
|
iname_d = self.inames[self.i_d]
|
|
2120
|
-
dic[f"
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
dic[f"
|
|
2124
|
-
f"{u.d(
|
|
2125
|
-
dic[
|
|
2126
|
-
f"
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2137
|
+
dic[f"Sum of spousal transfer to {iname_s} in year {ynx}"] = (f"{u.d(totSpousalNow)}")
|
|
2138
|
+
dic[f"- Sum of spousal transfer to {iname_s} in year {ynx} (nominal)"] = (
|
|
2139
|
+
f"{u.d(totSpousal)}")
|
|
2140
|
+
dic[f"-- Spousal transfer to {iname_s} in year {ynx} - taxable (nominal)"] = (
|
|
2141
|
+
f"{u.d(q_j[0])}")
|
|
2142
|
+
dic[f"-- Spousal transfer to {iname_s} in year {ynx} - tax-def (nominal)"] = (
|
|
2143
|
+
f"{u.d(q_j[1])}")
|
|
2144
|
+
dic[f"-- Spousal transfer to {iname_s} in year {ynx} - tax-free (nominal)"] = (
|
|
2145
|
+
f"{u.d(q_j[2])}")
|
|
2146
|
+
|
|
2147
|
+
dic[f"Sum of post-tax non-spousal bequests from {iname_d} in year {ynx}"] = (
|
|
2148
|
+
f"{u.d(totOthersNow)}")
|
|
2149
|
+
dic[f"- Sum of post-tax non-spousal bequests from {iname_d} in year {ynx} (nominal)"] = (
|
|
2150
|
+
f"{u.d(totOthers)}")
|
|
2151
|
+
dic[f"-- Post-tax non-spousal bequests from {iname_d} in year {ynx} - taxable (nominal)"] = (
|
|
2152
|
+
f"{u.d(p_j[0])}")
|
|
2153
|
+
dic[f"-- Post-tax non-spousal bequests from {iname_d} in year {ynx} - tax-def (nominal)"] = (
|
|
2154
|
+
f"{u.d(p_j[1])}")
|
|
2155
|
+
dic[f"-- Post-tax non-spousal bequests from {iname_d} in year {ynx} - tax-free (nominal)"] = (
|
|
2156
|
+
f"{u.d(p_j[2])}")
|
|
2131
2157
|
|
|
2132
2158
|
estate = np.sum(self.b_ijn[:, :, self.N_n], axis=0)
|
|
2133
2159
|
estate[1] *= 1 - self.nu
|
|
2134
2160
|
lastyear = self.year_n[-1]
|
|
2135
|
-
dic[f"Post-tax account values at the end of final plan year {lastyear} (nominal)"] = (
|
|
2136
|
-
f"taxable: {u.d(estate[0])} tax-def: {u.d(estate[1])} tax-free: {u.d(estate[2])}")
|
|
2137
|
-
|
|
2138
2161
|
totEstate = np.sum(estate)
|
|
2139
2162
|
totEstateNow = totEstate / self.gamma_n[-1]
|
|
2140
|
-
dic[f"Total estate value at the end of
|
|
2141
|
-
|
|
2163
|
+
dic[f"Total estate value at the end of {lastyear}"] = (f"{u.d(totEstateNow)}")
|
|
2164
|
+
dic[f"- Total estate value at the end of {lastyear} (nominal)"] = (f"{u.d(totEstate)}")
|
|
2165
|
+
dic[f"-- Post-tax account value at the end of {lastyear} - taxable (nominal)"] = (f"{u.d(estate[0])}")
|
|
2166
|
+
dic[f"-- Post-tax account value at the end of {lastyear} - tax-def (nominal)"] = (f"{u.d(estate[1])}")
|
|
2167
|
+
dic[f"-- Post-tax account value at the end of {lastyear} - tax-free (nominal)"] = (f"{u.d(estate[2])}")
|
|
2168
|
+
|
|
2142
2169
|
dic["Plan starting date"] = str(self.startDate)
|
|
2143
|
-
dic["Cumulative inflation factor from start date to end of
|
|
2170
|
+
dic[f"Cumulative inflation factor from start date to end of {lastyear}"] = (f"{self.gamma_n[-1]:.2f}")
|
|
2144
2171
|
for i in range(self.N_i):
|
|
2145
2172
|
dic[f"{self.inames[i]:>12}'s {self.horizons[i]:02}-year life horizon"] = (
|
|
2146
2173
|
f"{now} -> {now + self.horizons[i] - 1}")
|
owlplanner/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "2025.
|
|
1
|
+
__version__ = "2025.03.07"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: owlplanner
|
|
3
|
-
Version: 2025.
|
|
3
|
+
Version: 2025.3.7
|
|
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
|
|
@@ -711,7 +711,13 @@ Description-Content-Type: text/markdown
|
|
|
711
711
|
-------------------------------------------------------------------------------------
|
|
712
712
|
|
|
713
713
|
### TL;DR
|
|
714
|
-
Owl is a planning tool that uses a linear programming optimization algorithm
|
|
714
|
+
Owl is a retirement planning tool that uses a linear programming optimization algorithm
|
|
715
|
+
to provide guidance on retirement decisions, including Roth conversions.
|
|
716
|
+
Users can select varying return rates to perform historical back testing,
|
|
717
|
+
stochastic rates for performing Monte Carlo analyses,
|
|
718
|
+
or fixed rates either derived from historical averages, or set by the user.
|
|
719
|
+
|
|
720
|
+
There are a few ways to run Owl:
|
|
715
721
|
|
|
716
722
|
- Run Owl directly on the Streamlit Community Server at [owlplanner.streamlit.app](https://owlplanner.streamlit.app).
|
|
717
723
|
|
|
@@ -868,7 +874,7 @@ assets to support, even with no estate being left.
|
|
|
868
874
|
---------------------------------------------------------------
|
|
869
875
|
## Documentation
|
|
870
876
|
|
|
871
|
-
- Documentation for the app user interface is available from the interface itself.
|
|
877
|
+
- Documentation for the app user interface is available from the interface [itself](https://owlplanner.streamlit.app/Documentation).
|
|
872
878
|
- Installation guide and software requirements can be found [here](INSTALL.md).
|
|
873
879
|
- User guide for the underlying Python package as used in a Jupyter notebook can be found [here](USER_GUIDE.md).
|
|
874
880
|
|
|
@@ -2,16 +2,16 @@ owlplanner/__init__.py,sha256=QqrdT0Qks20osBTg7h0vJHAxpP9lL7DA99xb0nYbtw4,254
|
|
|
2
2
|
owlplanner/abcapi.py,sha256=LbzW_KcNy0IeHp42MUHwGu_H67B2h_e1_vu-c2ACTkQ,6646
|
|
3
3
|
owlplanner/config.py,sha256=bvqoOWrdPrWYQF9-PS_n00MoyZkAsmMOSczhTUFRGVU,12257
|
|
4
4
|
owlplanner/logging.py,sha256=tYMw04O-XYSzjTj36fmKJGLcE1VkK6k6oJNeqtKXzuc,2530
|
|
5
|
-
owlplanner/plan.py,sha256=
|
|
5
|
+
owlplanner/plan.py,sha256=KSQafUjDijtAFS-G6SK-0qrbML8MOOWZboHVkFwU-yk,115398
|
|
6
6
|
owlplanner/progress.py,sha256=8jlCvvtgDI89zXVNMBg1-lnEyhpPvKQS2X5oAIpoOVQ,384
|
|
7
7
|
owlplanner/rates.py,sha256=TN407qU4n-bac1oymkQ_n2QKEPwFQxy6JZVGwgIkLQU,15585
|
|
8
8
|
owlplanner/tax2025.py,sha256=B-A5eU3wxdcAaxRCbT3qI-JEKoD_ZeNbg_86XhNdQEI,7745
|
|
9
9
|
owlplanner/timelists.py,sha256=tYieZU67FT6TCcQQis36JaXGI7dT6NqD7RvdEjgJL4M,4026
|
|
10
10
|
owlplanner/utils.py,sha256=HM70W60qB41zfnbl2LltNwAuLYHyy5XYbwnbNcaa6FE,2351
|
|
11
|
-
owlplanner/version.py,sha256=
|
|
11
|
+
owlplanner/version.py,sha256=oFLGanJ_QJZi21IR_SCxLKDCLsykO3ijgdBcyw2kymg,28
|
|
12
12
|
owlplanner/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
13
|
owlplanner/data/rates.csv,sha256=6fxg56BVVORrj9wJlUGFdGXKvOX5r7CSca8uhUbbuIU,3734
|
|
14
|
-
owlplanner-2025.
|
|
15
|
-
owlplanner-2025.
|
|
16
|
-
owlplanner-2025.
|
|
17
|
-
owlplanner-2025.
|
|
14
|
+
owlplanner-2025.3.7.dist-info/METADATA,sha256=BSvC46OldPiE6rDniinaJbgHwagtlXnTVFs1xTqlnGg,53799
|
|
15
|
+
owlplanner-2025.3.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
16
|
+
owlplanner-2025.3.7.dist-info/licenses/LICENSE,sha256=IwGE9guuL-ryRPEKi6wFPI_zOhg7zDZbTYuHbSt_SAk,35823
|
|
17
|
+
owlplanner-2025.3.7.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|