owlplanner 2025.12.5__py3-none-any.whl → 2026.1.26__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.
Files changed (38) hide show
  1. owlplanner/In Discussion #58, the case of Kim and Sam.md +307 -0
  2. owlplanner/__init__.py +20 -1
  3. owlplanner/abcapi.py +24 -23
  4. owlplanner/cli/README.md +50 -0
  5. owlplanner/cli/_main.py +52 -0
  6. owlplanner/cli/cli_logging.py +56 -0
  7. owlplanner/cli/cmd_list.py +83 -0
  8. owlplanner/cli/cmd_run.py +86 -0
  9. owlplanner/config.py +315 -136
  10. owlplanner/data/__init__.py +21 -0
  11. owlplanner/data/awi.csv +75 -0
  12. owlplanner/data/bendpoints.csv +49 -0
  13. owlplanner/data/newawi.csv +75 -0
  14. owlplanner/data/rates.csv +99 -98
  15. owlplanner/debts.py +315 -0
  16. owlplanner/fixedassets.py +288 -0
  17. owlplanner/mylogging.py +157 -25
  18. owlplanner/plan.py +1044 -332
  19. owlplanner/plotting/__init__.py +16 -3
  20. owlplanner/plotting/base.py +17 -3
  21. owlplanner/plotting/factory.py +16 -3
  22. owlplanner/plotting/matplotlib_backend.py +30 -7
  23. owlplanner/plotting/plotly_backend.py +33 -10
  24. owlplanner/progress.py +66 -9
  25. owlplanner/rates.py +366 -361
  26. owlplanner/socialsecurity.py +142 -22
  27. owlplanner/tax2026.py +170 -57
  28. owlplanner/timelists.py +316 -32
  29. owlplanner/utils.py +204 -5
  30. owlplanner/version.py +20 -1
  31. {owlplanner-2025.12.5.dist-info → owlplanner-2026.1.26.dist-info}/METADATA +50 -158
  32. owlplanner-2026.1.26.dist-info/RECORD +36 -0
  33. owlplanner-2026.1.26.dist-info/entry_points.txt +2 -0
  34. owlplanner-2026.1.26.dist-info/licenses/AUTHORS +15 -0
  35. owlplanner/tax2025.py +0 -339
  36. owlplanner-2025.12.5.dist-info/RECORD +0 -24
  37. {owlplanner-2025.12.5.dist-info → owlplanner-2026.1.26.dist-info}/WHEEL +0 -0
  38. {owlplanner-2025.12.5.dist-info → owlplanner-2026.1.26.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,307 @@
1
+ In Discussion #58, the case of Kim and Sam are used to investigate the effect of Roth conversions on a retirement plan. The previous case was focusing on the effect of Roth conversions on the net spending amount, and all cases considered had a target of zero bequest. We found that Roth conversions increase the net spending amount by a magnitude of the order of 1%. This finding was consistent across two scenarios involving fixed rates, and for two historical rate sequences, one staring from 1966, and the other from 1991.
2
+
3
+ Here we use the same case for comparison. Kim and Sam have the same account balances and allocations. Instead of optimizing for net spending however, we will now change the objective function to maximize the bequest under the constraint of a fixed yearly net spending amount of $145,000, adjusted annually for inflation.
4
+
5
+ The same four cases will be considered:
6
+ **Case 1)** Fixed conservative return rates at 6.0% S&P 500, 4.0% for Baa Corporate bonds, 3.3% for 10-year T-Notes, and 2.8% inflation
7
+ **Case 2)** Fixed optimistic return rates at 8.0% S&P 500, 4.5% for Baa Corporate bonds, 3.3% for 10-year T-Notes, and 2.8% inflation
8
+ **Case 3)** Historical rates starting in 1966
9
+ **Case 4)** Historical rates starting in 1991
10
+ Cases were optimized in two different ways for Medicare (selected as optimize or loop in the user interface):
11
+ a) Medicare/IRMAA (income-related monthly adjusted amounts) premiums were directly solved in the optimization
12
+ b) Medicare/IRMAA premiums were solved iteratively using a self-consistent loop wrapping an optimization step
13
+
14
+ Since the heirs' marginal income tax rate will influence the net after-tax bequest, three different rates are considered: 33%, 22%, and 0%.
15
+
16
+ ## Case 1a-33: Fixed conservative rates with Medicare/IRMAA optimized with 33% heirs' marginal tax rate
17
+
18
+ | max Roth X (nominal k$) | net spending (today's $) | bequest (today's $) | net benefit (today's $) | net benefit (%) | total Roth X (nominal $) |
19
+ |-------------------------|--------------------------|---------------------|--------------------------|------------------|---------------------------|
20
+ | 200 | $145,000 | $1,069,166 | $166,051 | 15.53% | $1,025,607 |
21
+ | 150 | $145,000 | $1,069,166 | $166,051 | 15.53% | $1,025,607 |
22
+ | 100 | $145,000 | $1,069,275 | $166,160 | 15.54% | $1,033,921 |
23
+ | 50 | $145,000 | $1,047,152 | $144,037 | 13.76% | $998,896 |
24
+ | 10 | $145,000 | $978,573 | $75,458 | 7.71% | $610,001 |
25
+ | 0 | $145,000 | $903,115 | $0 | 0.00% | $0 |
26
+
27
+ ## Case 1a-22: Fixed conservative rates with Medicare/IRMAA optimized with 22% heirs' marginal tax rate
28
+
29
+ | max Roth X (nominal k$) | net spending (today's $) | bequest (today's $) | net benefit (today's $) | net benefit (%) | total Roth X (nominal $) |
30
+ |-------------------------|--------------------------|---------------------|--------------------------|------------------|---------------------------|
31
+ | 200 | $145,000 | $1,069,166 | $131,463 | 12.30% | $1,025,607 |
32
+ | 150 | $145,000 | $1,069,166 | $131,463 | 12.30% | $1,025,607 |
33
+ | 100 | $145,000 | $1,069,275 | $131,572 | 12.30% | $1,033,921 |
34
+ | 50 | $145,000 | $1,055,429 | $117,726 | 11.15% | $800,000 |
35
+ | 10 | $145,000 | $995,230 | $57,527 | 5.78% | $397,992 |
36
+ | 0 | $145,000 | $937,703 | $0 | 0.00% | $0 |
37
+
38
+ ## Case 1a-0: Fixed conservative rates with Medicare/IRMAA optimized with 0% heirs' marginal tax rate
39
+
40
+ | max Roth X (nominal k$) | net spending (today's $) | bequest (today's $) | net benefit (today's $) | net benefit (%) | total Roth X (nominal $) |
41
+ |-------------------------|--------------------------|---------------------|--------------------------|------------------|---------------------------|
42
+ | 200 | $145,000 | $1,162,471 | $75,603 | 6.50% | $818,525 |
43
+ | 150 | $145,000 | $1,162,471 | $75,603 | 6.50% | $818,525 |
44
+ | 100 | $145,000 | $1,162,870 | $76,002 | 6.54% | $771,138 |
45
+ | 50 | $145,000 | $1,158,923 | $72,055 | 6.22% | $713,428 |
46
+ | 10 | $145,000 | $1,113,193 | $26,325 | 2.36% | $280,000 |
47
+ | 0 | $145,000 | $1,086,868 | $0 | 0.00% | $0 |
48
+
49
+ ## Case 1b-33: Fixed conservative rates with Medicare/IRMAA solved self-consistently with 33% heirs' marginal tax rate
50
+
51
+ | max Roth X (nominal k$) | net spending (today's $) | bequest (today's $) | net benefit (today's $) | net benefit (%) | total Roth X (nominal $) |
52
+ |-------------------------|--------------------------|---------------------|--------------------------|------------------|---------------------------|
53
+ | 200 | $145,000 | $1,064,285 | $178,884 | 16.81% | $1,022,709 |
54
+ | 150 | $145,000 | $1,064,285 | $178,884 | 16.81% | $1,022,709 |
55
+ | 100 | $145,000 | $1,067,113 | $181,712 | 17.03% | $1,033,921 |
56
+ | 50 | $145,000 | $1,038,173 | $152,772 | 14.72% | $993,873 |
57
+ | 10 | $145,000 | $970,478 | $85,077 | 8.77% | $610,001 |
58
+ | 0 | $145,000 | $885,401 | $0 | 0.00% | $0 |
59
+
60
+ ## Case 1b-22: Fixed conservative rates with Medicare/IRMAA solved self-consistently with 22% heirs' marginal tax rate
61
+
62
+ | max Roth X (nominal k$) | net spending (today's $) | bequest (today's $) | net benefit (today's $) | net benefit (%) | total Roth X (nominal $) |
63
+ |-------------------------|--------------------------|---------------------|--------------------------|------------------|---------------------------|
64
+ | 200 | $145,000 | $1,064,285 | $128,667 | 12.09% | $1,022,709 |
65
+ | 150 | $145,000 | $1,064,285 | $128,667 | 12.09% | $1,022,709 |
66
+ | 100 | $145,000 | $1,067,113 | $131,495 | 12.32% | $1,033,921 |
67
+ | 50 | $145,000 | $1,053,266 | $117,648 | 11.17% | $800,000 |
68
+ | 10 | $145,000 | $993,123 | $57,505 | 5.79% | $395,376 |
69
+ | 0 | $145,000 | $935,618 | $0 | 0.00% | $0 |
70
+
71
+ ## Case 1b-0: Fixed conservative rates with Medicare/IRMAA solved self-consistently with 0% heirs' marginal tax rate
72
+
73
+ | max Roth X (nominal k$) | net spending (today's $) | bequest (today's $) | net benefit (today's $) | net benefit (%) | total Roth X (nominal $) |
74
+ |-------------------------|--------------------------|---------------------|--------------------------|------------------|---------------------------|
75
+ | 200 | $145,000 | $1,160,358 | $80,814 | 6.96% | $818,525 |
76
+ | 150 | $145,000 | $1,160,358 | $80,814 | 6.96% | $818,525 |
77
+ | 100 | $145,000 | $1,160,332 | $80,788 | 6.96% | $818,525 |
78
+ | 50 | $145,000 | $1,156,796 | $77,252 | 6.68% | $713,428 |
79
+ | 10 | $145,000 | $1,111,080 | $31,536 | 2.84% | $280,000 |
80
+ | 0 | $145,000 | $1,079,544 | $0 | 0.00% | $0 |
81
+
82
+ ## Case 2a-33: Fixed optimistic rates with Medicare/IRMAA optimized with 33% heirs' marginal tax rate
83
+
84
+ | max Roth X (nominal k$) | net spending (today's $) | bequest (today's $) | net benefit (today's $) | net benefit (%) | total Roth X (nominal $) |
85
+ |-------------------------|--------------------------|---------------------|--------------------------|------------------|---------------------------|
86
+ | 200 | $145,000 | $2,536,155 | $391,246 | 15.43% | $1,257,908 |
87
+ | 150 | $145,000 | $2,536,155 | $391,246 | 15.43% | $1,257,908 |
88
+ | 100 | $145,000 | $2,529,949 | $385,040 | 15.22% | $1,242,205 |
89
+ | 50 | $145,000 | $2,476,579 | $331,670 | 13.39% | $105,000 |
90
+ | 10 | $145,000 | $2,273,553 | $128,644 | 5.66% | $590,001 |
91
+ | 0 | $145,000 | $2,144,909 | $0 | 0.00% | $0 |
92
+
93
+ ## Case 2a-22: Fixed optimistic rates with Medicare/IRMAA optimized with 22% heirs' marginal tax rate
94
+
95
+ | max Roth X (nominal k$) | net spending (today's $) | bequest (today's $) | net benefit (today's $) | net benefit (%) | total Roth X (nominal $) |
96
+ |-------------------------|--------------------------|---------------------|--------------------------|------------------|---------------------------|
97
+ | 200 | $145,000 | $2,536,155 | $324,858 | 12.81% | $1,205,857 |
98
+ | 150 | $145,000 | $2,536,155 | $324,858 | 12.81% | $1,205,857 |
99
+ | 100 | $145,000 | $2,533,590 | $322,293 | 12.72% | $1,180,111 |
100
+ | 50 | $145,000 | $2,491,684 | $280,387 | 11.25% | $1,000,000 |
101
+ | 10 | $145,000 | $2,325,163 | $113,866 | 4.90% | $590,001 |
102
+ | 0 | $145,000 | $2,211,297 | $0 | 0.00% | $0 |
103
+
104
+ ## Case 2a-0: Fixed optimistic rates with Medicare/IRMAA optimized with 0% heirs' marginal tax rate
105
+
106
+ | max Roth X (nominal k$) | net spending (today's $) | bequest (today's $) | net benefit (today's $) | net benefit (%) | total Roth X (nominal $) |
107
+ |-------------------------|--------------------------|---------------------|--------------------------|------------------|---------------------------|
108
+ | 200 | $145,000 | $2,664,088 | $223,593 | 8.39% | $820,763 |
109
+ | 150 | $145,000 | $2,664,062 | $223,567 | 8.39% | $820,763 |
110
+ | 100 | $145,000 | $2,663,007 | $222,512 | 8.36% | $820,763 |
111
+ | 50 | $145,000 | $2,651,217 | $210,722 | 7.95% | $792,944 |
112
+ | 10 | $145,000 | $2,525,721 | $85,226 | 3.37% | $350,000 |
113
+ | 0 | $145,000 | $2,440,495 | $0 | 0.00% | $0 |
114
+
115
+ ## Case 2b-33: Fixed optimistic rates with Medicare/IRMAA solved self-consistently with 33% heirs' marginal tax rate
116
+
117
+ | max Roth X (nominal k$) | net spending (today's $) | bequest (today's $) | net benefit (today's $) | net benefit (%) | total Roth X (nominal $) |
118
+ |-------------------------|--------------------------|---------------------|--------------------------|------------------|---------------------------|
119
+ | 200 | $145,000 | $2,516,421 | $391,903 | 15.57% | $1,235,627 |
120
+ | 150 | $145,000 | $2,516,421 | $391,903 | 15.57% | $1,235,627 |
121
+ | 100 | $145,000 | $2,513,287 | $388,769 | 15.47% | $1,238,060 |
122
+ | 50 | $145,000 | $2,464,280 | $339,762 | 13.79% | $1,092,714 |
123
+ | 10 | $145,000 | $2,251,867 | $127,349 | 5.66% | $590,001 |
124
+ | 0 | $145,000 | $2,124,518 | $0 | 0.00% | $0 |
125
+
126
+ ## Case 2b-22: Fixed optimistic rates with Medicare/IRMAA solved self-consistently with 22% heirs' marginal tax rate
127
+
128
+ | max Roth X (nominal k$) | net spending (today's $) | bequest (today's $) | net benefit (today's $) | net benefit (%) | total Roth X (nominal $) |
129
+ |-------------------------|--------------------------|---------------------|--------------------------|------------------|---------------------------|
130
+ | 200 | $145,000 | $2,516,421 | $322,752 | 12.83% | $1,235,627 |
131
+ | 150 | $145,000 | $2,516,421 | $322,752 | 12.83% | $1,235,627 |
132
+ | 100 | $145,000 | $2,526,764 | $333,095 | 13.18% | $1,184,983 |
133
+ | 50 | $145,000 | $2,479,386 | $285,717 | 11.52% | $1,000,000 |
134
+ | 10 | $145,000 | $2,309,678 | $116,009 | 5.02% | $590,001 |
135
+ | 0 | $145,000 | $2,193,669 | $0 | 0.00% | $0 |
136
+
137
+ ## Case 2b-0: Fixed optimistic rates with Medicare/IRMAA solved self-consistently with 0% heirs' marginal tax rate
138
+
139
+ | max Roth X (nominal k$) | net spending (today's $) | bequest (today's $) | net benefit (today's $) | net benefit (%) | total Roth X (nominal $) |
140
+ |-------------------------|--------------------------|---------------------|--------------------------|------------------|---------------------------|
141
+ | 200 | $145,000 | $2,656,383 | $198,591 | 7.48% | $820,763 |
142
+ | 150 | $145,000 | $2,656,358 | $198,566 | 7.48% | $820,763 |
143
+ | 100 | $145,000 | $2,655,303 | $197,511 | 7.44% | $820,763 |
144
+ | 50 | $145,000 | $2,643,512 | $185,720 | 7.03% | $792,944 |
145
+ | 10 | $145,000 | $2,537,957 | $80,165 | 3.16% | $303,990 |
146
+ | 0 | $145,000 | $2,457,792 | $0 | 0.00% | $0 |
147
+
148
+ ## Case 3a-33: Historical rates starting in 1966 with Medicare/IRMAA optimized with 33% heirs' marginal tax rate
149
+
150
+ | max Roth X (nominal k$) | net spending (today's $) | bequest (today's $) | net benefit (today's $) | net benefit (%) | total Roth X (nominal $) |
151
+ |-------------------------|--------------------------|---------------------|--------------------------|------------------|---------------------------|
152
+ | 200 | $145,000 | $1,152,831 | $341,441 | 29.62% | $783,901 |
153
+ | 150 | $145,000 | $1,152,831 | $341,441 | 29.62% | $783,901 |
154
+ | 100 | $145,000 | $1,152,831 | $341,441 | 29.62% | $783,901 |
155
+ | 50 | $145,000 | $1,137,204 | $325,814 | 28.65% | $700,000 |
156
+ | 10 | $145,000 | $949,827 | $138,437 | 14.57% | $320,000 |
157
+ | 0 | $145,000 | $811,390 | $0 | 0.00% | $0 |
158
+
159
+ ## Case 3a-22: Historical rates starting in 1966 with Medicare/IRMAA optimized with 22% heirs' marginal tax rate
160
+
161
+ | max Roth X (nominal k$) | net spending (today's $) | bequest (today's $) | net benefit (today's $) | net benefit (%) | total Roth X (nominal $) |
162
+ |-------------------------|--------------------------|---------------------|--------------------------|------------------|---------------------------|
163
+ | 200 | $145,000 | $1,152,831 | $341,441 | 29.62% | $783,901 |
164
+ | 150 | $145,000 | $1,152,831 | $341,441 | 29.62% | $803,367 |
165
+ | 100 | $145,000 | $1,152,831 | $341,441 | 29.62% | $783,901 |
166
+ | 50 | $145,000 | $1,137,204 | $325,814 | 28.65% | $700,000 |
167
+ | 10 | $145,000 | $949,827 | $138,437 | 14.57% | $320,000 |
168
+ | 0 | $145,000 | $811,390 | $0 | 0.00% | $0 |
169
+
170
+ ## Case 3a-0: Historical rates starting in 1966 with Medicare/IRMAA optimized with 0% heirs' marginal tax rate
171
+
172
+ | max Roth X (nominal k$) | net spending (today's $) | bequest (today's $) | net benefit (today's $) | net benefit (%) | total Roth X (nominal $) |
173
+ |-------------------------|--------------------------|---------------------|--------------------------|------------------|---------------------------|
174
+ | 200 | $145,000 | $1,222,679 | $223,127 | 18.25% | $783,901 |
175
+ | 150 | $145,000 | $1,222,679 | $223,127 | 18.25% | $783,901 |
176
+ | 100 | $145,000 | $1,222,679 | $223,127 | 18.25% | $783,901 |
177
+ | 50 | $145,000 | $1,207,373 | $207,821 | 17.21% | $695,708 |
178
+ | 10 | $145,000 | $1,125,137 | $125,585 | 11.16% | $260,000 |
179
+ | 0 | $145,000 | $999,552 | $0 | 0.00% | $0 |
180
+
181
+ ## Case 3b-33: Historical rates starting in 1966 with Medicare/IRMAA solved self-consistently with 33% heirs' marginal tax rate
182
+
183
+ | max Roth X (nominal k$) | net spending (today's $) | bequest (today's $) | net benefit (today's $) | net benefit (%) | total Roth X (nominal $) |
184
+ |-------------------------|--------------------------|---------------------|--------------------------|------------------|---------------------------|
185
+ | 200 | $145,000 | $1,150,869 | $340,733 | 29.61% | $783,901 |
186
+ | 150 | $145,000 | $1,150,869 | $340,733 | 29.61% | $783,901 |
187
+ | 100 | $145,000 | $1,150,869 | $340,733 | 29.61% | $783,901 |
188
+ | 50 | $145,000 | $1,124,033 | $313,897 | 27.93% | $695,239 |
189
+ | 10 | $145,000 | $945,858 | $135,722 | 14.35% | $320,000 |
190
+ | 0 | $145,000 | $810,136 | $0 | 0.00% | $0 |
191
+
192
+ ## Case 3b-22: Historical rates starting in 1966 with Medicare/IRMAA solved self-consistently with 22% heirs' marginal tax rate
193
+
194
+ | max Roth X (nominal k$) | net spending (today's $) | bequest (today's $) | net benefit (today's $) | net benefit (%) | total Roth X (nominal $) |
195
+ |-------------------------|--------------------------|---------------------|--------------------------|------------------|---------------------------|
196
+ | 200 | $145,000 | $1,150,869 | $340,733 | 29.61% | $783,901 |
197
+ | 150 | $145,000 | $1,150,869 | $340,733 | 29.61% | $783,901 |
198
+ | 100 | $145,000 | $1,150,869 | $340,733 | 29.61% | $783,901 |
199
+ | 50 | $145,000 | $1,124,033 | $313,897 | 27.93% | $695,239 |
200
+ | 10 | $145,000 | $945,858 | $135,722 | 14.35% | $320,000 |
201
+ | 0 | $145,000 | $810,136 | $0 | 0.00% | $0 |
202
+
203
+ ## Case 3b-0: Historical rates starting in 1966 with Medicare/IRMAA solved self-consistently with 0% heirs' marginal tax rate
204
+
205
+ | max Roth X (nominal k$) | net spending (today's $) | bequest (today's $) | net benefit (today's $) | net benefit (%) | total Roth X (nominal $) |
206
+ |-------------------------|--------------------------|---------------------|--------------------------|------------------|---------------------------|
207
+ | 200 | $145,000 | $1,218,844 | $225,877 | 18.53% | $783,901 |
208
+ | 150 | $145,000 | $1,218,844 | $225,877 | 18.53% | $783,901 |
209
+ | 100 | $145,000 | $1,218,844 | $225,877 | 18.53% | $783,901 |
210
+ | 50 | $145,000 | $1,203,538 | $210,571 | 17.50% | $695,708 |
211
+ | 10 | $145,000 | $1,121,353 | $128,386 | 11.45% | $260,000 |
212
+ | 0 | $145,000 | $992,967 | $0 | 0.00% | $0 |
213
+
214
+ ## Case 4a-33: Historical rates starting in 1991 with Medicare/IRMAA optimized with 33% heirs' marginal tax rate
215
+
216
+ | max Roth X (nominal k$) | net spending (today's $) | bequest (today's $) | net benefit (today's $) | net benefit (%) | total Roth X (nominal $) |
217
+ |-------------------------|--------------------------|---------------------|--------------------------|------------------|---------------------------|
218
+ | 400 | $145,000 | $12,056,950 | $2,171,374 | 18.01% | $1,762,786 |
219
+ | 200 | $145,000 | $12,058,040 | $2,172,464 | 18.02% | $1,705,975 |
220
+ | 150 | $145,000 | $12,025,790 | $2,140,214 | 17.80% | $2,131,802 |
221
+ | 100 | $145,000 | $12,003,202 | $2,117,626 | 17.64% | $2,943,074 |
222
+ | 50 | $145,000 | $11,480,661 | $1,595,085 | 13.89% | $2,816,227 |
223
+ | 10 | $145,000 | $11,014,110 | $1,128,534 | 10.25% | $556,256 |
224
+ | 0 | $145,000 | $9,885,576 | $0 | 0.00% | $0 |
225
+
226
+ ## Case 4a-22: Historical rates starting in 1991 with Medicare/IRMAA optimized with 22% heirs' marginal tax rate
227
+
228
+ | max Roth X (nominal k$) | net spending (today's $) | bequest (today's $) | net benefit (today's $) | net benefit (%) | total Roth X (nominal $) |
229
+ |-------------------------|--------------------------|---------------------|--------------------------|------------------|---------------------------|
230
+ | 400 | $145,000 | $12,073,210 | $1,870,449 | 15.49% | $1,760,142 |
231
+ | 200 | $145,000 | $12,074,606 | $1,871,845 | 15.50% | $1,719,322 |
232
+ | 150 | $145,000 | $12,042,813 | $1,840,052 | 15.28% | $1,930,667 |
233
+ | 100 | $145,000 | $12,024,366 | $1,821,605 | 15.15% | $2,707,227 |
234
+ | 50 | $145,000 | $11,608,739 | $1,405,978 | 12.11% | $2,709,587 |
235
+ | 10 | $145,000 | $10,496,321 | $293,560 | 2.80% | $555,571 |
236
+ | 0 | $145,000 | $10,202,761 | $0 | 0.00% | $0 |
237
+
238
+ ## Case 4a-0: Historical rates starting in 1991 with Medicare/IRMAA optimized with 0% heirs' marginal tax rate
239
+
240
+ | max Roth X (nominal k$) | net spending (today's $) | bequest (today's $) | net benefit (today's $) | net benefit (%) | total Roth X (nominal $) |
241
+ |-------------------------|--------------------------|---------------------|--------------------------|------------------|---------------------------|
242
+ | 400 | $145,000 | $12,292,982 | $1,446,509 | 11.77% | $1,669,855 |
243
+ | 200 | $145,000 | $12,292,643 | $1,446,170 | 11.76% | $1,669,855 |
244
+ | 150 | $145,000 | $12,251,697 | $1,405,224 | 11.47% | $1,662,303 |
245
+ | 100 | $145,000 | $12,234,459 | $1,387,986 | 11.34% | $2,199,350 |
246
+ | 50 | $145,000 | $11,947,720 | $1,101,247 | 9.22% | $2,281,495 |
247
+ | 10 | $145,000 | $11,070,668 | $224,195 | 2.03% | $424,505 |
248
+ | 0 | $145,000 | $10,846,473 | $0 | 0.00% | $0 |
249
+
250
+ ## Case 4b-33: Historical rates starting in 1991 with Medicare/IRMAA solved self-consistently with 33% heirs' marginal tax rate
251
+
252
+ | max Roth X (nominal k$) | net spending (today's $) | bequest (today's $) | net benefit (today's $) | net benefit (%) | total Roth X (nominal $) |
253
+ |-------------------------|--------------------------|---------------------|--------------------------|------------------|---------------------------|
254
+ | 400 | $145,000 | $12,006,601 | $2,182,176 | 18.17% | $1,906,415 |
255
+ | 200 | $145,000 | $11,996,099 | $2,171,674 | 18.10% | $2,022,783 |
256
+ | 150 | $145,000 | $11,968,496 | $2,144,071 | 17.91% | $2,079,499 |
257
+ | 100 | $145,000 | $11,941,959 | $2,117,534 | 17.73% | $2,877,714 |
258
+ | 50 | $145,000 | $11,407,587 | $1,583,162 | 13.88% | $2,850,001 |
259
+ | 10 | $145,000 | $10,150,350 | $325,925 | 3.21% | $570,001 |
260
+ | 0 | $145,000 | $9,824,425 | $0 | 0.00% | $0 |
261
+
262
+ ## Case 4b-22: Historical rates starting in 1991 with Medicare/IRMAA solved self-consistently with 22% heirs' marginal tax rate
263
+
264
+ | max Roth X (nominal k$) | net spending (today's $) | bequest (today's $) | net benefit (today's $) | net benefit (%) | total Roth X (nominal $) |
265
+ |-------------------------|--------------------------|---------------------|--------------------------|------------------|---------------------------|
266
+ | 400 | $145,000 | $12,021,047 | $1,852,757 | 15.41% | $1,872,324 |
267
+ | 200 | $145,000 | $12,023,136 | $1,854,846 | 15.43% | $1,855,304 |
268
+ | 150 | $145,000 | $11,994,614 | $1,826,324 | 15.23% | $1,753,840 |
269
+ | 100 | $145,000 | $11,977,328 | $1,809,038 | 15.10% | $2,482,833 |
270
+ | 50 | $145,000 | $11,546,402 | $1,378,112 | 11.94% | $2,764,699 |
271
+ | 10 | $145,000 | $10,458,881 | $290,591 | 2.78% | $520,001 |
272
+ | 0 | $145,000 | $10,168,290 | $0 | 0.00% | $0 |
273
+
274
+ ## Case 4b-0: Historical rates starting in 1991 with Medicare/IRMAA solved self-consistently with 0% heirs' marginal tax rate
275
+
276
+ | max Roth X (nominal k$) | net spending (today's $) | bequest (today's $) | net benefit (today's $) | net benefit (%) | total Roth X (nominal $) |
277
+ |-------------------------|--------------------------|---------------------|--------------------------|------------------|---------------------------|
278
+ | 400 | $145,000 | $12,295,204 | $1,151,599 | 9.37% | $1,508,030 |
279
+ | 200 | $145,000 | $12,290,863 | $1,147,258 | 9.33% | $1,510,600 |
280
+ | 150 | $145,000 | $12,285,610 | $1,142,005 | 9.30% | $1,511,254 |
281
+ | 100 | $145,000 | $12,259,890 | $1,116,285 | 9.11% | $1,739,219 |
282
+ | 50 | $145,000 | $12,060,261 | $916,656 | 7.60% | $1,676,283 |
283
+ | 10 | $145,000 | $11,381,993 | $238,388 | 2.09% | $398,028 |
284
+ | 0 | $145,000 | $11,143,605 | $0 | 0.00% | $0 |
285
+
286
+ ## Observations
287
+
288
+ The results presented above demonstrate several important patterns regarding the impact of Roth conversions on bequest maximization:
289
+
290
+ **1. Heir tax rate is the dominant factor**: The benefits of Roth conversions increase dramatically with the heirs' marginal tax rate. For example, in Case 1a with maximum conversions (200k), the net benefit ranges from 6.50% (0% tax rate) to 15.53% (33% tax rate). This pattern is consistent across all cases and reflects the fundamental advantage of Roth conversions: by paying taxes now at potentially lower rates, heirs avoid paying taxes later at their own (potentially higher) marginal rates.
291
+
292
+ **2. Magnitude of benefits**: Unlike the previous case study focusing on spending optimization, where Roth conversions provided benefits on the order of 1%, bequest optimization shows substantially larger benefits. The net benefit percentages range from approximately 2% to 30%, depending on the scenario and heir tax rate. This suggests that Roth conversions can be particularly valuable for those prioritizing legacy planning.
293
+
294
+ **3. Historical rate scenarios show the largest benefits**: Case 3 (historical rates from 1966) consistently shows the highest net benefit percentages, particularly for the 33% heir tax rate scenario (approximately 29-30%). This likely reflects the impact of historical market conditions and tax bracket changes over the extended time horizon. Case 4 (historical rates from 1991) also shows substantial benefits, with net benefits reaching 18% for 33% heir tax rates.
295
+
296
+ **4. Fixed rate scenarios**: Both conservative (Case 1) and optimistic (Case 2) fixed rate scenarios show meaningful but more modest benefits compared to historical scenarios. Case 1 shows benefits ranging from 6.5% to 17% depending on tax rate, while Case 2 shows benefits from 3.4% to 15.6%. The optimistic scenario (Case 2) actually shows slightly lower percentage benefits than the conservative scenario (Case 1), which may reflect the interaction between higher returns and tax bracket dynamics.
297
+
298
+ **5. Optimization method consistency**: The differences between Medicare/IRMAA optimization methods (direct optimization vs. self-consistent loop) are generally small, typically within 1-2 percentage points. This consistency provides confidence that the results are robust to the computational approach used.
299
+
300
+ **6. Diminishing returns at higher conversion limits**: While higher conversion limits (e.g., 200k vs. 100k) generally provide better benefits, the incremental gains diminish. In many cases, the benefits plateau or even decrease slightly at the highest conversion limits, suggesting there is an optimal conversion amount that balances current tax costs with future tax savings.
301
+
302
+ **7. Comparison to spending optimization**: The contrast with the previous case study (Discussion #58) is striking. When optimizing for spending with zero bequest, Roth conversions provided approximately 1% benefit. When optimizing for bequest with fixed spending, the benefits are an order of magnitude larger (2-30%). This highlights how the objective function fundamentally changes the value proposition of Roth conversions.
303
+
304
+ **8. Tax rate sensitivity**: The sensitivity to heir tax rates is particularly pronounced. For 0% heir tax rates, benefits are modest (2-8%), as there are no future taxes to avoid. However, for 33% heir tax rates, benefits can exceed 15-30% in many scenarios. This suggests that Roth conversions are most valuable when heirs are expected to be in high tax brackets.
305
+
306
+ These findings suggest that Roth conversions can be a powerful tool for legacy planning, especially when heirs are expected to face high marginal tax rates. The benefits are substantial and consistent across different market scenarios, though the magnitude varies significantly based on the heirs' tax situation. As with the previous case study, the conventional wisdom that taxes are more likely to increase than decrease in the future would further favor conversion strategies.
307
+
owlplanner/__init__.py CHANGED
@@ -1,6 +1,25 @@
1
+ """
2
+ Owl planner package initialization and public API exports.
3
+
4
+ Copyright (C) 2025-2026 The Owlplanner Authors
5
+
6
+ This program is free software: you can redistribute it and/or modify
7
+ it under the terms of the GNU General Public License as published by
8
+ the Free Software Foundation, either version 3 of the License, or
9
+ (at your option) any later version.
10
+
11
+ This program is distributed in the hope that it will be useful,
12
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ GNU General Public License for more details.
15
+
16
+ You should have received a copy of the GNU General Public License
17
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
18
+ """
19
+
1
20
  from owlplanner.plan import Plan # noqa: F401
2
21
  from owlplanner.plan import clone # noqa: F401
3
- from owlplanner.config import readConfig # noqa: F401
22
+ from owlplanner.config import readConfig, saveConfig # noqa: F401
4
23
  from owlplanner.rates import getRatesDistributions # noqa: F401
5
24
  from owlplanner.version import __version__ # noqa: F401
6
25
 
owlplanner/abcapi.py CHANGED
@@ -1,34 +1,34 @@
1
1
  """
2
+ Abstract API for building linear programming constraint matrices.
2
3
 
3
- Owl/abcapi
4
- ---
4
+ This module provides a solver-neutral API to build constraint matrices and
5
+ objective functions line by line, abstracting the building process to enable
6
+ use of various solvers (MOSEK, HiGHS, etc.) for comparison. The name ABCAPI
7
+ refers to A (matrix), B (bounds), C (constraints).
5
8
 
6
- A retirement planner using linear programming optimization.
9
+ Copyright (C) 2025-2026 The Owlplanner Authors
7
10
 
8
- See companion document for a complete explanation and description
9
- of all variables and parameters.
11
+ This program is free software: you can redistribute it and/or modify
12
+ it under the terms of the GNU General Public License as published by
13
+ the Free Software Foundation, either version 3 of the License, or
14
+ (at your option) any later version.
10
15
 
11
- This file contains basic functions to build a constraint matrix and
12
- objective function line by line. This is used to abstract the
13
- building of the constraint matrix in order to be able to use various
14
- solvers for comparison.
15
-
16
- This approach has been successful with the MOSEK and the HiGHS solvers.
17
- A for matrix, B for bounds, C for constraints. Thus the name ABCAPI.
18
-
19
- Copyright &copy; 2024 - Martin-D. Lacasse
20
-
21
- Disclaimers: This code is for educational purposes only and does not constitute financial advice.
16
+ This program is distributed in the hope that it will be useful,
17
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
18
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19
+ GNU General Public License for more details.
22
20
 
21
+ You should have received a copy of the GNU General Public License
22
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
23
23
  """
24
24
 
25
25
  import numpy as np
26
26
 
27
27
 
28
- class Row(object):
28
+ class Row:
29
29
  """
30
- Solver-neutral API to accomodate Mosek/HiGHS.
31
- A Row represent a row in matrix A.
30
+ Solver-neutral API to accommodate Mosek/HiGHS.
31
+ A Row represents a row in matrix A.
32
32
  """
33
33
 
34
34
  def __init__(self, nvars):
@@ -58,7 +58,7 @@ class Row(object):
58
58
  return self
59
59
 
60
60
 
61
- class ConstraintMatrix(object):
61
+ class ConstraintMatrix:
62
62
  """
63
63
  Solver-neutral API for expressing constraints.
64
64
  """
@@ -143,7 +143,7 @@ class ConstraintMatrix(object):
143
143
  return Alu, lb, ub
144
144
 
145
145
 
146
- class Bounds(object):
146
+ class Bounds:
147
147
  """
148
148
  Solver-neutral API for bounds on variables.
149
149
  """
@@ -176,7 +176,7 @@ class Bounds(object):
176
176
  self.ind.append(ii)
177
177
  self.lb.append(lb)
178
178
  self.ub.append(ub)
179
- if lb == ub:
179
+ if np.isclose(lb, ub):
180
180
  self.key.append("fx")
181
181
  elif ub == np.inf and lb == -np.inf:
182
182
  self.key.append("fr")
@@ -204,6 +204,7 @@ class Bounds(object):
204
204
  return lb, ub
205
205
 
206
206
  def integralityArray(self):
207
+ # All variables continuous by default.
207
208
  integrality = np.zeros(self.nvars, dtype=int)
208
209
  for ii in range(len(self.integrality)):
209
210
  integrality[self.integrality[ii]] = 1
@@ -214,7 +215,7 @@ class Bounds(object):
214
215
  return self.integrality
215
216
 
216
217
 
217
- class Objective(object):
218
+ class Objective:
218
219
  """
219
220
  Solver-neutral objective function.
220
221
  """
@@ -0,0 +1,50 @@
1
+ # OWLCLI - a Command Line Interface for OWL
2
+
3
+ OWLCLI is a command line interface tool to streamline the listing, running and experimenting with OWL plan files outside the streamlit interface.
4
+
5
+ ## Installation
6
+
7
+ OWLCLI is installed with the owlplanner module. Once OWLPLANNER is installed, owlcli can be run from the command line.
8
+
9
+ ## Usage
10
+
11
+ At present, OWLCLI provides two commands: `list` and `run`.
12
+
13
+ ```bash
14
+ ❯ owlcli list
15
+ ```
16
+
17
+ This command will list all the available OWL .toml plan files in the current directory.
18
+
19
+ To list all the available OWL plan files in the `examples` directory, relative to the current directory, use:
20
+
21
+ ```bash
22
+ ❯ owlcli list examples
23
+ FILE PLAN NAME TIME LISTS FILE
24
+ --------------------------------------------------------------------------------
25
+ Case_jack+jill jack+jill ✓HFP_jack+jill.xlsx
26
+ Case_joe joe ✓HFP_joe.xlsx
27
+ Case_john+sally john+sally ✓HFP_john+sally.xlsx
28
+ Case_jon+jane Jon+Jane ✗HFP_jon+jane.xslx
29
+ Case_kim+sam-bequest kim+sam-bequest ✓HFP_kim+sam.xlsx
30
+ Case_kim+sam-spending kim+sam-spending ✓HFP_kim+sam.xlsx
31
+ case_drawdowncalc-comparison-1 drawdowncalc-com... ✗edited values
32
+ ```
33
+
34
+ The listing shows the file name, plan name and Household Financial Plan file (timeListsFile) associated with each plan.
35
+
36
+ ✓ indicates that the Household Financial Plan file listed in the OWL Plan file exists.
37
+ ✗ indicates that the Household Financial Plan file was not found.
38
+ *edited values* indicates that the plan file may have been changed since the Household Financial Plan file was created.
39
+
40
+
41
+ To run an OWL plan file, use the `run` command followed by the plan file name:
42
+
43
+ ```bash
44
+ ❯ owlcli run examples/Case_kim+sam-spending
45
+ Case status: solved
46
+ Results saved to: examples/Case_kim+sam-spending_results.xlsx
47
+ ```
48
+
49
+ This example runs the `Case_kim+sam-spending` plan file located in the `examples` directory. The results of the run are saved to a new Excel file with `_results.xlsx` appended to the original plan file name. A copy of the input OWL plan file is saved as the new first tab in the Excel file.
50
+
@@ -0,0 +1,52 @@
1
+ """
2
+ Command-line interface main entry point for Owl retirement planner.
3
+
4
+ This module provides the main CLI group and command registration for the
5
+ Owl command-line interface.
6
+
7
+ Copyright (C) 2025-2026 The Owlplanner Authors
8
+
9
+ This program is free software: you can redistribute it and/or modify
10
+ it under the terms of the GNU General Public License as published by
11
+ the Free Software Foundation, either version 3 of the License, or
12
+ (at your option) any later version.
13
+
14
+ This program is distributed in the hope that it will be useful,
15
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
16
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
+ GNU General Public License for more details.
18
+
19
+ You should have received a copy of the GNU General Public License
20
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
21
+ """
22
+
23
+ import click
24
+
25
+
26
+ from .cli_logging import configure_logging, LOG_LEVELS
27
+ from .cmd_list import cmd_list
28
+ from .cmd_run import cmd_run
29
+
30
+
31
+ @click.group()
32
+ @click.option(
33
+ "--log-level",
34
+ type=click.Choice(LOG_LEVELS, case_sensitive=False),
35
+ default="INFO",
36
+ show_default=True,
37
+ help="Set logging verbosity.",
38
+ )
39
+ @click.pass_context
40
+ def cli(ctx, log_level: str):
41
+ """SSG command-line interface."""
42
+ ctx.ensure_object(dict)
43
+ ctx.obj["log_level"] = log_level.upper()
44
+
45
+ configure_logging(log_level)
46
+
47
+
48
+ cli.add_command(cmd_list)
49
+ cli.add_command(cmd_run)
50
+
51
+ if __name__ == "__main__":
52
+ cli()
@@ -0,0 +1,56 @@
1
+ """
2
+ CLI logging configuration utilities.
3
+
4
+ This module provides logging configuration functions for the command-line
5
+ interface, including log level management and formatting.
6
+
7
+ Copyright (C) 2025-2026 The Owlplanner Authors
8
+
9
+ This program is free software: you can redistribute it and/or modify
10
+ it under the terms of the GNU General Public License as published by
11
+ the Free Software Foundation, either version 3 of the License, or
12
+ (at your option) any later version.
13
+
14
+ This program is distributed in the hope that it will be useful,
15
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
16
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
+ GNU General Public License for more details.
18
+
19
+ You should have received a copy of the GNU General Public License
20
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
21
+ """
22
+
23
+ import sys
24
+ from loguru import logger
25
+
26
+ LOG_LEVELS = {
27
+ "TRACE",
28
+ "DEBUG",
29
+ "INFO",
30
+ "SUCCESS",
31
+ "WARNING",
32
+ "ERROR",
33
+ "CRITICAL",
34
+ }
35
+
36
+
37
+ def configure_logging(log_level: str = "INFO"):
38
+ log_level = log_level.upper()
39
+
40
+ if log_level not in LOG_LEVELS:
41
+ raise ValueError(f"Invalid log level: {log_level}")
42
+
43
+ logger.remove() # remove default handler
44
+
45
+ logger.add(
46
+ sys.stderr,
47
+ level=log_level,
48
+ format=(
49
+ # "<green>{time:YYYY-MM-DD HH:mm:ss}</green> | "
50
+ "<level>{level:8}</level> | "
51
+ "<cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - "
52
+ "<level>{message}</level>"
53
+ ),
54
+ backtrace=(log_level == "TRACE"),
55
+ diagnose=(log_level == "TRACE"),
56
+ )