This publication shows how to gather French Fama Carhart factors from the web, clean the factor data, and run a regression to examine the systematic risk of a security. In the example, a mutual fund is shown to exhibit sensitivity to the small cap and value premiums and create a positive alpha or return in excess of explained risk.
if(require(fImport) == FALSE) install.packages("fImport"); require(fImport)
if(require(zoo) == FALSE) install.packages("zoo"); require(zoo)
if(require(timeDate) == FALSE) install.packages("timeDate"); require(timeDate)
if(require(xts) == FALSE) install.packages("xts"); require(xts)
sourced from: http://stackoverflow.com/questions/13215246/function-to-calculate-nyse-trading-day-of-month
TradingDates <- function(year=format(Sys.Date(), "%Y"), FUN=holidayNYSE) {
year <- as.numeric(year)
fun <- match.fun(FUN)
do.call('c', lapply(year, function(y) {
holidays <- as.Date(fun(year=y))
all.days <- seq.Date(as.Date(paste(y, '01-01', sep='-')),
as.Date(paste(y, '12-31', sep='-')), by='days')
nohol <- all.days[!all.days %in% holidays]
nohol[!format(nohol, '%w') %in% c("6", "0")] #neither holiday nor weekend
}))
}
PrevTradingDate <- function(Date=Sys.Date(), n=1) {
D <- as.Date(Date)
y <- as.numeric(format(D, "%Y"))
trading.days <- TradingDates(y)
out <- trading.days[trading.days < Date]
if (length(out) >= n) {
first(last(out, n))
} else {
prev.year.td <- TradingDates(y - 1)
max.n <- length(out) + length(prev.year.td)
if (n > max.n) stop("'n' is too large. Try something less than 252.")
new.n <- n - length(out) # we need this many trading days from previous year
# if it's the 1st trading day of the year, return the last trading date of
# previous year
first(last(TradingDates(y - 1), new.n))
}
}
Ticker for security to analyze, example is Fidelity Small Cap Discovery - FSCRX Date range is for return history, frequency will be monthly
symbol = 'FSCRX'
begin.date = '2009-01-01'
end.date = '2015-01-01'
# create a sequence of last trading day of the month dates to help change
# the security's return frequency to monthly
secDates <- seq.Date(as.Date(begin.date), as.Date(end.date), by = "months")
secDates <- as.Date(sapply(secDates, "PrevTradingDate"))
# get security price history from yahoo finance
ohlc <- yahooSeries(symbol, from = begin.date, to = end.date, frequency = 'daily')
prices <- ohlc[, 6]
# create a temporary xts object to use the merge function to subset the daily price history
# to a monthly frequency
tempxts <- xts(1:length(secDates), secDates)
placeHolder <- merge(tempxts, prices)
prices_monthly <- na.omit(placeHolder)[, 2]
# calculate returns from monthly price history and add the dates as a new column
# in a data frame structure for later merging with factors
ret <- timeSeries::returns(prices_monthly, method = "discrete")
ret <- data.frame(as.yearmon(rownames(ret)), ret)
colnames(ret) <- c("date", symbol)
# get french fama data from french faculty page
ff.url <- paste("http://mba.tuck.dartmouth.edu",
"pages/faculty/ken.french/ftp",
"F-F_Research_Data_Factors.zip", sep = "/")
f <- tempfile()
download.file(ff.url, f)
file.list <- unzip(f, list = TRUE)
# parse data
ff_factors <- read.fwf(unzip(f, files = as.character(file.list[1,1])),
width = c(8, 8, 8, 8, 10), header = FALSE,
stringsAsFactors = FALSE, skip = 5)
# clean up data
# get rid of extra space and divide numbers by 100 because they are listed as %
for (i in 2:5) ff_factors[, i] <- as.numeric(gsub(" ", "", ff_factors[,i]))
for (i in 2:5) ff_factors[, i] <- ff_factors[, i]/100
names(ff_factors) <- c("date", "MKT.RP", "SMB", "HML", "RF")
# get dates in yearmon format
ff_factors$date <- paste(substr(ff_factors$date, 1, 4), substr(ff_factors$date, 5,6), sep = "-")
ff_factors$date <- as.yearmon(ff_factors$date)
# drop extra data (annual summary of factors follows monthly history)
ff_factors <- subset(ff_factors, subset = !is.na(date))
goodRows <- ff_factors[, 1] > "Jan 1900"
ff_factors <- ff_factors[goodRows, ]
# merge assets amd factors
regData <- merge(ret, ff_factors, by = "date")
names(regData) <- c("date", symbol, "MKT.RP", "SMB", "HML", "RF")
From the summary(fit) call we can see the fund has a significant alpha around 0.4% per month or 4.8% per year, market beta of .95, is sensative to the small cap premium at .81 - which is expected from a small cap manager, and has a value tilt shown by the .36 sensitivity to the value premium.
R also has a series of plots to examine the residuals of the regression that we call with the plot(fit) line. We can see that with an exception of one outlier, the residuals are will behaved. The high F-stat and adjusted R^2 combined with high t-values for each factor indicate that the regression is a good fit.
# subtract risk free from each asset
regData[, 2] <- regData[, 2] - regData[, "RF"]
attach(regData)
fit <- lm(FSCRX ~ MKT.RP + SMB + HML)
# view regression summary
summary(fit)
##
## Call:
## lm(formula = FSCRX ~ MKT.RP + SMB + HML)
##
## Residuals:
## Min 1Q Median 3Q Max
## -0.036049 -0.009170 -0.000063 0.007469 0.048936
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 0.004023 0.001884 2.135 0.0365 *
## MKT.RP 0.958118 0.050682 18.904 < 2e-16 ***
## SMB 0.813699 0.084394 9.642 3.20e-14 ***
## HML 0.362954 0.082269 4.412 3.88e-05 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.01449 on 66 degrees of freedom
## Multiple R-squared: 0.9414, Adjusted R-squared: 0.9387
## F-statistic: 353.3 on 3 and 66 DF, p-value: < 2.2e-16
plot(fit)