-
Notifications
You must be signed in to change notification settings - Fork 0
/
Program.cs
228 lines (182 loc) · 7.12 KB
/
Program.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
// Year & Rates
using System.Configuration;
using System.Numerics;
var currYear = DateTime.Now.Year;
var interestRate = 0.07;
var conversionTaxRate = 0.12;
var inflationRate = 0.04;
// Your current age
int currAge = 39;
if (!int.TryParse(ConfigurationManager.AppSettings["CurrentAge"], out currAge))
{
throw new InvalidOperationException("Invalid CurrentAge in app.config");
}
var earlyRetirementAge = 45;
if (!int.TryParse(ConfigurationManager.AppSettings["EarlyRetirementAge"], out earlyRetirementAge))
{
throw new InvalidOperationException("Invalid EarlyRetirementAge in app.config");
}
var retirementAge = 65;
if (!int.TryParse(ConfigurationManager.AppSettings["TraditionalRetirementAge"], out retirementAge))
{
throw new InvalidOperationException("Invalid TraditionalRetirementAge in app.config");
}
// Set your current balances
double brokerageBalance = 0;
if (!double.TryParse(ConfigurationManager.AppSettings["BrokerageBalance"], out brokerageBalance))
{
throw new InvalidOperationException("Invalid BrokerageBalance in app.config");
}
double cryptoBalance = 0;
if (!double.TryParse(ConfigurationManager.AppSettings["CryptoBalance"], out cryptoBalance))
{
throw new InvalidOperationException("Invalid CryptoBalance in app.config");
}
double tradBalance = 1000000;
if (!double.TryParse(ConfigurationManager.AppSettings["TraditionalBalance"], out tradBalance))
{
throw new InvalidOperationException("Invalid TraditionalBalance in app.config");
}
double rothBalance = 0;
if (!double.TryParse(ConfigurationManager.AppSettings["RothBalance"], out rothBalance))
{
throw new InvalidOperationException("Invalid RothBalance in app.config");
}
// Ladder needs 5 years to ramp, then you can start taking Roth distributions.
// If you want to early retire @ 45, you must start converting Trad=>Roth for retirement @ 40
var rothLadderStartAge = earlyRetirementAge - 5;
var rothLadderStartDistributionAge = earlyRetirementAge;
var rothLadderEndAge = earlyRetirementAge + 5;
if (earlyRetirementAge <= currAge || earlyRetirementAge <= (currAge + 5))
{
Console.WriteLine("Retirement age must be at least 5 years in the future to use the ladder calculator.");
}
// Controls how much money (in today's dollars) you want to distribute each year
double rothLadderStartingDistributionAmt = 55000 * (1 + conversionTaxRate);
// How much to distribute in retirement?
double retirementRothDistribution = 25000;
double retirementTradDistribution = 175000;
// Calculate Years
var startRothYear = currYear + (rothLadderStartAge - currAge);
var endRothYear = currYear + (rothLadderEndAge - currAge);
var distributionYear = currYear + (rothLadderStartDistributionAge - currAge);
var retirementYear = currYear + (retirementAge - currAge);
// Init
var yearlyBalances = new Dictionary<int, YearlyBalance>();
double rothLadderConversionAmt = rothLadderStartingDistributionAmt;
double rothDistributionAmt = 0;
double tradDistributionAmt = 0;
double previousTotal = 0;
// Lets not look past 100 years
var Centennial = DateTime.Now.Year + (100 - currAge);
for (var i = currYear; i < Centennial; i++)
{
bool isTradDistributionYear = false;
bool isRothDistributionYear = false;
List<string> actions = new List<string>();
var tradInterestGrowth = tradBalance * interestRate;
var rothInterestGrowth = rothBalance * interestRate;
var brokerageInterestGrowth = brokerageBalance * interestRate;
var cryptoInterestGrowth = cryptoBalance * 0.04; // Current ETH staked rate
// Starting balances shouldn't get a "bonus" year of interest
if (i != currYear)
{
tradBalance += tradInterestGrowth;
rothBalance += rothInterestGrowth;
brokerageBalance += brokerageInterestGrowth;
cryptoBalance += cryptoInterestGrowth;
}
// Reduce conversion after we've converted more than 50% needed to get to 59 1/2 years old
if (i > endRothYear && i < retirementYear)
{
rothLadderConversionAmt = rothLadderStartingDistributionAmt / 5;
}
// Conversion Years
if (i >= startRothYear && i < retirementYear)
{
tradBalance -= rothLadderConversionAmt;
rothBalance += rothLadderConversionAmt - (rothLadderConversionAmt * conversionTaxRate);
// Bump up by expected inflation rate for next year
rothLadderConversionAmt += (rothLadderConversionAmt * inflationRate);
}
// Distribution Years
if (i >= distributionYear && i < retirementYear)
{
if (rothDistributionAmt == 0) rothDistributionAmt = rothLadderStartingDistributionAmt;
rothDistributionAmt = rothDistributionAmt * (1 + inflationRate);
isRothDistributionYear = true;
rothBalance -= rothLadderStartingDistributionAmt;
}
if (i >= retirementYear)
{
if (tradDistributionAmt == 0) tradDistributionAmt = retirementTradDistribution;
if (rothDistributionAmt == 0) rothDistributionAmt = retirementRothDistribution;
tradDistributionAmt = (tradDistributionAmt * (1 + inflationRate));
rothDistributionAmt = (rothDistributionAmt * (1 + inflationRate));
isTradDistributionYear = true;
tradBalance -= retirementTradDistribution;
isRothDistributionYear = true;
rothBalance -= retirementRothDistribution;
rothLadderStartingDistributionAmt = retirementRothDistribution;
}
actions.Add($"Convert: (T=>R) {rothLadderConversionAmt,8:C0}");
if (isRothDistributionYear)
{
actions.Add($"DIST (R) {(rothDistributionAmt * -1),14:C0}");
}
else
{
actions.Add($"DIST (R) {(0),14:C00}");
}
if (isTradDistributionYear)
{
actions.Add($"DIST (T) {(tradDistributionAmt * -1),14:C0}");
}
else
{
actions.Add($"DIST (T) {(0),14:C00}");
}
if (i >= retirementYear || isTradDistributionYear || isRothDistributionYear)
{
actions.Add($"Total {((rothDistributionAmt + tradDistributionAmt) * -1),14:C0}");
}
else
{
actions.Add($"Total {(0),14:C00}");
}
var yearBalance = new YearlyBalance
{
Year = i,
Age = currAge++,
Actions = actions,
BrokerageBalance = brokerageBalance,
CryptoBalance = cryptoBalance,
RothBalance = rothBalance,
TradBalance = tradBalance
};
yearlyBalances.Add(i, yearBalance);
if (previousTotal == 0) previousTotal = yearBalance.TotalBalance;
var changed = (yearBalance.TotalBalance - previousTotal) / yearBalance.TotalBalance;
actions.Add($"Change {changed:P}");
previousTotal = yearBalance.TotalBalance;
}
foreach (var kvp in yearlyBalances)
{
Console.ForegroundColor = ConsoleColor.DarkGreen;
if (kvp.Key >= startRothYear && kvp.Key < endRothYear)
{
Console.ForegroundColor = ConsoleColor.Magenta;
}
else if (kvp.Key >= distributionYear && kvp.Key < retirementYear)
{
Console.ForegroundColor = ConsoleColor.Yellow;
}
// Negative is bad
if (kvp.Value.TotalBalance <= 0)
{
Console.ForegroundColor = ConsoleColor.Red;
}
Console.WriteLine($"{kvp.Value}");
}
Console.WriteLine($"\n\t\t\t\t(T)raditional | (R)oth | (B)rokerage | (C)rypto");
Console.ForegroundColor = ConsoleColor.White;