Playing with Retirement

One of the many differences between the US and the Netherlands is how people save for retirement. While in the Netherlands the majority of people rely for a large part on (heavily regulated) pension funds, in the US it is much more common for people to take control of their own retirement savings. Most companies and institutions offer 401(k) and 403(b) tax-deferred plans in which employees choose their own investments and saving rates, and almost anyone can open an Individual Retirement Account (IRA) or a popular Roth-IRA for complete freedom. The Roth-IRA is funded with after-tax dollars but then grows, and can be withdrawn, completely tax-free. I much prefer this flexibility (and the lower taxes!) in the US, but it comes with a lot of extra responsibility. You need to learn how to invest wisely and with low costs, take action early, and have the discipline to put enough money aside consistently to build a nest egg that truly lasts you a lifetime.

But how much money is enough? One famous concept, based on the Trinity Study, is to apply the “4% Rule”: if you start retirement withdrawing 4% of your retirement portfolio, and increase this amount with the actual inflation number each following year, you will very likely never run out of money in retirement (at least 30 years). In other words, you’ll need to save about 25 times the yearly salary you expect to withdraw in retirement. Because you will also receive Social Security (if you have worked for at least 10 years), the salary you withdraw from your own portfolio can be lower than the salary you earned before retirement. Recommendations range from 77-85% of your pre-retirement income. For example, say you now live a comfortable lifestyle based on an income of $100,000 a year, you would need to withdraw at least $77,000 each year from a total portfolio of $1,925,000 to live that same lifestyle in retirement until the day you die.

There are a lot of variables at play here, and we haven’t even considered inflation yet. So I have written a simple model in Python to play with this in more detail. Below I will share this model with you. If you would like to run the code yourself with your own input parameters, please run this Python Notebook on Google Colaboratory. There you can also save your own copy.

Instead of calculating how much money you need to never run out of income in retirement, I calculate at what age you do run out of money, based on your assumed lifestyle and saving habits. This is a more actionable and realistic approach and might lead to some useful insights to help make your future self more comfortable.

Python Retirement Calculator

So let’s dive right in! I define a retirement plan (a “class”) and I initialize it with 9 default variables, all of these we can change and play with later:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as mtick

class retirement_plan:
    """
    A retirement plan. 
    """

    def __init__(self, balance_start = 70000, salary_start = 150000, salary_retired = 80000, salary_growth = 0.02, 
                inflation = 0.025, annual_return = 0.06, savings_rate = 0.23, age_start = 41, age_retirement = 67, mute=True):
        """
        Initializes the retirement plan with the following parameters:
        
        balance_start: starting balance in dollars (default = $70,000)
        salary_start: gross salary in dollars per year (default = $150,000)
        salary_retired: yearly withdrawal AFTER Social Security, in current dollars (default = $80,000)
        salary_growth: percentage annual salary raise (default = 0.025)     
        inflation: percentage inflation (default = 0.025)     
        savings_rate: percentage of yearly gross salary going to savings (default =0.23)     
        age_start: starting age in years (default = 41) 
        age_retirement: age starting retirement in years (default = 67)
        annual_return: percentage annual return on investments (default = 0.06)
        mute: switch off (False) for printing output (default = True (muted))
        """
        self.balance_start = balance_start
        self.salary_start = salary_start
        self.salary_retired = salary_retired
        self.salary_growth = salary_growth
        self.inflation = inflation
        self.annual_return = annual_return
        self.savings_rate = savings_rate
        self.age_start = age_start
        self.age_retirement = age_retirement
        self.mute = mute

So all we need for this model is:

  • A starting age, initial savings balance and yearly gross salary
  • The average raise in salary and the average inflation rate
  • The savings rate (the percentage of yearly gross salary you save for retirement)
  • The average annual return of your investment portfolio
  • The retirement age
  • The withdrawal salary at the start of retirement (but in current dollars, and after accounting for social security)

