In two previous posts, we explored IPOs and IPO returns by sector and year since 2004 and then examined the returns of portfolios constructed by investing in IPOs each year. In today’s post, we will add a benchmark so that we can compare our IPO portfolios to something besides themselves. Next time, we will delve into return attribution to visualize how individual equities have contributed to portfolios over time.
I won’t review the code from the previous posts, but briefly we imported prices for every ticker that IPO’d between 2004 and 2014, found the monthly returns of those tickers, then constructed portfolios on a year-by-year basis, so that we had a portfolio formed each year consisting of equal weights in every IPO for that year.
# object holding time series of monthly closing prices, monthly returns, tickers, IOP year and sector
ipo_riingo_prices_pins
# function to calculate returns of portfolios constructed by investing equally in each IPO in each year
ipo_by_year_portfolios <- function(year, show_growth = F){
ipo_riingo_prices_pins %>%
select(ticker, date, monthly_returns, ipo.year) %>%
filter(ipo.year == year) %>%
tq_portfolio(assets_col = ticker,
returns_col = monthly_returns,
col_rename = paste(year, "_port_returns", sep = ""),
wealth.index = show_growth,
rebalance_on = "months")
}
# vector of years
years_numeric <- seq(2004, 2014, by = 1)
# pass the years and the indiviual returns object to the function
returns_each_year_ipo_portfolios <-
map(years_numeric, ipo_by_year_portfolios) %>%
reduce(left_join)
And here is the resulting object of portfolio returns:
returns_each_year_ipo_portfolios %>%
tail()
# A tibble: 6 x 12
date `2004_port_retu… `2005_port_retu… `2006_port_retu…
<dttm> <dbl> <dbl> <dbl>
1 2019-05-31 00:00:00 -0.101 -0.0575 0.261
2 2019-06-28 00:00:00 0.382 0.0635 0.0533
3 2019-07-31 00:00:00 0.00659 0.0185 0.0346
4 2019-08-30 00:00:00 -0.0229 -0.0317 -0.00789
5 2019-09-30 00:00:00 0.0256 0.00545 -0.00539
6 2019-10-31 00:00:00 0.0347 0.0180 0.0233
# … with 8 more variables: `2007_port_returns` <dbl>,
# `2008_port_returns` <dbl>, `2009_port_returns` <dbl>,
# `2010_port_returns` <dbl>, `2011_port_returns` <dbl>,
# `2012_port_returns` <dbl>, `2013_port_returns` <dbl>,
# `2014_port_returns` <dbl>
All that was done last time, and it gave us the returns of our 11 IPO portfolios from formation to today.
Now, let’s calculate the returns of a benchmark portfolio so we can compare those IPO portfolios to something besides themselves. We will use SPY as the benchmark and start by importing monthly prices since 2004. I’ll also go ahead and calculate monthly returns in the same piped flow.
spy_benchmark <-
"SPY" %>%
riingo_prices(start_date = "2004-01-01", end_date = "2019-10-31", resample_frequency = "monthly") %>%
select(ticker, date, close) %>%
mutate(spy_monthly_returns = close/lag(close) - 1) %>%
na.omit()
spy_benchmark %>%
head()
# A tibble: 6 x 4
ticker date close spy_monthly_returns
<chr> <dttm> <dbl> <dbl>
1 SPY 2004-02-27 00:00:00 115. 0.0136
2 SPY 2004-03-31 00:00:00 113. -0.0167
3 SPY 2004-04-30 00:00:00 111. -0.0189
4 SPY 2004-05-31 00:00:00 113. 0.0171
5 SPY 2004-06-30 00:00:00 115. 0.0148
6 SPY 2004-07-30 00:00:00 111. -0.0322
From here, it’s straightforward to compare these benchmark returns to those of the 2004 IPO portfolio. First, we line up the two columns of returns.
returns_each_year_ipo_portfolios %>%
select(date, `2004_port_returns`) %>%
add_column(benchmark = spy_benchmark$spy_monthly_returns) %>%
tail()
# A tibble: 6 x 3
date `2004_port_returns` benchmark
<dttm> <dbl> <dbl>
1 2019-05-31 00:00:00 -0.101 -0.0638
2 2019-06-28 00:00:00 0.382 0.0644
3 2019-07-31 00:00:00 0.00659 0.0151
4 2019-08-30 00:00:00 -0.0229 -0.0167
5 2019-09-30 00:00:00 0.0256 0.0148
6 2019-10-31 00:00:00 0.0347 0.0221
Then we pivot_longer()
and apply the SharpeRatio()
function, same as we did last time.
returns_each_year_ipo_portfolios %>%
select(date, `2004_port_returns`) %>%
add_column(benchmark = spy_benchmark$spy_monthly_returns) %>%
pivot_longer(-date, names_to = "portfolio", values_to = "monthly_return") %>%
group_by(portfolio) %>%
arrange(portfolio, date) %>%
filter(!is.na(monthly_return)) %>%
tq_performance(Ra = monthly_return,
performance_fun = SharpeRatio,
Rf = 0,
FUN= "StdDev")
# A tibble: 2 x 2
# Groups: portfolio [2]
portfolio `StdDevSharpe(Rf=0%,p=95%)`
<chr> <dbl>
1 2004_port_returns 0.234
2 benchmark 0.153
Here’s the result piped straight to ggplot()
.
returns_each_year_ipo_portfolios %>%
select(date, `2004_port_returns`) %>%
add_column(benchmark = spy_benchmark$spy_monthly_returns) %>%
pivot_longer(-date, names_to = "portfolio", values_to = "monthly_return") %>%
group_by(portfolio) %>%
arrange(portfolio, date) %>%
filter(!is.na(monthly_return)) %>%
tq_performance(Ra = monthly_return,
performance_fun = SharpeRatio,
Rf = 0,
FUN= "StdDev") %>%
`colnames<-`(c("portfolio", "port_sharpe")) %>%
ggplot(aes(x = portfolio, y = port_sharpe, fill = portfolio)) +
geom_col(width = .2) +
labs(y = "sharpe ratio", title = "Benchmark v. IPO Portfolio")
Our IPO portfolio has a higher Sharpe Ratio, but remember that we built this without regard to survivorship bias, we didn’t invest in any companies that haven’t survived to 2019.
That’s a nice comparison of one portfolio to the benchmark, but we want to run this same analysis on all of our portfolios.
First, let’s calculate the Sharpes for all of our IPO portfolios, same as we did last time.
years_numeric <- seq(2004, 2014, by = 1)
port_sharpes <-
returns_each_year_ipo_portfolios %>%
pivot_longer(-date, names_to = "portfolio_by_year", values_to = "monthly_return") %>%
group_by(portfolio_by_year) %>%
arrange(portfolio_by_year, date) %>%
filter(!is.na(monthly_return)) %>%
tq_performance(Ra = monthly_return,
performance_fun = SharpeRatio,
Rf = 0,
FUN= "StdDev") %>%
`colnames<-`(c("portfolio_by_year", "port_sharpe"))%>%
add_column(year = years_numeric)
port_sharpes
# A tibble: 11 x 3
# Groups: portfolio_by_year [11]
portfolio_by_year port_sharpe year
<chr> <dbl> <dbl>
1 2004_port_returns 0.234 2004
2 2005_port_returns 0.192 2005
3 2006_port_returns 0.249 2006
4 2007_port_returns 0.190 2007
5 2008_port_returns 0.142 2008
6 2009_port_returns 0.220 2009
7 2010_port_returns 0.279 2010
8 2011_port_returns 0.152 2011
9 2012_port_returns 0.309 2012
10 2013_port_returns 0.182 2013
11 2014_port_returns 0.218 2014
And now, let’s calculate the Sharpe Ratio for the benchmark for each year. That means we will build or organize 11 different return streams for SPY, each starting in a year from 2004 to 2014, and then calculate the Sharpes for each of those 11 return streams.
Here’s how we do it for just 2004.
start_year <- "2004"
start_date <- ymd(parse_date(start_year, format = "%Y"))
spy_benchmark %>%
filter(date >= start_date) %>%
tq_performance(Ra = spy_monthly_returns,
performance_fun = SharpeRatio,
Rf = 0,
FUN= "StdDev")
# A tibble: 1 x 1
`StdDevSharpe(Rf=0%,p=95%)`
<dbl>
1 0.153
This looks like a good candidate for a function that accepts one argument, the start_year
, that we can pass a vector of years.
spy_sharpe_function <- function(start_year){
start_date <- ymd(parse_date(start_year, format = "%Y"))
spy_benchmark %>%
filter(date >= start_date) %>%
tq_performance(Ra = spy_monthly_returns,
performance_fun = SharpeRatio,
Rf = 0,
FUN = "StdDev") %>%
`colnames<-`("spy_sharpe") %>%
mutate(year = as.numeric(start_year))
}
Let’s pass in one year and peek at the result.
spy_sharpe_function("2005")
# A tibble: 1 x 2
spy_sharpe year
<dbl> <dbl>
1 0.150 2005
Now, let’s map across different years.
years_character <- as.character(years_numeric)
spy_sharpes <-
map_dfr(years_character, spy_sharpe_function)
spy_sharpes
# A tibble: 11 x 2
spy_sharpe year
<dbl> <dbl>
1 0.153 2004
2 0.150 2005
3 0.152 2006
4 0.139 2007
5 0.141 2008
6 0.258 2009
7 0.253 2010
8 0.263 2011
9 0.310 2012
10 0.300 2013
11 0.229 2014
That worked! Let’s join our benchmark results with the IPO portfolio results for ease of comparison and pipe straight to ggplot()
.
port_sharpes %>%
left_join(spy_sharpes, by = "year") %>%
pivot_longer(c(-year, -portfolio_by_year), names_to = "port_type", values_to = "sharpe") %>%
ggplot(aes(x = year, y = sharpe, fill = port_type)) +
geom_col(position = position_dodge2(padding = .2)) +
scale_x_continuous(breaks = scales::pretty_breaks(n = 10))
It looks like our IPO portfolios outperformed in the years 2004-2007. That might be due to our survivorship bias since we’re only investing in companies that we know, with hindsight, have survived to 2019.
Let’s also remember that Sharpe Ratios aren’t everything. Our IPO portfolios might be so volatile that we wouldn’t have a the gumption to stick with them through the hard times. To get a better sense of what we’d have faced, let’s visualize the drawdowns for the 2004 IPO portfolio versus the benchmark.
returns_each_year_ipo_portfolios %>%
select(date, `2004_port_returns`) %>%
left_join(spy_benchmark %>% select(date, spy_monthly_returns), by = "date") %>%
pivot_longer(-date, names_to = "fund", values_to = "drawdown") %>%
mutate(drawdown = case_when(drawdown > 0 ~ 0,
TRUE ~ drawdown),
drawdown = drawdown * 100) %>%
plot_ly(type = 'scatter', x = ~date, y = ~drawdown, color = ~fund,
mode = 'lines', fill = 'tonexty') %>%
layout(yaxis = list(ticksuffix = "%"))
Click on the legend to isolate the chart of either the IPO portfolio or the benchmark and notice the much rougher history of the IPO portfolio. Our IPO portfolio had a large drawdown of around 26% in 2008 - would we have stuck with it?
I’ve been making my way through more of the courses over at Business Science U and one habit I’ve picked up is to wrap visualizations into functions. Let’s do that for the drawdown chart so that all we have to do is supply a year. This would be helpful in a Shiny application where we wanted to explore different years interactively.
drawdown_vis_fun <- function(start_year){
start_date <- ymd(parse_date(start_year, format = "%Y"))
spy_benchmark <-
spy_benchmark %>%
filter(date >= start_date)
ipo_port <- paste(start_year, "_port_returns", sep = "")
returns_each_year_ipo_portfolios %>%
select(date, `ipo_port`) %>%
left_join(spy_benchmark %>% select(date, spy_monthly_returns), by = "date") %>%
pivot_longer(-date, names_to = "fund", values_to = "drawdown") %>%
mutate(drawdown = case_when(drawdown > 0 ~ 0,
TRUE ~ drawdown),
drawdown = drawdown * 100) %>%
plot_ly(type = 'scatter', x = ~date, y = ~drawdown, color = ~fund,
mode = 'lines', fill = 'tonexty') %>%
layout(yaxis = list(ticksuffix = "%"))
}
Now, let’s pass the function a single year and examine the drawdown history.
drawdown_vis_fun("2007")
That’s all for today’s addendum.
I’m also going to be posting weekly code snippets on linkedin, connect with me there if you’re keen for some R finance stuff.
Thanks for reading and see you next time when we’ll tackle asset contribution to portfolio return!
You may leave a comment below or discuss the post in the forum community.rstudio.com.