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.
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.resetContributions()
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 True
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 resetContributions(self):
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
- 3.08,
491
- 3.16,
492
- # 1930
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 list of dataframes.
44
+ Returs a dictionary of dataframes by individual's names.
29
45
  """
30
46
 
31
- # Expected headers in each excel sheet, one per individual.
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
- dfDict = finput
50
+ timeLists = finput
48
51
  finput = 'dictionary of DataFrames'
49
- filename = 'dictionary of DataFrames'
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
- filename = "file '%s'" % finput
59
+ streamName = "file '%s'" % finput
57
60
 
58
- mylog.vprint('Reading wages, contributions, conversions, and big-ticket items over time...')
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 RuntimeError("Could not find a sheet for %s in %s." % (iname, filename))
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('\tAdding %d missing years for %s: %r.' % (len(missing), iname, missing))
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
- mylog.vprint("Successfully read time horizons from %s." % filename)
96
-
97
- return finput, timeLists
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
- def check(inames, timeLists, horizons):
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.24"
1
+ __version__ = "2025.1.27"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: owlplanner
3
- Version: 2025.1.24
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,,