Then we need a method to calculate the retirement plan. In the “accumulation phase” we save a percentage of our salary, our investments are growing (compounding monthly) and we correct for a raise in salary and inflation at the end of each year. This is followed by the “retirement phase”, where we no longer make contributions, but we pay ourself a salary from the portfolio, the remaining balance keeps growing, and we increase the retirement salary for inflation at the end of each year:

    def calculate(self): 
        self.age_list = []
        self.balance_list = []
        self.salary_list = []
        age = self.age_start
        balance = self.balance_start
        salary = self.salary_start
        salary_retired = self.salary_retired
        if not self.mute:
            print("age", "balance", "salary", "salary_retired")

        # Accumulation Phase:
        while age < self.age_retirement:
            # Values at the start of the year
            if not self.mute:
                print(age, round(balance), round(salary), round(salary_retired))
            self.age_list.append(age)
            self.balance_list.append(round(balance))
            self.salary_list.append(round(salary))

            #Yearly savings and return on investment, compounding monthly
            yearly_savings = self.savings_rate*salary
            for month in range(12):
                balance += yearly_savings/12.
                balance *= (1+self.annual_return)**(1./12)
        
            # Salary raise and inflation correction at the end of the year
            salary += salary*self.salary_growth
            salary_retired += salary_retired*self.inflation 

            # Turn older at the end of the year
            age += 1

        # Retirement Phase:
        while (balance > 0) & (age < 121):
            # Values at the start of the year
            salary = round(salary_retired)
            balance -= salary      
            if not self.mute:
                print(age, round(balance), round(salary), round(salary_retired))
            
            self.age_list.append(age)                  
            self.balance_list.append(round(balance))
            self.salary_list.append(salary)
    
            #Yearly return on investment, compounding monthly
            for month in range(12):
                balance *= (1+self.annual_return)**(1./12)
 
            # Salary inflation correction at the end of the year
            salary_retired += salary_retired*self.inflation

            # Turn older at the end of the year
            age += 1    
            
        self.age_zero = self.age_list[-1]

Finally, we need some code to print out a summary of the simulation and plot a figure of how our savings and income evolve over time:

    def summary(self):
        age_index = self.age_list.index(self.age_retirement)
        print("Balance at retirement: ${0:.0f}".format(self.balance_list[age_index]))
        print("Salary at retirement: ${0:.0f}".format(self.salary_list[age_index]))
        print("Starting withdrawal is {0:.1f}% of savings".format(100.*self.salary_list[age_index]/self.balance_list[age_index])) 
        if self.age_zero == 120:
            print("You'll never run out of money!")
        else:
            print("You'll run out of money when you are:", self.age_zero)
    
    def plot(self):
        age_index = self.age_list.index(self.age_retirement)
        fig, (ax1, ax2) = plt.subplots(2, sharex=False, figsize=(12,12))
        ax1.plot(self.age_list,self.balance_list)
        ax2.plot(self.age_list,self.salary_list)
        ax1.set_title('Savings vs Age')
        ax2.set_title('Income vs Age')
        ax1.set_ylabel('Total Savings')
        ax2.set_ylabel('Income')
        fmt = '${x:,.0f}'
        tick = mtick.StrMethodFormatter(fmt)
        ax1.yaxis.set_major_formatter(tick) 
        ax2.yaxis.set_major_formatter(tick) 
        ax1.tick_params(axis='both', which='major', labelsize=16)    
        ax2.tick_params(axis='both', which='major', labelsize=16)    
        ax1.grid(True)
        ax2.grid(True)
        plt.xlabel('Age')
        for ax in [ax1, ax2]:
            ax.axvline(x=self.age_retirement, linestyle = '--', color='blue')
                if self.age_zero < 120:
                    ax.axvline(x=self.age_zero, linestyle = '--', color='red')
            ax1.text(self.age_retirement, 0.85*self.balance_list[age_index], 'Retirement at '+str(self.age_retirement),  horizontalalignment='center',
            fontsize=12,bbox=dict(boxstyle = "square",
                  facecolor = "white"))
            if self.age_zero < 120:
                ax1.text(self.age_zero, 0.15*self.balance_list[age_index], 'Out of money at '+str(self.age_zero),  horizontalalignment='center',
            fontsize=12,bbox=dict(boxstyle = "square",
                  facecolor = "white"))
        plt.savefig('retirement_plan.png', bbox_inches = 'tight')

That’s it! Now, let’s run a simulation and see what it does:

plan = retirement_plan(balance_start = 70000, age_start = 41, 
            age_retirement = 67, savings_rate = 0.23, 
            salary_start = 100000, salary_retired=77000, 
            salary_growth=0.02, inflation=0.025, 
            annual_return=0.06)
