I wrote this Monte Carlo experiment to calculate the probability of a retirement account reaching $1 Million.
At a high level, this is done by generating many randomized annual returns. The current balance is multiplied by a return percentage and added (or subtracted for a negative annual return) to make a new balance. For each year before the retirement age, the calculation with a new random annual return to get the new balance is repeated until the end balance is found.
The simulation repeated 500 times and finally, from the 500 end balances, the success rate where the end balance exceeds $1 Million is calculated.
The annual return of the S&P-500 index from 1927 until now can be found at macrotrends.net.
Using this data, the average annual return is 7.9% with a standard deviation of 19.
These values are fed into the numpy.random.normal
method to generate random return values with a Normal distribution.
Better random annual returns can be had with a distribution model that more accurately fits historical S&P-500 returns.
Always remember: Past performance is no guarantee of future results.
This is a programming exercise and not financial advice.
$ git clone <repo url>
$ cd montecarlo
$ docker build . -t "montecarlo"
$ docker run --rm -v /tmp:/code/results montecarlo
Output files such as the end balance distribution histogram and summary statistics can be examined in the local host's /tmp
directory
Modern version of python3, ie. 3.11 or newer.
Set up environment:
$ git clone <repo url>
$ cd montecarlo
$ mkdir results
$ python3 -m venv .
$ source bin/activate
$ pip install -r requirements.txt
$ python main.py
====== End Balance Summary Table 500 Runs ======
start_balance contribution end_balance average_return
count 500.0 500.0 500.0 500.0
mean 1000.0 250000.0 2701162.0 8.0
std 0.0 0.0 3918757.0 3.0
min 1000.0 250000.0 152394.0 1.0
25% 1000.0 250000.0 824622.0 6.0
50% 1000.0 250000.0 1552246.0 8.0
75% 1000.0 250000.0 3082941.0 10.0
max 1000.0 250000.0 48320946.0 16.0
============================================================
Success rate of end balance greater than 1000000: 0.692
Edit the desired values
age = 16
start_balance = 1000
retire_age = 65
annual_contribution = 5000
end_balance_goal = 1000000
For each repetition, the annual return percentage distribution plot can be shown by changing the view_annual_return_plot = True
.
Default False
Similarly, the balance of the account can be shown in a line plot by changing the view_annual_balance_plot = True
.
This is useful to get a visual of the balance over time. Default False
NOTE: The num_reps
value should be lowered to 10
or less otherwise each rep will require manually closing the plot for the experiment to conclude.
This is because by default the program runs 500 reps of the simulation to finally calculate the success rate.
view_annual_return_plot = True
view_annual_balance_plot = True
Useful if running the experiment in a script or for post run testing, set the output_files = True
to save summary table and plots to disk.
that way the plots and summary table can be examined asynchronously.
The files save to the results
directory. Default False
.
output_files = True
The main components of this Monte Carlo experiment are the randomization of the annual returns and calculation of the balances.
The approach to verifying a Normal distribution of the annual return values is to view the Annual Return Distribution histogram plot and verify the summary stats outputted match the mean and standard deviation values set in the program.
Using the view_annual_return_plot = True
setting, the histogram is shown where we can visually confirm the expected distribution.
The setting also outputs a summary table to the console, where we can verify the annual return mean and the standard deviation values
are closely matching the parameters passed into the np.random.normal
method.
=== Annual Return Summary Table ===
annual_return balance
...
mean 8.349184 4.197155e+05
std 17.996669 3.763619e+05
...
The balances calculated every year can be verified by the view_annual_balance_plot = True
setting.
A line graph of the balance over time will be shown to visually check the balances are going up and down as expected.
An indirect way to verify the annual contributions are getting added to the balance, is to set the annual_contribution
to 0
or a negative value and see how this affects the success rate values.
There are two test programs that will parse output files for expected values generated after the experiment is run.
The output_files = True
setting, will save to disk, several files that are consumed by the test programs.
A Continuous Integration (CI) job can be built to run these test programs to verify code changes did not unexpectedly affect the experiment results.
For example, view the Github Workflow:
$ python test_summary.py
$ python test_success_rate.py
The allstats.csv
file contains the summary statistics from a run of the experiment. Running main.py
100 times will result in 100 of these .csv files.
The test_summary.py
program reads every summary statistics csv file and verifies both the mean and standard deviation of the end balance and average return are within an expected range.
After each run of the experiment, the success rate is appended to the success_rate.csv
file. The file is created automatically if it does not exist.
Running main.py
100 times will generate a single csv file containing 100 lines.
The test_success_rate.py
program will read the success_rate.csv
file and verify the success rate's mean and standard deviation are within an expected range.