owlplanner 2025.1.24__py3-none-any.whl → 2025.1.27__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/data/__init__.py +0 -0
- owlplanner/data/rates.csv +98 -0
- owlplanner/plan.py +13 -5
- owlplanner/rates.py +18 -658
- owlplanner/timelists.py +55 -76
- owlplanner/version.py +1 -1
- {owlplanner-2025.1.24.dist-info → owlplanner-2025.1.27.dist-info}/METADATA +11 -1
- owlplanner-2025.1.27.dist-info/RECORD +17 -0
- owlplanner/tax2024.py +0 -234
- owlplanner-2025.1.24.dist-info/RECORD +0 -16
- {owlplanner-2025.1.24.dist-info → owlplanner-2025.1.27.dist-info}/WHEEL +0 -0
- {owlplanner-2025.1.24.dist-info → owlplanner-2025.1.27.dist-info}/licenses/LICENSE +0 -0
|
File without changes
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
year,S&P 500,Bonds Baa,Bonds Aaa,TBills,TNotes,Inflation
|
|
2
|
+
1928,43.81,3.22,3.22,3.08,0.84,-1.16
|
|
3
|
+
1929,-8.3,3.02,3.02,3.16,4.2,0.58
|
|
4
|
+
1930,-25.12,0.54,0.54,4.55,4.54,-6.4
|
|
5
|
+
1931,-43.84,-15.68,-15.68,2.31,-2.56,-9.32
|
|
6
|
+
1932,-8.64,23.59,23.59,1.07,8.79,-10.27
|
|
7
|
+
1933,49.98,12.97,12.97,0.96,1.86,0.76
|
|
8
|
+
1934,-1.19,18.82,18.82,0.28,7.96,1.52
|
|
9
|
+
1935,46.74,13.31,13.31,0.17,4.47,2.99
|
|
10
|
+
1936,31.94,11.38,11.38,0.17,5.02,1.45
|
|
11
|
+
1937,-35.34,-4.42,-4.42,0.28,1.38,2.86
|
|
12
|
+
1938,29.28,9.24,9.24,0.07,4.21,-2.78
|
|
13
|
+
1939,-1.1,7.98,7.98,0.05,4.41,0.0
|
|
14
|
+
1940,-10.67,8.65,8.65,0.04,5.4,0.71
|
|
15
|
+
1941,-12.77,5.01,5.01,0.13,-2.02,9.93
|
|
16
|
+
1942,19.17,5.18,5.18,0.34,2.29,9.03
|
|
17
|
+
1943,25.06,8.04,8.04,0.38,2.49,2.96
|
|
18
|
+
1944,19.03,6.57,6.57,0.38,2.58,2.3
|
|
19
|
+
1945,35.82,6.8,6.8,0.38,3.8,2.25
|
|
20
|
+
1946,-8.43,2.51,2.51,0.38,3.13,18.13
|
|
21
|
+
1947,5.2,0.26,0.26,0.6,0.92,8.84
|
|
22
|
+
1948,5.7,3.44,3.44,1.05,1.95,2.99
|
|
23
|
+
1949,18.3,5.38,5.38,1.12,4.66,-2.07
|
|
24
|
+
1950,30.81,4.24,4.24,1.2,0.43,5.93
|
|
25
|
+
1951,23.68,-0.19,-0.19,1.52,-0.3,6.0
|
|
26
|
+
1952,18.15,4.44,4.44,1.72,2.27,0.75
|
|
27
|
+
1953,-1.21,1.62,1.62,1.89,4.14,0.75
|
|
28
|
+
1954,52.56,6.16,6.16,0.94,3.29,-0.74
|
|
29
|
+
1955,32.6,2.04,2.04,1.72,-1.34,0.37
|
|
30
|
+
1956,7.44,-2.35,-2.35,2.62,-2.26,2.99
|
|
31
|
+
1957,-10.46,-0.72,-0.72,3.22,6.8,2.9
|
|
32
|
+
1958,43.72,6.43,6.43,1.77,-2.1,1.76
|
|
33
|
+
1959,12.06,1.57,1.57,3.39,-2.65,1.73
|
|
34
|
+
1960,0.34,6.66,6.66,2.87,11.64,1.36
|
|
35
|
+
1961,26.64,5.1,5.1,2.35,2.06,0.67
|
|
36
|
+
1962,-8.81,6.5,6.5,2.77,5.69,1.33
|
|
37
|
+
1963,22.61,5.46,5.46,3.16,1.68,1.64
|
|
38
|
+
1964,16.42,5.16,5.16,3.55,3.73,0.97
|
|
39
|
+
1965,12.4,3.19,3.19,3.95,0.72,1.92
|
|
40
|
+
1966,-9.97,-3.45,-3.45,4.86,2.91,3.46
|
|
41
|
+
1967,23.8,0.9,0.9,4.29,-1.58,3.04
|
|
42
|
+
1968,10.81,4.85,4.85,5.34,3.27,4.72
|
|
43
|
+
1969,-8.24,-2.03,-2.03,6.67,-5.01,6.2
|
|
44
|
+
1970,3.56,5.65,5.65,6.39,16.75,5.57
|
|
45
|
+
1971,14.22,14.0,14.0,4.33,9.79,3.27
|
|
46
|
+
1972,18.76,11.41,11.41,4.06,2.82,3.41
|
|
47
|
+
1973,-14.31,4.32,4.32,7.04,3.66,8.71
|
|
48
|
+
1974,-25.9,-4.38,-4.38,7.85,1.99,12.34
|
|
49
|
+
1975,37.0,11.05,11.05,5.79,3.61,6.94
|
|
50
|
+
1976,23.83,19.75,19.75,4.98,15.98,4.86
|
|
51
|
+
1977,-6.98,9.95,9.95,5.26,1.29,6.7
|
|
52
|
+
1978,6.51,3.14,3.14,7.18,-0.78,9.02
|
|
53
|
+
1979,18.52,-2.01,-2.01,10.05,0.67,13.29
|
|
54
|
+
1980,31.74,-3.32,-3.32,11.39,-2.99,12.52
|
|
55
|
+
1981,-4.7,8.46,8.46,14.04,8.2,8.92
|
|
56
|
+
1982,20.42,29.05,29.05,10.6,32.81,3.83
|
|
57
|
+
1983,22.34,16.19,16.19,8.62,3.2,3.79
|
|
58
|
+
1984,6.15,15.62,15.62,9.54,13.73,3.95
|
|
59
|
+
1985,31.24,23.86,23.86,7.47,25.71,3.8
|
|
60
|
+
1986,18.49,21.35,21.35,5.97,24.28,1.1
|
|
61
|
+
1987,5.81,2.81,2.81,5.78,-4.96,4.43
|
|
62
|
+
1988,16.54,14.38,14.38,6.67,8.22,4.42
|
|
63
|
+
1989,31.48,15.95,15.95,8.11,17.69,4.65
|
|
64
|
+
1990,-3.06,6.28,6.28,7.5,6.24,6.11
|
|
65
|
+
1991,30.23,18.93,18.93,5.38,15.0,3.06
|
|
66
|
+
1992,7.49,11.31,11.31,3.43,9.36,2.9
|
|
67
|
+
1993,9.97,15.47,15.47,3.0,14.21,2.75
|
|
68
|
+
1994,1.33,-0.97,-0.97,4.25,-8.04,2.67
|
|
69
|
+
1995,37.2,21.29,21.29,5.49,23.48,2.54
|
|
70
|
+
1996,22.68,3.42,3.42,5.01,1.43,3.32
|
|
71
|
+
1997,33.1,12.75,12.75,5.06,9.94,1.7
|
|
72
|
+
1998,28.34,7.63,7.63,4.78,14.92,1.61
|
|
73
|
+
1999,20.89,0.91,0.91,4.64,-8.25,2.68
|
|
74
|
+
2000,-9.03,9.39,9.39,5.82,16.66,3.39
|
|
75
|
+
2001,-11.85,8.54,8.54,3.4,5.57,1.55
|
|
76
|
+
2002,-21.97,12.14,12.14,1.61,15.12,2.38
|
|
77
|
+
2003,28.36,12.32,12.32,1.01,0.38,1.88
|
|
78
|
+
2004,10.74,10.35,10.35,1.37,4.49,3.26
|
|
79
|
+
2005,4.83,5.3,5.3,3.15,2.87,3.42
|
|
80
|
+
2006,15.61,5.2,5.2,4.73,1.96,2.54
|
|
81
|
+
2007,5.48,4.84,4.84,4.36,10.21,4.08
|
|
82
|
+
2008,-36.55,-3.54,-3.54,1.37,20.1,0.09
|
|
83
|
+
2009,25.94,20.21,20.21,0.15,-11.12,2.72
|
|
84
|
+
2010,14.82,9.41,9.41,0.14,8.46,1.5
|
|
85
|
+
2011,2.1,12.26,12.26,0.05,16.04,2.96
|
|
86
|
+
2012,15.89,9.33,9.33,0.09,2.97,1.74
|
|
87
|
+
2013,32.15,-0.98,-0.98,0.06,-9.1,1.5
|
|
88
|
+
2014,13.52,10.78,10.78,0.03,10.75,0.76
|
|
89
|
+
2015,1.38,-1.5,-1.5,0.05,1.28,0.73
|
|
90
|
+
2016,11.77,11.52,11.52,0.32,0.69,2.07
|
|
91
|
+
2017,21.61,9.23,9.23,0.93,2.8,2.11
|
|
92
|
+
2018,-4.23,-3.27,-3.27,1.94,-0.02,1.91
|
|
93
|
+
2019,31.21,15.25,15.25,2.06,9.64,2.29
|
|
94
|
+
2020,18.02,10.6,10.6,0.35,11.33,1.36
|
|
95
|
+
2021,28.47,0.93,0.93,0.05,-4.42,7.04
|
|
96
|
+
2022,-18.04,-15.14,-15.14,2.02,-17.83,6.45
|
|
97
|
+
2023,26.06,8.74,8.74,5.07,3.88,3.35
|
|
98
|
+
2024,24.88,1.74,1.74,4.97,-1.64,2.75
|
owlplanner/plan.py
CHANGED
|
@@ -315,7 +315,7 @@ class Plan(object):
|
|
|
315
315
|
self._adjustedParameters = False
|
|
316
316
|
self.timeListsFileName = "None"
|
|
317
317
|
self.timeLists = {}
|
|
318
|
-
self.
|
|
318
|
+
self.zeroContributions()
|
|
319
319
|
self.caseStatus = 'unsolved'
|
|
320
320
|
self.rateMethod = None
|
|
321
321
|
|
|
@@ -900,10 +900,18 @@ class Plan(object):
|
|
|
900
900
|
try:
|
|
901
901
|
filename, self.timeLists = timelists.read(filename, self.inames, self.horizons, self.mylog)
|
|
902
902
|
except Exception as e:
|
|
903
|
-
raise Exception('Unsuccessful read: %s' % e)
|
|
903
|
+
raise Exception('Unsuccessful read of contributions: %s' % e)
|
|
904
|
+
return False
|
|
904
905
|
|
|
905
|
-
timelists.check(self.inames, self.timeLists, self.horizons)
|
|
906
906
|
self.timeListsFileName = filename
|
|
907
|
+
self.setContributions()
|
|
908
|
+
|
|
909
|
+
return True
|
|
910
|
+
|
|
911
|
+
def setContributions(self, timeLists=None):
|
|
912
|
+
if timeLists is not None:
|
|
913
|
+
timelists.check(timeLists, self.inames, self.horizons)
|
|
914
|
+
self.timeLists = timeLists
|
|
907
915
|
|
|
908
916
|
# Now fill in parameters which are in $.
|
|
909
917
|
for i, iname in enumerate(self.inames):
|
|
@@ -926,7 +934,7 @@ class Plan(object):
|
|
|
926
934
|
|
|
927
935
|
self.caseStatus = 'modified'
|
|
928
936
|
|
|
929
|
-
return
|
|
937
|
+
return self.timeLists
|
|
930
938
|
|
|
931
939
|
def saveContributions(self):
|
|
932
940
|
"""
|
|
@@ -954,7 +962,7 @@ class Plan(object):
|
|
|
954
962
|
|
|
955
963
|
return wb
|
|
956
964
|
|
|
957
|
-
def
|
|
965
|
+
def zeroContributions(self):
|
|
958
966
|
"""
|
|
959
967
|
Reset all contributions variables to zero.
|
|
960
968
|
"""
|
owlplanner/rates.py
CHANGED
|
@@ -31,6 +31,8 @@ Disclaimer: This program comes with no guarantee. Use at your own risk.
|
|
|
31
31
|
###################################################################
|
|
32
32
|
import numpy as np
|
|
33
33
|
import pandas as pd
|
|
34
|
+
import pkgutil
|
|
35
|
+
import io
|
|
34
36
|
from datetime import date
|
|
35
37
|
|
|
36
38
|
from owlplanner import logging
|
|
@@ -41,673 +43,31 @@ from owlplanner import utils as u
|
|
|
41
43
|
FROM = 1928
|
|
42
44
|
TO = 2024
|
|
43
45
|
|
|
46
|
+
try:
|
|
47
|
+
csvBytes = pkgutil.get_data('owlplanner', 'data/rates.csv')
|
|
48
|
+
csvFile = io.BytesIO(csvBytes)
|
|
49
|
+
df = pd.read_csv(csvFile)
|
|
50
|
+
except Exception as e:
|
|
51
|
+
raise RuntimeError(f'Could not find rates data file: {e}')
|
|
52
|
+
|
|
53
|
+
|
|
44
54
|
# Annual rate of return (%) of S&P 500 since 1928, including dividends.
|
|
45
|
-
SP500 = [
|
|
46
|
-
43.81,
|
|
47
|
-
-8.30,
|
|
48
|
-
# 1930
|
|
49
|
-
-25.12,
|
|
50
|
-
-43.84,
|
|
51
|
-
-8.64,
|
|
52
|
-
49.98,
|
|
53
|
-
-1.19,
|
|
54
|
-
46.74,
|
|
55
|
-
31.94,
|
|
56
|
-
-35.34,
|
|
57
|
-
29.28,
|
|
58
|
-
-1.10,
|
|
59
|
-
# 1940
|
|
60
|
-
-10.67,
|
|
61
|
-
-12.77,
|
|
62
|
-
19.17,
|
|
63
|
-
25.06,
|
|
64
|
-
19.03,
|
|
65
|
-
35.82,
|
|
66
|
-
-8.43,
|
|
67
|
-
5.20,
|
|
68
|
-
5.70,
|
|
69
|
-
18.30,
|
|
70
|
-
# 1950
|
|
71
|
-
30.81,
|
|
72
|
-
23.68,
|
|
73
|
-
18.15,
|
|
74
|
-
-1.21,
|
|
75
|
-
52.56,
|
|
76
|
-
32.60,
|
|
77
|
-
7.44,
|
|
78
|
-
-10.46,
|
|
79
|
-
43.72,
|
|
80
|
-
12.06,
|
|
81
|
-
# 1960
|
|
82
|
-
0.34,
|
|
83
|
-
26.64,
|
|
84
|
-
-8.81,
|
|
85
|
-
22.61,
|
|
86
|
-
16.42,
|
|
87
|
-
12.40,
|
|
88
|
-
-9.97,
|
|
89
|
-
23.80,
|
|
90
|
-
10.81,
|
|
91
|
-
-8.24,
|
|
92
|
-
# 1970
|
|
93
|
-
3.56,
|
|
94
|
-
14.22,
|
|
95
|
-
18.76,
|
|
96
|
-
-14.31,
|
|
97
|
-
-25.90,
|
|
98
|
-
37.00,
|
|
99
|
-
23.83,
|
|
100
|
-
-6.98,
|
|
101
|
-
6.51,
|
|
102
|
-
18.52,
|
|
103
|
-
# 1980
|
|
104
|
-
31.74,
|
|
105
|
-
-4.70,
|
|
106
|
-
20.42,
|
|
107
|
-
22.34,
|
|
108
|
-
6.15,
|
|
109
|
-
31.24,
|
|
110
|
-
18.49,
|
|
111
|
-
5.81,
|
|
112
|
-
16.54,
|
|
113
|
-
31.48,
|
|
114
|
-
# 1990
|
|
115
|
-
-3.06,
|
|
116
|
-
30.23,
|
|
117
|
-
7.49,
|
|
118
|
-
9.97,
|
|
119
|
-
1.33,
|
|
120
|
-
37.20,
|
|
121
|
-
22.68,
|
|
122
|
-
33.10,
|
|
123
|
-
28.34,
|
|
124
|
-
20.89,
|
|
125
|
-
# 2000
|
|
126
|
-
-9.03,
|
|
127
|
-
-11.85,
|
|
128
|
-
-21.97,
|
|
129
|
-
28.36,
|
|
130
|
-
10.74,
|
|
131
|
-
4.83,
|
|
132
|
-
15.61,
|
|
133
|
-
5.48,
|
|
134
|
-
-36.55,
|
|
135
|
-
25.94,
|
|
136
|
-
# 2010
|
|
137
|
-
14.82,
|
|
138
|
-
2.10,
|
|
139
|
-
15.89,
|
|
140
|
-
32.15,
|
|
141
|
-
13.52,
|
|
142
|
-
1.38,
|
|
143
|
-
11.77,
|
|
144
|
-
21.61,
|
|
145
|
-
-4.23,
|
|
146
|
-
31.21,
|
|
147
|
-
# 2020
|
|
148
|
-
18.02,
|
|
149
|
-
28.47,
|
|
150
|
-
-18.04,
|
|
151
|
-
26.06,
|
|
152
|
-
24.88,
|
|
153
|
-
]
|
|
55
|
+
SP500 = df['S&P 500']
|
|
154
56
|
|
|
155
57
|
# Annual rate of return (%) of Baa Corporate Bonds since 1928.
|
|
156
|
-
BondsBaa = [
|
|
157
|
-
3.22,
|
|
158
|
-
3.02,
|
|
159
|
-
# 1930
|
|
160
|
-
0.54,
|
|
161
|
-
-15.68,
|
|
162
|
-
23.59,
|
|
163
|
-
12.97,
|
|
164
|
-
18.82,
|
|
165
|
-
13.31,
|
|
166
|
-
11.38,
|
|
167
|
-
-4.42,
|
|
168
|
-
9.24,
|
|
169
|
-
7.98,
|
|
170
|
-
# 1940
|
|
171
|
-
8.65,
|
|
172
|
-
5.01,
|
|
173
|
-
5.18,
|
|
174
|
-
8.04,
|
|
175
|
-
6.57,
|
|
176
|
-
6.80,
|
|
177
|
-
2.51,
|
|
178
|
-
0.26,
|
|
179
|
-
3.44,
|
|
180
|
-
5.38,
|
|
181
|
-
# 1950
|
|
182
|
-
4.24,
|
|
183
|
-
-0.19,
|
|
184
|
-
4.44,
|
|
185
|
-
1.62,
|
|
186
|
-
6.16,
|
|
187
|
-
2.04,
|
|
188
|
-
-2.35,
|
|
189
|
-
-0.72,
|
|
190
|
-
6.43,
|
|
191
|
-
1.57,
|
|
192
|
-
# 1960
|
|
193
|
-
6.66,
|
|
194
|
-
5.10,
|
|
195
|
-
6.50,
|
|
196
|
-
5.46,
|
|
197
|
-
5.16,
|
|
198
|
-
3.19,
|
|
199
|
-
-3.45,
|
|
200
|
-
0.90,
|
|
201
|
-
4.85,
|
|
202
|
-
-2.03,
|
|
203
|
-
# 1970
|
|
204
|
-
5.65,
|
|
205
|
-
14.00,
|
|
206
|
-
11.41,
|
|
207
|
-
4.32,
|
|
208
|
-
-4.38,
|
|
209
|
-
11.05,
|
|
210
|
-
19.75,
|
|
211
|
-
9.95,
|
|
212
|
-
3.14,
|
|
213
|
-
-2.01,
|
|
214
|
-
# 1980
|
|
215
|
-
-3.32,
|
|
216
|
-
8.46,
|
|
217
|
-
29.05,
|
|
218
|
-
16.19,
|
|
219
|
-
15.62,
|
|
220
|
-
23.86,
|
|
221
|
-
21.35,
|
|
222
|
-
2.81,
|
|
223
|
-
14.38,
|
|
224
|
-
15.95,
|
|
225
|
-
# 1990
|
|
226
|
-
6.28,
|
|
227
|
-
18.93,
|
|
228
|
-
11.31,
|
|
229
|
-
15.47,
|
|
230
|
-
-0.97,
|
|
231
|
-
21.29,
|
|
232
|
-
3.42,
|
|
233
|
-
12.75,
|
|
234
|
-
7.63,
|
|
235
|
-
0.91,
|
|
236
|
-
# 2000
|
|
237
|
-
9.39,
|
|
238
|
-
8.54,
|
|
239
|
-
12.14,
|
|
240
|
-
12.32,
|
|
241
|
-
10.35,
|
|
242
|
-
5.30,
|
|
243
|
-
5.20,
|
|
244
|
-
4.84,
|
|
245
|
-
-3.54,
|
|
246
|
-
20.21,
|
|
247
|
-
# 2010
|
|
248
|
-
9.41,
|
|
249
|
-
12.26,
|
|
250
|
-
9.33,
|
|
251
|
-
-0.98,
|
|
252
|
-
10.78,
|
|
253
|
-
-1.50,
|
|
254
|
-
11.52,
|
|
255
|
-
9.23,
|
|
256
|
-
-3.27,
|
|
257
|
-
15.25,
|
|
258
|
-
# 2020
|
|
259
|
-
10.60,
|
|
260
|
-
0.93,
|
|
261
|
-
-15.14,
|
|
262
|
-
8.74,
|
|
263
|
-
1.74,
|
|
264
|
-
]
|
|
58
|
+
BondsBaa = df['Bonds Baa']
|
|
265
59
|
|
|
266
60
|
# Annual rate of return (%) of Aaa Corporate Bonds since 1928.
|
|
267
|
-
BondsAaa = [
|
|
268
|
-
3.28,
|
|
269
|
-
4.14,
|
|
270
|
-
# 1930
|
|
271
|
-
5.86,
|
|
272
|
-
-1.56,
|
|
273
|
-
11.07,
|
|
274
|
-
5.30,
|
|
275
|
-
10.15,
|
|
276
|
-
6.90,
|
|
277
|
-
6.33,
|
|
278
|
-
2.17,
|
|
279
|
-
4.31,
|
|
280
|
-
4.28,
|
|
281
|
-
# 1940
|
|
282
|
-
4.93,
|
|
283
|
-
1.93,
|
|
284
|
-
2.71,
|
|
285
|
-
3.42,
|
|
286
|
-
3.09,
|
|
287
|
-
3.48,
|
|
288
|
-
2.61,
|
|
289
|
-
0.46,
|
|
290
|
-
3.46,
|
|
291
|
-
4.62,
|
|
292
|
-
# 1950
|
|
293
|
-
1.80,
|
|
294
|
-
-0.23,
|
|
295
|
-
3.35,
|
|
296
|
-
1.61,
|
|
297
|
-
5.10,
|
|
298
|
-
0.78,
|
|
299
|
-
-1.78,
|
|
300
|
-
3.26,
|
|
301
|
-
1.63,
|
|
302
|
-
0.14,
|
|
303
|
-
# 1960
|
|
304
|
-
6.41,
|
|
305
|
-
3.79,
|
|
306
|
-
5.86,
|
|
307
|
-
3.36,
|
|
308
|
-
3.64,
|
|
309
|
-
2.56,
|
|
310
|
-
-0.70,
|
|
311
|
-
-0.45,
|
|
312
|
-
4.32,
|
|
313
|
-
-2.18,
|
|
314
|
-
# 1970
|
|
315
|
-
8.27,
|
|
316
|
-
10.35,
|
|
317
|
-
8.44,
|
|
318
|
-
3.00,
|
|
319
|
-
-0.12,
|
|
320
|
-
9.54,
|
|
321
|
-
14.23,
|
|
322
|
-
6.58,
|
|
323
|
-
2.01,
|
|
324
|
-
-0.25,
|
|
325
|
-
# 1980
|
|
326
|
-
-2.55,
|
|
327
|
-
7.94,
|
|
328
|
-
27.89,
|
|
329
|
-
7.85,
|
|
330
|
-
14.80,
|
|
331
|
-
25.97,
|
|
332
|
-
18.95,
|
|
333
|
-
-0.85,
|
|
334
|
-
12.87,
|
|
335
|
-
14.39,
|
|
336
|
-
# 1990
|
|
337
|
-
7.72,
|
|
338
|
-
14.98,
|
|
339
|
-
9.84,
|
|
340
|
-
14.30,
|
|
341
|
-
-2.78,
|
|
342
|
-
21.16,
|
|
343
|
-
2.83,
|
|
344
|
-
11.26,
|
|
345
|
-
10.20,
|
|
346
|
-
-3.39,
|
|
347
|
-
# 2000
|
|
348
|
-
10.99,
|
|
349
|
-
11.09,
|
|
350
|
-
10.42,
|
|
351
|
-
9.54,
|
|
352
|
-
7.14,
|
|
353
|
-
6.73,
|
|
354
|
-
3.75,
|
|
355
|
-
5.84,
|
|
356
|
-
10.97,
|
|
357
|
-
-0.09,
|
|
358
|
-
# 2010
|
|
359
|
-
8.83,
|
|
360
|
-
13.99,
|
|
361
|
-
4.59,
|
|
362
|
-
-3.43,
|
|
363
|
-
11.56,
|
|
364
|
-
1.13,
|
|
365
|
-
4.53,
|
|
366
|
-
8.40,
|
|
367
|
-
-0.93,
|
|
368
|
-
12.08,
|
|
369
|
-
# 2020
|
|
370
|
-
10.23,
|
|
371
|
-
-1.93,
|
|
372
|
-
-12.88,
|
|
373
|
-
5.09,
|
|
374
|
-
-1.03,
|
|
375
|
-
]
|
|
61
|
+
BondsAaa = df['Bonds Aaa']
|
|
376
62
|
|
|
377
63
|
# Annual rate of return (%) for 10-y Treasury notes since 1928.
|
|
378
|
-
TNotes = [
|
|
379
|
-
0.84,
|
|
380
|
-
4.20,
|
|
381
|
-
# 1930
|
|
382
|
-
4.54,
|
|
383
|
-
-2.56,
|
|
384
|
-
8.79,
|
|
385
|
-
1.86,
|
|
386
|
-
7.96,
|
|
387
|
-
4.47,
|
|
388
|
-
5.02,
|
|
389
|
-
1.38,
|
|
390
|
-
4.21,
|
|
391
|
-
4.41,
|
|
392
|
-
# 1940
|
|
393
|
-
5.40,
|
|
394
|
-
-2.02,
|
|
395
|
-
2.29,
|
|
396
|
-
2.49,
|
|
397
|
-
2.58,
|
|
398
|
-
3.80,
|
|
399
|
-
3.13,
|
|
400
|
-
0.92,
|
|
401
|
-
1.95,
|
|
402
|
-
4.66,
|
|
403
|
-
# 1950
|
|
404
|
-
0.43,
|
|
405
|
-
-0.30,
|
|
406
|
-
2.27,
|
|
407
|
-
4.14,
|
|
408
|
-
3.29,
|
|
409
|
-
-1.34,
|
|
410
|
-
-2.26,
|
|
411
|
-
6.80,
|
|
412
|
-
-2.10,
|
|
413
|
-
-2.65,
|
|
414
|
-
# 1960
|
|
415
|
-
11.64,
|
|
416
|
-
2.06,
|
|
417
|
-
5.69,
|
|
418
|
-
1.68,
|
|
419
|
-
3.73,
|
|
420
|
-
0.72,
|
|
421
|
-
2.91,
|
|
422
|
-
-1.58,
|
|
423
|
-
3.27,
|
|
424
|
-
-5.01,
|
|
425
|
-
# 1970
|
|
426
|
-
16.75,
|
|
427
|
-
9.79,
|
|
428
|
-
2.82,
|
|
429
|
-
3.66,
|
|
430
|
-
1.99,
|
|
431
|
-
3.61,
|
|
432
|
-
15.98,
|
|
433
|
-
1.29,
|
|
434
|
-
-0.78,
|
|
435
|
-
0.67,
|
|
436
|
-
# 1980
|
|
437
|
-
-2.99,
|
|
438
|
-
8.20,
|
|
439
|
-
32.81,
|
|
440
|
-
3.20,
|
|
441
|
-
13.73,
|
|
442
|
-
25.71,
|
|
443
|
-
24.28,
|
|
444
|
-
-4.96,
|
|
445
|
-
8.22,
|
|
446
|
-
17.69,
|
|
447
|
-
# 1990
|
|
448
|
-
6.24,
|
|
449
|
-
15.00,
|
|
450
|
-
9.36,
|
|
451
|
-
14.21,
|
|
452
|
-
-8.04,
|
|
453
|
-
23.48,
|
|
454
|
-
1.43,
|
|
455
|
-
9.94,
|
|
456
|
-
14.92,
|
|
457
|
-
-8.25,
|
|
458
|
-
# 2000
|
|
459
|
-
16.66,
|
|
460
|
-
5.57,
|
|
461
|
-
15.12,
|
|
462
|
-
0.38,
|
|
463
|
-
4.49,
|
|
464
|
-
2.87,
|
|
465
|
-
1.96,
|
|
466
|
-
10.21,
|
|
467
|
-
20.10,
|
|
468
|
-
-11.12,
|
|
469
|
-
# 2010
|
|
470
|
-
8.46,
|
|
471
|
-
16.04,
|
|
472
|
-
2.97,
|
|
473
|
-
-9.10,
|
|
474
|
-
10.75,
|
|
475
|
-
1.28,
|
|
476
|
-
0.69,
|
|
477
|
-
2.80,
|
|
478
|
-
-0.02,
|
|
479
|
-
9.64,
|
|
480
|
-
# 2020
|
|
481
|
-
11.33,
|
|
482
|
-
-4.42,
|
|
483
|
-
-17.83,
|
|
484
|
-
3.88,
|
|
485
|
-
-1.64,
|
|
486
|
-
]
|
|
64
|
+
TNotes = df['TNotes']
|
|
487
65
|
|
|
488
66
|
# Annual rates of return for 3-month Treasury bills since 1928.
|
|
489
|
-
TBills = [
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
4.55,
|
|
494
|
-
2.31,
|
|
495
|
-
1.07,
|
|
496
|
-
0.96,
|
|
497
|
-
0.28,
|
|
498
|
-
0.17,
|
|
499
|
-
0.17,
|
|
500
|
-
0.28,
|
|
501
|
-
0.07,
|
|
502
|
-
0.05,
|
|
503
|
-
# 1940
|
|
504
|
-
0.04,
|
|
505
|
-
0.13,
|
|
506
|
-
0.34,
|
|
507
|
-
0.38,
|
|
508
|
-
0.38,
|
|
509
|
-
0.38,
|
|
510
|
-
0.38,
|
|
511
|
-
0.60,
|
|
512
|
-
1.05,
|
|
513
|
-
1.12,
|
|
514
|
-
# 1950
|
|
515
|
-
1.20,
|
|
516
|
-
1.52,
|
|
517
|
-
1.72,
|
|
518
|
-
1.89,
|
|
519
|
-
0.94,
|
|
520
|
-
1.72,
|
|
521
|
-
2.62,
|
|
522
|
-
3.22,
|
|
523
|
-
1.77,
|
|
524
|
-
3.39,
|
|
525
|
-
# 1960
|
|
526
|
-
2.87,
|
|
527
|
-
2.35,
|
|
528
|
-
2.77,
|
|
529
|
-
3.16,
|
|
530
|
-
3.55,
|
|
531
|
-
3.95,
|
|
532
|
-
4.86,
|
|
533
|
-
4.29,
|
|
534
|
-
5.34,
|
|
535
|
-
6.67,
|
|
536
|
-
# 1970
|
|
537
|
-
6.39,
|
|
538
|
-
4.33,
|
|
539
|
-
4.06,
|
|
540
|
-
7.04,
|
|
541
|
-
7.85,
|
|
542
|
-
5.79,
|
|
543
|
-
4.98,
|
|
544
|
-
5.26,
|
|
545
|
-
7.18,
|
|
546
|
-
10.05,
|
|
547
|
-
# 1980
|
|
548
|
-
11.39,
|
|
549
|
-
14.04,
|
|
550
|
-
10.60,
|
|
551
|
-
8.62,
|
|
552
|
-
9.54,
|
|
553
|
-
7.47,
|
|
554
|
-
5.97,
|
|
555
|
-
5.78,
|
|
556
|
-
6.67,
|
|
557
|
-
8.11,
|
|
558
|
-
# 1990
|
|
559
|
-
7.50,
|
|
560
|
-
5.38,
|
|
561
|
-
3.43,
|
|
562
|
-
3.00,
|
|
563
|
-
4.25,
|
|
564
|
-
5.49,
|
|
565
|
-
5.01,
|
|
566
|
-
5.06,
|
|
567
|
-
4.78,
|
|
568
|
-
4.64,
|
|
569
|
-
# 2000
|
|
570
|
-
5.82,
|
|
571
|
-
3.40,
|
|
572
|
-
1.61,
|
|
573
|
-
1.01,
|
|
574
|
-
1.37,
|
|
575
|
-
3.15,
|
|
576
|
-
4.73,
|
|
577
|
-
4.36,
|
|
578
|
-
1.37,
|
|
579
|
-
0.15,
|
|
580
|
-
# 2010
|
|
581
|
-
0.14,
|
|
582
|
-
0.05,
|
|
583
|
-
0.09,
|
|
584
|
-
0.06,
|
|
585
|
-
0.03,
|
|
586
|
-
0.05,
|
|
587
|
-
0.32,
|
|
588
|
-
0.93,
|
|
589
|
-
1.94,
|
|
590
|
-
2.06,
|
|
591
|
-
# 2020
|
|
592
|
-
0.35,
|
|
593
|
-
0.05,
|
|
594
|
-
2.02,
|
|
595
|
-
5.07,
|
|
596
|
-
4.97,
|
|
597
|
-
]
|
|
598
|
-
|
|
599
|
-
# Inflation rate as U.S. CPI index (%) since 1928 (1914).
|
|
600
|
-
Inflation = [
|
|
601
|
-
# 1.00, 1.98, 12.62, 18.10, 20.44, 14.55, 2.65, # 1920
|
|
602
|
-
# -10.82, -2.31, 2.37, 0.00, 3.47, -1.12, -2.26,
|
|
603
|
-
-1.16,
|
|
604
|
-
0.58,
|
|
605
|
-
# 1930
|
|
606
|
-
-6.40,
|
|
607
|
-
-9.32,
|
|
608
|
-
-10.27,
|
|
609
|
-
0.76,
|
|
610
|
-
1.52,
|
|
611
|
-
2.99,
|
|
612
|
-
1.45,
|
|
613
|
-
2.86,
|
|
614
|
-
-2.78,
|
|
615
|
-
0.00,
|
|
616
|
-
# 1940
|
|
617
|
-
0.71,
|
|
618
|
-
9.93,
|
|
619
|
-
9.03,
|
|
620
|
-
2.96,
|
|
621
|
-
2.30,
|
|
622
|
-
2.25,
|
|
623
|
-
18.13,
|
|
624
|
-
8.84,
|
|
625
|
-
2.99,
|
|
626
|
-
-2.07,
|
|
627
|
-
# 1950
|
|
628
|
-
5.93,
|
|
629
|
-
6.00,
|
|
630
|
-
0.75,
|
|
631
|
-
0.75,
|
|
632
|
-
-0.74,
|
|
633
|
-
0.37,
|
|
634
|
-
2.99,
|
|
635
|
-
2.90,
|
|
636
|
-
1.76,
|
|
637
|
-
1.73,
|
|
638
|
-
# 1960
|
|
639
|
-
1.36,
|
|
640
|
-
0.67,
|
|
641
|
-
1.33,
|
|
642
|
-
1.64,
|
|
643
|
-
0.97,
|
|
644
|
-
1.92,
|
|
645
|
-
3.46,
|
|
646
|
-
3.04,
|
|
647
|
-
4.72,
|
|
648
|
-
6.20,
|
|
649
|
-
# 1970
|
|
650
|
-
5.57,
|
|
651
|
-
3.27,
|
|
652
|
-
3.41,
|
|
653
|
-
8.71,
|
|
654
|
-
12.34,
|
|
655
|
-
6.94,
|
|
656
|
-
4.86,
|
|
657
|
-
6.70,
|
|
658
|
-
9.02,
|
|
659
|
-
13.29,
|
|
660
|
-
# 1980
|
|
661
|
-
12.52,
|
|
662
|
-
8.92,
|
|
663
|
-
3.83,
|
|
664
|
-
3.79,
|
|
665
|
-
3.95,
|
|
666
|
-
3.80,
|
|
667
|
-
1.10,
|
|
668
|
-
4.43,
|
|
669
|
-
4.42,
|
|
670
|
-
4.65,
|
|
671
|
-
# 1990
|
|
672
|
-
6.11,
|
|
673
|
-
3.06,
|
|
674
|
-
2.90,
|
|
675
|
-
2.75,
|
|
676
|
-
2.67,
|
|
677
|
-
2.54,
|
|
678
|
-
3.32,
|
|
679
|
-
1.70,
|
|
680
|
-
1.61,
|
|
681
|
-
2.68,
|
|
682
|
-
# 2000
|
|
683
|
-
3.39,
|
|
684
|
-
1.55,
|
|
685
|
-
2.38,
|
|
686
|
-
1.88,
|
|
687
|
-
3.26,
|
|
688
|
-
3.42,
|
|
689
|
-
2.54,
|
|
690
|
-
4.08,
|
|
691
|
-
0.09,
|
|
692
|
-
2.72,
|
|
693
|
-
# 2010
|
|
694
|
-
1.50,
|
|
695
|
-
2.96,
|
|
696
|
-
1.74,
|
|
697
|
-
1.50,
|
|
698
|
-
0.76,
|
|
699
|
-
0.73,
|
|
700
|
-
2.07,
|
|
701
|
-
2.11,
|
|
702
|
-
1.91,
|
|
703
|
-
2.29,
|
|
704
|
-
# 2020
|
|
705
|
-
1.36,
|
|
706
|
-
7.04,
|
|
707
|
-
6.45,
|
|
708
|
-
3.35,
|
|
709
|
-
2.75,
|
|
710
|
-
]
|
|
67
|
+
TBills = df['TBills']
|
|
68
|
+
|
|
69
|
+
# Inflation rate as U.S. CPI index (%) since 1928.
|
|
70
|
+
Inflation = df['Inflation']
|
|
711
71
|
|
|
712
72
|
|
|
713
73
|
def getRatesDistributions(frm, to, mylog=None):
|
owlplanner/timelists.py
CHANGED
|
@@ -19,60 +19,76 @@ from datetime import date
|
|
|
19
19
|
import pandas as pd
|
|
20
20
|
|
|
21
21
|
|
|
22
|
+
# Expected headers in each excel sheet, one per individual.
|
|
23
|
+
timeHorizonItems = [
|
|
24
|
+
'year',
|
|
25
|
+
'anticipated wages',
|
|
26
|
+
'ctrb taxable',
|
|
27
|
+
'ctrb 401k',
|
|
28
|
+
'ctrb Roth 401k',
|
|
29
|
+
'ctrb IRA',
|
|
30
|
+
'ctrb Roth IRA',
|
|
31
|
+
'Roth X',
|
|
32
|
+
'big-ticket items',
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
|
|
22
36
|
def read(finput, inames, horizons, mylog):
|
|
23
37
|
"""
|
|
24
38
|
Read listed parameters from an excel spreadsheet or through
|
|
25
39
|
a dictionary of dataframes through Pandas.
|
|
26
|
-
Use one sheet for each individual with the following columns
|
|
40
|
+
Use one sheet for each individual with the following 9 columns:
|
|
41
|
+
year, anticipated wages, ctrb taxable, ctrb 401k, ctrb Roth 401k,
|
|
42
|
+
ctrb IRA, ctrb Roth IRA, Roth X, and big-ticket items.
|
|
27
43
|
Supports xls, xlsx, xlsm, xlsb, odf, ods, and odt file extensions.
|
|
28
|
-
Returs a
|
|
44
|
+
Returs a dictionary of dataframes by individual's names.
|
|
29
45
|
"""
|
|
30
46
|
|
|
31
|
-
|
|
32
|
-
timeHorizonItems = [
|
|
33
|
-
'year',
|
|
34
|
-
'anticipated wages',
|
|
35
|
-
'ctrb taxable',
|
|
36
|
-
'ctrb 401k',
|
|
37
|
-
'ctrb Roth 401k',
|
|
38
|
-
'ctrb IRA',
|
|
39
|
-
'ctrb Roth IRA',
|
|
40
|
-
'Roth X',
|
|
41
|
-
'big-ticket items',
|
|
42
|
-
]
|
|
47
|
+
mylog.vprint('Reading wages, contributions, conversions, and big-ticket items over time...')
|
|
43
48
|
|
|
44
|
-
timeLists = {}
|
|
45
|
-
thisyear = date.today().year
|
|
46
49
|
if isinstance(finput, dict):
|
|
47
|
-
|
|
50
|
+
timeLists = finput
|
|
48
51
|
finput = 'dictionary of DataFrames'
|
|
49
|
-
|
|
52
|
+
streamName = 'dictionary of DataFrames'
|
|
50
53
|
else:
|
|
51
54
|
# Read all worksheets in memory but only process those with proper names.
|
|
52
55
|
try:
|
|
53
56
|
dfDict = pd.read_excel(finput, sheet_name=None)
|
|
54
57
|
except Exception as e:
|
|
55
58
|
raise Exception('Could not read file %r: %s.' % (finput, e))
|
|
56
|
-
|
|
59
|
+
streamName = "file '%s'" % finput
|
|
57
60
|
|
|
58
|
-
|
|
61
|
+
timeLists = condition(dfDict, inames, horizons, mylog)
|
|
62
|
+
|
|
63
|
+
mylog.vprint("Successfully read time horizons from %s." % streamName)
|
|
64
|
+
|
|
65
|
+
return finput, timeLists
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def condition(dfDict, inames, horizons, mylog):
|
|
69
|
+
"""
|
|
70
|
+
Make sure that time horizons contain all years up to life expectancy,
|
|
71
|
+
and that values are positive (except big-ticket items).
|
|
72
|
+
"""
|
|
73
|
+
timeLists = {}
|
|
74
|
+
thisyear = date.today().year
|
|
59
75
|
for i, iname in enumerate(inames):
|
|
60
|
-
mylog.vprint('\tfor %s...' % iname)
|
|
61
76
|
endyear = thisyear + horizons[i]
|
|
77
|
+
|
|
62
78
|
if iname not in dfDict:
|
|
63
|
-
raise
|
|
79
|
+
raise ValueError(f"No sheet found for {iname}.")
|
|
64
80
|
|
|
65
81
|
df = dfDict[iname]
|
|
66
|
-
# Check all columns.
|
|
67
|
-
for col in timeHorizonItems:
|
|
68
|
-
if col not in df.columns:
|
|
69
|
-
raise ValueError('Missing column %s in dataframe for %s.' % (col, iname))
|
|
70
82
|
|
|
71
83
|
df = df.loc[:, ~df.columns.str.contains('^Unnamed')]
|
|
72
84
|
for col in df.columns:
|
|
73
85
|
if col == '' or col not in timeHorizonItems:
|
|
74
86
|
df.drop(col, axis=1)
|
|
75
87
|
|
|
88
|
+
for item in timeHorizonItems:
|
|
89
|
+
if item not in df.columns:
|
|
90
|
+
raise ValueError(f"Item {item} not found for {iname}.")
|
|
91
|
+
|
|
76
92
|
# Only consider lines in proper year range.
|
|
77
93
|
df = df[df['year'] >= thisyear]
|
|
78
94
|
df = df[df['year'] < endyear]
|
|
@@ -82,9 +98,15 @@ def read(finput, inames, horizons, mylog):
|
|
|
82
98
|
if not (df[df['year'] == year]).any(axis=None):
|
|
83
99
|
df.loc[len(df)] = [year, 0, 0, 0, 0, 0, 0, 0, 0]
|
|
84
100
|
missing.append(year)
|
|
101
|
+
else:
|
|
102
|
+
for item in timeHorizonItems:
|
|
103
|
+
if item != 'big-ticket items' and df[item].iloc[n] < 0:
|
|
104
|
+
raise ValueError('Item %s for %s in year %d is < 0.'
|
|
105
|
+
% (item, iname, df['year'].iloc[n])
|
|
106
|
+
)
|
|
85
107
|
|
|
86
108
|
if len(missing) > 0:
|
|
87
|
-
mylog.vprint('
|
|
109
|
+
mylog.vprint('Adding %d missing years for %s: %r.' % (len(missing), iname, missing))
|
|
88
110
|
|
|
89
111
|
df.sort_values('year', inplace=True)
|
|
90
112
|
# Replace empty (NaN) cells with 0 value.
|
|
@@ -92,53 +114,10 @@ def read(finput, inames, horizons, mylog):
|
|
|
92
114
|
|
|
93
115
|
timeLists[iname] = df
|
|
94
116
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
117
|
+
if df['year'].iloc[-1] != endyear - 1:
|
|
118
|
+
raise ValueError('Time horizon for', iname,
|
|
119
|
+
'is too short.\n\tIt should end in',
|
|
120
|
+
endyear, 'but ends in', df['year'].iloc[-1]
|
|
121
|
+
)
|
|
99
122
|
|
|
100
|
-
|
|
101
|
-
"""
|
|
102
|
-
Make sure that time horizons contain all years up to life expectancy.
|
|
103
|
-
"""
|
|
104
|
-
if len(inames) == 2:
|
|
105
|
-
# Verify that both sheets start on the same year.
|
|
106
|
-
if timeLists[inames[0]]['year'].iloc[0] != timeLists[inames[1]]['year'].iloc[0]:
|
|
107
|
-
raise RuntimeError('Time horizons not starting on same year.')
|
|
108
|
-
|
|
109
|
-
# Verify that year range covers life expectancy for each individual
|
|
110
|
-
thisyear = date.today().year
|
|
111
|
-
for i, iname in enumerate(inames):
|
|
112
|
-
yend = thisyear + horizons[i]
|
|
113
|
-
if timeLists[iname]['year'].iloc[-1] < yend - 1:
|
|
114
|
-
raise RuntimeError(
|
|
115
|
-
'Time horizon for',
|
|
116
|
-
iname,
|
|
117
|
-
'is too short.\n\tIt should end in',
|
|
118
|
-
yend,
|
|
119
|
-
'but ends in',
|
|
120
|
-
timeLists[iname]['year'].iloc[-1],
|
|
121
|
-
)
|
|
122
|
-
|
|
123
|
-
timeHorizonItems = [
|
|
124
|
-
'year',
|
|
125
|
-
'anticipated wages',
|
|
126
|
-
'ctrb taxable',
|
|
127
|
-
'ctrb 401k',
|
|
128
|
-
'ctrb Roth 401k',
|
|
129
|
-
'ctrb IRA',
|
|
130
|
-
'ctrb Roth IRA',
|
|
131
|
-
'Roth X',
|
|
132
|
-
]
|
|
133
|
-
|
|
134
|
-
# Verify that all numbers except bti are positive.
|
|
135
|
-
for i, iname in enumerate(inames):
|
|
136
|
-
for item in timeHorizonItems:
|
|
137
|
-
for n in range(horizons[i]):
|
|
138
|
-
assert timeLists[iname][item].iloc[n] >= 0, 'Item %s for %s in year %d is < 0.' % (
|
|
139
|
-
item,
|
|
140
|
-
iname,
|
|
141
|
-
timeLists[iname]['year'].iloc[n],
|
|
142
|
-
)
|
|
143
|
-
|
|
144
|
-
return
|
|
123
|
+
return timeLists
|
owlplanner/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "2025.1.
|
|
1
|
+
__version__ = "2025.1.27"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: owlplanner
|
|
3
|
-
Version: 2025.1.
|
|
3
|
+
Version: 2025.1.27
|
|
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
|
|
@@ -698,6 +698,7 @@ Requires-Dist: pandas
|
|
|
698
698
|
Requires-Dist: scipy
|
|
699
699
|
Requires-Dist: seaborn
|
|
700
700
|
Requires-Dist: streamlit
|
|
701
|
+
Requires-Dist: toml
|
|
701
702
|
Description-Content-Type: text/markdown
|
|
702
703
|
|
|
703
704
|
|
|
@@ -707,6 +708,15 @@ Description-Content-Type: text/markdown
|
|
|
707
708
|
|
|
708
709
|
<img align=right src="https://raw.github.com/mdlacasse/Owl/main/docs/images/owl.png" width="250">
|
|
709
710
|
|
|
711
|
+
-----
|
|
712
|
+
|
|
713
|
+
### TLDR
|
|
714
|
+
Owl is a planning tool that uses a linear programming optimization algorithm to provide guidance on retirement decisions.
|
|
715
|
+
|
|
716
|
+
Go to [owlplanner.streamlit.app](https://owlplanner.streamlit.app) and explore the app to learn about its capabilities.
|
|
717
|
+
|
|
718
|
+
-----
|
|
719
|
+
|
|
710
720
|
This package is a retirement modeling framework for exploring the sensitivity of retirement financial decisions.
|
|
711
721
|
Strictly speaking, it is not a planning tool, but more an environment for exploring *what if* scenarios.
|
|
712
722
|
It provides different realizations of a financial strategy through the rigorous
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
owlplanner/__init__.py,sha256=QqrdT0Qks20osBTg7h0vJHAxpP9lL7DA99xb0nYbtw4,254
|
|
2
|
+
owlplanner/abcapi.py,sha256=eemIsdbtzdWCIj5VuuswgphxXMcxJ_GZfUlDi6lttFM,6658
|
|
3
|
+
owlplanner/config.py,sha256=ouADb6YES5Zgv0UwnEK9Axwvs8drp-ahboQjI4WTrr0,12069
|
|
4
|
+
owlplanner/logging.py,sha256=pXg_mMgBll-kklqaDRLDNVUFo-5DAa-yqTKtiVrhNWw,2530
|
|
5
|
+
owlplanner/plan.py,sha256=eRX04KT8DVkWD6sFzqm18OZZazSONQviuIYe7WNW7BM,115405
|
|
6
|
+
owlplanner/progress.py,sha256=YZjL5_m4MMgKPlWlhhKacPLt54tVhVGF1eXxxZapMYs,386
|
|
7
|
+
owlplanner/rates.py,sha256=7o4F4hlRHr8epZGBl7rUDQQDhKR6OMnDSRg7OEgh3sc,15628
|
|
8
|
+
owlplanner/tax2025.py,sha256=W3yXKC3rgcqPjZMguOyejgsox9J42w3ogBNN1mIBHBI,6965
|
|
9
|
+
owlplanner/timelists.py,sha256=ifxbyMlRW3IMwsiu8zsoodA1CKJQthgk3iPq50vQIds,4104
|
|
10
|
+
owlplanner/utils.py,sha256=adIwqGVQFfvekke0JCxYJD3PKHbptVCj3NrQT2TQIB4,2351
|
|
11
|
+
owlplanner/version.py,sha256=DmL4UDn0l9KwBvfubL0ZjwP_lUKHu2amlvxJShNQMO0,27
|
|
12
|
+
owlplanner/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
+
owlplanner/data/rates.csv,sha256=6fxg56BVVORrj9wJlUGFdGXKvOX5r7CSca8uhUbbuIU,3734
|
|
14
|
+
owlplanner-2025.1.27.dist-info/METADATA,sha256=qAUeEqSZPQiN6N37IkvN4M81R38TS5AaS9BpEirwHkc,64515
|
|
15
|
+
owlplanner-2025.1.27.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
16
|
+
owlplanner-2025.1.27.dist-info/licenses/LICENSE,sha256=IwGE9guuL-ryRPEKi6wFPI_zOhg7zDZbTYuHbSt_SAk,35823
|
|
17
|
+
owlplanner-2025.1.27.dist-info/RECORD,,
|
owlplanner/tax2024.py
DELETED
|
@@ -1,234 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
|
|
3
|
-
Owl/tax2024
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
A retirement planner using linear programming optimization.
|
|
7
|
-
|
|
8
|
-
See companion document for a complete explanation and description
|
|
9
|
-
of all variables and parameters.
|
|
10
|
-
|
|
11
|
-
Module to handle all tax calculations.
|
|
12
|
-
|
|
13
|
-
Copyright (C) 2024 -- Martin-D. Lacasse
|
|
14
|
-
|
|
15
|
-
Disclaimer: This program comes with no guarantee. Use at your own risk.
|
|
16
|
-
"""
|
|
17
|
-
|
|
18
|
-
import numpy as np
|
|
19
|
-
from datetime import date
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
##############################################################################
|
|
23
|
-
# Prepare the data.
|
|
24
|
-
|
|
25
|
-
taxBracketNames = ['10%', '12/15%', '22/25%', '24/28%', '32/33%', '35%', '37/40%']
|
|
26
|
-
|
|
27
|
-
rates_2024 = np.array([0.10, 0.12, 0.22, 0.24, 0.32, 0.35, 0.370])
|
|
28
|
-
rates_2026 = np.array([0.10, 0.15, 0.25, 0.28, 0.33, 0.35, 0.396])
|
|
29
|
-
|
|
30
|
-
# Single [0] and married filing jointly [1].
|
|
31
|
-
taxBrackets_2024 = np.array(
|
|
32
|
-
[
|
|
33
|
-
[11600, 47150, 100525, 191950, 243450, 609350, 9999999],
|
|
34
|
-
[23200, 94300, 201050, 383900, 487450, 731200, 9999999],
|
|
35
|
-
]
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
irmaaBrackets = np.array([[0, 103000, 129000, 161000, 193000, 500000], [0, 206000, 258000, 322000, 386000, 750000]])
|
|
39
|
-
|
|
40
|
-
# medicareBasis_2024 = 12 * 174.70
|
|
41
|
-
# [174.70, 244.60, 349.40, 454.20, 559.00, 594.00]
|
|
42
|
-
irmaaFees = 12 * np.array([174.70, 69.90, 104.80, 104.80, 104.80, 35.00])
|
|
43
|
-
|
|
44
|
-
# taxBrackets_2017 = np.array(
|
|
45
|
-
# [[9325, 37950, 91900, 191650, 416700, 418400, 9999999],
|
|
46
|
-
# [18650, 75900, 153100, 233350, 416700, 470000, 9999999]])
|
|
47
|
-
|
|
48
|
-
# Adjusted from 2017 to 2024 with 27% increase.
|
|
49
|
-
taxBrackets_2026 = np.array(
|
|
50
|
-
[
|
|
51
|
-
[11850, 48200, 116700, 243400, 529200, 531400, 9999999],
|
|
52
|
-
[23700, 96400, 194400, 296350, 529200, 596900, 9999999],
|
|
53
|
-
]
|
|
54
|
-
)
|
|
55
|
-
|
|
56
|
-
stdDeduction_2024 = np.array([14600, 29200])
|
|
57
|
-
stdDeduction_2026 = np.array([8300, 16600])
|
|
58
|
-
extraDeduction_65 = np.array([1950, 1550])
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
##############################################################################
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def mediCosts(yobs, horizons, magi, gamma_n, Nn):
|
|
65
|
-
"""
|
|
66
|
-
Compute Medicare costs directly.
|
|
67
|
-
Birth years, life horizons, MAGI time series, inflation time series gamma_n,
|
|
68
|
-
and total number of years in the plan are provided.
|
|
69
|
-
"""
|
|
70
|
-
thisyear = date.today().year
|
|
71
|
-
Ni = len(yobs)
|
|
72
|
-
medicosts = np.zeros(Nn)
|
|
73
|
-
for n in range(Nn):
|
|
74
|
-
medicount = 0
|
|
75
|
-
for i in range(Ni):
|
|
76
|
-
if thisyear + n - yobs[i] >= 65 and n < horizons[i]:
|
|
77
|
-
medicount += 1
|
|
78
|
-
|
|
79
|
-
if medicount == 0:
|
|
80
|
-
continue
|
|
81
|
-
|
|
82
|
-
fac = medicount * gamma_n[n]
|
|
83
|
-
medicosts[n] += fac * irmaaFees[0]
|
|
84
|
-
nx = max(0, n - 2)
|
|
85
|
-
for q in range(1, 6):
|
|
86
|
-
if magi[nx] > gamma_n[n] * irmaaBrackets[medicount - 1][q]:
|
|
87
|
-
medicosts[n] += fac * irmaaFees[q]
|
|
88
|
-
|
|
89
|
-
return medicosts
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
def taxParams(yobs, i_d, n_d, N_n):
|
|
93
|
-
"""
|
|
94
|
-
Return 3 time series:
|
|
95
|
-
1) Standard deductions at year n (sigma_n).
|
|
96
|
-
2) Tax rate in year n (theta_tn)
|
|
97
|
-
3) Delta from top to bottom of tax brackets (Delta_tn)
|
|
98
|
-
This is pure speculation on future values.
|
|
99
|
-
Returned values are not indexed for inflation.
|
|
100
|
-
"""
|
|
101
|
-
# Compute the deltas in-place between brackets, starting from the end.
|
|
102
|
-
deltaBrackets_2024 = np.array(taxBrackets_2024)
|
|
103
|
-
deltaBrackets_2026 = np.array(taxBrackets_2026)
|
|
104
|
-
for t in range(6, 0, -1):
|
|
105
|
-
for i in range(2):
|
|
106
|
-
deltaBrackets_2024[i, t] -= deltaBrackets_2024[i, t - 1]
|
|
107
|
-
deltaBrackets_2026[i, t] -= deltaBrackets_2026[i, t - 1]
|
|
108
|
-
|
|
109
|
-
# Prepare the 3 arrays to return - use transpose for easy slicing.
|
|
110
|
-
sigma = np.zeros((N_n))
|
|
111
|
-
Delta = np.zeros((N_n, 7))
|
|
112
|
-
theta = np.zeros((N_n, 7))
|
|
113
|
-
|
|
114
|
-
filingStatus = len(yobs) - 1
|
|
115
|
-
souls = list(range(len(yobs)))
|
|
116
|
-
thisyear = date.today().year
|
|
117
|
-
|
|
118
|
-
for n in range(N_n):
|
|
119
|
-
# First check if shortest-lived individual is still with us.
|
|
120
|
-
if n == n_d:
|
|
121
|
-
souls.remove(i_d)
|
|
122
|
-
filingStatus -= 1
|
|
123
|
-
|
|
124
|
-
if thisyear + n < 2026:
|
|
125
|
-
sigma[n] = stdDeduction_2024[filingStatus]
|
|
126
|
-
Delta[n, :] = deltaBrackets_2024[filingStatus, :]
|
|
127
|
-
else:
|
|
128
|
-
sigma[n] = stdDeduction_2026[filingStatus]
|
|
129
|
-
Delta[n, :] = deltaBrackets_2026[filingStatus, :]
|
|
130
|
-
|
|
131
|
-
# Add 65+ additional exemption(s).
|
|
132
|
-
for i in souls:
|
|
133
|
-
if thisyear + n - yobs[i] >= 65:
|
|
134
|
-
sigma[n] += extraDeduction_65[filingStatus]
|
|
135
|
-
|
|
136
|
-
# Fill in future tax rates for year n.
|
|
137
|
-
if thisyear + n < 2026:
|
|
138
|
-
theta[n, :] = rates_2024[:]
|
|
139
|
-
else:
|
|
140
|
-
theta[n, :] = rates_2026[:]
|
|
141
|
-
|
|
142
|
-
Delta = Delta.transpose()
|
|
143
|
-
theta = theta.transpose()
|
|
144
|
-
|
|
145
|
-
# Return series unadjusted for inflation, in STD order.
|
|
146
|
-
return sigma, theta, Delta
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
def taxBrackets(N_i, n_d, N_n):
|
|
150
|
-
"""
|
|
151
|
-
Return dictionary containing future tax brackets
|
|
152
|
-
unadjusted for inflation for plotting.
|
|
153
|
-
"""
|
|
154
|
-
assert 0 < N_i and N_i <= 2, 'Cannot process %d individuals.' % N_i
|
|
155
|
-
# This 2 is the number of years left in TCJA from 2024.
|
|
156
|
-
ytc = 2
|
|
157
|
-
status = N_i - 1
|
|
158
|
-
n_d = min(n_d, N_n)
|
|
159
|
-
|
|
160
|
-
data = {}
|
|
161
|
-
for t in range(len(taxBracketNames) - 1):
|
|
162
|
-
array = np.zeros(N_n)
|
|
163
|
-
array[0:ytc] = taxBrackets_2024[status][t]
|
|
164
|
-
array[ytc:n_d] = taxBrackets_2026[status][t]
|
|
165
|
-
array[n_d:N_n] = taxBrackets_2026[0][t]
|
|
166
|
-
data[taxBracketNames[t]] = array
|
|
167
|
-
|
|
168
|
-
return data
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
def rho_in(yobs, N_n):
|
|
172
|
-
"""
|
|
173
|
-
Return Required Minimum Distribution fractions for each individual.
|
|
174
|
-
This implementation does not support spouses with more than
|
|
175
|
-
10-year difference.
|
|
176
|
-
It starts at age 73 until it goes to 75 in 2033.
|
|
177
|
-
"""
|
|
178
|
-
# Notice that table starts at age 72.
|
|
179
|
-
rmdTable = [
|
|
180
|
-
27.4,
|
|
181
|
-
26.5,
|
|
182
|
-
25.5,
|
|
183
|
-
24.6,
|
|
184
|
-
23.7,
|
|
185
|
-
22.9,
|
|
186
|
-
22.0,
|
|
187
|
-
21.1,
|
|
188
|
-
20.2,
|
|
189
|
-
19.4,
|
|
190
|
-
18.5,
|
|
191
|
-
17.7,
|
|
192
|
-
16.8,
|
|
193
|
-
16.0,
|
|
194
|
-
15.2,
|
|
195
|
-
14.4,
|
|
196
|
-
13.7,
|
|
197
|
-
12.9,
|
|
198
|
-
12.2,
|
|
199
|
-
11.5,
|
|
200
|
-
10.8,
|
|
201
|
-
10.1,
|
|
202
|
-
9.5,
|
|
203
|
-
8.9,
|
|
204
|
-
8.4,
|
|
205
|
-
7.8,
|
|
206
|
-
7.3,
|
|
207
|
-
6.8,
|
|
208
|
-
6.4,
|
|
209
|
-
6.0,
|
|
210
|
-
5.6,
|
|
211
|
-
5.2,
|
|
212
|
-
4.9,
|
|
213
|
-
4.6,
|
|
214
|
-
]
|
|
215
|
-
|
|
216
|
-
N_i = len(yobs)
|
|
217
|
-
if N_i == 2 and abs(yobs[0] - yobs[1]) > 10:
|
|
218
|
-
raise RuntimeError('RMD: Unsupported age difference of more than 10 years.')
|
|
219
|
-
|
|
220
|
-
rho = np.zeros((N_i, N_n))
|
|
221
|
-
thisyear = date.today().year
|
|
222
|
-
for i in range(N_i):
|
|
223
|
-
agenow = thisyear - yobs[i]
|
|
224
|
-
for n in range(N_n):
|
|
225
|
-
year = thisyear + n
|
|
226
|
-
yage = agenow + n
|
|
227
|
-
|
|
228
|
-
# Account for increase of RMD age between 2023 and 2032.
|
|
229
|
-
if (yage < 73) or (year > 2032 and yage < 75):
|
|
230
|
-
pass # rho[i][n] = 0
|
|
231
|
-
else:
|
|
232
|
-
rho[i][n] = 1.0 / rmdTable[yage - 72]
|
|
233
|
-
|
|
234
|
-
return rho
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
owlplanner/__init__.py,sha256=QqrdT0Qks20osBTg7h0vJHAxpP9lL7DA99xb0nYbtw4,254
|
|
2
|
-
owlplanner/abcapi.py,sha256=eemIsdbtzdWCIj5VuuswgphxXMcxJ_GZfUlDi6lttFM,6658
|
|
3
|
-
owlplanner/config.py,sha256=ouADb6YES5Zgv0UwnEK9Axwvs8drp-ahboQjI4WTrr0,12069
|
|
4
|
-
owlplanner/logging.py,sha256=pXg_mMgBll-kklqaDRLDNVUFo-5DAa-yqTKtiVrhNWw,2530
|
|
5
|
-
owlplanner/plan.py,sha256=hBDT8twND4ddXxfnm3O9MG3HAtwbPAyfhej1BzbrZpE,115173
|
|
6
|
-
owlplanner/progress.py,sha256=YZjL5_m4MMgKPlWlhhKacPLt54tVhVGF1eXxxZapMYs,386
|
|
7
|
-
owlplanner/rates.py,sha256=MY-jnTdld50gRytPa_LtlS0RwTDFbhN-YlgCg9fF1jw,22801
|
|
8
|
-
owlplanner/tax2024.py,sha256=qiGZCWv02Y8Ip3lmQCZT1kFpsnFYwj5cHlpB2Lh1dt8,6820
|
|
9
|
-
owlplanner/tax2025.py,sha256=W3yXKC3rgcqPjZMguOyejgsox9J42w3ogBNN1mIBHBI,6965
|
|
10
|
-
owlplanner/timelists.py,sha256=n1ZWCN8cyW1o9sAJLKZRDzZITgwjMWUUKcGjXqyXcNE,4729
|
|
11
|
-
owlplanner/utils.py,sha256=adIwqGVQFfvekke0JCxYJD3PKHbptVCj3NrQT2TQIB4,2351
|
|
12
|
-
owlplanner/version.py,sha256=UAr2KgRbggn8uPi5Qz9zJrAznnf2fpeRuFB_a4TVwhE,27
|
|
13
|
-
owlplanner-2025.1.24.dist-info/METADATA,sha256=cdMc1W357-STYficByVJ_CSNA0Xycqzd3ka0_ITSktA,64228
|
|
14
|
-
owlplanner-2025.1.24.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
15
|
-
owlplanner-2025.1.24.dist-info/licenses/LICENSE,sha256=IwGE9guuL-ryRPEKi6wFPI_zOhg7zDZbTYuHbSt_SAk,35823
|
|
16
|
-
owlplanner-2025.1.24.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|