Savings Plan Scenario Simulator


Definitely, you have heard a mantra from many asset managers that want your money: in the long term your investment in stocks or an index ETF will grow. Though for a one-time investment it is generally true (however, not always, recall NIKKEI), it is far away from truth for a savings plan. For instance, even if you run your savings plan for 30 years and assume annually 6% expected return and 20% volatility (very optimistic, indeed), you will make losses in ca. 15% of scenarios. And if your saving plan lasts "only" 10 years, the number of scenarios with losses will be about 30%! Additionally, they delude you showing the mean (or expected) scenario. Mean is highly influenced by a couple of extremely good outcomes: finally, your investment cannot fall below zero but there is no upper bound, at least theoretically. That's why the average scenario often looks too optimistic. It is much better to consider the median as the measure of central tendency instead. Try to simulate your savings plan yourself!

Expected mean return %
Volatility %
Annual Savings USD, EUR, YEN or whatever currency 🙂
(Immediate) One-time investment
Projection horizon years



Source code in R to keep the results reproducible

#change accordingly
N_SCENARIOS=100
N_YEARS=10
MEAN_RET = 0.06
VOLA = 0.2
INSTALLMENT = 2000.0
ONE_TIME_INVESTMENT = 0.0
#########################
yields =  array(0.0, dim=N_YEARS)
savings = array(INSTALLMENT, dim=N_YEARS)
savings[1] = savings[1] + ONE_TIME_INVESTMENT
zinsBauSteine =  array(0.0, dim=c(N_YEARS, N_YEARS))
simulationResults = array(0.0, dim=c((N_YEARS+1), N_SCENARIOS))
meanScenario =array(0.0, dim=(N_YEARS+1))
medianScenario =array(0.0, dim=(N_YEARS+1))
zbList = list()
yieldList = list()
annRetsList = list()
for(sc in 1:N_SCENARIOS)
{
  annRets = rnorm(N_YEARS, MEAN_RET, VOLA) #rep(0.05,30) 
  for(j in 1:N_YEARS)
  {
    for(zbs in 1:j)
    {
       if(zbs==j)
           zinsBauSteine[j, zbs] =  savings[j] * (1+annRets[j])
       else
           zinsBauSteine[j, zbs] =  zinsBauSteine[(j-1), zbs] * (1+annRets[j])
    }
    yields[j]=sum(zinsBauSteine[j,])
    simulationResults[(j+1), sc] = yields[j] #+ savings[j]
    meanScenario[j+1] = meanScenario[j+1] + simulationResults[(j+1), sc]
  }
  zbList[[sc]] = zinsBauSteine;
  yieldList[[sc]] = yields;
  annRetsList[[sc]] = annRets;
}

meanScenario = meanScenario / N_SCENARIOS
justSavings=c(0, cumsum(savings))
for(m in 1:N_YEARS) {  medianScenario[m+1] = median(simulationResults[m+1, ]) }
nLoss = length(which(simulationResults[(N_YEARS+1),] < justSavings[(N_YEARS+1)]))
nBelMe = length(which(simulationResults[(N_YEARS+1),] < meanScenario[(N_YEARS+1)]))
titel=paste0(N_SCENARIOS," scenarios for your savings plan. Thereof ", nBelMe, " below mean and ", nLoss," with losses!") 
ts.plot(simulationResults, main=titel, xlab="Year", ylab="Total Wealth")
lines(meanScenario, col="blue", lwd=3)
lines(justSavings, col="red", lwd=3)
lines(medianScenario, col="green", lwd=3)
x=1
y=0.99*max(simulationResults)
mMean = format(meanScenario[(N_YEARS+1)], digits=2, nsmall=2)
mMedian = format(medianScenario[(N_YEARS+1)], digits=2, nsmall=2)
mJS = format(justSavings[(N_YEARS+1)],  digits=2, nsmall=2)
legend(x,y, c(paste0("mean scenario: ", mMean), paste0("median scenario: ", mMedian), paste0("just savings: ", mJS)), lty=c(1,1), lwd=c(2.5,2.5), col=c("blue","green","red")) 
Like this post and wanna learn more? Have a look at Knowledge rather than Hope: A Book for Retail Investors and Mathematical Finance Students

FinViz - an advanced stock screener (both for technical and fundamental traders)