owlplanner 2025.2.6__tar.gz → 2025.2.8__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.
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/PKG-INFO +1 -1
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/examples/case_jack+jill.toml +1 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/examples/case_joe.toml +1 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/examples/case_john+sally.toml +1 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/examples/case_kim+sam-bequest.toml +1 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/examples/case_kim+sam-spending.toml +1 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/pyproject.toml +1 -1
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/src/owlplanner/config.py +3 -1
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/src/owlplanner/plan.py +13 -5
- owlplanner-2025.2.8/src/owlplanner/version.py +1 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/tests/test_toml_cases.py +21 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/ui/Asset_Allocation.py +7 -7
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/ui/Fixed_Income.py +13 -4
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/ui/owlbridge.py +8 -7
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/ui/requirements.txt +1 -1
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/ui/sskeys.py +14 -26
- owlplanner-2025.2.6/src/owlplanner/version.py +0 -1
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/.devcontainer/devcontainer.json +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/.flake8 +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/.github/workflows/github-actions-runtests.yml +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/.gitignore +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/INSTALL.md +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/LICENSE +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/README.md +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/docs/images/AD-taxDef.png +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/docs/images/AD-taxFree.png +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/docs/images/AD-taxable.png +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/docs/images/Hist_Bequest.png +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/docs/images/Hist_Spending.png +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/docs/images/MC-tutorial2a.png +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/docs/images/MC-tutorial2b.png +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/docs/images/OwlUI.png +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/docs/images/allocations.png +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/docs/images/owl.png +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/docs/images/profile.png +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/docs/images/ratesCorrelations.png +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/docs/images/ratesPlot.png +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/docs/images/savingsPlot.png +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/docs/images/sourcesPlot.png +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/docs/images/spendingPlot.png +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/docs/images/taxIncomePlot.png +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/docs/images/taxesPlot.png +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/docs/owl.pdf +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/docs/owl.tex +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/examples/jack+jill.xlsx +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/examples/joe.xlsx +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/examples/john+sally.xlsx +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/examples/template.xlsx +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/notebooks/john+sally.ipynb +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/notebooks/kim+sam.ipynb +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/notebooks/template.ipynb +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/notebooks/tutorial_1.ipynb +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/notebooks/tutorial_2.ipynb +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/notebooks/tutorial_3.ipynb +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/owlplanner.cmd +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/requirements.txt +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/src/owlplanner/__init__.py +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/src/owlplanner/abcapi.py +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/src/owlplanner/data/__init__.py +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/src/owlplanner/data/rates.csv +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/src/owlplanner/logging.py +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/src/owlplanner/progress.py +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/src/owlplanner/rates.py +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/src/owlplanner/tax2025.py +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/src/owlplanner/timelists.py +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/src/owlplanner/utils.py +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/tests/test_logger.py +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/tests/test_regressions.py +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/tests/test_repro.py +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/tests/test_units.py +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/ui/About_Owl.py +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/ui/Assets.py +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/ui/Create_Case.py +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/ui/Documentation.py +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/ui/Graphs.py +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/ui/Historical_Range.py +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/ui/Logs.py +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/ui/Monte_Carlo.py +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/ui/Optimization_Parameters.py +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/ui/Output_Files.py +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/ui/Quick_Start.py +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/ui/README.md +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/ui/Rates_Selection.py +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/ui/Settings.py +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/ui/Wages_And_Contributions.py +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/ui/Worksheets.py +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/ui/main.py +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/ui/plots.py +0 -0
- {owlplanner-2025.2.6 → owlplanner-2025.2.8}/ui/progress.py +0 -0
|
@@ -52,6 +52,7 @@ def saveConfig(plan, file, mylog):
|
|
|
52
52
|
# Fixed Income.
|
|
53
53
|
diconf['Fixed Income'] = {'Pension amounts': (plan.pensionAmounts/1000).tolist(),
|
|
54
54
|
'Pension ages': plan.pensionAges.tolist(),
|
|
55
|
+
'Pension indexed': plan.pensionIndexed,
|
|
55
56
|
'Social security amounts': (plan.ssecAmounts/1000).tolist(),
|
|
56
57
|
'Social security ages': plan.ssecAges.tolist(),
|
|
57
58
|
}
|
|
@@ -213,7 +214,8 @@ def readConfig(file, *, verbose=True, logstreams=None, readContributions=True):
|
|
|
213
214
|
p.setSocialSecurity(ssecAmounts, ssecAges)
|
|
214
215
|
pensionAmounts = np.array(diconf['Fixed Income']['Pension amounts'], dtype=np.float32)
|
|
215
216
|
pensionAges = np.array(diconf['Fixed Income']['Pension ages'], dtype=np.int32)
|
|
216
|
-
|
|
217
|
+
pensionIndexed = diconf['Fixed Income']['Pension indexed']
|
|
218
|
+
p.setPension(pensionAmounts, pensionAges, pensionIndexed)
|
|
217
219
|
|
|
218
220
|
# Rate Selection.
|
|
219
221
|
p.setDividendRate(float(diconf['Rate Selection']['Dividend tax rate']))
|
|
@@ -286,6 +286,7 @@ class Plan(object):
|
|
|
286
286
|
self.zeta_in = np.zeros((self.N_i, self.N_n))
|
|
287
287
|
self.pensionAmounts = np.zeros(self.N_i)
|
|
288
288
|
self.pensionAges = 65 * np.ones(self.N_i, dtype=np.int32)
|
|
289
|
+
self.pensionIndexed = [False, False]
|
|
289
290
|
self.ssecAmounts = np.zeros(self.N_i)
|
|
290
291
|
self.ssecAges = 67 * np.ones(self.N_i, dtype=np.int32)
|
|
291
292
|
|
|
@@ -481,13 +482,14 @@ class Plan(object):
|
|
|
481
482
|
self.nu = nu
|
|
482
483
|
self.caseStatus = 'modified'
|
|
483
484
|
|
|
484
|
-
def setPension(self, amounts, ages, units='k'):
|
|
485
|
+
def setPension(self, amounts, ages, indexed=[False, False], units='k'):
|
|
485
486
|
"""
|
|
486
487
|
Set value of pension for each individual and commencement age.
|
|
487
488
|
Units are in $k, unless specified otherwise: 'k', 'M', or '1'.
|
|
488
489
|
"""
|
|
489
490
|
assert len(amounts) == self.N_i, 'Amounts must have %d entries.' % self.N_i
|
|
490
491
|
assert len(ages) == self.N_i, 'Ages must have %d entries.' % self.N_i
|
|
492
|
+
assert len(indexed) >= self.N_i, 'Indexed list must have at least %d entries.' % self.N_i
|
|
491
493
|
|
|
492
494
|
fac = u.getUnits(units)
|
|
493
495
|
amounts = u.rescale(amounts, fac)
|
|
@@ -508,7 +510,9 @@ class Plan(object):
|
|
|
508
510
|
|
|
509
511
|
self.pensionAmounts = np.array(amounts)
|
|
510
512
|
self.pensionAges = np.array(ages, dtype=np.int32)
|
|
513
|
+
self.pensionIndexed = indexed
|
|
511
514
|
self.caseStatus = 'modified'
|
|
515
|
+
self._adjustedParameters = False
|
|
512
516
|
|
|
513
517
|
def setSocialSecurity(self, amounts, ages, units='k'):
|
|
514
518
|
"""
|
|
@@ -1001,6 +1005,10 @@ class Plan(object):
|
|
|
1001
1005
|
self.zetaBar_in = self.zeta_in * self.gamma_n[:-1]
|
|
1002
1006
|
self.sigmaBar_n = self.sigma_n * self.gamma_n[:-1]
|
|
1003
1007
|
self.xiBar_n = self.xi_n * self.gamma_n[:-1]
|
|
1008
|
+
self.piBar_in = self.pi_in
|
|
1009
|
+
for i in range(self.N_i):
|
|
1010
|
+
if self.pensionIndexed[i]:
|
|
1011
|
+
self.piBar_in[i] *= self.gamma_n[:-1]
|
|
1004
1012
|
|
|
1005
1013
|
self._adjustedParameters = True
|
|
1006
1014
|
|
|
@@ -1269,7 +1277,7 @@ class Plan(object):
|
|
|
1269
1277
|
rhs += (
|
|
1270
1278
|
self.omega_in[i, n]
|
|
1271
1279
|
+ self.zetaBar_in[i, n]
|
|
1272
|
-
+ self.
|
|
1280
|
+
+ self.piBar_in[i, n]
|
|
1273
1281
|
+ self.Lambda_in[i, n]
|
|
1274
1282
|
- 0.5 * fac * self.mu * self.kappa_ijn[i, 0, n]
|
|
1275
1283
|
)
|
|
@@ -1299,7 +1307,7 @@ class Plan(object):
|
|
|
1299
1307
|
row = A.newRow()
|
|
1300
1308
|
row.addElem(_q1(Ce, n, Nn), 1)
|
|
1301
1309
|
for i in range(Ni):
|
|
1302
|
-
rhs += self.omega_in[i, n] + 0.85 * self.zetaBar_in[i, n] + self.
|
|
1310
|
+
rhs += self.omega_in[i, n] + 0.85 * self.zetaBar_in[i, n] + self.piBar_in[i, n]
|
|
1303
1311
|
# Taxable income from tax-deferred withdrawals.
|
|
1304
1312
|
row.addElem(_q3(Cw, i, 1, n, Ni, Nj, Nn), -1)
|
|
1305
1313
|
row.addElem(_q2(Cx, i, n, Ni, Nn), -1)
|
|
@@ -2000,7 +2008,7 @@ class Plan(object):
|
|
|
2000
2008
|
sources = {}
|
|
2001
2009
|
sources['wages'] = self.omega_in
|
|
2002
2010
|
sources['ssec'] = self.zetaBar_in
|
|
2003
|
-
sources['pension'] = self.
|
|
2011
|
+
sources['pension'] = self.piBar_in
|
|
2004
2012
|
sources['txbl acc wdrwl'] = self.w_ijn[:, 0, :]
|
|
2005
2013
|
sources['RMD'] = self.rmd_in
|
|
2006
2014
|
sources['+dist'] = self.dist_in
|
|
@@ -2706,7 +2714,7 @@ class Plan(object):
|
|
|
2706
2714
|
cashFlowDic = {
|
|
2707
2715
|
'net spending': self.g_n,
|
|
2708
2716
|
'all wages': np.sum(self.omega_in, axis=0),
|
|
2709
|
-
'all pensions': np.sum(self.
|
|
2717
|
+
'all pensions': np.sum(self.piBar_in, axis=0),
|
|
2710
2718
|
'all soc sec': np.sum(self.zetaBar_in, axis=0),
|
|
2711
2719
|
"all BTI's": np.sum(self.Lambda_in, axis=0),
|
|
2712
2720
|
'all wdrwls': np.sum(self.w_ijn, axis=(0, 1)),
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2025.02.08"
|
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
import owlplanner as owl
|
|
3
3
|
|
|
4
4
|
|
|
5
|
+
def getWac(exdir, base):
|
|
6
|
+
import os
|
|
7
|
+
wac = base.replace('case_', '')
|
|
8
|
+
wac = wac.replace('-spending', '')
|
|
9
|
+
wac = wac.replace('-bequest', '')
|
|
10
|
+
wac = exdir + wac + '.xlsx'
|
|
11
|
+
if os.path.exists(wac):
|
|
12
|
+
return wac
|
|
13
|
+
else:
|
|
14
|
+
return ''
|
|
15
|
+
|
|
16
|
+
|
|
5
17
|
def test_allcases():
|
|
6
18
|
exdir = './examples/'
|
|
7
19
|
for case in ['case_john+sally',
|
|
@@ -10,6 +22,9 @@ def test_allcases():
|
|
|
10
22
|
'case_kim+sam-spending',
|
|
11
23
|
'case_kim+sam-bequest']:
|
|
12
24
|
p = owl.readConfig(exdir + case)
|
|
25
|
+
wac = getWac(exdir, case)
|
|
26
|
+
if wac != '':
|
|
27
|
+
p.readContributions(wac)
|
|
13
28
|
p.resolve()
|
|
14
29
|
|
|
15
30
|
|
|
@@ -17,6 +32,9 @@ def test_historical():
|
|
|
17
32
|
exdir = './examples/'
|
|
18
33
|
case = 'case_jack+jill'
|
|
19
34
|
p = owl.readConfig(exdir + case)
|
|
35
|
+
wac = getWac(exdir, case)
|
|
36
|
+
if wac != '':
|
|
37
|
+
p.readContributions(wac)
|
|
20
38
|
options = p.solverOptions
|
|
21
39
|
objective = p.objective
|
|
22
40
|
p.runHistoricalRange(objective, options, 1969, 2023)
|
|
@@ -26,6 +44,9 @@ def test_MC():
|
|
|
26
44
|
exdir = './examples/'
|
|
27
45
|
case = 'case_jack+jill'
|
|
28
46
|
p = owl.readConfig(exdir + case)
|
|
47
|
+
wac = getWac(exdir, case)
|
|
48
|
+
if wac != '':
|
|
49
|
+
p.readContributions(wac)
|
|
29
50
|
options = p.solverOptions
|
|
30
51
|
objective = p.objective
|
|
31
52
|
p.runMC(objective, options, 20)
|
|
@@ -5,7 +5,7 @@ import owlbridge as owb
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
def getPercentInput(i, j, keybase, text, defval=0):
|
|
8
|
-
nkey = keybase
|
|
8
|
+
nkey = f"{keybase}{j}_{i}"
|
|
9
9
|
kz.initKey(nkey, defval)
|
|
10
10
|
st.number_input(text, min_value=0, step=1, max_value=100,
|
|
11
11
|
value=kz.getKey(nkey),
|
|
@@ -14,7 +14,7 @@ def getPercentInput(i, j, keybase, text, defval=0):
|
|
|
14
14
|
|
|
15
15
|
ACC = ['taxable', 'tax-deferred', 'tax-free']
|
|
16
16
|
ASSET = ['S&P 500', 'Corp Bonds Baa', 'T-Notes', 'Cash Assets']
|
|
17
|
-
|
|
17
|
+
DEFALLOC = [60, 20, 10, 10]
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
def getIndividualAllocs(i, title, deco):
|
|
@@ -24,25 +24,25 @@ def getIndividualAllocs(i, title, deco):
|
|
|
24
24
|
cols = st.columns(4, gap='large', vertical_alignment='top')
|
|
25
25
|
for k1 in range(4):
|
|
26
26
|
with cols[k1]:
|
|
27
|
-
getPercentInput(i, k1, mydeco, ASSET[k1],
|
|
27
|
+
getPercentInput(i, k1, mydeco, ASSET[k1], DEFALLOC[k1])
|
|
28
28
|
checkIndividualAllocs(i, mydeco)
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
def getAccountAllocs(i, j, title, deco):
|
|
32
32
|
iname = kz.getKey('iname'+str(i))
|
|
33
|
-
mydeco = f
|
|
33
|
+
mydeco = f"j{j}_" + deco
|
|
34
34
|
st.write("###### %s's %s allocation for %s account (%%)" % (iname, title, ACC[j]))
|
|
35
35
|
cols = st.columns(4, gap='large', vertical_alignment='top')
|
|
36
36
|
for k1 in range(4):
|
|
37
37
|
with cols[k1]:
|
|
38
|
-
getPercentInput(i, k1, mydeco, ASSET[k1],
|
|
38
|
+
getPercentInput(i, k1, mydeco, ASSET[k1], DEFALLOC[k1])
|
|
39
39
|
checkAccountAllocs(i, mydeco)
|
|
40
40
|
|
|
41
41
|
|
|
42
42
|
def checkAccountAllocs(i, deco):
|
|
43
43
|
tot = 0
|
|
44
44
|
for k1 in range(4):
|
|
45
|
-
tot += int(kz.getKey(deco
|
|
45
|
+
tot += int(kz.getKey(f"{deco}{k1}_{i}"))
|
|
46
46
|
if abs(100-tot) > 0:
|
|
47
47
|
st.error('Percentages must add to 100%.')
|
|
48
48
|
return False
|
|
@@ -52,7 +52,7 @@ def checkAccountAllocs(i, deco):
|
|
|
52
52
|
def checkIndividualAllocs(i, deco):
|
|
53
53
|
tot = 0
|
|
54
54
|
for k1 in range(4):
|
|
55
|
-
tot += int(kz.getKey(deco
|
|
55
|
+
tot += int(kz.getKey(f"{deco}{k1}_{i}"))
|
|
56
56
|
if abs(100-tot) > 0:
|
|
57
57
|
st.error('Percentages must add to 100%.')
|
|
58
58
|
return False
|
|
@@ -3,24 +3,31 @@ import streamlit as st
|
|
|
3
3
|
import sskeys as kz
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
def getIntInput(i, key,
|
|
6
|
+
def getIntInput(i, key, thing, defval=0):
|
|
7
7
|
nkey = key+str(i)
|
|
8
8
|
kz.initKey(nkey, defval)
|
|
9
9
|
inamex = kz.getKey('iname'+str(i))
|
|
10
|
-
st.number_input("
|
|
10
|
+
st.number_input(f"{inamex}'s {thing}", min_value=0,
|
|
11
11
|
value=kz.getKey(nkey),
|
|
12
12
|
on_change=kz.setpull, args=[nkey], key='_'+nkey)
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
def getFloatInput(i, key,
|
|
15
|
+
def getFloatInput(i, key, thing, defval=0.):
|
|
16
16
|
nkey = key+str(i)
|
|
17
17
|
kz.initKey(nkey, defval)
|
|
18
18
|
inamex = kz.getKey('iname'+str(i))
|
|
19
|
-
st.number_input("
|
|
19
|
+
st.number_input(f"{inamex}'s {thing}", min_value=0., help=kz.help1000,
|
|
20
20
|
value=float(kz.getKey(nkey)), format='%.1f', step=10.,
|
|
21
21
|
on_change=kz.setpull, args=[nkey], key='_'+nkey)
|
|
22
22
|
|
|
23
23
|
|
|
24
|
+
def getToggleInput(i, key, thing):
|
|
25
|
+
nkey = key+str(i)
|
|
26
|
+
kz.initKey(nkey, False)
|
|
27
|
+
defval = kz.getKey(nkey)
|
|
28
|
+
st.toggle(thing, on_change=kz.setpull, value=defval, args=[nkey], key='_'+nkey)
|
|
29
|
+
|
|
30
|
+
|
|
24
31
|
ret = kz.titleBar('fixed')
|
|
25
32
|
kz.caseHeader("Fixed Income")
|
|
26
33
|
|
|
@@ -44,8 +51,10 @@ else:
|
|
|
44
51
|
with col1:
|
|
45
52
|
getFloatInput(0, 'pAmt', 'pension annual amount (\\$k)')
|
|
46
53
|
getIntInput(0, 'pAge', 'pension age', 65)
|
|
54
|
+
getToggleInput(0, 'pIdx', 'Inflafion adjusted')
|
|
47
55
|
|
|
48
56
|
with col2:
|
|
49
57
|
if kz.getKey('status') == 'married':
|
|
50
58
|
getFloatInput(1, 'pAmt', 'pension annual amount (\\$k)')
|
|
51
59
|
getIntInput(1, 'pAge', 'pension age', 65)
|
|
60
|
+
getToggleInput(1, 'pIdx', 'Inflafion adjusted')
|
|
@@ -69,14 +69,14 @@ def prepareRun(plan):
|
|
|
69
69
|
st.error('Setting account balances failed: %s' % e)
|
|
70
70
|
return
|
|
71
71
|
|
|
72
|
-
amounts, ages = kz.getFixedIncome(ni, 'p')
|
|
72
|
+
amounts, ages, indexed = kz.getFixedIncome(ni, 'p')
|
|
73
73
|
try:
|
|
74
|
-
plan.setPension(amounts, ages)
|
|
74
|
+
plan.setPension(amounts, ages, indexed)
|
|
75
75
|
except Exception as e:
|
|
76
76
|
st.error('Failed setting pensions: %s' % e)
|
|
77
77
|
return
|
|
78
78
|
|
|
79
|
-
amounts, ages = kz.getFixedIncome(ni, 'ss')
|
|
79
|
+
amounts, ages, indexed = kz.getFixedIncome(ni, 'ss')
|
|
80
80
|
try:
|
|
81
81
|
plan.setSocialSecurity(amounts, ages)
|
|
82
82
|
except Exception as e:
|
|
@@ -597,19 +597,20 @@ def genDic(plan):
|
|
|
597
597
|
dic['ssAmt'+str(i)] = plan.ssecAmounts[i]/1000
|
|
598
598
|
dic['pAge'+str(i)] = plan.pensionAges[i]
|
|
599
599
|
dic['pAmt'+str(i)] = plan.pensionAmounts[i]/1000
|
|
600
|
+
dic['pIdx'+str(i)] = plan.pensionIndexed[i]
|
|
600
601
|
for j1 in range(plan.N_j):
|
|
601
602
|
dic[accName[j1]+str(i)] = plan.beta_ij[i, j1]/1000
|
|
602
603
|
|
|
603
604
|
if plan.ARCoord == 'individual':
|
|
604
605
|
for k1 in range(plan.N_k):
|
|
605
|
-
dic[
|
|
606
|
-
dic[
|
|
606
|
+
dic[f"j3_init%{k1}_{i}"] = int(plan.boundsAR['generic'][i][0][k1])
|
|
607
|
+
dic[f"j3_fin%{k1}_{i}"] = int(plan.boundsAR['generic'][i][1][k1])
|
|
607
608
|
elif plan.ARCoord == 'account':
|
|
608
609
|
longAccName = ['taxable', 'tax-deferred', 'tax-free']
|
|
609
610
|
for j2 in range(3):
|
|
610
611
|
for k2 in range(plan.N_k):
|
|
611
|
-
dic[f
|
|
612
|
-
dic[f
|
|
612
|
+
dic[f"j{j2}_init%{k2}_{i}"] = int(plan.boundsAR[longAccName[j2]][i][0][k2])
|
|
613
|
+
dic[f"j{j2}_fin%{k2}_{i}"] = int(plan.boundsAR[longAccName[j2]][i][1][k2])
|
|
613
614
|
else:
|
|
614
615
|
st.error("Only 'individual' and 'account' asset allocations are currently supported")
|
|
615
616
|
return None
|
|
@@ -339,44 +339,29 @@ def getSolveParameters():
|
|
|
339
339
|
|
|
340
340
|
def getIndividualAllocationRatios():
|
|
341
341
|
generic = []
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
for k1 in range(4):
|
|
345
|
-
initial.append(int(getKey('j3_init%'+str(k1)+'_0')))
|
|
346
|
-
final.append(int(getKey('j3_fin%'+str(k1)+'_0')))
|
|
347
|
-
gen0 = [initial, final]
|
|
348
|
-
generic = [gen0]
|
|
349
|
-
|
|
350
|
-
if getKey('status') == 'married':
|
|
342
|
+
ni = 2 if getKey('status') == 'married' else 1
|
|
343
|
+
for i in range(ni):
|
|
351
344
|
initial = []
|
|
352
345
|
final = []
|
|
353
346
|
for k1 in range(4):
|
|
354
|
-
initial.append(int(getKey(
|
|
355
|
-
final.append(int(getKey(
|
|
356
|
-
|
|
357
|
-
generic.append(
|
|
347
|
+
initial.append(int(getKey(f"j3_init%{k1}_{i}")))
|
|
348
|
+
final.append(int(getKey(f"j3_fin%{k1}_{i}")))
|
|
349
|
+
gen = [initial, final]
|
|
350
|
+
generic.append(gen)
|
|
358
351
|
|
|
359
352
|
return generic
|
|
360
353
|
|
|
361
354
|
|
|
362
355
|
def getAccountAllocationRatios():
|
|
363
356
|
accounts = [[], [], []]
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
final = []
|
|
367
|
-
for k1 in range(4):
|
|
368
|
-
initial.append(int(getKey(f'j{j1}_init%'+str(k1)+'_0')))
|
|
369
|
-
final.append(int(getKey(f'j{j1}_fin%'+str(k1)+'_0')))
|
|
370
|
-
tmp = [initial, final]
|
|
371
|
-
accounts[j1].append(tmp)
|
|
372
|
-
|
|
373
|
-
if getKey('status') == 'married':
|
|
357
|
+
ni = 2 if getKey('status') == 'married' else 1
|
|
358
|
+
for i in range(ni):
|
|
374
359
|
for j1 in range(3):
|
|
375
360
|
initial = []
|
|
376
361
|
final = []
|
|
377
362
|
for k1 in range(4):
|
|
378
|
-
initial.append(int(getKey(f
|
|
379
|
-
final.append(int(getKey(f
|
|
363
|
+
initial.append(int(getKey(f"j{j1}_init%{k1}_{i}")))
|
|
364
|
+
final.append(int(getKey(f"j{j1}_fin%{k1}_{i}")))
|
|
380
365
|
tmp = [initial, final]
|
|
381
366
|
accounts[j1].append(tmp)
|
|
382
367
|
|
|
@@ -396,11 +381,14 @@ def getPreviousMAGIs():
|
|
|
396
381
|
def getFixedIncome(ni, what):
|
|
397
382
|
amounts = []
|
|
398
383
|
ages = []
|
|
384
|
+
indexed = []
|
|
399
385
|
for i in range(ni):
|
|
400
386
|
amounts.append(getKey(what+'Amt'+str(i)))
|
|
401
387
|
ages.append(getKey(what+'Age'+str(i)))
|
|
388
|
+
if what == 'p':
|
|
389
|
+
indexed.append(getKey(what+'Idx'+str(i)))
|
|
402
390
|
|
|
403
|
-
return amounts, ages
|
|
391
|
+
return amounts, ages, indexed
|
|
404
392
|
|
|
405
393
|
|
|
406
394
|
def getIntNum(text, nkey, disabled=False, callback=setpull, step=1, help=None, min_value=0, max_value=None):
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "2025.02.06"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|