Today, we begin a project to build a Shiny application that allows a user to build a portfolio and calculate/visualize its Sortino Ratio.
The final app is viewable here but we’ll spend the next 3 posts constructing that.
By way of brief background and motivation for this project, the Sortino Ratio is a measure of the return/risk ratio of a portfolio and is an important tool for evaluating the risk adjusted returns of a portfolio and the skill of the portfolio manager. A higher Sortino indicates a portfolio manager who is generating higher returns per unit of risk absorbed. Identifying better performing portfolios and portfolio managers is crucial for quants, data scientist and analysts at banks, endowments, investment advisors, funds of funds, hedge funds - all enterprise-level participants in the world of asset management. From a data science perspective, we want to accomplish this using a reproducible work flow and an appealing, digestible 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!
The Sortino Ratio equation is as follows:
The denominator in that equation (called the Downside Deviation, semi-deviation 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 and 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 contradistinction to the more commonly used Sharpe Ratio, which measures return/risk by the ratio of the returns above the risk free rate divided by the standard deviation of all returns. By way of history, Harry Markowitz, Nobel laureate and father of modern portfolio theory, noted that downside deviation might be a better measure of risk than the standard deviation of all returns, but its calculation was computationally too expensive1 (it was 1959, if he only he’d had R on his laptop).
Frank Sortino’s original article on downside deviation, “On the Use and Misuse of Downside Risk,” was published in the Journal of Portfolio Management in 1996 but you’ll need a paid subscription to access that.
As for our project, we will proceed in 3 steps:
1. Build a portfolio and calculate the Sortino Ratio using 3 methods (today's post) 2. Visualize the Sortino Ratio using ggplot and highcharter (next week) 3. Wrap to an interactive Shiny App (in two weeks)
When working with the Sortino Ratio, we have two critical choices: how to construct the portfolio using assets and weights, and which MAR to use. Our Shiny application at project’s end will allow a user to make these choices and see how the Sortino Ratio changes. For today, we will go with the following portfolio and MAR:
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%
Minimum Acceptable Rate
+ MAR = .008 or .8% + Note we are holding this portfolio to a higher standard than being above 0%.
Let’s load our packages:
library(tidyverse) library(tidyquant) library(timetk)
First, we import daily prices for the five ETFs, using
getSymbols to grab the data,
map(~Ad(get(.))) to select adjusted prices only, and
reduce(merge) to mash our five prices into one
# 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) %>% map(~Ad(get(.))) %>% reduce(merge) %>% `colnames<-`(symbols)
Next we choose our asset weights and assign them to the variable
w. We 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 we convert those to monthly log returns, using two methods. For the first method, we stay in the
prices_monthly <- to.monthly(prices, indexAt = "last", OHLC = FALSE) asset_returns_xts <- na.omit(Return.calculate(prices_monthly, method = "log"))
We invoke the function
Return.portfolio(asset_returns_xts, weights = w) from
PerformanceAnalytics and pass in our asset returns and weights. This will return portfolio returns in
portfolio_returns_xts <- Return.portfolio(asset_returns_xts, weights = w)
We now have an
xts object of portfolio returns called
Now let’s perform the same transformations in the tidy world.
First, we go from 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, we call the
tq_portfolio function from
portfolio_returns_tidy <- asset_returns_long %>% tq_portfolio(assets_col = asset, returns_col = returns, weights = w, col_rename = "returns")
We now have a tidy
tibble object of portfolio returns called
Let’s take a quick peek at both of our objects for a sanity check.
## portfolio.returns ## 2005-02-28 0.03829297 ## 2005-03-31 -0.03418759 ## 2005-04-29 -0.02018237 ## 2005-05-31 0.02441793 ## 2005-06-30 0.02032339 ## 2005-07-29 0.04228071
## # A tibble: 6 x 2 ## date returns ## <date> <dbl> ## 1 2005-01-31 0.00000000 ## 2 2005-02-28 0.03829297 ## 3 2005-03-31 -0.03418759 ## 4 2005-04-29 -0.02018237 ## 5 2005-05-31 0.02441793 ## 6 2005-06-30 0.02032339
There is one big difference that we will handle below: for January of 2005,
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 almost depressingly convenient. We call
SortinoRatio(portfolio_returns_xts, MAR = MAR), passing our portfolio returns and MAR tot he built-in function from
sortino_xts <- SortinoRatio(portfolio_returns_xts, MAR = MAR) %>% `colnames<-`("ratio")
From a substantive perspective, we could stop here and start visualizing with
Instead, we will 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, we 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. I want to delete that first 0.00 for January of 2005 to be consistent with the
xts operations, but that is an important choice and one that could be questioned. Perhaps we should instead re-wrangle our
xts object to make it consistent? Either way, we want to be explicit about the choice so that others can reproduce this work later.
sortino_byhand <- portfolio_returns_tidy %>% slice(-1) %>% summarise(ratio = mean(returns - MAR)/sqrt(sum(pmin(returns - MAR, 0)^2)/nrow(.))) sortino_byhand
## # A tibble: 1 x 1 ## ratio ## <dbl> ## 1 -0.06831889
Now on to
tidyquant, which allows us to apply the
SortinoRatio function from
PerformanceAnalytics to a
tibble. As long as we are passing it the same data as we passed originally with the
xts object, we expect the same result.
sortino_tidy <- portfolio_returns_tidy %>% slice(-1) %>% tq_performance(Ra = returns, performance_fun = SortinoRatio, MAR = MAR, method = "full") %>% `colnames<-`("ratio")
Let’s compare our 3 Sortino objects.
##  -0.06831889
##  -0.06831889
##  -0.06831889
We have consistent results from
tidyquant and our by-hand piped calculation. It might feel like a lot of work to get the same result three times but it forced us to look under the hood of the built-in functions and it might serve us well in the future should we have data or a project that fits better with one of the three methods.
That’s all for today. Next time we will visualize the Sortino Ratio and its data slicing implications using
highcharter. Thanks for reading.
Markowitz, Harry. Portfolio Selection: Efficient Diversification of Investments, John Wiley & Sons, 1959.↩