owlplanner 2025.2.4__py3-none-any.whl → 2025.2.5__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 +101 -109
- owlplanner/version.py +1 -1
- {owlplanner-2025.2.4.dist-info → owlplanner-2025.2.5.dist-info}/METADATA +25 -40
- {owlplanner-2025.2.4.dist-info → owlplanner-2025.2.5.dist-info}/RECORD +6 -6
- {owlplanner-2025.2.4.dist-info → owlplanner-2025.2.5.dist-info}/WHEEL +0 -0
- {owlplanner-2025.2.4.dist-info → owlplanner-2025.2.5.dist-info}/licenses/LICENSE +0 -0
owlplanner/plan.py
CHANGED
|
@@ -1413,11 +1413,11 @@ class Plan(object):
|
|
|
1413
1413
|
|
|
1414
1414
|
progcall.finish()
|
|
1415
1415
|
self.mylog.resetVerbose()
|
|
1416
|
-
fig,
|
|
1417
|
-
self.mylog.print(
|
|
1416
|
+
fig, description = self._showResults(objective, df, N, figure)
|
|
1417
|
+
self.mylog.print(description.getvalue())
|
|
1418
1418
|
|
|
1419
1419
|
if figure:
|
|
1420
|
-
return fig,
|
|
1420
|
+
return fig, description.getvalue()
|
|
1421
1421
|
|
|
1422
1422
|
return N, df
|
|
1423
1423
|
|
|
@@ -1469,11 +1469,11 @@ class Plan(object):
|
|
|
1469
1469
|
|
|
1470
1470
|
progcall.finish()
|
|
1471
1471
|
self.mylog.resetVerbose()
|
|
1472
|
-
fig,
|
|
1473
|
-
self.mylog.print(
|
|
1472
|
+
fig, description = self._showResults(objective, df, N, figure)
|
|
1473
|
+
self.mylog.print(description.getvalue())
|
|
1474
1474
|
|
|
1475
1475
|
if figure:
|
|
1476
|
-
return fig,
|
|
1476
|
+
return fig, description.getvalue()
|
|
1477
1477
|
|
|
1478
1478
|
return N, df
|
|
1479
1479
|
|
|
@@ -1483,9 +1483,9 @@ class Plan(object):
|
|
|
1483
1483
|
"""
|
|
1484
1484
|
import seaborn as sbn
|
|
1485
1485
|
|
|
1486
|
-
|
|
1486
|
+
description = io.StringIO()
|
|
1487
1487
|
|
|
1488
|
-
print('Success rate: %s on %d samples.' % (u.pc(len(df) / N), N), file=
|
|
1488
|
+
print('Success rate: %s on %d samples.' % (u.pc(len(df) / N), N), file=description)
|
|
1489
1489
|
title = '$N$ = %d, $P$ = %s' % (N, u.pc(len(df) / N))
|
|
1490
1490
|
means = df.mean(axis=0, numeric_only=True)
|
|
1491
1491
|
medians = df.median(axis=0, numeric_only=True)
|
|
@@ -1498,7 +1498,7 @@ class Plan(object):
|
|
|
1498
1498
|
# or if solution led to empty accounts at the end of first spouse's life.
|
|
1499
1499
|
if np.all(self.phi_j == 1) or medians.iloc[0] < 1:
|
|
1500
1500
|
if medians.iloc[0] < 1:
|
|
1501
|
-
print('Optimized solutions all have null partial bequest in year %d.' % my[0], file=
|
|
1501
|
+
print('Optimized solutions all have null partial bequest in year %d.' % my[0], file=description)
|
|
1502
1502
|
df.drop('partial', axis=1, inplace=True)
|
|
1503
1503
|
means = df.mean(axis=0, numeric_only=True)
|
|
1504
1504
|
medians = df.median(axis=0, numeric_only=True)
|
|
@@ -1550,16 +1550,16 @@ class Plan(object):
|
|
|
1550
1550
|
# plt.show()
|
|
1551
1551
|
|
|
1552
1552
|
for q in range(len(means)):
|
|
1553
|
-
print('%12s: Median (%d $): %s' % (leads[q], self.year_n[0], u.d(medians.iloc[q])), file=
|
|
1554
|
-
print('%12s: Mean (%d $): %s' % (leads[q], self.year_n[0], u.d(means.iloc[q])), file=
|
|
1553
|
+
print('%12s: Median (%d $): %s' % (leads[q], self.year_n[0], u.d(medians.iloc[q])), file=description)
|
|
1554
|
+
print('%12s: Mean (%d $): %s' % (leads[q], self.year_n[0], u.d(means.iloc[q])), file=description)
|
|
1555
1555
|
print(
|
|
1556
1556
|
'%12s: Range: %s - %s'
|
|
1557
1557
|
% (leads[q], u.d(1000 * df.iloc[:, q].min()), u.d(1000 * df.iloc[:, q].max())),
|
|
1558
|
-
file=
|
|
1558
|
+
file=description)
|
|
1559
1559
|
nzeros = len(df.iloc[:, q][df.iloc[:, q] < 0.001])
|
|
1560
|
-
print('%12s: N zero solns: %d' % (leads[q], nzeros), file=
|
|
1560
|
+
print('%12s: N zero solns: %d' % (leads[q], nzeros), file=description)
|
|
1561
1561
|
|
|
1562
|
-
return fig,
|
|
1562
|
+
return fig, description
|
|
1563
1563
|
|
|
1564
1564
|
def resolve(self):
|
|
1565
1565
|
"""
|
|
@@ -2038,141 +2038,133 @@ class Plan(object):
|
|
|
2038
2038
|
@_checkCaseStatus
|
|
2039
2039
|
def summary(self):
|
|
2040
2040
|
"""
|
|
2041
|
-
Print summary
|
|
2041
|
+
Print summary in logs.
|
|
2042
2042
|
"""
|
|
2043
2043
|
self.mylog.print('SUMMARY ================================================================')
|
|
2044
|
-
|
|
2045
|
-
for
|
|
2046
|
-
self.mylog.print(
|
|
2044
|
+
dic = self.summaryDic()
|
|
2045
|
+
for key, value in dic.items():
|
|
2046
|
+
self.mylog.print(f"{key}: {value}")
|
|
2047
2047
|
self.mylog.print('------------------------------------------------------------------------')
|
|
2048
2048
|
|
|
2049
2049
|
return None
|
|
2050
2050
|
|
|
2051
|
+
def summaryList(self):
|
|
2052
|
+
"""
|
|
2053
|
+
Return summary as a list.
|
|
2054
|
+
"""
|
|
2055
|
+
mylist = []
|
|
2056
|
+
dic = self.summaryDic()
|
|
2057
|
+
for key, value in dic.items():
|
|
2058
|
+
mylist.append(f"{key}: {value}")
|
|
2059
|
+
|
|
2060
|
+
return mylist
|
|
2061
|
+
|
|
2051
2062
|
def summaryString(self):
|
|
2052
2063
|
"""
|
|
2053
|
-
|
|
2064
|
+
Return summary as a string.
|
|
2054
2065
|
"""
|
|
2055
|
-
string = ''
|
|
2056
|
-
|
|
2057
|
-
for
|
|
2058
|
-
string +=
|
|
2066
|
+
string = 'Synopsis\n'
|
|
2067
|
+
dic = self.summaryDic()
|
|
2068
|
+
for key, value in dic.items():
|
|
2069
|
+
string += f"{key:>70}: {value}\n"
|
|
2070
|
+
# string += "%60s: %s\n" % (key, value)
|
|
2059
2071
|
|
|
2060
2072
|
return string
|
|
2061
2073
|
|
|
2062
|
-
def
|
|
2074
|
+
def summaryDic(self):
|
|
2063
2075
|
"""
|
|
2064
|
-
Return
|
|
2076
|
+
Return dictionary containing summary of values.
|
|
2065
2077
|
"""
|
|
2066
2078
|
now = self.year_n[0]
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
lines.append('Contributions file: %s' % self.timeListsFileName)
|
|
2073
|
-
lines.append('Initial balances [taxable, tax-deferred, tax-free]:')
|
|
2074
|
-
for i in range(self.N_i):
|
|
2075
|
-
lines.append("%12s's accounts: %s" % (self.inames[i], [u.d(self.beta_ij[i][j]) for j in range(self.N_j)]))
|
|
2076
|
-
|
|
2077
|
-
lines.append('Return rates: %s' % self.rateMethod)
|
|
2078
|
-
if self.rateMethod in ['historical', 'historical average', 'histochastic']:
|
|
2079
|
-
lines.append('Rates used: from %d to %d' % (self.rateFrm, self.rateTo))
|
|
2080
|
-
elif self.rateMethod == 'stochastic':
|
|
2081
|
-
lines.append(
|
|
2082
|
-
'Mean rates used (%%): %s' % (['{:.1f}'.format(100 * self.rateValues[k]) for k in range(self.N_k)])
|
|
2083
|
-
)
|
|
2084
|
-
lines.append(
|
|
2085
|
-
'Standard deviation used (%%): %s'
|
|
2086
|
-
% (['{:.1f}'.format(100 * self.rateStdev[k]) for k in range(self.N_k)])
|
|
2087
|
-
)
|
|
2088
|
-
lines.append('Correlation matrix used:')
|
|
2089
|
-
lines.append('\t\t' + str(self.rateCorr).replace('\n', '\n\t\t'))
|
|
2090
|
-
else:
|
|
2091
|
-
lines.append('Rates used (%%): %s' % (['{:.1f}'.format(100 * self.rateValues[k]) for k in range(self.N_k)]))
|
|
2092
|
-
lines.append("This year's starting date: %s" % self.startDate)
|
|
2093
|
-
lines.append('Optimized for: %s' % self.objective)
|
|
2094
|
-
lines.append('Solver options: %s' % self.solverOptions)
|
|
2095
|
-
lines.append('Number of decision variables: %d' % self.A.nvars)
|
|
2096
|
-
lines.append('Number of constraints: %d' % self.A.ncons)
|
|
2097
|
-
lines.append('Spending profile: %s' % self.spendingProfile)
|
|
2098
|
-
if self.spendingProfile == 'smile':
|
|
2099
|
-
lines.append('\twith increase: %d%%, dip: %d%%, delay: %dy'
|
|
2100
|
-
% (self.smileIncrease, self.smileDip, self.smileDelay))
|
|
2101
|
-
if self.N_i == 2:
|
|
2102
|
-
lines.append('Surviving spouse spending needs: %s' % u.pc(self.chi, f=0))
|
|
2103
|
-
|
|
2104
|
-
lines.append('Net yearly spending in year %d: %s' % (now, u.d(self.g_n[0] / self.yearFracLeft)))
|
|
2105
|
-
lines.append('Net spending remaining in year %d: %s' % (now, u.d(self.g_n[0])))
|
|
2106
|
-
lines.append('Net yearly spending profile basis in %d$: %s' % (now, u.d(self.g_n[0] / self.xi_n[0])))
|
|
2107
|
-
|
|
2108
|
-
lines.append('Assumed heirs marginal tax rate: %s' % u.pc(self.nu, f=0))
|
|
2109
|
-
|
|
2110
|
-
if self.N_i == 2 and self.n_d < self.N_n:
|
|
2111
|
-
lines.append("Spousal surplus deposit fraction in %s's taxable account: %s"
|
|
2112
|
-
% (self.inames[1], self.eta))
|
|
2113
|
-
lines.append('Spousal beneficiary fractions to %s: %s' % (self.inames[self.i_s], self.phi_j.tolist()))
|
|
2114
|
-
p_j = self.partialEstate_j * (1 - self.phi_j)
|
|
2115
|
-
p_j[1] *= 1 - self.nu
|
|
2116
|
-
nx = self.n_d - 1
|
|
2117
|
-
totOthers = np.sum(p_j)
|
|
2118
|
-
totOthersNow = totOthers / self.gamma_n[nx + 1]
|
|
2119
|
-
q_j = self.partialEstate_j * self.phi_j
|
|
2120
|
-
totSpousal = np.sum(q_j)
|
|
2121
|
-
totSpousalNow = totSpousal / self.gamma_n[nx + 1]
|
|
2122
|
-
lines.append('Spousal wealth transfer from %s to %s in year %d (nominal):'
|
|
2123
|
-
% (self.inames[self.i_d], self.inames[self.i_s], self.year_n[nx]))
|
|
2124
|
-
lines.append(' taxable: %s tax-def: %s tax-free: %s' % (u.d(q_j[0]), u.d(q_j[1]), u.d(q_j[2])))
|
|
2125
|
-
lines.append('Sum of spousal bequests to %s in year %d in %d$: %s (%s nominal)'
|
|
2126
|
-
% (self.inames[self.i_s], self.year_n[nx], now, u.d(totSpousalNow), u.d(totSpousal)))
|
|
2127
|
-
lines.append(
|
|
2128
|
-
'Post-tax non-spousal bequests from %s in year %d (nominal):' % (self.inames[self.i_d], self.year_n[nx])
|
|
2129
|
-
)
|
|
2130
|
-
lines.append(' taxable: %s tax-def: %s tax-free: %s' % (u.d(p_j[0]), u.d(p_j[1]), u.d(p_j[2])))
|
|
2131
|
-
lines.append(
|
|
2132
|
-
'Sum of post-tax non-spousal bequests from %s in year %d in %d$: %s (%s nominal)'
|
|
2133
|
-
% (self.inames[self.i_d], self.year_n[nx], now, u.d(totOthersNow), u.d(totOthers))
|
|
2134
|
-
)
|
|
2079
|
+
dic = {}
|
|
2080
|
+
# Results
|
|
2081
|
+
dic[f"Net yearly spending basis in {now}$"] = (u.d(self.g_n[0] / self.xi_n[0]))
|
|
2082
|
+
dic[f"Net yearly spending for year {now}"] = (u.d(self.g_n[0] / self.yearFracLeft))
|
|
2083
|
+
dic[f"Net spending remaining in year {now}"] = u.d(self.g_n[0])
|
|
2135
2084
|
|
|
2136
2085
|
totIncome = np.sum(self.g_n, axis=0)
|
|
2137
2086
|
totIncomeNow = np.sum(self.g_n / self.gamma_n[:-1], axis=0)
|
|
2138
|
-
|
|
2087
|
+
dic[f"Total net spending in {now}$"] = (
|
|
2088
|
+
"%s (%s nominal)" % (u.d(totIncomeNow), u.d(totIncome))
|
|
2089
|
+
)
|
|
2139
2090
|
|
|
2140
2091
|
totRoth = np.sum(self.x_in, axis=(0, 1))
|
|
2141
2092
|
totRothNow = np.sum(np.sum(self.x_in, axis=0) / self.gamma_n[:-1], axis=0)
|
|
2142
|
-
|
|
2093
|
+
dic[f"Total Roth conversions in {now}$"] = (
|
|
2094
|
+
"%s (%s nominal)" % (u.d(totRothNow), u.d(totRoth))
|
|
2095
|
+
)
|
|
2143
2096
|
|
|
2144
2097
|
taxPaid = np.sum(self.T_n, axis=0)
|
|
2145
2098
|
taxPaidNow = np.sum(self.T_n / self.gamma_n[:-1], axis=0)
|
|
2146
|
-
|
|
2147
|
-
|
|
2099
|
+
dic[f"Total income tax paid on ordinary income in {now}$"] = (
|
|
2100
|
+
"%s (%s nominal)" % (u.d(taxPaidNow), u.d(taxPaid))
|
|
2101
|
+
)
|
|
2148
2102
|
|
|
2149
2103
|
taxPaid = np.sum(self.U_n, axis=0)
|
|
2150
2104
|
taxPaidNow = np.sum(self.U_n / self.gamma_n[:-1], axis=0)
|
|
2151
|
-
|
|
2152
|
-
|
|
2105
|
+
dic[f"Total tax paid on gains and dividends in {now}$"] = (
|
|
2106
|
+
"%s (%s nominal)" % (u.d(taxPaidNow), u.d(taxPaid))
|
|
2107
|
+
)
|
|
2153
2108
|
|
|
2154
2109
|
taxPaid = np.sum(self.M_n, axis=0)
|
|
2155
2110
|
taxPaidNow = np.sum(self.M_n / self.gamma_n[:-1], axis=0)
|
|
2156
|
-
|
|
2111
|
+
dic[f"Total Medicare premiums paid in {now}$"] = (
|
|
2112
|
+
"%s (%s nominal)" % (u.d(taxPaidNow), u.d(taxPaid))
|
|
2113
|
+
)
|
|
2114
|
+
|
|
2115
|
+
if self.N_i == 2 and self.n_d < self.N_n:
|
|
2116
|
+
p_j = self.partialEstate_j * (1 - self.phi_j)
|
|
2117
|
+
p_j[1] *= 1 - self.nu
|
|
2118
|
+
nx = self.n_d - 1
|
|
2119
|
+
totOthers = np.sum(p_j)
|
|
2120
|
+
totOthersNow = totOthers / self.gamma_n[nx + 1]
|
|
2121
|
+
q_j = self.partialEstate_j * self.phi_j
|
|
2122
|
+
totSpousal = np.sum(q_j)
|
|
2123
|
+
totSpousalNow = totSpousal / self.gamma_n[nx + 1]
|
|
2124
|
+
dic["Spousal wealth transfer from %s to %s in year %d (nominal)" %
|
|
2125
|
+
(self.inames[self.i_d], self.inames[self.i_s], self.year_n[nx])] = (
|
|
2126
|
+
"taxable: %s tax-def: %s tax-free: %s" % (u.d(q_j[0]), u.d(q_j[1]), u.d(q_j[2]))
|
|
2127
|
+
)
|
|
2128
|
+
|
|
2129
|
+
dic["Sum of spousal bequests to %s in year %d in %d$" %
|
|
2130
|
+
(self.inames[self.i_s], self.year_n[nx], now)] = (
|
|
2131
|
+
"%s (%s nominal)" % (u.d(totSpousalNow), u.d(totSpousal))
|
|
2132
|
+
)
|
|
2133
|
+
dic["Post-tax non-spousal bequests from %s in year %d (nominal)" %
|
|
2134
|
+
(self.inames[self.i_d], self.year_n[nx])] = (
|
|
2135
|
+
"taxable: %s tax-def: %s tax-free: %s" % (u.d(p_j[0]), u.d(p_j[1]), u.d(p_j[2]))
|
|
2136
|
+
)
|
|
2137
|
+
dic["Sum of post-tax non-spousal bequests from %s in year %d in %d$" %
|
|
2138
|
+
(self.inames[self.i_d], self.year_n[nx], now)] = (
|
|
2139
|
+
"%s (%s nominal)" % (u.d(totOthersNow), u.d(totOthers))
|
|
2140
|
+
)
|
|
2157
2141
|
|
|
2158
2142
|
estate = np.sum(self.b_ijn[:, :, self.N_n], axis=0)
|
|
2159
2143
|
estate[1] *= 1 - self.nu
|
|
2160
|
-
|
|
2161
|
-
|
|
2144
|
+
dic["Post-tax account values at the end of final plan year %d (nominal)" % self.year_n[-1]] = (
|
|
2145
|
+
"taxable: %s tax-def: %s tax-free: %s" % (u.d(estate[0]), u.d(estate[1]), u.d(estate[2]))
|
|
2146
|
+
)
|
|
2162
2147
|
|
|
2163
2148
|
totEstate = np.sum(estate)
|
|
2164
2149
|
totEstateNow = totEstate / self.gamma_n[-1]
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
% (self.year_n[-1], now, u.d(totEstateNow), u.d(totEstate))
|
|
2150
|
+
dic["Total estate value at the end of final plan year %d in %d$" % (self.year_n[-1], now)] = (
|
|
2151
|
+
"%s (%s nominal)" % (u.d(totEstateNow), u.d(totEstate))
|
|
2168
2152
|
)
|
|
2169
|
-
|
|
2170
|
-
|
|
2153
|
+
dic["Plan starting date"] = str(self.startDate)
|
|
2154
|
+
dic["Cumulative inflation factor from start date to end of plan"] = (
|
|
2155
|
+
"%.2f" % (self.gamma_n[-1])
|
|
2171
2156
|
)
|
|
2157
|
+
for i in range(self.N_i):
|
|
2158
|
+
dic["%12s's %02d-year life horizon" % (self.inames[i], self.horizons[i])] = (
|
|
2159
|
+
"%d -> %d" % (now, now + self.horizons[i] - 1)
|
|
2160
|
+
)
|
|
2172
2161
|
|
|
2173
|
-
|
|
2162
|
+
dic["Plan name"] = self._name
|
|
2163
|
+
dic["Number of decision variables"] = str(self.A.nvars)
|
|
2164
|
+
dic["Number of constraints"] = str(self.A.ncons)
|
|
2165
|
+
dic["Case executed on"] = str(self._timestamp)
|
|
2174
2166
|
|
|
2175
|
-
return
|
|
2167
|
+
return dic
|
|
2176
2168
|
|
|
2177
2169
|
def showRatesCorrelations(self, tag='', shareRange=False, figure=False):
|
|
2178
2170
|
"""
|
owlplanner/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "2025.02.
|
|
1
|
+
__version__ = "2025.02.05"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: owlplanner
|
|
3
|
-
Version: 2025.2.
|
|
3
|
+
Version: 2025.2.5
|
|
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
|
|
@@ -731,7 +731,8 @@ This is exactly where this tool fits it. Given your savings capabilities and spe
|
|
|
731
731
|
it can generate different future realizations of
|
|
732
732
|
your strategy under different market assumptions, helping to better understand your financial situation.
|
|
733
733
|
|
|
734
|
-
Disclaimers: I am not a financial planner. You make your own decisions.
|
|
734
|
+
Disclaimers: I am not a financial planner. You make your own decisions.
|
|
735
|
+
This program comes with no guarantee. Use at your own risk.
|
|
735
736
|
|
|
736
737
|
More disclaimers: While some output of the code has been verified with other approaches,
|
|
737
738
|
this code is still under development and I cannot guarantee the accuracy of the results.
|
|
@@ -975,47 +976,31 @@ The output of the last command reports that if future rates are exactly like tho
|
|
|
975
976
|
starting from 1969 and the following years, Jack and Jill could afford an annual spending of
|
|
976
977
|
\\$97k starting this year
|
|
977
978
|
(with a basis of \\$88.8k - the basis multiplies the profile which can vary over the course of the plan).
|
|
978
|
-
The summary also contains
|
|
979
|
+
The summary also contains some details:
|
|
979
980
|
```
|
|
980
981
|
SUMMARY ================================================================
|
|
982
|
+
Net yearly spending basis in 2025$: $91,812
|
|
983
|
+
Net yearly spending for year 2025: $100,448
|
|
984
|
+
Net spending remaining in year 2025: $100,448
|
|
985
|
+
Total net spending in 2025$: $2,809,453 ($7,757,092 nominal)
|
|
986
|
+
Total Roth conversions in 2025$: $320,639 ($456,454 nominal)
|
|
987
|
+
Total income tax paid on ordinary income in 2025$: $247,788 ($469,522 nominal)
|
|
988
|
+
Total tax paid on gains and dividends in 2025$: $3,313 ($3,768 nominal)
|
|
989
|
+
Total Medicare premiums paid in 2025$: $117,660 ($343,388 nominal)
|
|
990
|
+
Spousal wealth transfer from Jack to Jill in year 2051 (nominal): taxable: $0 tax-def: $57,224 tax-free: $2,102,173
|
|
991
|
+
Sum of spousal bequests to Jill in year 2051 in 2025$: $499,341 ($2,159,397 nominal)
|
|
992
|
+
Post-tax non-spousal bequests from Jack in year 2051 (nominal): taxable: $0 tax-def: $0 tax-free: $0
|
|
993
|
+
Sum of post-tax non-spousal bequests from Jack in year 2051 in 2025$: $0 ($0 nominal)
|
|
994
|
+
Post-tax account values at the end of final plan year 2057 (nominal): taxable: $0 tax-def: $0 tax-free: $2,488,808
|
|
995
|
+
Total estate value at the end of final plan year 2057 in 2025$: $500,000 ($2,488,808 nominal)
|
|
996
|
+
Plan starting date: 01-01
|
|
997
|
+
Cumulative inflation factor from start date to end of plan: 4.98
|
|
998
|
+
Jack's 27-year life horizon: 2025 -> 2051
|
|
999
|
+
Jill's 33-year life horizon: 2025 -> 2057
|
|
981
1000
|
Plan name: jack & jill - tutorial
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
Initial balances [taxable, tax-deferred, tax-free]:
|
|
986
|
-
Jack's accounts: ['$90,500', '$600,500', '$70,000']
|
|
987
|
-
Jill's accounts: ['$60,200', '$150,000', '$40,000']
|
|
988
|
-
Return rates: historical
|
|
989
|
-
Rates used: from 1969 to 2002
|
|
990
|
-
This year's starting date: 01-01
|
|
991
|
-
Optimized for: maxSpending
|
|
992
|
-
Solver options: {'maxRothConversion': 100, 'bequest': 500, 'noRothConversions': 'Jill'}
|
|
993
|
-
Number of decision variables: 1026
|
|
994
|
-
Number of constraints: 894
|
|
995
|
-
Spending profile: smile
|
|
996
|
-
Surviving spouse spending needs: 60%
|
|
997
|
-
Net yearly spending in year 2024: $97,098
|
|
998
|
-
Net spending remaining in year 2024: $97,098
|
|
999
|
-
Net yearly spending profile basis in 2024$: $88,763
|
|
1000
|
-
Assumed heirs tax rate: 30%
|
|
1001
|
-
Spousal surplus deposit fraction: 0.5
|
|
1002
|
-
Spousal beneficiary fractions to Jill: [1, 1, 1]
|
|
1003
|
-
Spousal wealth transfer from Jack to Jill in year 2051 (nominal):
|
|
1004
|
-
taxable: $0 tax-def: $63,134 tax-free: $2,583,303
|
|
1005
|
-
Sum of spousal bequests to Jill in year 2051 in 2024$: $592,103 ($2,646,437 nominal)
|
|
1006
|
-
Post-tax non-spousal bequests from Jack in year 2051 (nominal):
|
|
1007
|
-
taxable: $0 tax-def: $0 tax-free: $0
|
|
1008
|
-
Sum of post-tax non-spousal bequests from Jack in year 2051 in 2024$: $0 ($0 nominal)
|
|
1009
|
-
Total net spending in 2024$: $2,804,910 ($7,916,623 nominal)
|
|
1010
|
-
Total Roth conversions in 2024$: $311,760 ($443,005 nominal)
|
|
1011
|
-
Total ordinary income tax paid in 2024$: $236,710 ($457,922 nominal)
|
|
1012
|
-
Total dividend tax paid in 2024$: $3,437 ($3,902 nominal)
|
|
1013
|
-
Total Medicare premiums paid in 2024$: $117,817 ($346,404 nominal)
|
|
1014
|
-
Post-tax account values at the end of final plan year 2057: (nominal)
|
|
1015
|
-
taxable: $0 tax-def: $0 tax-free: $2,553,871
|
|
1016
|
-
Total estate value at the end of final plan year 2057 in 2024$: $500,000 ($2,553,871 nominal)
|
|
1017
|
-
Inflation factor from this year's start date to the end of plan final year: 5.11
|
|
1018
|
-
Case executed on: 2024-12-09 at 22:11:57
|
|
1001
|
+
Number of decision variables: 996
|
|
1002
|
+
Number of constraints: 867
|
|
1003
|
+
Case executed on: 2025-02-04 at 22:55:03
|
|
1019
1004
|
------------------------------------------------------------------------
|
|
1020
1005
|
```
|
|
1021
1006
|
And an Excel workbook can be saved with all the detailed amounts over the years by using the following command:
|
|
@@ -2,16 +2,16 @@ owlplanner/__init__.py,sha256=QqrdT0Qks20osBTg7h0vJHAxpP9lL7DA99xb0nYbtw4,254
|
|
|
2
2
|
owlplanner/abcapi.py,sha256=eemIsdbtzdWCIj5VuuswgphxXMcxJ_GZfUlDi6lttFM,6658
|
|
3
3
|
owlplanner/config.py,sha256=ouADb6YES5Zgv0UwnEK9Axwvs8drp-ahboQjI4WTrr0,12069
|
|
4
4
|
owlplanner/logging.py,sha256=pXg_mMgBll-kklqaDRLDNVUFo-5DAa-yqTKtiVrhNWw,2530
|
|
5
|
-
owlplanner/plan.py,sha256=
|
|
5
|
+
owlplanner/plan.py,sha256=LJwNf6UngC2WepPyvO6zQzx9V0RbsQd2H9LAg5V8Fto,113815
|
|
6
6
|
owlplanner/progress.py,sha256=YZjL5_m4MMgKPlWlhhKacPLt54tVhVGF1eXxxZapMYs,386
|
|
7
7
|
owlplanner/rates.py,sha256=aKOmau8i3uqxZGi7HQJpzooT3X-yAZhga5MZJ56pBzk,15627
|
|
8
8
|
owlplanner/tax2025.py,sha256=b2RgM6TBQa8ggo6ODyh0p_J7j79UUm8z5NiENqa1l_k,7016
|
|
9
9
|
owlplanner/timelists.py,sha256=WwymsYAGWcrEzMtc-wrLbn1EVA2fhqXGN4NHLJsH3Fs,4110
|
|
10
10
|
owlplanner/utils.py,sha256=adIwqGVQFfvekke0JCxYJD3PKHbptVCj3NrQT2TQIB4,2351
|
|
11
|
-
owlplanner/version.py,sha256=
|
|
11
|
+
owlplanner/version.py,sha256=3SB4x2fk8FWUTRIEqgNzXA2CeRNyNlNPfgW0-yoyV_A,28
|
|
12
12
|
owlplanner/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
13
|
owlplanner/data/rates.csv,sha256=6fxg56BVVORrj9wJlUGFdGXKvOX5r7CSca8uhUbbuIU,3734
|
|
14
|
-
owlplanner-2025.2.
|
|
15
|
-
owlplanner-2025.2.
|
|
16
|
-
owlplanner-2025.2.
|
|
17
|
-
owlplanner-2025.2.
|
|
14
|
+
owlplanner-2025.2.5.dist-info/METADATA,sha256=RpUVgZp48DCU53VP6HjcQvojsnaZPsqSaav2jJ0OglA,63946
|
|
15
|
+
owlplanner-2025.2.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
16
|
+
owlplanner-2025.2.5.dist-info/licenses/LICENSE,sha256=IwGE9guuL-ryRPEKi6wFPI_zOhg7zDZbTYuHbSt_SAk,35823
|
|
17
|
+
owlplanner-2025.2.5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|