Since the initial publication of the Three Factor Model by Eugene Fama and Kenneth French in their influential 1993 paper (Common Risk Factors in the Returns of Stocks and Bonds) a lot of academic research has been dedicated to the analysis of factors driving security returns. With the rise of quantitative investment management, this field has also attracted increasing awareness of practitioners trying to find factors that help them to select securities that generate superior returns or analyze the driving forces behind realized portfolio returns.

Fortunately, Eugene Fama and Kenneth French still publish the returns of various investment factors analyzed by them on their homepage on a regular basis. While this can be quite handy for academics as well as practitioners, the structure of the Research Library makes accessing these resources a time consuming manual task. The following script, therefore, provides its users with a handy tool that downloads, transforms and visualizes this data.

The user thereby needs to provide the respective CSV file's name as well as a number of rows to skip in the CSV until the portfolio names. Unfortunately, this number differs and therefore needs to be provided individually for each factor. Please examine the structure of the respective CSV files first to provide the correct number of rows to skip otherwise the function will return an error. Setting this up is an annoying manual work at the beginning but, assuming that Eugene Fama and Kenneth French won't change the structure of their CSV sheets, it only needs to be accomplished once. The function then provides a list element, containing the original returns data as well as the indexed returns since start.

The script includes a complete example based on the Three Factor Model.

Compile Function

get_fama_french_data<-function(database,skip_rows)
{
  # The URL for the data.
  ff.url.partial <- paste("http://mba.tuck.dartmouth.edu",
                          "pages/faculty/ken.french/ftp", sep="/")


  #*******************************************************************************#
  #             First download Fama-French three-factor data                      #
  #*******************************************************************************#


  # Download the data and unzip it
  #ff.url <- paste(ff.url.partial, "F-F_Research_Data_Factors_CSV.zip", sep="/")
  #ff.url <- paste(ff.url.partial, "Portfolios_Formed_on_OP_CSV.zip", sep="/")
  ff.url <- paste(ff.url.partial, database, sep="/")

  # get the file url
  today = as.Date(Sys.time())
  forecasturl = ff.url
  # create a temporary directory
  td = tempdir()
  # create the placeholder file
  tf = tempfile(tmpdir=td, fileext=".zip")
  # download into the placeholder file
  download.file(forecasturl, tf)

  #*******************************************************************************#
  #                     Second Unzip File and Create a data.frame                 #
  #*******************************************************************************#
  # get the name of the first file in the zip archive
  fname = unzip(tf, list=TRUE)$Name[1]
  # unzip the file to the temporary directory
  unzip(tf, files=fname, exdir=td, overwrite=TRUE)
  # fpath is the full path to the extracted file
  fpath = file.path(td, fname)

  # stringsAsFactors=TRUE will screw up conversion to numeric!
  #skip_rows<-18
  d = read.csv(fpath, header=T, row.names=NULL, 
               stringsAsFactors=F, skip=skip_rows)

  #
  d<-d[!is.na(d$X),]


  #*******************************************************************************#
  #                     Third Calculate Indexed Portfolio Returns                 #
  #*******************************************************************************#  
  indexed_ret<-function(x)
  {
    x<-as.numeric(x)
    x<-cumprod(1+(x/100))
    return(x)
  }

  indexed_ret<-as.data.frame(apply(subset(d,select=c(-X)),2,indexed_ret))

  d$date<-as.Date(paste(substr(d$X,1,4),"-",substr(d$X,5,6),"-01",sep=""))
  indexed_ret$date<-d$date

  indexed_ret<-indexed_ret[!is.na(indexed_ret[,1]),]
  d<-head(d,nrow(indexed_ret))

  #*******************************************************************************#
  #                         Fourth Combine to List Element                        #
  #*******************************************************************************#  
  factor_ret<-list(d=d,indexed_ret=indexed_ret)
}
#-----------------------------------------------------------------------------------------------------------------------

Retrieve Data

three_factor_model<-get_fama_french_data("F-F_Research_Data_Factors_CSV.zip",3)
## Error in download.file(forecasturl, tf): cannot open URL 'http://mba.tuck.dartmouth.edu/pages/faculty/ken.french/ftp/F-F_Research_Data_Factors_CSV.zip'

Examine Returns Data

head(three_factor_model$d,5)
##        X   Mkt.RF      SMB      HML       RF       date
## 1 192607     2.96    -2.30    -2.87     0.22 1926-07-01
## 2 192608     2.64    -1.40     4.19     0.25 1926-08-01
## 3 192609     0.36    -1.32     0.01     0.23 1926-09-01
## 4 192610    -3.24     0.04     0.51     0.32 1926-10-01
## 5 192611     2.53    -0.20    -0.35     0.31 1926-11-01

