Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
263 lines (172 sloc) 8.16 KB

The Long and Short of Fama-French Momentum

Here, we construct three portfolios out of the Fama-French Momentum data-set -- long-only, short-only and long-short -- to get an idea of how they intersect.

The Fama-French data-set has returns for portfolios constructed out of each decile of prior returns. With HI_PRIOR and LO_PRIOR returns, long-only, long-short and short-only portfolio daily returns can be calculated. These returns can then be compared with market returns contained in the 5 Factors (2x3) data-set by adding back the Rf to Rm-Rf.

The documentation for the Fama-French data-set can be found here and here

library(tidyverse)
library(ggthemes)
library(odbc)
library(plutoR)
library(quantmod)
library(lubridate)
library(reshape2)
library(PerformanceAnalytics)
library(ggrepel)

options("scipen"=999)
options(stringsAsFactors = FALSE)
options(repr.plot.width=16, repr.plot.height=8)

source("config.R")
source("goofy/plot.common.R")

#initialize
famaFrench <- FamaFrench()
── �[1mAttaching packages�[22m ─────────────────────────────────────── tidyverse 1.2.1 ──
�[32m✔�[39m �[34mggplot2�[39m 3.2.1     �[32m✔�[39m �[34mpurrr  �[39m 0.3.2
�[32m✔�[39m �[34mtibble �[39m 2.1.3     �[32m✔�[39m �[34mdplyr  �[39m 0.8.3
�[32m✔�[39m �[34mtidyr  �[39m 0.8.3     �[32m✔�[39m �[34mstringr�[39m 1.4.0
�[32m✔�[39m �[34mreadr  �[39m 1.3.1     �[32m✔�[39m �[34mforcats�[39m 0.4.0
── �[1mConflicts�[22m ────────────────────────────────────────── tidyverse_conflicts() ──
�[31m✖�[39m �[34mdplyr�[39m::�[32mfilter()�[39m masks �[34mstats�[39m::filter()
�[31m✖�[39m �[34mdplyr�[39m::�[32mlag()�[39m    masks �[34mstats�[39m::lag()
Loading required package: xts
Loading required package: zoo

Attaching package:zooThe following objects are masked frompackage:base:

    as.Date, as.Date.numeric

Registered S3 method overwritten by 'xts':
  method     from
  as.zoo.xts zoo 

Attaching package:xtsThe following objects are masked frompackage:dplyr:

    first, last

Loading required package: TTR
Registered S3 method overwritten by 'quantmod':
  method            from
  as.zoo.data.frame zoo 
Version 0.4-0 included new data defaults. See ?getSymbols.

Attaching package:lubridateThe following object is masked frompackage:base:

    date


Attaching package:reshape2The following object is masked frompackage:tidyr:

    smiths


Attaching package:PerformanceAnalyticsThe following object is masked frompackage:graphics:

    legend

Registering fonts with R
momStartDt <- (famaFrench$MomentumDaily() %>% summarize(MAX = min(TIME_STAMP)) %>% collect())$MAX[[1]]
mktStartDt <- (famaFrench$FiveFactor3x2Daily() %>% summarize(MAX = min(TIME_STAMP)) %>% collect())$MAX[[1]]
#startDt <- max(momStartDt, mktStartDt)
startDt <- as.Date("1995-01-01")

hiMom <- famaFrench$MomentumDaily() %>%
    filter(KEY_ID == 'HI_PRIOR' & RET_TYPE == 'AVWRD' & TIME_STAMP >= startDt) %>%
    select(TIME_STAMP, RET) %>%
    collect() %>%
    as.data.frame()

loMom <- famaFrench$MomentumDaily() %>%
    filter(KEY_ID == 'LO_PRIOR' & RET_TYPE == 'AVWRD' & TIME_STAMP >= startDt) %>%
    select(TIME_STAMP, RET) %>%
    collect() %>%
    as.data.frame()

mktRet <- famaFrench$FiveFactor3x2Daily() %>%
    inner_join(famaFrench$FiveFactor3x2Daily(), by=c('TIME_STAMP')) %>%
    filter(KEY_ID.x == 'MKT-RF' & KEY_ID.y == 'RF' & TIME_STAMP >= startDt) %>%
    mutate(R = RET.x + RET.y) %>%
    select(TIME_STAMP, R) %>%
    collect() %>%
    as.data.frame()
Warning message:Missing values are always removed in SQL.
Use `MIN(x, na.rm = TRUE)` to silence this warning
This warning is displayed only once per session.”
retXts <- merge(xts(hiMom$RET, hiMom$TIME_STAMP), xts(loMom$RET, loMom$TIME_STAMP), xts(mktRet$R, mktRet$TIME_STAMP))
retXts <- na.omit(retXts)
retXts <- retXts/100

names(retXts) <- c('HI', 'LO', 'MKT')

print(head(retXts))
print(tail(retXts))
                HI      LO     MKT
1995-01-03 -0.0150  0.0033 -0.0024
1995-01-04  0.0010  0.0101  0.0035
1995-01-05 -0.0035  0.0054 -0.0003
1995-01-06  0.0071  0.0018  0.0020
1995-01-09  0.0022  0.0031  0.0010
1995-01-10  0.0098 -0.0005  0.0022
                HI      LO      MKT
2019-06-21 -0.0074 -0.0006 -0.00201
2019-06-24  0.0005 -0.0251 -0.00331
2019-06-25 -0.0156 -0.0006 -0.00971
2019-06-26 -0.0051  0.0169 -0.00051
2019-06-27  0.0047  0.0107  0.00609
2019-06-28  0.0035  0.0124  0.00689
longOnly <- merge(retXts$HI, retXts$LO, retXts$MKT)
names(longOnly) <- c('L', '~S', 'MKT')

pxXts <- merge(cumprod(1+longOnly[,1]), cumprod(1+longOnly[,2]), cumprod(1+longOnly[,3]))
names(pxXts) <- c('L', '~S', 'MKT')
longOnlyYearlies <- 100*merge(yearlyReturn(pxXts[,1]), yearlyReturn(pxXts[,2]), yearlyReturn(pxXts[,3]))
names(longOnlyYearlies) <- c('L', '~S', 'MKT')

shortOnly <- merge(-retXts$LO, retXts$MKT)
names(shortOnly) <- c('S', 'MKT')

pxXts <- merge(cumprod(1+shortOnly[,1]), cumprod(1+shortOnly[,2]))
names(pxXts) <- c('S', 'MKT')
shortOnlyYearlies <- 100*merge(yearlyReturn(pxXts[,1]), yearlyReturn(pxXts[,2]))
names(shortOnlyYearlies) <- c('S', 'MKT')

longShort <- merge(retXts$HI-retXts$LO, retXts$MKT)
names(longShort) <- c('LS', 'MKT')

pxXts <- merge(cumprod(1+longShort[,1]), cumprod(1+longShort[,2]))
names(pxXts) <- c('LS', 'MKT')
longShortYearlies <- 100*merge(yearlyReturn(pxXts[,1]), yearlyReturn(pxXts[,2]))
names(longShortYearlies) <- c('LS', 'MKT')

lsl <- merge(retXts$HI, -retXts$LO, retXts$HI-retXts$LO, retXts$MKT)
names(lsl) <- c('L', 'S', 'LS', 'MKT')
plotAnnualReturns <- function(yearlies, mainTitle){
    yDf <- data.frame(yearlies)
    yDf$T <- year(index(yearlies))

    toPlot <- melt(yDf, id='T')

    ggplot(toPlot, aes(x=T, y=value, fill=variable)) +
        theme_economist() +
        geom_bar(stat="identity", position=position_dodge()) +
        scale_x_continuous(labels=yDf$T, breaks=yDf$T) +
        geom_text_repel(aes(label= round(value, 2)), position = position_dodge(0.9)) +
        labs(x='', y='(%)', fill='', title=mainTitle, subtitle="Annual Returns") +
        annotate("text", x=max(yDf$T), y=min(toPlot$value), 
                 label = "@StockViz", hjust=1.1, vjust=-1.1, 
                 col="white", cex=6, fontface = "bold", alpha = 0.8)  
}
Common.PlotCumReturns(longOnly, "Long-only (Value-weight)", "Fama-French")

png

plotAnnualReturns(longOnlyYearlies, "Long-only Fama-French (Value-weight)")

png

Common.PlotCumReturns(shortOnly, "Short-only (Value-weight)", "Fama-French")

png

plotAnnualReturns(shortOnlyYearlies, "Short-only Fama-French (Value-weight)")

png

Common.PlotCumReturns(longShort, "Long-Short (Value-weight)", "Fama-French")

png

plotAnnualReturns(longShortYearlies, "Long-Short Fama-French (Value-weight)")

png

Common.PlotCumReturns(lsl, "Long, Short and Long-Short (Value-weight)", "Fama-French")

png

This notebook was created using pluto. Learn more here

You can’t perform that action at this time.