Portfolio Volatility Shiny App

by Jonathan Regenstein

In our 3 previous posts, we walked through how to calculate portfolio volatility, then how to calculate rolling volatility, and then how to visualize rolling volatility. Today, we will wrap all of that work into a Shiny app that allows a user to construct his or her own five-asset portfolio, choose a benchmark and a time period, and visualize the rolling volatilities over time.

Here is the final app:

There will be a slight departure in form today because we will use a helpers.r file to hold our functions - those same functions that we worked so hard to create in the previous three posts. There are a few reasons to put them into a helper file.

  1. The end user won’t be able to see them, which leads to a tangent on the idea of reproducibility. We need to ask, “Reproducible by whom?”. In this case, the Shiny app is 100% reproducible by anyone who has access to that helper file, which would be my colleagues with access to my files or Github repository. But, if the end user is an external client, for example, that end user wouldn’t have access and the analytic functions would remain a black box. In the world of finance, that’s necessary most of the time.

  2. From a workflow perspective, that helper file allows us to test those functions in different formats. I can create an R Markdown report that uses the file, test different Shiny apps, or tweak the functions themselves without having to alter the actual application code.

  3. From a stylistic perspective, the helper file keeps the Shiny app code a bit cleaner and much shorter. It’s not right or wrong to use a helpers file, but it’s worth thinking about as our Shiny apps get more involved.

Here’s how we load that file and have access to the functions and objects in it:

source("function-folder/simple-vol-helpers.r")

Since this is a blog post and we want to be complete, the code chunk below contains all the code from that simple-vol-helpers.r file.

# Calculate component returns

componentReturns_df <- function(stock1, stock2, stock3, stock4, stock5, start_date){
  
  symbols <- c(stock1, stock2, stock3, stock4, stock5)
  
  prices <- 
    getSymbols(symbols, src = 'yahoo', from = start_date, 
               auto.assign = TRUE, warnings = FALSE) %>% 
    map(~Cl(get(.))) %>% 
    reduce(merge) %>%
    `colnames<-`(symbols)
  
  # generate daily return series for funds
  prices_monthly <- to.monthly(prices, indexAt = "first", OHLC = FALSE)
  returns <- na.omit(ROC(prices_monthly, 1, type = "continuous"))
  
  
  returns_df <- returns %>% 
    as_tibble(preserve_row_names = TRUE) %>% 
    mutate(date = ymd(row.names)) %>% 
    select(-row.names) %>% 
    select(date, everything())
}


# Calculate rolling Portfolio Standard Deviation

rolling_portfolio_sd <- function(returns_df, start = 1, window = 6, weights){
  
  start_date <- returns_df$date[start]
  
  end_date <-  returns_df$date[c(start + window)]
  
  interval_to_use <- returns_df %>% filter(date >= start_date & date < end_date)
  
  returns_xts <- interval_to_use %>% as_xts(date_col = date) 
  
  w <- weights
  
  results_as_xts <- StdDev(returns_xts, weights = w, portfolio_method = "single")
  results_as_xts <- round(results_as_xts, 4) * 100
  
  results_to_tibble <- as_tibble(t(results_as_xts[,1])) %>% 
    mutate(date = ymd(end_date)) %>% 
    select(date, everything()) 
  
}

# Look how long this code chunk is. Easier to stash this in a helpers.r file!

All of those functions were explained and constructed in our previous Notebooks, so we won’t dwell on them today. Let’s move on to the appearance of the app itself!

First, we need to create an input sidebar where the user can choose assets, weights, a date, and a benchmark for comparison.

# This creates the sidebar input for the first stock and its weight.
# We'll need to copy and paste this fluidRow for each of the assets in our portfolio. 
fluidRow(
  column(6,
  textInput("stock1", "Stock 1", "SPY")),
  column(4,
  numericInput("w1", "Portf. %", 40, min = 1, max = 100))
)  

# Let the user choose a benchmark to compare to the portfolio volatility.
# We'll default to the Russell 2000 small cap index.
textInput("benchmark", "Benchmark for Comparison", "^RUT")


