In this Reproducible Finance note, we will review and walk through the R code for importing price data from CSV, coercing it to monthly returns data, and then running a Fama French (FF) factor model on the data. That will also necessitate importing and wrangling the FF data before running the model and visualizing the results. The goal of this note is to introduce some useful and reuseable R code that can serve as a starting point for more complex analysis.

First, let’s load up our packages that we will need for the project. If you are unfamiliar with R, packages are an important part of the ecosystem. They can contain useful and intuitive functions that save us from having to write our functions in base R.

We will be using the following packages.

library(tidyverse)
library(tidyquant)
library(broom)
library(tibbletime)

With our packages loaded, we need to import some data. We will be working with the following SPDR sector ETFs.

We could import price data on those ETFs from a source like Factset or Bloomberg, but to make this fully reproducible, I have saved the price data as a CSV file that can be downloaded.

To import the data into our working environment, we will use the read_csv() function and download directly the website where I stored this data.

sector_prices_2000_2018 <- 
read_csv("https://colorado.rstudio.com/rsc/content/2138/sector_prices_2000_2018.csv") %>% 
select(-X1) %>% 
group_by(sector)

Next let’s use the slice() function to glance at the first price observation for each of the sectors, to confirm that the data looks how we were expecting it to look. Note that the first date is December 29, 1999. I set that as the start date because we eventually want to capture the monthly return of January 2000 and we will index that off of the last day of December 1999.

sector_prices_2000_2018 %>% 
  slice(1)

The ultimate goal is to regress the monthly sector returns on the the Fama French factors, so let’s coerce this daily price data to monthly price data.

For that task, we use as_period(period = "month", side = "end"), which will convert our data to monthly, indexed at the end of each month.

