Intro
In this article we will investigate if following management trading can provide superior investment returns. To do this, I’ve gathered a list of all the U.S. companies wherein management accounted for .01% (or more) of all volume traded for that company’s stock in the week prior. Other constraints, like a minimum company market capitalization, were also applied. I then retrieved each company’s stock prices for the following 3 months. Here is the resulting data since 2000:
Code
%>%
mgmt_data head() %>%
gt() %>%
::fmt_currency(
gtcolumns = c(market_cap, price),
suffixing = T
%>%
) ::fmt_number(
gtcolumns = c(p_e), decimals = 0
%>%
) ::fmt_percent(
gtcolumns = c(mgmt_buy_volume, total_return_ytd)
)
screen_date | date | symbol | short_name | mgmt_buy_volume | market_cap | p_e | total_return_ytd | price |
---|---|---|---|---|---|---|---|---|
2000-07-07 | 2000-07-07 | IFCIQ | INTL FIBERCOM | 19.59% | $733.64M | 80 | 205.56% | $24.06 |
2000-07-07 | 2000-07-10 | IFCIQ | INTL FIBERCOM | 19.59% | $733.64M | 80 | 205.56% | $24.06 |
2000-07-07 | 2000-07-11 | IFCIQ | INTL FIBERCOM | 19.59% | $733.64M | 80 | 205.56% | $23.31 |
2000-07-07 | 2000-07-12 | IFCIQ | INTL FIBERCOM | 19.59% | $733.64M | 80 | 205.56% | $23.62 |
2000-07-07 | 2000-07-13 | IFCIQ | INTL FIBERCOM | 19.59% | $733.64M | 80 | 205.56% | $25.69 |
2000-07-07 | 2000-07-14 | IFCIQ | INTL FIBERCOM | 19.59% | $733.64M | 80 | 205.56% | $25.25 |
Variable | Definition |
---|---|
screen_date | The date where the computer went back in time and filtered all the companies that had management buy volume account for .01% (or more) of all trading volume in the week prior. You will notice that screen_date is always a Saturday. |
date | The date associated with the company’s stock price (all other variables are constant). |
symbol | The company’s stock ticker |
short_name | The company’s name |
mgmt_buy_volume | Management’s proportion of trade volume |
market_cap | The company’s current market value (number of shares * share price) |
p_e | The company’s Price-to-Earnings ratio (the amount of money paid for $1 of the company’s earnings) |
total_return_ytd | The Year-to-Date return on the company’s stock |
price | The company’s closing stock price |
Examining the Data
Let’s dig into the data and get a feel for what we are looking at. As usual, we will use visuals to help us.
Code
%>%
mgmt_data group_by(screen_date) %>%
distinct(symbol) %>%
ungroup() %>%
count(screen_date) %>%
ggplot(aes(screen_date, n)) +
geom_col() +
theme_bw() +
labs(
x = "",
y = "",
title = "Count of Companies that Adhere to our Conditions",
subtitle = "Each Bar represents a Week"
+
) theme(text = element_text(size = 15))
As we can see from our plot, the weekly number of companies that adhere to our conditions gradually increase over time and rapidly increase after 2020. There are a few logical reasons for this:
I filtered for companies with market caps greater than $200M, but $200M in 2001 is worth much more than $200M in 2022. It would have been better to filter while adjusting for inflation, but I forgot to do this…
The overall number of companies in the U.S. has greatly increased since 2001
Over the past several years, we have had very low interest rates. With the cost of borrowing money so low, people have been spending money, creating companies, and driving valuations up.
Since we don’t have many companies during 2000-2004, let’s make the executive decision to only use data from 2005 and on. Here is the same plot as before but lets group by year this time:
Code
<- mgmt_data %>%
mgmt_data filter(screen_date >= ymd("2005-01-01"))
%>%
mgmt_data group_by(screen_date) %>%
distinct(symbol) %>%
ungroup() %>%
mutate(year = year(screen_date)) %>%
count(year) %>%
ggplot(aes(year, n)) +
geom_col() +
theme_bw() +
labs(
x = "",
y = "",
title = "Count of Companies that Adhere to our Conditions",
subtitle = "Each Bar represents a Year"
+
) theme(text = element_text(size = 15))
While we would still like to see more companies is the earlier years, this will have to do. Let’s continue forward and assess the performance of our filtered stocks relative to traditional index funds.
Performance Comparison
Let’s use the Russell 2000 Index and the SP500 Index as our benchmarks. However, rather than use the indices themselves, let’s use ETFs instead as these are a better representation of actual investment performance since individuals cannot actually invest in the indices. Here is the code to obtain that data and the following graph representing their performance since 2005:
Code
<- tq_get(c("IWM", "SPY"), from = "2005-01-01") %>%
price_data select(symbol, date, adjusted) %>%
mutate(name = ifelse(symbol == "IWM", "Russell 2000 ETF", "SP500 ETF"))
%>%
price_data group_by(symbol) %>%
mutate(adjusted = adjusted / first(adjusted)) %>%
ungroup() %>%
ggplot(aes(date, adjusted, color = name)) +
geom_line() +
theme_bw() +
scale_color_grey() +
scale_y_continuous(labels = scales::dollar_format()) +
labs(y = "Portfolio Wealth ($1)",
x = "",
color = "") +
theme(legend.position = "top", text = element_text(size = 15))
Now that we have this data, let’s compare the performance of the ETFs to that of our companies. In order to do this, we need to establish an investment horizon that makes sense for our companies. In other words, should we pretend that we invest in our companies for a day? 3 days? A week? A Year? etc. Let’s investigate this:
Code
%>%
mgmt_data group_by(screen_date, symbol) %>%
mutate(days_after_screen = row_number() - 1,
price_index = price / first(price)) %>%
ungroup() %>%
filter(days_after_screen <= 60) %>%
group_by(days_after_screen) %>%
summarize(average_return = mean(price_index, na.rm = T) - 1) %>%
ggplot(aes(days_after_screen, average_return)) +
geom_col() +
theme_bw() +
labs(
title = "Average Return vs. Investment Horizon",
y = "Average Return",
x = "Investment Horizon (in Days)"
+
) scale_y_continuous(labels = scales::percent_format(), n.breaks = 6) +
scale_x_continuous(n.breaks = 7) +
theme(text = element_text(size = 15))
We can already tell from the data that management clearly has some inside information that the markets are oblivious to; the average return for each of these companies after just 5 days is approximately 0.5%. This may not sound like much at first, but if you stop to do some simple calculations, you will realize that if you invest $1 at this rate, and compound your investment at a weekly (5 day) frequency over 52 weeks, then you will have approximately $1.3 at the end of the year. This equates to a 30% yearly return while the SP500 averages 10%.
Moving forward, lets continue with a hypothetical investment horizon of a week (5 days) as this will deliver the most ‘bang for our buck’. While the 60 day average return is approximately 2.5%, we will lose out on the benefits from a shorter compounding frequency. Moreover, a week is a clean frequency to work with, and it matches nicely with the fact that our screens are run at a weekly frequency.
Now that we have established our ‘investment horizon,’ let’s pretend that we invest equally in these companies each week and compare our performance to that of the SP500 and the Russell 2000:
Code
%>%
mgmt_data group_by(screen_date, symbol) %>%
mutate(days_after_screen = row_number() - 1,
price_index = price / first(price)) %>%
ungroup() %>%
filter(days_after_screen == 5) %>%
group_by(screen_date) %>%
summarize(portfolio_index = mean(price_index, na.rm = T)) %>%
left_join(
%>%
price_data mutate(date = floor_date(date, unit = "weeks") - days(2)) %>%
group_by(name, date) %>%
summarize(return = adjusted/first(adjusted)) %>%
slice_tail() %>%
ungroup() %>%
pivot_wider(names_from = name, values_from = return) %>%
::clean_names(),
janitorby = c("screen_date" = "date")
%>%
) mutate(across(-screen_date, .fns = cumprod)) %>%
pivot_longer(-screen_date) %>%
ggplot(aes(screen_date, value, color = name)) +
geom_line() +
theme_bw() +
scale_color_grey() +
scale_y_continuous(labels = scales::dollar_format()) +
labs(
y = "Portfolio Wealth Index ($1)",
color = "",
x = ""
+
) theme(legend.position = "top", text = element_text(size = 15))
Code
%>%
mgmt_data group_by(screen_date, symbol) %>%
mutate(days_after_screen = row_number() - 1,
price_index = price / first(price)) %>%
ungroup() %>%
filter(days_after_screen == 5) %>%
group_by(screen_date) %>%
summarize(portfolio_index = mean(price_index, na.rm = T)) %>%
left_join(
%>%
price_data mutate(date = floor_date(date, unit = "weeks") - days(2)) %>%
group_by(name, date) %>%
summarize(return = adjusted/first(adjusted)) %>%
slice_tail() %>%
ungroup() %>%
pivot_wider(names_from = name, values_from = return) %>%
::clean_names(),
janitorby = c("screen_date" = "date")
%>%
) mutate(year = year(screen_date)) %>%
group_by(year) %>%
mutate(across(-screen_date, .fns = cumprod)) %>%
slice_tail() %>%
ungroup() %>%
select(-screen_date, -russell_2000_etf) %>%
pivot_longer(-year) %>%
mutate(value = value - 1) %>%
ggplot(aes(year, value, fill = name)) +
geom_col(position = "dodge") +
theme_bw() +
scale_fill_grey() +
scale_y_continuous(labels = scales::percent_format()) +
labs(
y = "Return (%)",
x = "",
fill = ""
+
) theme(legend.position = "top", text = element_text(size = 15))
It is obviously apparent, from the above, that following management trading can provide superior investment returns…
Final Remarks
The above is intended as an exploration of historical data, and all statements and opinions are expressly my own; neither should be construed as investment advice.