For any asset management business, the ability to determine whether an investment portfolio is performing well is key. But equally important is the ability to deliver that information to the people who need it in an easy-to-use format, such as an interactive application.

In this three-part series, I’ll be demonstrating how to build a Shiny application in RStudio that allows a user to build a portfolio and visualize its Sortino ratio, or the return he or she is likely to get from a given level of risk. You can see the final application below — and try it out yourself — but before you start building your own, let’s talk about why the Sortino ratio is important and how to calculate it.

Why is the Sortino Ratio Important?

The Sortino ratio is an important tool for evaluating the risk-adjusted returns of a portfolio, as well as the skill of the portfolio manager. A higher Sortino ratio indicates a portfolio manager who is generating higher returns per unit of risk absorbed.

Identifying better performing portfolios — and portfolio managers — is a crucial skill for all enterprise-level participants in the world of asset management, including quantitative analysts, data scientists, investment advisers, and many others. From a data science perspective, this is best accomplished using a reproducible workflow and an appealing end product. As someone who used to work in finance and now works at RStudio, I find a Sortino ratio Shiny app to be a thrilling combination of portfolio theory, statistical analysis, and data visualization. Let's get to it!

Here is the equation for the Sortino ratio:

The denominator in this equation (called the downside deviation, semideviation, or downside risk) can be thought of as the deviation of the returns that fall below some target rate of return for the portfolio. That target rate is called the minimum acceptable rate, or MAR. The numerator is the mean portfolio return minus the MAR, which can be thought of as excess returns.

The theory behind the Sortino ratio is that the riskiness of a portfolio is better measured by the deviation of returns below a target return, instead of by the standard deviation of all returns. This stands in contrast to the more commonly used Sharpe ratio, which calculates risk-adjusted return by the ratio of returns above the risk-free rate divided by the standard deviation of all returns. Today, analysts commonly prefer to use the Sharpe ratio to evaluate low-volatility investment portfolios, and the Sortino ratio to evaluate high-volatility portfolios.

Getting Started: The Sortino Ratio Project

This project has three distinct steps:

1. Build a portfolio and calculate the Sortino ratio using three different methods
2. Visualize the Sortino ratio using ggplot and highcharter
3. Wrap into an interactive Shiny app

When working with the Sortino ratio, there are two critical choices you must make: how to construct the portfolio using assets and weights, and which MAR to use. By the end of this project, the Shiny application you build will allow a user to make these changes and see how the Sortino ratio changes as a result.

For this purposes of this project, let's use the following assets and weights:

• SPY (S&P500 fund), weighted 25%
• EFA (a non-US equities fund), weighted 25%
• IJS (a small-cap value fund), weighted 20%
• EEM (an emerging-mkts fund), weighted 20%
• AGG (a bond fund), weighted 10%

We will also use a MAR that is equal to .008 or 0.8%. Just a note, we are holding this portfolio to a higher standard than normal — typically, you'd just want the rate to be above 0%.

# install.packages("tidyverse")
# install.packages("tidyquant")
# install.packages("timetk")

library(tidyverse)
library(tidyquant)
library(timetk) 

Next, import daily prices for the five exhange-traded funds (ETFs), using the R package getSymbols to grab the data, map(~Ad(get(.))) to select adjusted prices only, and reduce(merge) to mash the five prices into one xts object.

 # The symbols vector holds our tickers.
symbols <- c("SPY","EFA", "IJS", "EEM","AGG")

# The prices object will hold our raw price data
prices <-
getSymbols (symbols, src = 'yahoo', from = "2005-01-01",
auto.assign = TRUE, warnings = FALSE) %>%
reduce(merge) %>%
colnames<-(symbols) 

Choose asset weights and assign them to the variable w. You will also assign the MAR of .008 to the variable.

w <- c(0.25, 0.25, 0.20, 0.20, 0.10)

MAR <- .008


Next, you will use two methods to convert those asset weights to monthly log returns. For the first method, we will stay in the xts world.

prices_monthly <- to.monthly(prices, indexAt = "last", OHLC = FALSE)
asset_returns_xts <- na.omit(Return.calculate(prices_monthly, method = "log"))  

Invoke the function Return.portfolio(asset_returns_xts, weights = w) from PerformanceAnalytics and pass in your asset returns and weights. This will give you portfolio returns in xts format.

portfolio_returns_xts <- Return.portfolio(asset_returns_xts, weights = w) 

You now have an xts object of portfolio returns called portfolio_returns_xts.

Now, you will perform the same transformations in the tidy world, starting with the transformation of daily prices to monthly asset returns.

 # Tidyverse method, to long, tidy format
asset_returns_long <-
prices %>%
to.monthly(indexAt = "last", OHLC = FALSE) %>%
tk_tbl(preserve_index = TRUE, rename_index = "date") %>%
gather(asset, returns, -date) %>%
group_by(asset) %>%
mutate(returns = (log(returns) - log(lag(returns)))) 

For portfolio returns, call the tq_portfolio function from tidyquant.

portfolio_returns_tidy <-
asset_returns_long %>%
tq_portfolio(assets_col = asset,
returns_col = returns,
weights = w,
col_rename = "returns") 

You now have a tidy tibble object of portfolio returns called portfolio_returns_tidy. Take a quick peek at both of the objects you just created for a quick check.

head(portfolio_returns_xts)
head(portfolio_returns_tidy) 

There is one big difference that we will handle below: For January 2005, portfolio_returns_tidy contains 0.00, and portfolio_returns_xts excludes the observation completely. That will make a difference because 0.00 is below our MAR.

On to the Sortino analysis. Calculating the Sortino ratio in the xts world is very convenient. Call:

SortinoRatio(portfolio_returns_xts, MAR = MAR)

Then pass the portfolio returns and MAR to the built-in function from PerformanceAnalytics.

sortino_xts <-
SortinoRatio(portfolio_returns_xts, MAR = MAR) %>%
colnames' <- ("ratio")

From a substantive perspective, you could stop here and start visualizing with highcharter.

Instead, I would suggest you run the calculation by hand, implementing the equation for the Sortino Ratio via pipes and dplyr. It's not a verbose piped workflow. In short, you would call:

 summarise(ratio = mean(returns - MAR)/sqrt(sum(pmin(returns - MAR, 0)^2)/nrow(.)))

Note the use of slice(-1) to remove the first row. You want to delete that first 0.00 for January 2005 to be consistent with the xts operations, but that is an important choice and one that could be questioned. Perhaps you should instead re-wrangle your xts object to make it consistent? Either way, you want to be explicit about your choice so that others can reproduce your work later.

 sortino_byhand <-
portfolio_returns_tidy %>%
slice(-1) %>%
summarise(ratio = mean(returns - MAR)/sqrt(sum(pmin(returns - MAR, 0)^2)/nrow(.)))

sortino_byhand

Now on to tidyquant, which allows you to apply the SortinoRatio function from PerformanceAnalytics to a tibble. As long as you are passing it the same data as you passed originally with the xts object, you should get the same result.

 sortino_tidy <
portfolio_returns_tidy %>%
slice(-1) %>%
tq_performance(Ra = returns,
performance_fun = SortinoRatio,
MAR = MAR,
method = "full") %>%
colnames<-("ratio")

You have consistent results from xts, tidyquant, and your by-hand piped calculation. It might feel like a lot of work to get the same result three times, but it forces you to look under the hood of the built-in functions, which might serve you well in the future should you have data or a project that fits better with one of the three methods.

In the next installment, I will walk you through how to visualize the Sortino ratio and its data slicing implications using ggplot2 and highcharter`. Thanks for reading!