With our data converted to monthly, we can calculate monthly returns using mutate(monthly_return = log(adjClose) - log(lag(adjClose). The mutate() function adds a new column to the data and we set the name of the new column to be monthly_returns. We calculate monthly returns as th log of the adjusted close on the final day of the current month, minus the log of the adjusted close on the final day of the previous month. This is for illustration purposes - your team might choose to use a price other than adjusted close and different date periodicities.

sector_monthly_returns_2000_2018 <-
sector_prices_2000_2018 %>% 
  as_tbl_time(index = date) %>% 
  as_period(period = "month", side = "end") %>% 
  mutate(monthly_return = log(adjClose) - log(lag(adjClose))) %>% 
  na.omit() 

sector_monthly_returns_2000_2018 %>% 
  slice(1)

We now have monthly returns for our sectors, starting in January of 2000.

Next we need to import Fama French factor data and, luckily, FF make their factor data available on their website.

Have a look at the website where the factor data is available.

http://mba.tuck.dartmouth.edu/pages/faculty/ken.french/data_library.html

The data are packaged as zip files so we will need to do a bit more than call read_csv().

We will use the tempfile() function from base R to create a variable called temp, and will store the zipped file there.

Then we invoke download.file() and pass it the URL address of the zip, which is “http://mba.tuck.dartmouth.edu/pages/faculty/ken.french/ftp/Global_5_Factors_CSV.zip”.

temp <- tempfile()

download.file(
  "http://mba.tuck.dartmouth.edu/pages/faculty/ken.french/ftp/Global_5_Factors_CSV.zip",
  temp, 
  quiet = TRUE)

Next we unzip that data with the unz() function read and the csv file using read_csv().

Global_5_Factors <- 
  read_csv(unz(temp, "Global_5_Factors.csv"), skip =6) %>%
  rename(date = X1)

That loads the data into our environment but we need to do some wrangling to get this raw data into a format that we can join with our monthly returns data.

Have a look at the date column.

Global_5_Factors %>% 
  select(date) %>% 
  head(2)

We want to join this data to our monthly returns using the date column, and that means we want the factor date to be in the same format as our monthly returns, but it’s currently in a character string with the format 1990007 instead of 1990-07-31.

We can call mutate(date = ymd(parse_date_time(date, "%Y%m"))) to make this transformation.

  Global_5_Factors %>%
  mutate(date = ymd(parse_date_time(date, "%Y%m"))) %>% 
  head()

That worked well for the date column, but note that all our factor columns have been read in as character strings and we want them to be numeric, and we also want to coerce our date to be indexed at the end of the month, not at the beginning. Here’s the code flow to make those transformations.

Global_5_Factors_for_join <- 
  Global_5_Factors %>%
  mutate(date = ymd(parse_date_time(date, "%Y%m"))) %>% 
  mutate_at(vars(-date), as.numeric) %>% 
  mutate_if(is.numeric, funs(. / 100)) %>% 
  as_tbl_time(index = date) %>%
  mutate(date = ceiling_date(date, unit = "month") - days(1)) %>% 
  rename(MKT = `Mkt-RF`)

Global_5_Factors_for_join %>% 
  head()

The data frame looks good now, but that was quite a bit of work to add a nicely formatted date, indexed to the end of the month and with numeric instead of character data in the other columns. Now is a good time to note that manipulating and cleaning dates is a crucial and often time-consuming part of financial data work, especially when a project involves mashing together disparate data sets. The more comfortable and efficient we can get at transforming and aligning dates, the more quickly we can start to work with new and interesting data sources.

We now have the Fama French factors going back to 1990 and the next task is to regress our sector monthly returns on those factors. Let’s use left_join() to combine our sector monthly returns data with our factors data, and use the by = "date" argument so that only the factor and returns observations with the same dates will remain in the data frame.

sector_factor_data_joined <- 
sector_monthly_returns_2000_2018 %>% 
  left_join(Global_5_Factors_for_join, by = "date") 

sector_factor_data_joined %>% 
  select(-ticker, -adjClose) %>% 
  slice(1)

Our data is now in a nicely structured data frame, with our sector names in one column, our sector returns in a separate column, and each of the Fama French factors in a separate column. From here, we want to run our factor analysis, which we do by regressing our monthly returns on the 5 factors.

For this exercise, let’s run the regression on just the Financials sectors by first calling filter(sector == Financials). If you wish to reproduce this work but run the regression on a different sector, you can change the code to filter(sector == your sector of choice).

After filtering, we use do(model = lm(monthly_return ~ Mkt + SMB + HML + RMW + CMA)) to run our regression.

sector_factor_data_joined %>% 
  filter(sector == "Financials") %>% 
  do(model = lm(monthly_return ~ MKT + SMB + HML + RMW + CMA, 
                data = .))

That worked, but the results of our 5-factor regression are stored as an S3 object, which can be hard to access. To clean up the results, and add confidence intervals for our beta estimates, we call tidy(model, conf.int = T, conf.level = .95) and rename a couple of columns for convenience.

ff_results <-
sector_factor_data_joined %>% 
  filter(sector == "Financials") %>% 
  do(model = lm(monthly_return ~ MKT + SMB + HML + RMW + CMA, 
                data = .)) %>% 
  tidy(model, conf.int = T, conf.level = .95) %>% 
  rename(coefficient = estimate, factor = term)

ff_results

Let’s finish our work with a chart, showing the coefficient estimates and confidence intervals for our factors. We will use ggplot() to build this charting mapping our factor names to the x-axis and our coefficients to the y-axis with aes(x = factor, y = coefficient). Let’s place a point for each coefficient by calling geom_point() and add confidence intervals with geom_errorbar().

ff_results %>% 
filter(factor != "(Intercept)") %>% 
  ggplot(aes(x = factor, 
             y = coefficient, 
             shape = factor, 
             color = factor)) + 
  geom_point() +
  geom_errorbar(aes(ymin = conf.low, 
                    ymax = conf.high)) +
  labs(title = "FF 5-Factor Betas with Conf Intervals",
       x = "",
       y = "coefficients",
       caption = "data source: Fama-French website") +
  theme_minimal() +
  theme(plot.title = element_text(hjust = 0.5),
        plot.caption  = element_text(hjust = 0))

We can glance at this and quickly see that the MKT and HML factors have positive coefficients near 1, whereas both RMW and SMB have negative coefficients.

Remember that we ran the regression on the Financials sector but we have monthly returns for several more ETFs. We could expand this work by filtering on a different sector or by adding more factors to the data frame before running the regression. We could also convert our regression model to a rolling regression, to see where we might have missed any significant regime or market shifts, and then visualize our rolling factor coefficients.

We hope that this short note provided some useful R code and some data wrangling functions that might save some time when different date formats confront you. Thanks for reading.

LS0tCnRpdGxlOiAiU2VjdG9yIEZhY3RvciBOb3RlIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdAotLS0KCgpgYGB7ciBzZXR1cCwgaW5jbHVkZSA9IEZBTFNFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQobWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgY29tbWVudCA9IE5BKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShicm9vbSkKbGlicmFyeSh0aWJibGV0aW1lKQpsaWJyYXJ5KHRpZHlxdWFudCkKYGBgCgpJbiB0aGlzIFJlcHJvZHVjaWJsZSBGaW5hbmNlIG5vdGUsIHdlIHdpbGwgcmV2aWV3IGFuZCB3YWxrIHRocm91Z2ggdGhlIFIgY29kZSBmb3IgaW1wb3J0aW5nIHByaWNlIGRhdGEgZnJvbSBDU1YsIGNvZXJjaW5nIGl0IHRvIG1vbnRobHkgcmV0dXJucyBkYXRhLCBhbmQgdGhlbiBydW5uaW5nIGEgRmFtYSBGcmVuY2ggKEZGKSBmYWN0b3IgbW9kZWwgb24gdGhlIGRhdGEuIFRoYXQgd2lsbCBhbHNvIG5lY2Vzc2l0YXRlIGltcG9ydGluZyBhbmQgd3JhbmdsaW5nIHRoZSBGRiBkYXRhIGJlZm9yZSBydW5uaW5nIHRoZSBtb2RlbCBhbmQgdmlzdWFsaXppbmcgdGhlIHJlc3VsdHMuIFRoZSBnb2FsIG9mIHRoaXMgbm90ZSBpcyB0byBpbnRyb2R1Y2Ugc29tZSB1c2VmdWwgYW5kIHJldXNlYWJsZSBSIGNvZGUgdGhhdCBjYW4gc2VydmUgYXMgYSBzdGFydGluZyBwb2ludCBmb3IgbW9yZSBjb21wbGV4IGFuYWx5c2lzLiAgCgpGaXJzdCwgbGV0J3MgbG9hZCB1cCBvdXIgcGFja2FnZXMgdGhhdCB3ZSB3aWxsIG5lZWQgZm9yIHRoZSBwcm9qZWN0LiBJZiB5b3UgYXJlIHVuZmFtaWxpYXIgd2l0aCBSLCBwYWNrYWdlcyBhcmUgYW4gaW1wb3J0YW50IHBhcnQgb2YgdGhlIGVjb3N5c3RlbS4gVGhleSBjYW4gY29udGFpbiB1c2VmdWwgYW5kIGludHVpdGl2ZSBmdW5jdGlvbnMgdGhhdCBzYXZlIHVzIGZyb20gaGF2aW5nIHRvIHdyaXRlIG91ciBmdW5jdGlvbnMgaW4gYmFzZSBSLiAKCldlIHdpbGwgYmUgdXNpbmcgdGhlIGZvbGxvd2luZyBwYWNrYWdlcy4KCmBgYHtyLCBldmFsPUZBTFNFfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeSh0aWR5cXVhbnQpCmxpYnJhcnkoYnJvb20pCmxpYnJhcnkodGliYmxldGltZSkKYGBgCgpXaXRoIG91ciBwYWNrYWdlcyBsb2FkZWQsIHdlIG5lZWQgdG8gaW1wb3J0IHNvbWUgZGF0YS4gV2Ugd2lsbCBiZSB3b3JraW5nIHdpdGggdGhlIGZvbGxvd2luZyBTUERSIHNlY3RvciBFVEZzLgoKYGBge3IsIGVjaG8gPSBGQUxTRX0KCmV0Zl90aWNrZXJfc2VjdG9yIDwtIHRpYmJsZSgKICB0aWNrZXIgPSBjKCJYTFkiLCAiWExQIiwgIlhMRSIsCQogICAgICAgICAgIlhMRiIsICJYTFYiLAkiWExJIiwgIlhMQiIsIAogICAgICAgICAgIlhMSyIsICJYTFUiLCAgIlNQWSIpLAkKICBzZWN0b3IgPSBjKCJDb25zdW1lciBEaXNjcmV0aW9uYXJ5IiwgIkNvbnN1bWVyIFN0YXBsZXMiLCAiRW5lcmd5IiwgCiAgICAgICAgICAiRmluYW5jaWFscyIsICJIZWFsdGggQ2FyZSIsICJJbmR1c3RyaWFscyIsICJNYXRlcmlhbHMiLCAKICAgICAgICAgICJJbmZvcm1hdGlvbiBUZWNobm9sb2d5IiwgIlV0aWxpdGllcyIsICJNYXJrZXQiKQopCgpldGZfdGlja2VyX3NlY3RvcgpgYGAKCldlIGNvdWxkIGltcG9ydCBwcmljZSBkYXRhIG9uIHRob3NlIEVURnMgZnJvbSBhIHNvdXJjZSBsaWtlIEZhY3RzZXQgb3IgQmxvb21iZXJnLCBidXQgdG8gbWFrZSB0aGlzIGZ1bGx5IHJlcHJvZHVjaWJsZSwgSSBoYXZlIHNhdmVkIHRoZSBwcmljZSBkYXRhIGFzIGEgQ1NWIGZpbGUgdGhhdCBjYW4gYmUgZG93bmxvYWRlZC4KClRvIGltcG9ydCB0aGUgZGF0YSBpbnRvIG91ciB3b3JraW5nIGVudmlyb25tZW50LCB3ZSB3aWxsIHVzZSB0aGUgYHJlYWRfY3N2KClgIGZ1bmN0aW9uIGFuZCBkb3dubG9hZCBkaXJlY3RseSB0aGUgd2Vic2l0ZSB3aGVyZSBJIHN0b3JlZCB0aGlzIGRhdGEuCgoKYGBge3J9CnNlY3Rvcl9wcmljZXNfMjAwMF8yMDE4IDwtIApyZWFkX2NzdigiaHR0cHM6Ly9jb2xvcmFkby5yc3R1ZGlvLmNvbS9yc2MvY29udGVudC8yMTM4L3NlY3Rvcl9wcmljZXNfMjAwMF8yMDE4LmNzdiIpICU+JSAKc2VsZWN0KC1YMSkgJT4lIApncm91cF9ieShzZWN0b3IpCmBgYAoKTmV4dCBsZXQncyB1c2UgdGhlIGBzbGljZSgpYCBmdW5jdGlvbiB0byBnbGFuY2UgYXQgdGhlIGZpcnN0IHByaWNlIG9ic2VydmF0aW9uIGZvciBlYWNoIG9mIHRoZSBzZWN0b3JzLCB0byBjb25maXJtIHRoYXQgdGhlIGRhdGEgbG9va3MgaG93IHdlIHdlcmUgZXhwZWN0aW5nIGl0IHRvIGxvb2suIE5vdGUgdGhhdCB0aGUgZmlyc3QgZGF0ZSBpcyBEZWNlbWJlciAyOSwgMTk5OS4gSSBzZXQgdGhhdCBhcyB0aGUgc3RhcnQgZGF0ZSBiZWNhdXNlIHdlIGV2ZW50dWFsbHkgd2FudCB0byBjYXB0dXJlIHRoZSBtb250aGx5IHJldHVybiBvZiBKYW51YXJ5IDIwMDAgYW5kIHdlIHdpbGwgaW5kZXggdGhhdCBvZmYgb2YgdGhlIGxhc3QgZGF5IG9mIERlY2VtYmVyIDE5OTkuCgpgYGB7cn0Kc2VjdG9yX3ByaWNlc18yMDAwXzIwMTggJT4lIAogIHNsaWNlKDEpCmBgYAoKClRoZSB1bHRpbWF0ZSBnb2FsIGlzIHRvIHJlZ3Jlc3MgdGhlIG1vbnRobHkgc2VjdG9yIHJldHVybnMgb24gdGhlIHRoZSBGYW1hIEZyZW5jaCBmYWN0b3JzLCBzbyBsZXQncyBjb2VyY2UgdGhpcyBkYWlseSBwcmljZSBkYXRhIHRvIG1vbnRobHkgcHJpY2UgZGF0YS4gIAoKRm9yIHRoYXQgdGFzaywgd2UgdXNlIGBhc19wZXJpb2QocGVyaW9kID0gIm1vbnRoIiwgc2lkZSA9ICJlbmQiKWAsIHdoaWNoIHdpbGwgY29udmVydCBvdXIgZGF0YSB0byBtb250aGx5LCBpbmRleGVkIGF0IHRoZSBlbmQgb2YgZWFjaCBtb250aC4gIAoKV2l0aCBvdXIgZGF0YSBjb252ZXJ0ZWQgdG8gbW9udGhseSwgd2UgY2FuIGNhbGN1bGF0ZSBtb250aGx5IHJldHVybnMgdXNpbmcgYG11dGF0ZShtb250aGx5X3JldHVybiA9IGxvZyhhZGpDbG9zZSkgLSBsb2cobGFnKGFkakNsb3NlKWAuIFRoZSBgbXV0YXRlKClgIGZ1bmN0aW9uIGFkZHMgYSBuZXcgY29sdW1uIHRvIHRoZSBkYXRhIGFuZCB3ZSBzZXQgdGhlIG5hbWUgb2YgdGhlIG5ldyBjb2x1bW4gdG8gYmUgYG1vbnRobHlfcmV0dXJuc2AuIFdlIGNhbGN1bGF0ZSBtb250aGx5IHJldHVybnMgYXMgdGggbG9nIG9mIHRoZSBhZGp1c3RlZCBjbG9zZSBvbiB0aGUgZmluYWwgZGF5IG9mIHRoZSBjdXJyZW50IG1vbnRoLCBtaW51cyB0aGUgbG9nIG9mIHRoZSBhZGp1c3RlZCBjbG9zZSBvbiB0aGUgZmluYWwgZGF5IG9mIHRoZSBwcmV2aW91cyBtb250aC4gVGhpcyBpcyBmb3IgaWxsdXN0cmF0aW9uIHB1cnBvc2VzIC0geW91ciB0ZWFtIG1pZ2h0IGNob29zZSB0byB1c2UgYSBwcmljZSBvdGhlciB0aGFuIGFkanVzdGVkIGNsb3NlIGFuZCBkaWZmZXJlbnQgZGF0ZSBwZXJpb2RpY2l0aWVzLgoKYGBge3J9CnNlY3Rvcl9tb250aGx5X3JldHVybnNfMjAwMF8yMDE4IDwtCnNlY3Rvcl9wcmljZXNfMjAwMF8yMDE4ICU+JSAKICBhc190YmxfdGltZShpbmRleCA9IGRhdGUpICU+JSAKICBhc19wZXJpb2QocGVyaW9kID0gIm1vbnRoIiwgc2lkZSA9ICJlbmQiKSAlPiUgCiAgbXV0YXRlKG1vbnRobHlfcmV0dXJuID0gbG9nKGFkakNsb3NlKSAtIGxvZyhsYWcoYWRqQ2xvc2UpKSkgJT4lIAogIG5hLm9taXQoKSAKCnNlY3Rvcl9tb250aGx5X3JldHVybnNfMjAwMF8yMDE4ICU+JSAKICBzbGljZSgxKQpgYGAKCldlIG5vdyBoYXZlIG1vbnRobHkgcmV0dXJucyBmb3Igb3VyIHNlY3RvcnMsIHN0YXJ0aW5nIGluIEphbnVhcnkgb2YgMjAwMC4KCk5leHQgd2UgbmVlZCB0byBpbXBvcnQgRmFtYSBGcmVuY2ggZmFjdG9yIGRhdGEgYW5kLCBsdWNraWx5LCBGRiBtYWtlIHRoZWlyIGZhY3RvciBkYXRhIGF2YWlsYWJsZSBvbiB0aGVpciB3ZWJzaXRlLgoKSGF2ZSBhIGxvb2sgYXQgdGhlIHdlYnNpdGUgd2hlcmUgdGhlIGZhY3RvciBkYXRhIGlzIGF2YWlsYWJsZS4KCmh0dHA6Ly9tYmEudHVjay5kYXJ0bW91dGguZWR1L3BhZ2VzL2ZhY3VsdHkva2VuLmZyZW5jaC9kYXRhX2xpYnJhcnkuaHRtbAoKVGhlIGRhdGEgYXJlIHBhY2thZ2VkIGFzIHppcCBmaWxlcyBzbyB3ZSB3aWxsIG5lZWQgdG8gZG8gYSBiaXQgbW9yZSB0aGFuIGNhbGwgYHJlYWRfY3N2KClgLgoKV2Ugd2lsbCB1c2UgdGhlIGB0ZW1wZmlsZSgpYCBmdW5jdGlvbiBmcm9tIGJhc2UgUiB0byBjcmVhdGUgYSB2YXJpYWJsZSBjYWxsZWQgYHRlbXBgLCBhbmQgd2lsbCBzdG9yZSB0aGUgemlwcGVkIGZpbGUgdGhlcmUuCgpUaGVuIHdlIGludm9rZSBgZG93bmxvYWQuZmlsZSgpYCBhbmQgcGFzcyBpdCB0aGUgVVJMIGFkZHJlc3Mgb2YgdGhlIHppcCwgd2hpY2ggaXMg4oCcaHR0cDovL21iYS50dWNrLmRhcnRtb3V0aC5lZHUvcGFnZXMvZmFjdWx0eS9rZW4uZnJlbmNoL2Z0cC9HbG9iYWxfNV9GYWN0b3JzX0NTVi56aXDigJ0uCgpgYGB7cn0KdGVtcCA8LSB0ZW1wZmlsZSgpCgpkb3dubG9hZC5maWxlKAogICJodHRwOi8vbWJhLnR1Y2suZGFydG1vdXRoLmVkdS9wYWdlcy9mYWN1bHR5L2tlbi5mcmVuY2gvZnRwL0dsb2JhbF81X0ZhY3RvcnNfQ1NWLnppcCIsCiAgdGVtcCwgCiAgcXVpZXQgPSBUUlVFKQpgYGAKCk5leHQgd2UgdW56aXAgdGhhdCBkYXRhIHdpdGggdGhlIGB1bnooKWAgZnVuY3Rpb24gcmVhZCBhbmQgdGhlIGNzdiBmaWxlIHVzaW5nIGByZWFkX2NzdigpYC4KCmBgYHtyfQpHbG9iYWxfNV9GYWN0b3JzIDwtIAogIHJlYWRfY3N2KHVueih0ZW1wLCAiR2xvYmFsXzVfRmFjdG9ycy5jc3YiKSwgc2tpcCA9NikgJT4lCiAgcmVuYW1lKGRhdGUgPSBYMSkKYGBgCgpUaGF0IGxvYWRzIHRoZSBkYXRhIGludG8gb3VyIGVudmlyb25tZW50IGJ1dCB3ZSBuZWVkIHRvIGRvIHNvbWUgd3JhbmdsaW5nIHRvIGdldCB0aGlzIHJhdyBkYXRhIGludG8gYSBmb3JtYXQgdGhhdCB3ZSBjYW4gam9pbiB3aXRoIG91ciBtb250aGx5IHJldHVybnMgZGF0YS4gCgpIYXZlIGEgbG9vayBhdCB0aGUgZGF0ZSBjb2x1bW4uCgpgYGB7cn0KR2xvYmFsXzVfRmFjdG9ycyAlPiUgCiAgc2VsZWN0KGRhdGUpICU+JSAKICBoZWFkKDIpCmBgYAoKV2Ugd2FudCB0byBqb2luIHRoaXMgZGF0YSB0byBvdXIgbW9udGhseSByZXR1cm5zIHVzaW5nIHRoZSBkYXRlIGNvbHVtbiwgYW5kIHRoYXQgbWVhbnMgd2Ugd2FudCB0aGUgZmFjdG9yIGRhdGUgdG8gYmUgaW4gdGhlIHNhbWUgZm9ybWF0IGFzIG91ciBtb250aGx5IHJldHVybnMsIGJ1dCBpdCdzIGN1cnJlbnRseSBpbiBhIGNoYXJhY3RlciBzdHJpbmcgd2l0aCB0aGUgZm9ybWF0IGAxOTkwMDA3YCBpbnN0ZWFkIG9mIGAxOTkwLTA3LTMxYC4gCgpXZSBjYW4gY2FsbCBgbXV0YXRlKGRhdGUgPSB5bWQocGFyc2VfZGF0ZV90aW1lKGRhdGUsICIlWSVtIikpKWAgdG8gbWFrZSB0aGlzIHRyYW5zZm9ybWF0aW9uLiAKCmBgYHtyfQogCiAgR2xvYmFsXzVfRmFjdG9ycyAlPiUKICBtdXRhdGUoZGF0ZSA9IHltZChwYXJzZV9kYXRlX3RpbWUoZGF0ZSwgIiVZJW0iKSkpICU+JSAKICBoZWFkKCkKYGBgCgpUaGF0IHdvcmtlZCB3ZWxsIGZvciB0aGUgZGF0ZSBjb2x1bW4sIGJ1dCBub3RlIHRoYXQgYWxsIG91ciBmYWN0b3IgY29sdW1ucyBoYXZlIGJlZW4gcmVhZCBpbiBhcyBjaGFyYWN0ZXIgc3RyaW5ncyBhbmQgd2Ugd2FudCB0aGVtIHRvIGJlIG51bWVyaWMsIGFuZCB3ZSBhbHNvIHdhbnQgdG8gY29lcmNlIG91ciBkYXRlIHRvIGJlIGluZGV4ZWQgYXQgdGhlIGVuZCBvZiB0aGUgbW9udGgsIG5vdCBhdCB0aGUgYmVnaW5uaW5nLiBIZXJlJ3MgdGhlIGNvZGUgZmxvdyB0byBtYWtlIHRob3NlIHRyYW5zZm9ybWF0aW9ucy4KCmBgYHtyfQpHbG9iYWxfNV9GYWN0b3JzX2Zvcl9qb2luIDwtIAogIEdsb2JhbF81X0ZhY3RvcnMgJT4lCiAgbXV0YXRlKGRhdGUgPSB5bWQocGFyc2VfZGF0ZV90aW1lKGRhdGUsICIlWSVtIikpKSAlPiUgCiAgbXV0YXRlX2F0KHZhcnMoLWRhdGUpLCBhcy5udW1lcmljKSAlPiUgCiAgbXV0YXRlX2lmKGlzLm51bWVyaWMsIGZ1bnMoLiAvIDEwMCkpICU+JSAKICBhc190YmxfdGltZShpbmRleCA9IGRhdGUpICU+JQogIG11dGF0ZShkYXRlID0gY2VpbGluZ19kYXRlKGRhdGUsIHVuaXQgPSAibW9udGgiKSAtIGRheXMoMSkpICU+JSAKICByZW5hbWUoTUtUID0gYE1rdC1SRmApCgpHbG9iYWxfNV9GYWN0b3JzX2Zvcl9qb2luICU+JSAKICBoZWFkKCkKYGBgCgpUaGUgZGF0YSBmcmFtZSBsb29rcyBnb29kIG5vdywgYnV0IHRoYXQgd2FzIHF1aXRlIGEgYml0IG9mIHdvcmsgdG8gYWRkIGEgbmljZWx5IGZvcm1hdHRlZCBkYXRlLCBpbmRleGVkIHRvIHRoZSBlbmQgb2YgdGhlIG1vbnRoIGFuZCB3aXRoIG51bWVyaWMgaW5zdGVhZCBvZiBjaGFyYWN0ZXIgZGF0YSBpbiB0aGUgb3RoZXIgY29sdW1ucy4gIE5vdyBpcyBhIGdvb2QgdGltZSB0byBub3RlIHRoYXQgbWFuaXB1bGF0aW5nIGFuZCBjbGVhbmluZyBkYXRlcyBpcyBhIGNydWNpYWwgYW5kIG9mdGVuIHRpbWUtY29uc3VtaW5nIHBhcnQgb2YgZmluYW5jaWFsIGRhdGEgd29yaywgZXNwZWNpYWxseSB3aGVuIGEgcHJvamVjdCBpbnZvbHZlcyBtYXNoaW5nIHRvZ2V0aGVyIGRpc3BhcmF0ZSBkYXRhIHNldHMuICBUaGUgbW9yZSBjb21mb3J0YWJsZSBhbmQgZWZmaWNpZW50IHdlIGNhbiBnZXQgYXQgdHJhbnNmb3JtaW5nIGFuZCBhbGlnbmluZyBkYXRlcywgdGhlIG1vcmUgcXVpY2tseSB3ZSBjYW4gc3RhcnQgdG8gd29yayB3aXRoIG5ldyBhbmQgaW50ZXJlc3RpbmcgZGF0YSBzb3VyY2VzLiAgCgpXZSBub3cgaGF2ZSB0aGUgRmFtYSBGcmVuY2ggZmFjdG9ycyBnb2luZyBiYWNrIHRvIDE5OTAgYW5kIHRoZSBuZXh0IHRhc2sgaXMgdG8gcmVncmVzcyBvdXIgc2VjdG9yIG1vbnRobHkgcmV0dXJucyBvbiB0aG9zZSBmYWN0b3JzLiBMZXQncyB1c2UgYGxlZnRfam9pbigpYCB0byBjb21iaW5lIG91ciBzZWN0b3IgbW9udGhseSByZXR1cm5zIGRhdGEgd2l0aCBvdXIgZmFjdG9ycyBkYXRhLCBhbmQgdXNlIHRoZSBgYnkgPSAiZGF0ZSJgIGFyZ3VtZW50IHNvIHRoYXQgb25seSB0aGUgZmFjdG9yIGFuZCByZXR1cm5zIG9ic2VydmF0aW9ucyB3aXRoIHRoZSBzYW1lIGRhdGVzIHdpbGwgcmVtYWluIGluIHRoZSBkYXRhIGZyYW1lLiAKCmBgYHtyfQpzZWN0b3JfZmFjdG9yX2RhdGFfam9pbmVkIDwtIApzZWN0b3JfbW9udGhseV9yZXR1cm5zXzIwMDBfMjAxOCAlPiUgCiAgbGVmdF9qb2luKEdsb2JhbF81X0ZhY3RvcnNfZm9yX2pvaW4sIGJ5ID0gImRhdGUiKSAKCnNlY3Rvcl9mYWN0b3JfZGF0YV9qb2luZWQgJT4lIAogIHNlbGVjdCgtdGlja2VyLCAtYWRqQ2xvc2UpICU+JSAKICBzbGljZSgxKQpgYGAKCk91ciBkYXRhIGlzIG5vdyBpbiBhIG5pY2VseSBzdHJ1Y3R1cmVkIGRhdGEgZnJhbWUsIHdpdGggb3VyIHNlY3RvciBuYW1lcyBpbiBvbmUgY29sdW1uLCBvdXIgc2VjdG9yIHJldHVybnMgaW4gYSBzZXBhcmF0ZSBjb2x1bW4sIGFuZCBlYWNoIG9mIHRoZSBGYW1hIEZyZW5jaCBmYWN0b3JzIGluIGEgc2VwYXJhdGUgY29sdW1uLiBGcm9tIGhlcmUsIHdlIHdhbnQgdG8gcnVuIG91ciBmYWN0b3IgYW5hbHlzaXMsIHdoaWNoIHdlIGRvIGJ5IHJlZ3Jlc3Npbmcgb3VyIG1vbnRobHkgcmV0dXJucyBvbiB0aGUgNSBmYWN0b3JzLiAKCkZvciB0aGlzIGV4ZXJjaXNlLCBsZXQncyBydW4gdGhlIHJlZ3Jlc3Npb24gb24ganVzdCB0aGUgYEZpbmFuY2lhbHNgIHNlY3RvcnMgYnkgZmlyc3QgY2FsbGluZyBgZmlsdGVyKHNlY3RvciA9PSBGaW5hbmNpYWxzKWAuIElmIHlvdSB3aXNoIHRvIHJlcHJvZHVjZSB0aGlzIHdvcmsgYnV0IHJ1biB0aGUgcmVncmVzc2lvbiBvbiBhIGRpZmZlcmVudCBzZWN0b3IsIHlvdSBjYW4gY2hhbmdlIHRoZSBjb2RlIHRvIGBmaWx0ZXIoc2VjdG9yID09IHlvdXIgc2VjdG9yIG9mIGNob2ljZSlgLgoKQWZ0ZXIgZmlsdGVyaW5nLCB3ZSB1c2UgYGRvKG1vZGVsID0gbG0obW9udGhseV9yZXR1cm4gfiBNa3QgKyBTTUIgKyBITUwgKyBSTVcgKyBDTUEpKWAgdG8gcnVuIG91ciByZWdyZXNzaW9uLiAKCgpgYGB7cn0KCnNlY3Rvcl9mYWN0b3JfZGF0YV9qb2luZWQgJT4lIAogIGZpbHRlcihzZWN0b3IgPT0gIkZpbmFuY2lhbHMiKSAlPiUgCiAgZG8obW9kZWwgPSBsbShtb250aGx5X3JldHVybiB+IE1LVCArIFNNQiArIEhNTCArIFJNVyArIENNQSwgCiAgICAgICAgICAgICAgICBkYXRhID0gLikpCmBgYAoKVGhhdCB3b3JrZWQsIGJ1dCB0aGUgcmVzdWx0cyBvZiBvdXIgNS1mYWN0b3IgcmVncmVzc2lvbiBhcmUgc3RvcmVkIGFzIGFuIGBTM2Agb2JqZWN0LCB3aGljaCBjYW4gYmUgaGFyZCB0byBhY2Nlc3MuIFRvIGNsZWFuIHVwIHRoZSByZXN1bHRzLCBhbmQgYWRkIGNvbmZpZGVuY2UgaW50ZXJ2YWxzIGZvciBvdXIgYmV0YSBlc3RpbWF0ZXMsIHdlIGNhbGwgYHRpZHkobW9kZWwsIGNvbmYuaW50ID0gVCwgY29uZi5sZXZlbCA9IC45NSlgIGFuZCByZW5hbWUgYSBjb3VwbGUgb2YgY29sdW1ucyBmb3IgY29udmVuaWVuY2UuCgpgYGB7cn0KZmZfcmVzdWx0cyA8LQpzZWN0b3JfZmFjdG9yX2RhdGFfam9pbmVkICU+JSAKICBmaWx0ZXIoc2VjdG9yID09ICJGaW5hbmNpYWxzIikgJT4lIAogIGRvKG1vZGVsID0gbG0obW9udGhseV9yZXR1cm4gfiBNS1QgKyBTTUIgKyBITUwgKyBSTVcgKyBDTUEsIAogICAgICAgICAgICAgICAgZGF0YSA9IC4pKSAlPiUgCiAgdGlkeShtb2RlbCwgY29uZi5pbnQgPSBULCBjb25mLmxldmVsID0gLjk1KSAlPiUgCiAgcmVuYW1lKGNvZWZmaWNpZW50ID0gZXN0aW1hdGUsIGZhY3RvciA9IHRlcm0pCgpmZl9yZXN1bHRzCmBgYAoKTGV0J3MgZmluaXNoIG91ciB3b3JrIHdpdGggYSBjaGFydCwgc2hvd2luZyB0aGUgY29lZmZpY2llbnQgZXN0aW1hdGVzIGFuZCBjb25maWRlbmNlIGludGVydmFscyBmb3Igb3VyIGZhY3RvcnMuIFdlIHdpbGwgdXNlIGBnZ3Bsb3QoKWAgdG8gYnVpbGQgdGhpcyBjaGFydGluZyBtYXBwaW5nIG91ciBmYWN0b3IgbmFtZXMgdG8gdGhlIHgtYXhpcyBhbmQgb3VyIGNvZWZmaWNpZW50cyB0byB0aGUgeS1heGlzIHdpdGggYGFlcyh4ID0gZmFjdG9yLCB5ID0gY29lZmZpY2llbnQpYC4gTGV0J3MgcGxhY2UgYSBwb2ludCBmb3IgZWFjaCBjb2VmZmljaWVudCBieSBjYWxsaW5nIGBnZW9tX3BvaW50KClgIGFuZCBhZGQgY29uZmlkZW5jZSBpbnRlcnZhbHMgd2l0aCBgZ2VvbV9lcnJvcmJhcigpYC4KCmBgYHtyfQpmZl9yZXN1bHRzICU+JSAKZmlsdGVyKGZhY3RvciAhPSAiKEludGVyY2VwdCkiKSAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gZmFjdG9yLCAKICAgICAgICAgICAgIHkgPSBjb2VmZmljaWVudCwgCiAgICAgICAgICAgICBzaGFwZSA9IGZhY3RvciwgCiAgICAgICAgICAgICBjb2xvciA9IGZhY3RvcikpICsgCiAgZ2VvbV9wb2ludCgpICsKICBnZW9tX2Vycm9yYmFyKGFlcyh5bWluID0gY29uZi5sb3csIAogICAgICAgICAgICAgICAgICAgIHltYXggPSBjb25mLmhpZ2gpKSArCiAgbGFicyh0aXRsZSA9ICJGRiA1LUZhY3RvciBCZXRhcyB3aXRoIENvbmYgSW50ZXJ2YWxzIiwKICAgICAgIHggPSAiIiwKICAgICAgIHkgPSAiY29lZmZpY2llbnRzIiwKICAgICAgIGNhcHRpb24gPSAiZGF0YSBzb3VyY2U6IEZhbWEtRnJlbmNoIHdlYnNpdGUiKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSwKICAgICAgICBwbG90LmNhcHRpb24gID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMCkpCmBgYAoKV2UgY2FuIGdsYW5jZSBhdCB0aGlzIGFuZCBxdWlja2x5IHNlZSB0aGF0IHRoZSBNS1QgYW5kIEhNTCBmYWN0b3JzIGhhdmUgcG9zaXRpdmUgY29lZmZpY2llbnRzIG5lYXIgMSwgd2hlcmVhcyBib3RoIFJNVyBhbmQgU01CIGhhdmUgbmVnYXRpdmUgY29lZmZpY2llbnRzLiAgCgpSZW1lbWJlciB0aGF0IHdlIHJhbiB0aGUgcmVncmVzc2lvbiBvbiB0aGUgYEZpbmFuY2lhbHNgIHNlY3RvciBidXQgd2UgaGF2ZSBtb250aGx5IHJldHVybnMgZm9yIHNldmVyYWwgbW9yZSBFVEZzLiBXZSBjb3VsZCBleHBhbmQgdGhpcyB3b3JrIGJ5IGZpbHRlcmluZyBvbiBhIGRpZmZlcmVudCBzZWN0b3Igb3IgYnkgYWRkaW5nIG1vcmUgZmFjdG9ycyB0byB0aGUgZGF0YSBmcmFtZSBiZWZvcmUgcnVubmluZyB0aGUgcmVncmVzc2lvbi4gV2UgY291bGQgYWxzbyBjb252ZXJ0IG91ciByZWdyZXNzaW9uIG1vZGVsIHRvIGEgcm9sbGluZyByZWdyZXNzaW9uLCB0byBzZWUgd2hlcmUgd2UgbWlnaHQgaGF2ZSBtaXNzZWQgYW55IHNpZ25pZmljYW50IHJlZ2ltZSBvciBtYXJrZXQgc2hpZnRzLCBhbmQgdGhlbiB2aXN1YWxpemUgb3VyIHJvbGxpbmcgZmFjdG9yIGNvZWZmaWNpZW50cy4KCldlIGhvcGUgdGhhdCB0aGlzIHNob3J0IG5vdGUgcHJvdmlkZWQgc29tZSB1c2VmdWwgUiBjb2RlIGFuZCBzb21lIGRhdGEgd3JhbmdsaW5nIGZ1bmN0aW9ucyB0aGF0IG1pZ2h0IHNhdmUgc29tZSB0aW1lIHdoZW4gZGlmZmVyZW50IGRhdGUgZm9ybWF0cyBjb25mcm9udCB5b3UuIFRoYW5rcyBmb3IgcmVhZGluZy4gCg==