IPO Exploration Part Two
chartsnthings 2019-11-14
Summary:
In a previous post, we explored IPOs and IPO returns by sector and year since 2004. Today, let’s investigate how portfolios formed with those IPOs have performed. We will need to grab the price histories of the tickers, then form portfolios, then calculate their performance, and then rank those performances in some way.
Since there are several hundred IPOs for which we need to pull returns data, today’s post will be a bit data intensive. If you don’t want to tax your machine too much, you can always subset down the data before running the script. With that, let’s do this!
I won’t go through the code again in detail since we covered it last time, but our first step is to get the tickers and IPO years for a large universe of companies. Here is the code to retrieve the tickers for all companies that went public between 2004 and 2014 - and have survived (meaning today’s analysis is going to have serious survivor bias).
nasdaq %
filter(!is.na(ipo.year) & ipo.year __ 2015)
And here is what we get:
company_ipo_sector %__%
head()
# A tibble: 6 x 4
symbol company ipo.year sector
1 PIH 1347 Property Insurance Holdings, Inc. 2014 Finance
2 FLWS 1-800 FLOWERS.COM, Inc. 1999 Consumer Services
3 VNET 21Vianet Group, Inc. 2011 Technology
4 TWOU 2U, Inc. 2014 Technology
5 JOBS 51job, Inc. 2004 Technology
6 ABIL Ability Inc. 2014 Capital Goods
There are about 1,830 tickers in this data frame and it conveniently includes a column called ipo.year, which we’ll make us of later in this post. Our next step is to download the price histories of these tickers from tiingo.
We’ll need to divide our tickers into smaller subsets and pull in the data in pieces so we don’t hit our tiingo API limits.
tickers_2004_2006 %
distinct(company, .keep_all = TRUE) %__%
filter(!is.na(sector) & between(ipo.year, 2004, 2006)) %__%
group_by(ipo.year) %__%
pull(symbol)
tickers_2007_2009 %
distinct(company, .keep_all = TRUE) %__%
filter(!is.na(sector) & between(ipo.year, 2007, 2009)) %__%
group_by(ipo.year) %__%
pull(symbol)
tickers_2010_2012 %
distinct(company, .keep_all = TRUE) %__%
filter(!is.na(sector) & between(ipo.year, 2010, 2012)) %__%
group_by(ipo.year) %__%
pull(symbol)
tickers_2013_2014 %
distinct(company, .keep_all = TRUE) %__%
filter(!is.na(sector) & between(ipo.year, 2013, 2014)) %__%
group_by(ipo.year) %__%
pull(symbol)
We now have 4 sets of tickers. Let’s pass them to the riingo function one at a time and set the resample_frequency to monthly, so we’re pulling in 12 prices per year per company instead of ~252.
prices_riingo_2004_2006 %
riingo_prices(start_date = "2004-01-01", resample_frequency = "monthly") %__%
group_by(ticker)
prices_riingo_2007_2009 %
riingo_prices(start_date = "2007-01-01", resample_frequency = "monthly") %__%
group_by(ticker)
prices_riingo_2010_2012 %
riingo_prices(start_date = "2010-01-01", resample_frequency = "monthly") %__%
group_by(ticker)
prices_riingo_2013_2014 %
riingo_prices(start_date = "2013-01-01", resample_frequency = "monthly") %__%
group_by(ticker)
prices_riingo_full %
bind_rows(prices_riingo_2007_2009) %__%
bind_rows(prices_riingo_2010_2012) %__%
bind_rows(prices_riingo_2013_2014)
We have our prices, now let’s add a column of monthly returns with a call to mutate(monthly_returns = close/lag(close) - 1).
prices_riingo_full %
group_by(ticker) %__%
mutate(monthly_returns = close/lag(close) - 1)
One more step, I won’t bore you with the details but after a lot of slogging, I discovered a few errors in my data, where the ipo.year came after the first year in which the ticker was traded. That’s probably because of some equities being relisted on other exchanges or changing ticker names, but suffice it to say, it jacked up my portfolio analysis! I’m going to remove those problematic tickers with the code below. First, we filter(date == min(date)) to get the first day for which we have returns. Then we isolate the year of that first trade with mutate(first_trade = year(date)). If that year doesn’t match ipo.year, we pull() the ticker.
tickers_remove_mismatch_dates %
left_join(company_ipo_sector, by = c("ticker" = "symbol")) %__%
filter(date == min(date)) %__%
select(ticker, date, ipo.year) %__%
mutate(first_trade = year(date)) %__%
filter(ipo.year != first_trade) %__%
pull(ticker)
We now have a vector of the tickers to remove, but we haven’t removed them yet. I separated these two steps so I could take a peak at those tickers first, and maybe come back to later to address this issue in a better way.
Let’s go ahead and remove them (I’m going to remove PGTI also, it was showing up duplicate downstream in my code).
prices_riingo_full %
filter(!(ticker %in% tickers_remove_mismatch_dates) & ticker != 'PGTI'