In a previous post, we reviewed how to set up and run a Monte Carlo (MC) simulation of future portfolio returns and growth of a dollar. Today, we will run that simulation many, many, times and then visualize the results.
Our ultimate goal is to build a Shiny app that allows an end user to build a custom portfolio, simulate returns, and visualize the results. If you just can’t wait, a link to the final Shiny app is available here.
This post builds off the work we did previously. I won’t go through the logic again, but the code for building a portfolio, calculating returns, mean and standard deviation of returns, and using them for a simulation is here:
library(tidyquant)
library(tidyverse)
library(timetk)
library(broom)
library(highcharter)
symbols %
reduce(merge) %__% `colnames%
gather(asset, returns, -date) %__% group_by(asset) %__% mutate(returns = (log(returns) - log(lag(returns)))) %__% na.omit()
portfolio_returns_tq_rebalanced_monthly %
tq_portfolio(assets_col = asset, returns_col = returns,
weights = w,
col_rename = &quot;returns&quot;,
rebalance_on = &quot;months&quot;)
mean_port_return % `colnames% mutate_all(funs(round(., 2))) tail(monte_carlo_sim_51 %__% select(month, sim1, sim2,
sim49, sim50), 3)
# A tibble: 3 x 5
month sim1 sim2 sim49 sim50
1 119 2.16 1.81 1.46 2.32
2 120 2.28 1.84 1.46 2.38
3 121 2.26 1.82 1.46 2.39
We have accomplished our goal of running 51 simulations, and could head to data visualization now, but let’s explore an alternative method using the the rerun() function from purrr. As its name implies, this function will “rerun” another function, and we stipulate how many times to do that by setting .n = number of times to rerun. For example to run the simulation_accum_1 function 5 times, we would set the following:
monte_carlo_rerun_5 % mutate(month = seq(1:nrow(.))) %__% select(month, everything())
tail(monte_carlo_rerun_51 %__% select(`sim 1`, `sim 2`,
`sim 49`, `sim 50`), 3)
# A tibble: 3 x 4
`sim 1` `sim 2` `sim 49` `sim 50`
1 1.99 1.97 3.66 2.20
2 2.00 1.86 3.68 2.25
3 2.02 1.95 3.78 2.18
Now we have two objects holding the results of 51 simulations, monte_carlo_rerun_51 and monte_carlo_sim_51.
Each has 51 columns of simulations and 1 column of months. Note that we have 121 rows because we started with an initial value of 1, and then simulated returns over 120 months.
Now let’s get to visualization with ggplot() and visualize the results in monte_carlo_sim_51. The same code flows for visualization would also apply to monte_carlo_rerun_51, but we will run them for only monte_carlo_sim_51 here.
We start with a chart of all 51 simulations, and assign a different color to each one by setting ggplot(aes(x = month, y = growth, color = sim)). ggplot() will automatically generate a legend for all 51 time series, but that gets quite crowded. We will suppress the legend with theme(legend.position = &quot;none&quot;).
monte_carlo_sim_51 %__% gather(sim, growth, -month) %__% group_by(sim) %__% ggplot(aes(x = month, y = growth, color = sim)) + geom_line() +
theme(legend.position=&quot;none&quot;)
We can check the minimum, maximum and median simulation with the summarise() function here.
sim_summary % gather(sim, growth, -month) %__% group_by(sim) %__% summarise(final = last(growth)) %__% summarise(
max = max(final), min = min(final),
median = median(final))
sim_summary
# A tibble: 1 x 3
max min median
1 4.81 1.31 2.3
We can clean up our original visualization by including only the max, min and median that were just calculated.
monte_carlo_sim_51 %__% gather(sim, growth, -month) %__% group_by(sim) %__%
filter(
last(growth) == sim_summary$max || last(growth) == sim_summary$median ||
last(growth) == sim_summary$min) %__% ggplot(aes(x = month, y = growth)) + geom_line(aes(color = sim)) Now let’s port our results over to highcharter, but in a major departure from our usual code flow, we will pass a tidy tibble instead of an xts object.
Our first step is to convert the data from wide to long tidy format with the gather() function.
mc_gathered % gather(sim, growth, -month) %__% group_by(sim)
head(mc_gathered)
# A tibble: 6 x 3
# Groups: sim [1]
month sim growth
1 1 sim1 1 2 2 sim1 0.99
3 3 sim1 1.01
4 4 sim1 1.06
5 5 sim1 1.08
6 6 sim1 1.1 We can now pass this tibble directly to the hchart() function, specify the type of chart as line, and then work with a similar grammar to ggplot(). The difference is we use hcaes, which stands for highcharter aesthetic, instead of aes.
# This takes a few seconds to run
hchart(mc_gathered, type = 'line', hcaes(y = growth,
x = month,
group = sim)) %__% hc_title(text = &quot;51 Simulations&quot;) %__%
hc_xAxis(title = list(text = &quot;months&quot;)) %__%
hc_yAxis(title = list(text = &quot;dollar growth&quot;),
labels = list(format = &quot;${value}&quot;)) %__%
hc_add_theme(hc_theme_flat()) %__%
hc_exporting(enabled = TRUE) %__% hc_legend(enabled = FALSE)
We just plotted 51 lines in highcharter using a tidy tibble. For tidy data fans out there, this is a big deal because we can stay in the grammar of the tidyverse but also use highcharter.
Very similar to what we did with ggplot, let’s isolate the maximum, minimum and median simulations and save them to an object called mc_max_med_min.
mc_max_med_min %
filter(
last(growth) == sim_summary$max || last(growth) == sim_summary$median ||
last(growth) == sim_summary$min) %__% group_by(sim)
Now we pass that filtered object to hchart().
hchart(mc_max_med_min, type = 'line', hcaes(y = growth,
x = month,
group = sim)) %__% hc_title(text = &quot;Min, Max, Median Simulations&quot;) %__%
hc_xAxis(title = list(text = &quot;months&quot;)) %__%
hc_yAxis(title = list(text = &quot;dollar growth&quot;),
labels = list(format = &quot;${value}&quot;)) %__%
hc_add_theme(hc_theme_flat()) %__%
hc_exporting(enabled = TRUE) %__% hc_legend(enabled = FALSE)
That concludes our visualization of Monte Carlo simulations.