Examine Indexed Returns Data

head(three_factor_model$indexed_ret,5)
##     Mkt.RF       SMB      HML       RF       date
## 1 1.029600 0.9770000 0.971300 1.002200 1926-07-01
## 2 1.056781 0.9633220 1.011997 1.004706 1926-08-01
## 3 1.060586 0.9506061 1.012099 1.007016 1926-09-01
## 4 1.026223 0.9509864 1.017260 1.010239 1926-10-01
## 5 1.052186 0.9490844 1.013700 1.013371 1926-11-01

Plot Indexed Portfolio Returns with Plotly

This is just included as an example. Unfortunately Plotly Charts don't seem to get rendered very nicely on DataScience+. I am therefore providing another example with my own base-R plot function given below

#
#Fama French 3 Factor Model
#plot_ly(three_factor_model$indexed_ret,x=~date,y=~SMB,name="Size",type="scatter",mode="line")%>%
#  add_trace(three_factor_model$indexed_ret,x=~date,y=~HML,name="Value")%>%
#  add_trace(three_factor_model$indexed_ret,x=~date,y=~RF,name="Risk-Free")%>%
#  layout(title="3 Factor Model",xaxis = list(title=""), yaxis = list(title=""),legend = list(orientation = "h",xanchor = "center",x = 0.5))

Design Function

This function takes care of the chart's layout.

#First Axis
chart_design<-function(y_min,y_max,x_min,x_max,y_caption,titel,x_freq)
{
  options(warn=-1)
  par(mfrow=c(1,1))      # number of rows, number of columns
  par(bg = "transparent")
  #op <- par(mar = c(4,4,4,4) + 0.8)

  plot(as.Date(df$date),df$value,
       main=titel,                     # Chart Titel
       xlab="",                         # x-Achsenbeschriftung  
       ylab=y_caption,                     # y-Achsenbeschriftung
       type="l",                        # type
       col=col1,                        # farbe
       xlim=c(x_min, x_max),
       ylim=c(y_min,y_max),                # y-achse
       lwd = 2,                         # thickness of the line
       bty = "n",                       # Remove the box around the plot
       font.axis = 2,                   # Change axis font to bold italic
       col.axis = "black",              # Set the color of the axis
       las=1,                            # Make axis labels parallel to x-axis (2 for vertical)   
       xaxt='n',                       # Delete x-axis
       font.main=10,
       cex.main=1.1,
       cex.lab=1.1,
       font.lab=6,
       font.axis=6,
       cex.axis=1.1,
       tck=-0.01,
       col.axis="black",
       col.ticks="grey"
  )
  grid(NA,NULL,lwd=1.5,col = 'grey')

  abline(h = 0,
         col = 'grey', lty = "solid",lwd=1.5)

  abline(v=axis.Date(1,font.axis=6,tck=-0.01,cex.axis=1.1,col.ticks="grey",font.lab=6,lwd=1.3, at=seq(as.Date(x_min), x_max, by=x_freq), format="%m-%Y"),
         col = 'grey', lty = "dotted",lwd=1.5)

  lines(as.Date(df$date),df$value,col=col1,lwd=2)

  box(bty = "l")
  options(warn=0)
  #****
}

Annotate Last Value in Time Series

This function enables the user to add a caption to the last point in the plotted time-series.

last_value_function_a<-function(datum,zeitreihe,function_color,position_a)
{
  names(df)[names(df)==datum]<-"function_tmp_date"
  names(df)[names(df)==zeitreihe]<-"function_tmp_value"

  wert<-tail(df$function_tmp_value[!is.na(df$function_tmp_value)],1)
  round_wert<-round(wert,digits=2)
  datum_last<-as.Date(tail(df$function_tmp_date[!is.na(df$function_tmp_date)],1))
  text(datum_last, wert,paste(round_wert,sep=""),col=function_color,font =6,pos = position_a)

  names(df)[names(df)=="function_tmp_date"]<-datum
  names(df)[names(df)=="function_tmp_value"]<-zeitreihe

}

Plot Indexed Portfolio Returns Custom Function

df<-three_factor_model$indexed_ret
names(df)[names(df)=="SMB"]<-"value"

col1<-"blue"
chart_design(
  y_min=min(pretty(extendrange(df$value))),
  y_max=max(pretty(extendrange(df$value))),
  x_min=min(df$date),
  x_max=max(df$date),
  y_caption="Relative Return",
  titel=c("Fama-French Small/Large Portfolio Returns",format(max(df$date),"%B %d %Y")),
  x_freq="12 mon"
)
last_value_function_a("date","value","blue",1)