Foreword
How much money can you spend every year, if you want your money to last 30 years?
Does this change if you invest 100% in stocks? 100% in bonds? 60/40 in stocks/bonds?
Do you even know what the historical returns of stocks are?
As usual, a reminder that I am not a financial professional by training — I am a software engineer by training, and by trade. The following is based on my personal understanding, which is gained through self-study and working in finance for a few years.
If you find anything that you feel is incorrect, please feel free to leave a comment, and discuss your thoughts.
Raw data
Here is the annual total return (1) of the S&P500 index since 1926, taken from https://www.slickcharts.com/sp500/returns. I haven’t verified the data, but a quick glance suggests it’s probably close enough for our purposes. I’ve excluded 2021 because the year hasn’t ended yet. All numbers are in percentage terms.
18.40, # 2020
31.49, # 2019
-4.38, # 2018
21.83, # 2017
11.96, # 2016
1.38, # 2015
13.69, # 2014
32.39, # 2013
16.00, # 2012
2.11, # 2011
15.06, # 2010
26.46, # 2009
-37.00, # 2008
5.49, # 2007
15.79, # 2006
4.91, # 2005
10.88, # 2004
28.68, # 2003
-22.10, # 2002
-11.89, # 2001
-9.10, # 2000
21.04, # 1999
28.58, # 1998
33.36, # 1997
22.96, # 1996
37.58, # 1995
1.32, # 1994
10.08, # 1993
7.62, # 1992
30.47, # 1991
-3.10, # 1990
31.69, # 1989
16.61, # 1988
5.25, # 1987
18.67, # 1986
31.73, # 1985
6.27, # 1984
22.56, # 1983
21.55, # 1982
-4.91, # 1981
32.42, # 1980
18.44, # 1979
6.56, # 1978
-7.18, # 1977
23.84, # 1976
37.20, # 1975
-26.47, # 1974
-14.66, # 1973
18.98, # 1972
14.31, # 1971
4.01, # 1970
-8.50, # 1969
11.06, # 1968
23.98, # 1967
-10.06, # 1966
12.45, # 1965
16.48, # 1964
22.80, # 1963
-8.73, # 1962
26.89, # 1961
0.47, # 1960
11.96, # 1959
43.36, # 1958
-10.78, # 1957
6.56, # 1956
31.56, # 1955
52.62, # 1954
-0.99, # 1953
18.37, # 1952
24.02, # 1951
31.71, # 1950
18.79, # 1949
5.50, # 1948
5.71, # 1947
-8.07, # 1946
36.44, # 1945
19.75, # 1944
25.90, # 1943
20.34, # 1942
-11.59, # 1941
-9.78, # 1940
-0.41, # 1939
31.12, # 1938
-35.03, # 1937
33.92, # 1936
47.67, # 1935
-1.44, # 1934
53.99, # 1933
-8.19, # 1932
-43.34, # 1931
-24.90, # 1930
-8.42, # 1929
43.61, # 1928
37.49, # 1927
11.62, # 1926
Pop quiz 1
You can refer to the numbers above, but don’t use a calculator or anything, just try to estimate the answers:
- What do you think is the average annual return over the period covered?
- What do you think is the median annual return over the period covered?
- What do you think is the CAGR(2) over the period covered?
- Which of the following 3 numbers above should you use, if you want to estimate how much returns you’ll get over the next 10 years?
The answers are:
- 12.2%
- 14.3%
- 10.3%
- CAGR, because total returns over a period of time compounds multiplicatively. Average and median are non-compounding measures.
Are those numbers surprising? Most people find it surprising that the CAGR is so much lower than the other 2 measures, because they generally hear about the average/median returns thrown around in the media, but CAGR is a number that’s less frequently used, even though it’s more important. Some people, especially those who started investing since 2010, may find it surprising that the numbers are so low, because they are used to 15+% returns, in most years since 2010. However, because returns compound multiplicatively, a down year dramatically skews the CAGR, which is why we see these numbers.
Pop quiz 2
Now, let’s say we have some amount of money, $R, to retire on, and assume inflation rate of 3% (3), which is to say, if you need $X in year one, you’ll need $(1.03 * X) in year 2, and $(1.03^2 * X) in year 3, and so on.
The ratio X/R is your withdrawal rate in the first year. The safe withdrawal rate (or SWR) is the ratio X/R, such that you have a high probability of not running out of money within your retirement — in our case, 30 years.
Assuming we used $R to buy the S&P 500 index on day 1 of our retirement, and ignoring transaction costs, taxes, etc.,
- What do you think is the SWR if you want a 90% probability of not running out of money in 30 years?
- What about if we want a 95% probability?
- 99% probability?
Now, if you are like most people, you’ll probably do something like take average/median/CAGR of stocks return, subtract the inflation rate, and that’s your SWR. That’ll give you a number that is either 9.2%, 11.3% or 7.3%, depending on which measure of stocks return you used.
And all 3 answers are wrong. The correct answers are:
- 3.8%
- 3.1%
- 1.9%
Hopefully, you are surprised (4).
Whadafuqjuzhappened!?
The reason the numbers are so small, is because of volatility. Stocks don’t go up in a straight line, they often take little detours where the annual total returns is the wrong shade of green (5). During the years where stocks are down, you are actually spending a much larger percentage of your assets to maintain your lifestyle — since your spending strictly goes up due to inflation, X/R (or your withdrawal rate) goes up if X goes up and R goes down.
So, if you want to maintain the same lifestyle over time, you’ll need to start off by just withdrawing a smaller portion of your portfolio in the first year, i.e.: a lower SWR, to compensate for these episodic underperformance of stocks.
This is sometimes called “sequencing risk”.
I am never gonna retire
Well, maybe don’t despair yet — it’s not as bad as it sounds. Recall that you don’t have to invest (just) in stocks. You can also invest in bonds! Or real estate! Or fine art! Or in this blog! I take donations! (6)
Now, as we know, bonds have, in recent history, really low yields. At the time of this writing, the 30y US Treasury is yielding only 1.93%. Can bonds really help?
Pop quiz 3
Let’s say you use your entire retirement fund of $R to buy a 30y US Treasury yielding 2% (7). So, what do you think your SWR is for
- 90% probability of not running out of money in 30 years?
- 95% probability?
- 99% probability?
And the answers are… 2.9%. For all 3. Note that we are assuming US Treasuries won’t default, and you’ll always get your money back, on top of all the other assumptions above.
So yea, for 90/95%, it’s not as good as stocks, but the stability of bonds help in the 99% case.
What’s going on here?
Recall that I said the main reason why SWR for stocks is so low, is because of volatility and sequencing risk — you need money every year to survive, even if the stock market is being uncooperative. But bonds, being so helpfully stable (at least in our made up model world with semi-unrealistic assumptions), means that even at 99% (and 100%!) percentile levels, we can have the same SWR of 2.9%, higher, in fact, than their CAGR (which is 2%)!
To hammer home this point, I ran simulations of various scenarios, and the results are summarized below:
| Portfolio | Average return | Median return | CAGR | SWR 90% | SWR 95% | SWR 99% |
| Sampled stocks | 12.14% | 13.91% | 10.32% | 3.75% | 3.00% | 1.83% |
| Normalized stocks | 12.17% | 12.14% | 10.43% | 4.01% | 3.25% | 2.07% |
| Low vol stocks | 12.14% | 12.14% | 11.72% | 6.49% | 6.00% | 5.18% |
| Low vol, low return stocks | 6.06% | 6.08% | 5.62% | 3.25% | 2.90% | 2.36% |
| High vol, high return stocks | 24.34% | 24.42% | 14.30% | 2.77% | 1.17% | Impossible |
| 2% bonds | 2.00% | 2.00% | 2.00% | 2.88% | 2.88% | 2.88% |
| 6% bonds | 6.00% | 6.00% | 6.00% | 4.90% | 4.90% | 4.90% |
| 10% bonds | 10.00% | 10.00% | 10.00% | 7.39% | 7.39% | 7.39% |
| 60/40 stocks, 2% bonds | 8.05% | 8.07% | 7.42% | 3.79% | 3.35% | 2.66% |
| 55/35/10 stocks, 2% bonds, cash | 7.35% | 7.36% | 6.82% | 3.66% | 3.24% | 2.62% |
| 60/40 stocks, 2% bonds, 1.5x 1% margin | 11.62% | 11.65% | 10.22% | 4.25% | 3.54% | 2.49% |
| 60/40 stocks, 2% bonds, 1.5x 6% margin | 9.11% | 9.05% | 7.67% | 3.08% | 2.51% | 1.61% |
“Sampled stocks” is stocks using actual historical returns, uniformly sampled for each simulation year.
“Normalized stocks” is stocks using a random returns sampled from a normal distribution with 12.16% mean and 19.66% standard deviation (which is the mean/standard deviation of our historical data above). As you can see, these numbers are fairly similar. Because it’s easier to model different scenarios using a normal distribution, all other simulations involving stocks use variations of “normalized stocks”.
“Low vol stocks” is stocks where we simply halved the standard deviation for modeling purposes. “Low vol, low return stocks” is stocks where we halved both the standard deviation and the mean. “High vol, high return stocks” is stocks where we doubled both standard deviation and mean.
“2/6/10% bonds” are bonds where the yield is 2%, 6% or 10%. (8)
The remaining rows show composite portfolios where we have some percentage of assets in stocks, bonds or cash, and where we may apply leverage (buying 50% of the portfolio’s value on margin) at different margin interest rates.
Observations
If you go through the data carefully, you’ll quickly see that:
- Expected returns (either via average, median or CAGR) is not a good predictor of SWR at all, especially at the higher confidences.
- Instead, volatility, or lack thereof, is a much better predictor of SWR, again, especially at the higher confidences.
- So, you can sacrifice some expected returns, and get a higher SWR 99% rate, by swapping out some stocks for bonds, or even cash!
- If you can get cheap leverage, then some mild application of leverage on a balanced portfolio (for example, 60/40 1.5x leverage with 1% margin) can yield even better results.
- But using leverage without first tamping down volatility is a recipe for disaster (not shown here, but the high vol, high return stocks scenario is a good approximate).
Wrapping up
For a very long time, people have been asking me why I’m “leaving money on the table” by not being more aggressive in stocks, or why I’m not levering 100% into stocks, etc. Some have even suggested a portfolio of 100% UPRO (which is a 3x daily balanced SPY product).
But think of it this way — when you retire, you’ll depend essentially 100% on your portfolio for cashflow to survive. And as we discussed in “net worth“, net worth is only useful if it can be used somehow to generate cash flow. Because, say it with me now, you cannot eat net worth. Therefore, “expected net worth”, based on whatever modeling of expected returns from a risky portfolio, is only useful if I can depend on it, at retirement, to generate cash flow. It doesn’t matter if the expected value of my portfolio is $1B at retirement, if there’s a 50% probability I’d go bankrupt — What? Am I supposed to eat caviar on my mega yacht off Monaco 50% of the time and then jump off a building the other 50%? (9)
What about levering up now and then selling everything at retirement to buy safer assets? Sure, if you happen to retire when the stock markets are at a high. But I’m not inclined to time my retirement based on the whims of the stock market. Also, since I don’t have a crystal ball, that means I’ll have to go with a more conservative strategy.
Which is to say, in general, as you approach retirement, it is a good idea to reduce volatility in your portfolio, so that you can smooth out market madness and thus achieve a higher level of stable cash flow (higher SWR) from your portfolio. (10)
Monte Carlo
By now, you’re probably wondering why this post is titled “Monte Carlo”. That’s simply the name of the methodology I used to run the simulation for the numbers above. The code for the simulation is attached, feel free to play with the assumptions yourself to see what comes up.
Note that for all the stocks based portfolios, the inputs are random (which is why we need Monte Carlo in the first place), so your numbers may differ slightly. But I’ve found that the differences are relatively minor, typically in the 5-10bps range.
#!/usr/bin/python3.8
import numpy
# Number of times to run each simulation.
TOTAL_ITERATIONS = 10000
# Ratio of runs where we must end up with more than $0, before we consider the test a success.
THRESHOLDS = [0.9, 0.95, 0.99, 1]
# Number of years to run for in each simulation.
NUM_YEARS = 30
# Inflation rate of cash withdrawal.
INFLATION = 0.03
# This should be mostly irrelevant. Just use a large number.
START_CASH = 1000000
# Data from https://www.slickcharts.com/sp500/returns
HISTORICAL_RETURNS = numpy.array([
18.40, # 2020
31.49, # 2019
-4.38, # 2018
21.83, # 2017
11.96, # 2016
1.38, # 2015
13.69, # 2014
32.39, # 2013
16.00, # 2012
2.11, # 2011
15.06, # 2010
26.46, # 2009
-37.00, # 2008
5.49, # 2007
15.79, # 2006
4.91, # 2005
10.88, # 2004
28.68, # 2003
-22.10, # 2002
-11.89, # 2001
-9.10, # 2000
21.04, # 1999
28.58, # 1998
33.36, # 1997
22.96, # 1996
37.58, # 1995
1.32, # 1994
10.08, # 1993
7.62, # 1992
30.47, # 1991
-3.10, # 1990
31.69, # 1989
16.61, # 1988
5.25, # 1987
18.67, # 1986
31.73, # 1985
6.27, # 1984
22.56, # 1983
21.55, # 1982
-4.91, # 1981
32.42, # 1980
18.44, # 1979
6.56, # 1978
-7.18, # 1977
23.84, # 1976
37.20, # 1975
-26.47, # 1974
-14.66, # 1973
18.98, # 1972
14.31, # 1971
4.01, # 1970
-8.50, # 1969
11.06, # 1968
23.98, # 1967
-10.06, # 1966
12.45, # 1965
16.48, # 1964
22.80, # 1963
-8.73, # 1962
26.89, # 1961
0.47, # 1960
11.96, # 1959
43.36, # 1958
-10.78, # 1957
6.56, # 1956
31.56, # 1955
52.62, # 1954
-0.99, # 1953
18.37, # 1952
24.02, # 1951
31.71, # 1950
18.79, # 1949
5.50, # 1948
5.71, # 1947
-8.07, # 1946
36.44, # 1945
19.75, # 1944
25.90, # 1943
20.34, # 1942
-11.59, # 1941
-9.78, # 1940
-0.41, # 1939
31.12, # 1938
-35.03, # 1937
33.92, # 1936
47.67, # 1935
-1.44, # 1934
53.99, # 1933
-8.19, # 1932
-43.34, # 1931
-24.90, # 1930
-8.42, # 1929
43.61, # 1928
37.49, # 1927
11.62, # 1926
])
HISTORICAL_RETURNS /= 100
# Uncomment to print average, median and CAGR of HISTORICAL_RETURNS.
#print(numpy.mean(HISTORICAL_RETURNS) * 100)
#print(numpy.median(HISTORICAL_RETURNS) * 100)
#print((numpy.prod(HISTORICAL_RETURNS + 1) ** (1 / len(HISTORICAL_RETURNS)) - 1) * 100)
class Sim():
def __init__(self, label):
self.__label = label
def Name(self):
return self.__label
class FixedRate(Sim):
def __init__(self, label, interest_rate):
Sim.__init__(self, label)
self.__interest_rate = interest_rate
def Return(self):
return self.__interest_rate
class NormalDistribution(Sim):
def __init__(self, label, mean, std_dev):
Sim.__init__(self, label)
self.__mean = mean
self.__std_dev = std_dev
def Return(self):
return max(-1, numpy.random.normal(self.__mean, self.__std_dev))
class UniformSampling(Sim):
def __init__(self, label, data):
Sim.__init__(self, label)
self.__data = data
def Return(self):
return numpy.random.choice(self.__data)
class Cash(FixedRate):
def __init__(self, label):
FixedRate.__init__(self, label, 0)
class FullLoss(FixedRate):
def __init__(self, label):
FixedRate.__init__(self, label, -1)
class Composite(Sim):
def __init__(self, label, *args):
Sim.__init__(self, label)
self.__args = args
def Return(self):
result = 0
for asset, ratio in self.__args:
result += asset.Return() * ratio
return result
def RunOneIteration(model, rate, returns):
value = START_CASH
required_cash = START_CASH * rate
for i in range(NUM_YEARS):
if value < required_cash:
return False
value -= required_cash
value *= 1 + returns[i]
required_cash *= (1 + INFLATION)
return True
def RunSim(threshold, model, rate, returns):
num_pass_required = TOTAL_ITERATIONS * threshold
for i in range(TOTAL_ITERATIONS):
if RunOneIteration(model, rate, returns[i]):
num_pass_required -= 1
if num_pass_required <= 0:
return True
return False
def GenerateReturns(model):
output = numpy.empty([TOTAL_ITERATIONS, NUM_YEARS])
for i in range(TOTAL_ITERATIONS):
curr_results = output[i]
for j in range(NUM_YEARS):
curr_results[j] = model.Return()
return output
def Report(model, output):
print("{}:".format(model.Name()))
while output:
prefix = output[:5]
output = output[5:]
print(" " + " ".join(["{:>8s}: {:<7s}".format(metric, "{:.2f}%".format(result * 100)) for (metric, result) in prefix]))
print()
def MonteCarlo(model):
returns = GenerateReturns(model)
highest_rate = {}
for threshold in THRESHOLDS:
highest_rate[threshold] = float("nan")
min_rate = 0
max_rate = 1
while round(min_rate, 4) < round(max_rate, 4):
rate = (min_rate + max_rate) / 2
if RunSim(threshold, model, rate, returns):
min_rate = rate
highest_rate[threshold] = rate
else:
max_rate = rate
output = [
("Average", numpy.mean(numpy.mean(returns, axis=1))),
("Median", numpy.mean(numpy.median(returns, axis=1))),
("CAGR", numpy.mean(numpy.prod(returns + 1, axis=1) ** (1 / NUM_YEARS) - 1)),
]
for threshold, rate in highest_rate.items():
output.append(("SWR-{}%".format(int(threshold * 100)), rate))
Report(model, output)
def MakeLabelParams(label, *params):
full_label = label
first = True
for param in params:
if first:
first = False
full_label += " {:.2f}".format(param * 100)
else:
full_label += "/{:.2f}".format(param * 100)
return full_label, *params
def Main():
cash = Cash("Cash")
margin = FullLoss("MarginCost")
bonds2 = FixedRate("2% Bonds", 0.02)
MonteCarlo(bonds2)
bonds6 = FixedRate("6% Bonds", 0.06)
MonteCarlo(bonds6)
bonds10 = FixedRate("10% Bonds", 0.1)
MonteCarlo(bonds10)
sampled_stocks = UniformSampling("SampledStocks", HISTORICAL_RETURNS)
MonteCarlo(sampled_stocks)
stocks_mean = numpy.mean(HISTORICAL_RETURNS)
stocks_stdev = numpy.std(HISTORICAL_RETURNS, ddof=1)
stocks = NormalDistribution(*MakeLabelParams("Stocks", stocks_mean, stocks_stdev))
MonteCarlo(stocks)
low_vol = NormalDistribution(*MakeLabelParams("Stocks[LoVol]", stocks_mean, stocks_stdev * 0.5))
MonteCarlo(low_vol)
low_vol_mean = NormalDistribution(*MakeLabelParams("Stocks[LoVolMean]", stocks_mean * 0.5, stocks_stdev * 0.5))
MonteCarlo(low_vol_mean)
high_vol_mean = NormalDistribution(*MakeLabelParams("Stocks[HiVolMean]", stocks_mean * 2, stocks_stdev * 2))
MonteCarlo(high_vol_mean)
stocks60_bonds240 = Composite("60/40", (stocks, 0.6), (bonds2, 0.4))
MonteCarlo(stocks60_bonds240)
stocks55_bonds235_cash10 = Composite("55/35/10", (stocks, 0.55), (bonds2, 0.35), (cash, 0.1))
MonteCarlo(stocks55_bonds235_cash10)
# 50% margin loan, at 1% rate = 0.05% interest payments per year.
stocks60_bonds240_x15 = Composite("60/40 x1.5", (stocks, 0.9), (bonds2, 0.6), (margin, 0.005))
MonteCarlo(stocks60_bonds240_x15)
# 50% margin loan, at 6% rate = 3% interest payments per year.
stocks60_bonds240_x15 = Composite("60/40 x1.5", (stocks, 0.9), (bonds2, 0.6), (margin, 0.03))
MonteCarlo(stocks60_bonds240_x15)
if __name__ == "__main__":
Main()
Footnotes
- Total return is equal to dividends + gains in asset price.
- CAGR is “compounded annual growth rate”, which is, loosely speaking, the geometric mean of the returns, expressed in percentage terms.
- I’ve been using 3% for modeling inflation for a while now. People used to laugh at me for this, especially during the 2009-2019 period. After 2020, they are still laughing at me. But for very different reasons. For those who are more conservative, feel free to jack up the value to 5% (or more!) in the simulation to see how that affects the numbers.
- Look, buddy. I worked really hard to build up the suspense and everything. At least act surprised.
- Also known as “red”.
- I’m kidding, I don’t take donations.
- I’m using 2% because it’s easier to type than 1.93%. Also, US Treasuries have some tax advantages, so it’s probably not THAT crazy an assumption. Finally, there are other “safe’ish” assets that can yield as high as 5-6% “safely”.
- Yes, 6% and 10% bonds sound crazy in today’s low interest rates world. But there are assets (mostly for accredited investors) which can indeed yield up to 12%. They have varying degrees of risk, and certainly aren’t risk free like this modeling suggests. But they behave very similarly to bonds.
- Interestingly, someone made the argument to me recently that if there’s an asset with a 25% probability of 10x, and 75% probability of going to 0, the expected value is still 2.5x, which means (paraphrased) “you almost have an obligation to buy that asset”. Hopefully this post and the discussion have shown that the question (and thus answer) is not so simple, and that there are a lot of other considerations other than “expected return”.
- There are other withdrawal strategies that try to mitigate the sequencing risk issue. Most of them revolve around reducing your cash flow in bad market years (e.g.: keeping your withdrawal rate the same, or even reducing it). Some of them may work, but in general, I’m not really inclined to eat lobster one year and starve the next, just because the stock market decides to tank. I’d rather just have my daily, stable supply of ramen noodle.