# Connect to PostgreSQL database
library(RPostgreSQL)
drv <- dbDriver("PostgreSQL")
pg <- dbConnect(drv, dbname = "crsp")
rs <- dbGetQuery(pg, "DROP TABLE IF EXISTS destagger.split_fund_rank")
rs <- dbGetQuery(pg, "
CREATE TABLE destagger.split_fund_rank AS
SELECT instid, num_votes, rank() OVER (ORDER BY num_votes DESC) AS fund_rank
FROM (
SELECT instid, count(*) AS num_votes
FROM (
SELECT DISTINCT instid, itemonagendaid
FROM issvoting.fund_votes
WHERE issagendaitemid='S0107') AS b
GROUP BY instid
ORDER BY num_votes DESC) AS a")
# Pull in votes from ISS Voting Analytics
rs <- dbGetQuery(pg, "DROP TABLE IF EXISTS destagger.split_votes")
sql <- "
CREATE TABLE destagger.split_votes AS
SELECT * FROM issvoting.compvotes AS a
LEFT JOIN (
SELECT itemonagendaid, min(fund_rank) AS rank_fund_against
FROM issvoting.fund_votes AS a
INNER JOIN destagger.fund_rank AS b
USING (instid)
WHERE fundvote='Against'
GROUP BY itemonagendaid) AS b
USING (itemonagendaid)
WHERE issagendaitemid='S0107'
ORDER BY issagendaitemid"
rs <- dbGetQuery(pg, sql)
# Add PERMNO and GVKEY to the table (match to CUSIP)
rs <- dbGetQuery(pg,
"ALTER TABLE destagger.split_votes ADD COLUMN gvkey character(6)")
rs <- dbGetQuery(pg,
"ALTER TABLE destagger.split_votes ADD COLUMN permno integer")
rs <- dbGetQuery(pg,
"UPDATE destagger.split_votes AS a SET gvkey =
(SELECT gvkey FROM destagger.gvkeys AS b
WHERE b.cusip=a.cusip)")
rs <- dbGetQuery(pg,
"UPDATE destagger.split_votes AS a SET permno =
(SELECT DISTINCT permno FROM crsp.stocknames AS b
WHERE b.ncusip=substr(a.cusip,1,8))")
# Pull data into R
declassify <- dbGetQuery(pg, "SELECT * FROM destagger.split_votes")
dbDisconnect(pg)
## [1] TRUE
library(RPostgreSQL)
drv <- dbDriver("PostgreSQL")
pg <- dbConnect(drv, dbname = "crsp")
# Get returns around meeting dates
dropbox.path <- path.expand("~/Dropbox/research/AGL/")
source(paste(dropbox.path,"Code/R/getEventReturns.R",sep=""))
ret_data <- getEventReturns(declassify$permno, declassify$meetingdate,
days.before=0, days.after=1)
## Initializing database connection... 0.00 seconds
## Preparing data for upload to database... 0.00 seconds
## Writing list of PERMNOs and event dates to database...0.10 seconds
## Identifying relevant trading dates... 0.03 seconds
## Compounding raw returns... 0.21 seconds
## Compounding value-weighted market returns... 0.01 seconds
## Compounding size-decile market returns... 0.01 seconds
ret_data$row.names <- NULL
ret_data <- unique(ret_data) # Drop duplicates (multiple votes at a single meeting)
# Put return data back in the database
rs <- dbWriteTable(pg, c("destagger", "split_ret_data"), ret_data,
overwrite=TRUE, row.names=FALSE)
# Get data including a measure of shareholder support relative to "base"
# (e.g., shares outstanding) specified for each vote.
declassify <- dbGetQuery(pg, "SELECT *,
CASE WHEN base='Outstanding' AND outstandingshares>0 THEN votedfor/outstandingshares
WHEN base='F+A' AND votedfor+votedagainst>0
THEN votedfor/(votedfor+votedagainst)
WHEN base='F+A+AB' AND votedfor+votedagainst+votedabstain>0
THEN votedfor/(votedfor+votedagainst+votedabstain)
END AS percent_for
FROM destagger.split_votes AS a
LEFT JOIN destagger.split_ret_data AS b
USING (permno)
WHERE a.meetingdate=b.event_date AND result IN ('Fail', 'Pass')")
dbDisconnect(pg)
## [1] TRUE
Does ISS ever vary in its recommendation? Quite a bit, but it's “for” in about 68.78% of cases.
with(declassify, table(mgtrec, issrec))
## issrec
## mgtrec Against Do Not Vote For NULL None
## Against 68 1 162 1 3
## For 0 1 1 0 0
There are 237 votes on majority voting in the sample, with 94.09% being rejections by shareholders. Here they are by year.
## Descriptive statistics
declassify$year <- format(declassify$meetingdate, "%Y")
with(declassify, table(year, result))
## result
## year Fail Pass
## 2001 4 0
## 2002 2 0
## 2003 22 0
## 2004 35 0
## 2005 23 1
## 2006 49 2
## 2007 36 5
## 2008 25 1
## 2009 27 5
The votes are not smoothly dispersed around the cutoff.
Note that these are only shareholder-sponsored votes.
# Produce a histogram of (SVT - allowable cap)
library(ggplot2)
qplot(percent_for - voterequirements,
data=subset(declassify,
percent_for <1 & percent_for > 0.01),
binwidth=.01,
main="Histogram: Shareholder support minus vote requirement",
xlab="Shareholder support minus vote requirement")
But there a hints of a response by the market to the result of the vote.
qplot(percent_for - voterequirements, ret_mkt,
data=subset(declassify,
percent_for <1 & percent_for > 0.01),
main="Market-adjusted Stock Returns: Days (0,1) Around Meeting Dates",
xlab="Shareholder support minus vote requirement",
color=issrec)
qplot(percent_for - voterequirements, ret_mkt,
data=subset(declassify,
percent_for <1 & percent_for > 0.01),
main="Market-adjusted Stock Returns: Days (0,1) Around Meeting Dates",
xlab="Shareholder support minus vote requirement",
color=mgtrec)
The variable rank_fund_against represents the rank of the largest fund (based
on number of votes) voting against a given proposal. Most proposals are opposed by the
(one or more of) the largest funds, though some are supported by all of these funds.
source("~/Dropbox/research/rdd/rd_opt_bw.R")
# source('http://iangow.me/~iangow/rd_opt_bw.R') (4) Calculate the
# treatment effect and standard errors.
table(declassify$rank_fund_against)
##
## 1 2 3 4 5 8 39 42 135 157 227
## 135 5 1 2 1 3 1 14 40 1 2
rd_data <- subset(declassify, subset = percent_for < 1 & percent_for >
0.01, select = c(ret_mkt, percent_for, voterequirements))
rd_data$fundvote <- 1
rd_data$x <- (rd_data$percent_for - rd_data$voterequirements) * rd_data$fundvote
rd_data$y <- rd_data$ret_mkt
# First, get bandwidth
h <- with(rd_data, rd_opt_bw(y, x, c = 0))
X <- rd_data$x
c <- 0
## Weights based on triangular kernel
wgts <- with(rd_data, (1 - abs(X - c)/h) * (abs(X - c) <= h))
local.lm <- lm(y ~ (x >= c) * x, weights = wgts, data = rd_data)
rd.est <- coef(local.lm)[[2]]
sd.est <- sqrt(vcov(local.lm)[2, 2])
## Output
out <- c(h, rd.est, sd.est, rd.est/sd.est)
names(out) <- c("Optimal Bandwidth", "RD Estimate", "Standard Error",
"t-statistic")
print(out)
## Optimal Bandwidth RD Estimate Standard Error t-statistic
## 0.16076 0.04190 0.01402 2.98877
# A function to estimate local linear regression around a point (x_i)
# using bandwidth h, the triangular kernel and limiting observations to
# those on the same side of the cutoff c. In some sense, this is purely
# visual, as the IK bandwidth is optimized for x \in [c-h, c+h] and other
# data are not involved.
ll <- function(x_i, y, x, h, c) {
wgts <- (1 - abs(x - x_i)/h) * (abs(x - x_i) <= h) * (sign(x_i - c) == sign(x -
c) | x_i == c)
lm.fitted <- lm(y ~ x, weights = wgts)
if (sum(wgts > 0) > 10) {
# Require 10 observations around x_i
return(predict(lm.fitted, newdata = data.frame(x = x_i)))
} else {
return(NA)
}
}
# Add the fitted value to the dataset
library(parallel)
rd_data$y_fitted <- with(rd_data, unlist(mclapply(X, ll, y, X, h,
c, mc.cores = 12)))
# Make a plot
library(ggplot2)
ggplot(rd_data, aes(x)) + geom_point(aes(y = y, color = x > c)) +
geom_line(aes(y = y_fitted, color = x > c))
It seems there is quite a bit of variation in how mutual funds vote.
## institutionname percent_for num_votes
## 1 Fidelity Management & Research 0.32792 310
## 2 Transamerica Funds 0.43103 303
## 3 John Hancock Funds, LLC 0.45985 294
## 4 BlackRock Advisors, Inc. 0.32707 282
## 5 Prudential Financial 0.61509 279
## 6 AST Investment Services, Inc. 0.46591 275
## 7 Jackson National Asset Management, LLC 0.37453 271
## 8 Phoenix Funds 0.48031 263
## 9 T. Rowe Price Associates, Inc. (MD) 0.52778 257
## 10 AIM Management Group, Inc. 0.48594 252
## 11 Northern Trust Global Investments 0.15812 239
## 12 IQ Investment Advisors LLC 0.22439 237
## 13 Capital Research & Management Co. 0.31858 227
## 14 Janus Capital Management LLC 0.67488 224
## 15 RS Investment Management Co. LLC 0.56696 224
## 16 Northwestern Mutual Funds 0.52752 221
## 17 AIG SunAmerica Asset Management Corp. 0.58152 209
## 18 Munder Capital Management 0.62500 209
## 19 General Electric Asset Management 0.03743 189
## 20 New York Life Investment Management LLC 0.61290 188
## 21 RiverSource Investments LLC 0.73596 184
## 22 Vanguard Group, Inc. 0.01099 183
## 23 Rydex Investments 0.01714 180
## 24 TIAA-CREF Asset Management LLC 0.15000 179
## 25 Variable Annuity Life Insurance Company 0.56471 171
## 26 American Century Investment Management, Inc. 0.14013 159
## 27 Charles Schwab Investment Management, Inc. 0.43312 157
## 28 Evergreen Investment Management Co., Inc. 0.71533 142
## 29 Nationwide Fund Advisors 0.48031 133
## 30 Nationwide Fund<a0>Advisors 0.21951 83
## 31 Charles Schwab Investment Management, In 0.48837 44
## 32 American Century Investment Management, 0.07500 41
## 33 Evergreen Investment Management Co., Inc 0.44737 38
## 34 Nationwide Fund Advisors 0.33333 37