fluidRow(
  column(6,
  dateInput("start_date", "Start Date", value = "2013-01-01")),
  column(3,
  numericInput("window", "Window", 6, min = 3, max = 20, step = 1))
)

# This action button is important for user experience and server resources.
actionButton("go", "Submit")

That last line creates an actionButton, which is important for the end user. We have more than 10 user inputs in that sidebar, and without that actionButton, the app will start firing and reloading every time a user changes any of the inputs. This would be annoying for the user and taxing on the server! We will make sure the reactives wait for the user to click that button by using eventReactive.

For example, in the lines below, the app will wait to calculate the rolling portfolio volatility because the value of portfolio_rolling_vol is an eventReactive that won’t fire until input$go is true.

portfolio_rolling_vol <- eventReactive(input$go, {
  
  returns_df <- 
    componentReturns_df(input$stock1, input$stock2, input$stock3, input$stock4, 
                        input$stock5, input$start_date) %>% 
    mutate(date = ymd(date))
  
  weights <- c(input$w1/100, input$w2/100, input$w3/100, input$w4/100, input$w5/100)
  
  window <- input$window
  
  roll_portfolio_result <-
    map_df(1:(nrow(returns_df) - window), rolling_portfolio_sd, 
         returns_df = returns_df, window = window, weights = weights) %>%
    mutate(date = ymd(date)) %>% 
    select(date, everything()) %>%
    as_xts(date_col = date) %>% 
    `colnames<-`("Rolling Port SD")
   # an xts comes out of this
})

The user is going to choose a benchmark for comparison and we need another eventReactive to take that input and calculate rolling volatility for the benchmark. The asset is passed via input$benchmark from the sidebar.

benchmark_rolling_vol <- eventReactive(input$go, {
  
  benchmark_prices <- 
    getSymbols(input$benchmark, src = 'yahoo', from = input$start_date, 
               auto.assign = TRUE, warnings = FALSE) 
  benchmark_close <- Cl(get(benchmark_prices))
    
  benchmark_prices_monthly <- to.monthly(benchmark_close, indexAt = "first", OHLC = FALSE)
  benchmark_returns <- na.omit(ROC(benchmark_prices_monthly, 1, type = "continuous"))
  
  benchmark_rolling_sd <- rollapply(benchmark_returns,
                             input$window,
                             function(x) StdDev(x))
  benchmark_rolling_sd <- round(benchmark_rolling_sd, 4) * 100
  
  
})

Finally, when we visualize, it’s nice to include the chosen benchmark in the title. Thankfully, that is a simple eventReactive.

benchmark <- eventReactive(input$go, {input$benchmark})

We have now calculated three reactive objects: portfolio_rolling_vol(), benchmark_rolling_vol(), and benchmark(). We pass them to highcharter and tweak aesthetics on the y-axis.

renderHighchart({
  highchart(type = "stock") %>% 
    hc_title(text = paste("Portfolio Volatility vs", benchmark(), "Volatility", sep = " ")) %>%
    hc_yAxis(title = list(text = "Vol Percent"),
           labels = list(format = "{value}%"),
           opposite = FALSE) %>% 
    hc_add_series(portfolio_rolling_vol(), name = "Portfolio Vol", color = "blue") %>%
    hc_add_series(benchmark_rolling_vol(), 
                  name = paste(benchmark(), "Vol", sep = " "),
                  color = "green") %>%
    hc_add_theme(hc_theme_flat()) %>%
    hc_navigator(enabled = FALSE) %>% 
    hc_scrollbar(enabled = FALSE)
})

We’ve presented nothing new of substance today, as those analytical functions were all built in previous posts. However, this app does allow the user to build a custom portfolio and compare to a benchmark of his or her choosing. Have fun with it, and try to find some assets whose volatility has been increasing since the election last November.

Next time, we’ll take a closer look at the VIX and how it compares to realized volatility. Until then!

Share Comments · · · ·

You may leave a comment below or discuss the post in the forum community.rstudio.com.