plan.calculate()
plan.summary()
plan.plot()
Balance at retirement: $1879080
Salary at retirement: $146323
Starting withdrawal is 7.8% of savings
You'll run out of money when you are: 85

Great! We see that with these assumptions we start retirement with a portfolio of almost 1.9 million dollars, which will last our $100k lifestyle (in today’s dollars) until we are 85 and run out of money. The sudden change in salary at the retirement age is because this figure doesn’t show the contributions from Social Security. Our starting withdrawal will be a relatively high 7.8% of our savings (almost twice the 4% mentioned above), and indeed, we run out of money relatively fast. A more common goal would be to make the money last at least until we reach 95. Let’s see what it takes to reach 95 years with the two variables that are easiest to control yourself: the savings rate and the average annual return.

With the following code I run 143 simulations for different combinations of savings rate and average annual return (while keeping the other variables fixed). The resulting “Age Zero” (when you run out of money) is shown as a filled contour plot with our target of 95 years shown as the white line:

# Annual returns range from 0 to 0.12 in steps of 0.01
annual_returns = np.arange(0.0, 0.13,0.01)
# Saving rates range from 0 to 0.5 in steps of 0.05
saving_rates = np.arange(0.0, 0.55,0.05)
age_levels = np.arange(66,121,1)
ages = np.zeros((len(annual_returns), len(saving_rates)))

# Run the simulation for every combination of Annual Return 
# and Saving Rate
for i in range(len(saving_rates)):
    sav_rate = saving_rates[i]
    for j in range(len(annual_returns)):
        annual_return = annual_returns[j]
        plan = retirement_plan(savings_rate = sav_rate, 
                   salary_start = 100000, salary_retired=77000, 
                   salary_growth=0.02, inflation=0.025, 
                   annual_return=annual_return)
        plan.calculate()
        # Storing Age Zero
        ages[j,i] = plan.age_zero
    
# Filled contour plotting        
fig, ax = plt.subplots(figsize = (8,6)) 
CS = ax.contourf(saving_rates, annual_returns, ages, age_levels,
                extend='both', cmap = 'RdBu') 
CS.cmap.set_under('red')
CS.cmap.set_over('blue')
ax.set_xlabel('Saving Rate', fontsize = 14)
ax.set_ylabel('Avg Annual Return', fontsize = 14)
CS3 = ax.contour(saving_rates, annual_returns, ages, [95],
                 colors=('w',),
                 linewidths = (3))
ax.grid(True, color='w')
cbar = fig.colorbar(CS)
cbar.ax.set_ylabel('Age Zero', fontsize = 14)
cbar.ax.tick_params(axis='both', which='major', labelsize=14)    
cbar.add_lines(CS3)

This figure shows that if we save only 15% of our gross income, we would need our portfolio to return an average of 8% to reach 95 years, while if we would save 30% we could probably get by with an average return of only 6%. Portfolio performance is mostly out of our control, but can still be influenced by our choice of asset allocation. Historically, a balanced portfolio of 60% stocks and 40% bonds has returned 9.1% on average, which means (if history is any guide for the future) that we could reach our target by saving a little over 10%.

This is just an example of what you can do playing with a simple toy model in Python. I have made quite a few assumptions and simplifications in this model. A more advanced model would take things into account such as:

  • Yearly lump-sum contributions at the start of the year versus Dollar-Cost-Averaging each month
  • Differences between tax-deferred withdrawals and tax-free accounts
  • The likelihood of various inflation scenarios
  • The variability of your portfolio performance, including the gradual shift towards a more conservative asset allocation closer towards retirement

Nevertheless, if you live in the US, the most important things to do regardless of any model are: save early, consistently and as much as you can (make a budget, pay yourself first, get rid of all debt except your mortgage) and make maximal use of tax-deferred accounts (max out your 401(k)/403(b) and Roth-IRA before saving any leftovers in a taxable brokerage account).

Questions? Comments? Please share them below or let me know on social media.

2 thoughts on “Playing with Retirement

Add yours

    1. Thanks for the feedback, David! During the accumulation phase salary_retired is just to keep track of the desired (planned) retirement salary, and correct it for inflation each year. It is not modeled as income until the retirement phase. I hope that clarifies things.

      Like

Leave a reply to David Donayo Cancel reply

Website Powered by WordPress.com.

Up ↑