First backtests.

First: packages & data.

library(tidyverse)
library(lubridate)
library(xgboost)
library(keras)

load("data_ml.RData")
data_ml <- data_ml %>% arrange(date, stock_id)
dates <- levels(as.factor(data_ml$date))

Second: auxiliary variables.

depth <- c(6, 12, 24, 48, 96)                               # Looking back
label <- c("R1M_Usd", "R3M_Usd", "R6M_Usd", "R12M_Usd")     # Label
label_num <- c(1, 3, 6, 12)
sep_oos <- as.Date("2009-01-01")                            # Starting point for backtest
ticks <- data_ml$stock_id %>%                               # List of all asset ids
    as.factor() %>%
    levels()
N <- length(ticks)                                          # Max number of assets
dates <- data_ml$date %>%  unique()                         # All dates
t_oos <- dates[dates > sep_oos] %>%                         # Out-of-sample dates
    as.Date(origin = "1970-01-01")           
    
Tt <- length(t_oos)                      
features <- colnames(data_ml[3:95]) # Keep the feature's column names (hard-coded, beware!)

Third: the predictive engines.

pred_fun <- function(train_data, test_data, features, label, j){ 
    if(j == 1){
        train_label <- train_data %>% select(label) %>% as.matrix()               # Dependent variable
        train_features <- train_data %>% select(features) %>% as.matrix()         # Indep. variable
        train_matrix <- xgb.DMatrix(data = train_features, label = train_label)   # XGB format
        fit <- train_matrix %>% 
            xgb.train(data = .,                       # Data source (pipe input)
                      eta = 0.2,                      # Learning rate
                      objective = "reg:linear",       # Number of random trees
                      max_depth = 4,                  # Maximum depth of trees
                      nrounds = 80                    # Number of trees used
            )
        xgb_test <- test_data %>%                     # Test sample => XGB format
            select(features) %>% 
            as.matrix() %>%
            xgb.DMatrix()
        pred <- c()                                   # Initialize output
        pred$ret <- predict(fit, xgb_test)            # Fill output with predicted returns
        pred$names <- unique(test_data$stock_id)
        return(pred)                                  # Best predictions, equally-weighted
    }
    if(j == 2){
        NN_train_features <- select(train_data, features) %>% as.matrix()  # Matrix = important
        NN_train_labels <- train_data %>% select(label) %>% as.matrix()
        NN_test_features <- select(test_data, features) %>% as.matrix()    # Matrix = important
        model <- keras_model_sequential()
        model %>%   # This defines the structure of the network, i.e. how layers are organized
            layer_dense(units = 16, activation = 'relu', input_shape = ncol(NN_train_features)) %>%
            layer_dense(units = 8, activation = 'tanh') %>%
            layer_dense(units = 1) # No activation means linear activation: f(x) = x.
        model %>% compile(                             # Model specification
            loss = 'mean_squared_error',               # Loss function
            optimizer = optimizer_rmsprop(),           # Optimisation method (weight updating)
            metrics = c('mean_absolute_error')         # Output metric
        )
        fit_NN <- model %>% 
            fit(NN_train_features,                                       # Training features
                NN_train_labels,                                         # Training labels
                epochs = 10, batch_size = 512, verbose = 0)              # Training parameters
        pred <- c()                                   # Initialize output
        pred$ret <- predict(model, NN_test_features)  # Fill output with predicted returns
        pred$names <- unique(test_data$stock_id)
        return(pred)
    }
}

Fourth: backtesting loop! NOTE: it takes dozens of hours to run on CPUs.

pred <- array(NA, dim = c(Tt-1, length(depth), length(label), 2, N))
realized <- array(NA, dim = c(Tt-1,length(depth), length(label), 2, N))

for(t in 1:121){ #(Tt-1)){                              # Stop before the last date: no fwd return!
    if(t%%2==0){print(t_oos[t])}                        # Just checking the date status
    for(k in 1:length(depth)){                          # Estimation window
        for(i in 1:length(label)){                      # Label horizon
            train_buffer <- label_num[i]                # Training buffer size in months
            label_used <- label[i]
            ind_date = which(dates==t_oos[t])
            train_data <- data_ml %>% filter(date <= dates[ind_date-train_buffer],   # Rolling window with buffer
                                             date >= dates[ind_date-train_buffer-depth[k]])    
            test_data <- data_ml %>% filter(date == t_oos[t])          # Common with past info! 
            for(j in 1:2){                                             # Predictor
                pred_temp <- pred_fun(train_data, test_data, features, label_used, j)
                ind2 <- match(pred_temp$names, ticks) %>% na.omit()    # Index between all & test
                pred[t,k,i,j,ind2] <- pred_temp$ret  
                real_tmp <- data_ml %>% filter(date == t_oos[t]) %>% select(label_used) %>% as.matrix()
                realized[t,k,i,j, ind2] <- real_tmp 
            }
        }
    }
}

# save(pred, file = "pred.RData")
# save(realized, file = "realized.RData")

Fifth: analysis. Below we show how to compute the R^2 and Mariano-Dieblod statistics. An initial step is to clean and re-order the data.

library(reshape)
pred_df <- melt(pred)
realized_df <- melt(realized)
error_df <- cbind(pred_df[,1:5], realized_df[,6], realized_df[,6] - pred_df[,6])
colnames(error_df) <- c("Date", "Depth", "Label", "Method", "Stock", "Realized", "Error")
error_df$Depth[error_df$Depth == 5] <- 96
error_df$Depth[error_df$Depth == 4] <- 48
error_df$Depth[error_df$Depth == 3] <- 24
error_df$Depth[error_df$Depth == 2] <- 12
error_df$Depth[error_df$Depth == 1] <- 6

error_df$Label[error_df$Label == 4] <- 12
error_df$Label[error_df$Label == 3] <- 6
error_df$Label[error_df$Label == 2] <- 3

error_df$Method[error_df$Method == 1] <- "Boosted_trees"
error_df$Method[error_df$Method == 2] <- "Neural_network"

res <- error_df %>% 
    mutate(Error = Error / sqrt(Label),
           abs_err = abs(Error),
           sq_err = Error^2,
           sq_realized = Realized^2) %>%
    group_by(Depth, Label, Method) %>%
    summarise(mae = mean(abs_err, na.rm = T),
              r2 = 1-sum(sq_err, na.rm = T)/sum(sq_realized, na.rm = T)) %>%
    ungroup() %>%
    mutate(Label = as.factor(Label),
           Label = recode_factor(Label,
                                 "1"= "1 month label",
                                 "3"= "3 month label",
                                 "6"= "6 month label",
                                 "12"= "12 month label",
                                 .ordered = T)) 

Then, the R^2.

res <- error_df %>%
    group_by(Depth, Label, Method) %>%
    summarise(R2 = 1-mean(Error^2, na.rm = T)/mean(Realized^2, na.rm = T)) %>%
    ungroup() %>%
    mutate(Depth = as.factor(Depth),
           Method = as.factor(Method)) 
res %>%
    ggplot(aes(x = Depth, y = R2, fill = Method)) + geom_col(position = "dodge") + 
    facet_grid(. ~Label, scales = "free") +
    ylab("Out-of-sample R^2") + xlab("Sample depth (months)") + 
    scale_fill_manual(values = c("#133E7E","#0FD581"), 
                      name = "Method", 
                      labels = c("Boosted trees", "Neural networks")) +
    theme(legend.position = "top") 

And finally, the MD series.

library(viridis)
error_df %>%
    spread(key = Method, value = Error) %>%
    group_by(Date, Depth, Label) %>%
    summarise(MD = mean(Boosted_trees^2-Neural_network^2, na.rm=T) / sd(Boosted_trees^2-Neural_network^2, na.rm=T)) %>%
    ggplot(aes(x=t_oos[Date], y = MD, color = as.factor(Depth))) + geom_line() + facet_grid(Label ~.) +
    ylab("Diebold-Mariano statistic") + xlab("Date") + scale_color_viridis(option="plasma", discrete=TRUE) +
    labs(color = "Depth (months)")

LS0tCnRpdGxlOiBCYXNlbGluZSByZXN1bHRzCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCiMgRmlyc3QgYmFja3Rlc3RzLgoKKipGaXJzdCoqOiBwYWNrYWdlcyAmIGRhdGEuCgpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9CmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGx1YnJpZGF0ZSkKbGlicmFyeSh4Z2Jvb3N0KQpsaWJyYXJ5KGtlcmFzKQoKbG9hZCgiZGF0YV9tbC5SRGF0YSIpCmRhdGFfbWwgPC0gZGF0YV9tbCAlPiUgYXJyYW5nZShkYXRlLCBzdG9ja19pZCkKZGF0ZXMgPC0gbGV2ZWxzKGFzLmZhY3RvcihkYXRhX21sJGRhdGUpKQpgYGAKCioqU2Vjb25kKio6IGF1eGlsaWFyeSB2YXJpYWJsZXMuCgpgYGB7cn0KZGVwdGggPC0gYyg2LCAxMiwgMjQsIDQ4LCA5NikgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBMb29raW5nIGJhY2sKbGFiZWwgPC0gYygiUjFNX1VzZCIsICJSM01fVXNkIiwgIlI2TV9Vc2QiLCAiUjEyTV9Vc2QiKSAgICAgIyBMYWJlbApsYWJlbF9udW0gPC0gYygxLCAzLCA2LCAxMikKc2VwX29vcyA8LSBhcy5EYXRlKCIyMDA5LTAxLTAxIikgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBTdGFydGluZyBwb2ludCBmb3IgYmFja3Rlc3QKdGlja3MgPC0gZGF0YV9tbCRzdG9ja19pZCAlPiUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBMaXN0IG9mIGFsbCBhc3NldCBpZHMKICAgIGFzLmZhY3RvcigpICU+JQogICAgbGV2ZWxzKCkKTiA8LSBsZW5ndGgodGlja3MpICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBNYXggbnVtYmVyIG9mIGFzc2V0cwpkYXRlcyA8LSBkYXRhX21sJGRhdGUgJT4lICB1bmlxdWUoKSAgICAgICAgICAgICAgICAgICAgICAgICAjIEFsbCBkYXRlcwp0X29vcyA8LSBkYXRlc1tkYXRlcyA+IHNlcF9vb3NdICU+JSAgICAgICAgICAgICAgICAgICAgICAgICAjIE91dC1vZi1zYW1wbGUgZGF0ZXMKICAgIGFzLkRhdGUob3JpZ2luID0gIjE5NzAtMDEtMDEiKSAgICAgICAgICAgCiAgICAKVHQgPC0gbGVuZ3RoKHRfb29zKSAgICAgICAgICAgICAgICAgICAgICAKZmVhdHVyZXMgPC0gY29sbmFtZXMoZGF0YV9tbFszOjk1XSkgIyBLZWVwIHRoZSBmZWF0dXJlJ3MgY29sdW1uIG5hbWVzIChoYXJkLWNvZGVkLCBiZXdhcmUhKQpgYGAKCioqVGhpcmQqKjogdGhlIHByZWRpY3RpdmUgZW5naW5lcy4KCmBgYHtyfQpwcmVkX2Z1biA8LSBmdW5jdGlvbih0cmFpbl9kYXRhLCB0ZXN0X2RhdGEsIGZlYXR1cmVzLCBsYWJlbCwgail7IAogICAgaWYoaiA9PSAxKXsKICAgICAgICB0cmFpbl9sYWJlbCA8LSB0cmFpbl9kYXRhICU+JSBzZWxlY3QobGFiZWwpICU+JSBhcy5tYXRyaXgoKSAgICAgICAgICAgICAgICMgRGVwZW5kZW50IHZhcmlhYmxlCiAgICAgICAgdHJhaW5fZmVhdHVyZXMgPC0gdHJhaW5fZGF0YSAlPiUgc2VsZWN0KGZlYXR1cmVzKSAlPiUgYXMubWF0cml4KCkgICAgICAgICAjIEluZGVwLiB2YXJpYWJsZQogICAgICAgIHRyYWluX21hdHJpeCA8LSB4Z2IuRE1hdHJpeChkYXRhID0gdHJhaW5fZmVhdHVyZXMsIGxhYmVsID0gdHJhaW5fbGFiZWwpICAgIyBYR0IgZm9ybWF0CiAgICAgICAgZml0IDwtIHRyYWluX21hdHJpeCAlPiUgCiAgICAgICAgICAgIHhnYi50cmFpbihkYXRhID0gLiwgICAgICAgICAgICAgICAgICAgICAgICMgRGF0YSBzb3VyY2UgKHBpcGUgaW5wdXQpCiAgICAgICAgICAgICAgICAgICAgICBldGEgPSAwLjIsICAgICAgICAgICAgICAgICAgICAgICMgTGVhcm5pbmcgcmF0ZQogICAgICAgICAgICAgICAgICAgICAgb2JqZWN0aXZlID0gInJlZzpsaW5lYXIiLCAgICAgICAjIE51bWJlciBvZiByYW5kb20gdHJlZXMKICAgICAgICAgICAgICAgICAgICAgIG1heF9kZXB0aCA9IDQsICAgICAgICAgICAgICAgICAgIyBNYXhpbXVtIGRlcHRoIG9mIHRyZWVzCiAgICAgICAgICAgICAgICAgICAgICBucm91bmRzID0gODAgICAgICAgICAgICAgICAgICAgICMgTnVtYmVyIG9mIHRyZWVzIHVzZWQKICAgICAgICAgICAgKQogICAgICAgIHhnYl90ZXN0IDwtIHRlc3RfZGF0YSAlPiUgICAgICAgICAgICAgICAgICAgICAjIFRlc3Qgc2FtcGxlID0+IFhHQiBmb3JtYXQKICAgICAgICAgICAgc2VsZWN0KGZlYXR1cmVzKSAlPiUgCiAgICAgICAgICAgIGFzLm1hdHJpeCgpICU+JQogICAgICAgICAgICB4Z2IuRE1hdHJpeCgpCiAgICAgICAgcHJlZCA8LSBjKCkgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgSW5pdGlhbGl6ZSBvdXRwdXQKICAgICAgICBwcmVkJHJldCA8LSBwcmVkaWN0KGZpdCwgeGdiX3Rlc3QpICAgICAgICAgICAgIyBGaWxsIG91dHB1dCB3aXRoIHByZWRpY3RlZCByZXR1cm5zCiAgICAgICAgcHJlZCRuYW1lcyA8LSB1bmlxdWUodGVzdF9kYXRhJHN0b2NrX2lkKQogICAgICAgIHJldHVybihwcmVkKSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIEJlc3QgcHJlZGljdGlvbnMsIGVxdWFsbHktd2VpZ2h0ZWQKICAgIH0KICAgIGlmKGogPT0gMil7CiAgICAgICAgTk5fdHJhaW5fZmVhdHVyZXMgPC0gc2VsZWN0KHRyYWluX2RhdGEsIGZlYXR1cmVzKSAlPiUgYXMubWF0cml4KCkgICMgTWF0cml4ID0gaW1wb3J0YW50CiAgICAgICAgTk5fdHJhaW5fbGFiZWxzIDwtIHRyYWluX2RhdGEgJT4lIHNlbGVjdChsYWJlbCkgJT4lIGFzLm1hdHJpeCgpCiAgICAgICAgTk5fdGVzdF9mZWF0dXJlcyA8LSBzZWxlY3QodGVzdF9kYXRhLCBmZWF0dXJlcykgJT4lIGFzLm1hdHJpeCgpICAgICMgTWF0cml4ID0gaW1wb3J0YW50CiAgICAgICAgbW9kZWwgPC0ga2VyYXNfbW9kZWxfc2VxdWVudGlhbCgpCiAgICAgICAgbW9kZWwgJT4lICAgIyBUaGlzIGRlZmluZXMgdGhlIHN0cnVjdHVyZSBvZiB0aGUgbmV0d29yaywgaS5lLiBob3cgbGF5ZXJzIGFyZSBvcmdhbml6ZWQKICAgICAgICAgICAgbGF5ZXJfZGVuc2UodW5pdHMgPSAxNiwgYWN0aXZhdGlvbiA9ICdyZWx1JywgaW5wdXRfc2hhcGUgPSBuY29sKE5OX3RyYWluX2ZlYXR1cmVzKSkgJT4lCiAgICAgICAgICAgIGxheWVyX2RlbnNlKHVuaXRzID0gOCwgYWN0aXZhdGlvbiA9ICd0YW5oJykgJT4lCiAgICAgICAgICAgIGxheWVyX2RlbnNlKHVuaXRzID0gMSkgIyBObyBhY3RpdmF0aW9uIG1lYW5zIGxpbmVhciBhY3RpdmF0aW9uOiBmKHgpID0geC4KICAgICAgICBtb2RlbCAlPiUgY29tcGlsZSggICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgTW9kZWwgc3BlY2lmaWNhdGlvbgogICAgICAgICAgICBsb3NzID0gJ21lYW5fc3F1YXJlZF9lcnJvcicsICAgICAgICAgICAgICAgIyBMb3NzIGZ1bmN0aW9uCiAgICAgICAgICAgIG9wdGltaXplciA9IG9wdGltaXplcl9ybXNwcm9wKCksICAgICAgICAgICAjIE9wdGltaXNhdGlvbiBtZXRob2QgKHdlaWdodCB1cGRhdGluZykKICAgICAgICAgICAgbWV0cmljcyA9IGMoJ21lYW5fYWJzb2x1dGVfZXJyb3InKSAgICAgICAgICMgT3V0cHV0IG1ldHJpYwogICAgICAgICkKICAgICAgICBmaXRfTk4gPC0gbW9kZWwgJT4lIAogICAgICAgICAgICBmaXQoTk5fdHJhaW5fZmVhdHVyZXMsICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBUcmFpbmluZyBmZWF0dXJlcwogICAgICAgICAgICAgICAgTk5fdHJhaW5fbGFiZWxzLCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBUcmFpbmluZyBsYWJlbHMKICAgICAgICAgICAgICAgIGVwb2NocyA9IDEwLCBiYXRjaF9zaXplID0gNTEyLCB2ZXJib3NlID0gMCkgICAgICAgICAgICAgICMgVHJhaW5pbmcgcGFyYW1ldGVycwogICAgICAgIHByZWQgPC0gYygpICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIEluaXRpYWxpemUgb3V0cHV0CiAgICAgICAgcHJlZCRyZXQgPC0gcHJlZGljdChtb2RlbCwgTk5fdGVzdF9mZWF0dXJlcykgICMgRmlsbCBvdXRwdXQgd2l0aCBwcmVkaWN0ZWQgcmV0dXJucwogICAgICAgIHByZWQkbmFtZXMgPC0gdW5pcXVlKHRlc3RfZGF0YSRzdG9ja19pZCkKICAgICAgICByZXR1cm4ocHJlZCkKICAgIH0KfQpgYGAKCgoqKkZvdXJ0aCoqOiBiYWNrdGVzdGluZyBsb29wISAqKk5PVEUqKjogaXQgdGFrZXMgKmRvemVucyogb2YgaG91cnMgdG8gcnVuIG9uIENQVXMuCgoKYGBge3J9CnByZWQgPC0gYXJyYXkoTkEsIGRpbSA9IGMoVHQtMSwgbGVuZ3RoKGRlcHRoKSwgbGVuZ3RoKGxhYmVsKSwgMiwgTikpCnJlYWxpemVkIDwtIGFycmF5KE5BLCBkaW0gPSBjKFR0LTEsbGVuZ3RoKGRlcHRoKSwgbGVuZ3RoKGxhYmVsKSwgMiwgTikpCgpmb3IodCBpbiAxOjEyMSl7ICMoVHQtMSkpeyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgU3RvcCBiZWZvcmUgdGhlIGxhc3QgZGF0ZTogbm8gZndkIHJldHVybiEKICAgIGlmKHQlJTI9PTApe3ByaW50KHRfb29zW3RdKX0gICAgICAgICAgICAgICAgICAgICAgICAjIEp1c3QgY2hlY2tpbmcgdGhlIGRhdGUgc3RhdHVzCiAgICBmb3IoayBpbiAxOmxlbmd0aChkZXB0aCkpeyAgICAgICAgICAgICAgICAgICAgICAgICAgIyBFc3RpbWF0aW9uIHdpbmRvdwogICAgICAgIGZvcihpIGluIDE6bGVuZ3RoKGxhYmVsKSl7ICAgICAgICAgICAgICAgICAgICAgICMgTGFiZWwgaG9yaXpvbgogICAgICAgICAgICB0cmFpbl9idWZmZXIgPC0gbGFiZWxfbnVtW2ldICAgICAgICAgICAgICAgICMgVHJhaW5pbmcgYnVmZmVyIHNpemUgaW4gbW9udGhzCiAgICAgICAgICAgIGxhYmVsX3VzZWQgPC0gbGFiZWxbaV0KICAgICAgICAgICAgaW5kX2RhdGUgPSB3aGljaChkYXRlcz09dF9vb3NbdF0pCiAgICAgICAgICAgIHRyYWluX2RhdGEgPC0gZGF0YV9tbCAlPiUgZmlsdGVyKGRhdGUgPD0gZGF0ZXNbaW5kX2RhdGUtdHJhaW5fYnVmZmVyXSwgICAjIFJvbGxpbmcgd2luZG93IHdpdGggYnVmZmVyCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGUgPj0gZGF0ZXNbaW5kX2RhdGUtdHJhaW5fYnVmZmVyLWRlcHRoW2tdXSkgICAgCiAgICAgICAgICAgIHRlc3RfZGF0YSA8LSBkYXRhX21sICU+JSBmaWx0ZXIoZGF0ZSA9PSB0X29vc1t0XSkgICAgICAgICAgIyBDb21tb24gd2l0aCBwYXN0IGluZm8hIAogICAgICAgICAgICBmb3IoaiBpbiAxOjIpeyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgUHJlZGljdG9yCiAgICAgICAgICAgICAgICBwcmVkX3RlbXAgPC0gcHJlZF9mdW4odHJhaW5fZGF0YSwgdGVzdF9kYXRhLCBmZWF0dXJlcywgbGFiZWxfdXNlZCwgaikKICAgICAgICAgICAgICAgIGluZDIgPC0gbWF0Y2gocHJlZF90ZW1wJG5hbWVzLCB0aWNrcykgJT4lIG5hLm9taXQoKSAgICAjIEluZGV4IGJldHdlZW4gYWxsICYgdGVzdAogICAgICAgICAgICAgICAgcHJlZFt0LGssaSxqLGluZDJdIDwtIHByZWRfdGVtcCRyZXQgIAogICAgICAgICAgICAgICAgcmVhbF90bXAgPC0gZGF0YV9tbCAlPiUgZmlsdGVyKGRhdGUgPT0gdF9vb3NbdF0pICU+JSBzZWxlY3QobGFiZWxfdXNlZCkgJT4lIGFzLm1hdHJpeCgpCiAgICAgICAgICAgICAgICByZWFsaXplZFt0LGssaSxqLCBpbmQyXSA8LSByZWFsX3RtcCAKICAgICAgICAgICAgfQogICAgICAgIH0KICAgIH0KfQoKIyBzYXZlKHByZWQsIGZpbGUgPSAicHJlZC5SRGF0YSIpCiMgc2F2ZShyZWFsaXplZCwgZmlsZSA9ICJyZWFsaXplZC5SRGF0YSIpCmBgYAoKCioqRmlmdGgqKjogYW5hbHlzaXMuIEJlbG93IHdlIHNob3cgaG93IHRvIGNvbXB1dGUgdGhlIFJeMiBhbmQgTWFyaWFuby1EaWVibG9kIHN0YXRpc3RpY3MuCkFuIGluaXRpYWwgc3RlcCBpcyB0byBjbGVhbiBhbmQgcmUtb3JkZXIgdGhlIGRhdGEuCgpgYGB7cn0KbGlicmFyeShyZXNoYXBlKQpwcmVkX2RmIDwtIG1lbHQocHJlZCkKcmVhbGl6ZWRfZGYgPC0gbWVsdChyZWFsaXplZCkKZXJyb3JfZGYgPC0gY2JpbmQocHJlZF9kZlssMTo1XSwgcmVhbGl6ZWRfZGZbLDZdLCByZWFsaXplZF9kZlssNl0gLSBwcmVkX2RmWyw2XSkKY29sbmFtZXMoZXJyb3JfZGYpIDwtIGMoIkRhdGUiLCAiRGVwdGgiLCAiTGFiZWwiLCAiTWV0aG9kIiwgIlN0b2NrIiwgIlJlYWxpemVkIiwgIkVycm9yIikKZXJyb3JfZGYkRGVwdGhbZXJyb3JfZGYkRGVwdGggPT0gNV0gPC0gOTYKZXJyb3JfZGYkRGVwdGhbZXJyb3JfZGYkRGVwdGggPT0gNF0gPC0gNDgKZXJyb3JfZGYkRGVwdGhbZXJyb3JfZGYkRGVwdGggPT0gM10gPC0gMjQKZXJyb3JfZGYkRGVwdGhbZXJyb3JfZGYkRGVwdGggPT0gMl0gPC0gMTIKZXJyb3JfZGYkRGVwdGhbZXJyb3JfZGYkRGVwdGggPT0gMV0gPC0gNgoKZXJyb3JfZGYkTGFiZWxbZXJyb3JfZGYkTGFiZWwgPT0gNF0gPC0gMTIKZXJyb3JfZGYkTGFiZWxbZXJyb3JfZGYkTGFiZWwgPT0gM10gPC0gNgplcnJvcl9kZiRMYWJlbFtlcnJvcl9kZiRMYWJlbCA9PSAyXSA8LSAzCgplcnJvcl9kZiRNZXRob2RbZXJyb3JfZGYkTWV0aG9kID09IDFdIDwtICJCb29zdGVkX3RyZWVzIgplcnJvcl9kZiRNZXRob2RbZXJyb3JfZGYkTWV0aG9kID09IDJdIDwtICJOZXVyYWxfbmV0d29yayIKCnJlcyA8LSBlcnJvcl9kZiAlPiUgCiAgICBtdXRhdGUoRXJyb3IgPSBFcnJvciAvIHNxcnQoTGFiZWwpLAogICAgICAgICAgIGFic19lcnIgPSBhYnMoRXJyb3IpLAogICAgICAgICAgIHNxX2VyciA9IEVycm9yXjIsCiAgICAgICAgICAgc3FfcmVhbGl6ZWQgPSBSZWFsaXplZF4yKSAlPiUKICAgIGdyb3VwX2J5KERlcHRoLCBMYWJlbCwgTWV0aG9kKSAlPiUKICAgIHN1bW1hcmlzZShtYWUgPSBtZWFuKGFic19lcnIsIG5hLnJtID0gVCksCiAgICAgICAgICAgICAgcjIgPSAxLXN1bShzcV9lcnIsIG5hLnJtID0gVCkvc3VtKHNxX3JlYWxpemVkLCBuYS5ybSA9IFQpKSAlPiUKICAgIHVuZ3JvdXAoKSAlPiUKICAgIG11dGF0ZShMYWJlbCA9IGFzLmZhY3RvcihMYWJlbCksCiAgICAgICAgICAgTGFiZWwgPSByZWNvZGVfZmFjdG9yKExhYmVsLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiMSI9ICIxIG1vbnRoIGxhYmVsIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIjMiPSAiMyBtb250aCBsYWJlbCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICI2Ij0gIjYgbW9udGggbGFiZWwiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiMTIiPSAiMTIgbW9udGggbGFiZWwiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAub3JkZXJlZCA9IFQpKSAKYGBgCgpUaGVuLCB0aGUgUl4yLgoKYGBge3J9CnJlcyA8LSBlcnJvcl9kZiAlPiUKICAgIGdyb3VwX2J5KERlcHRoLCBMYWJlbCwgTWV0aG9kKSAlPiUKICAgIHN1bW1hcmlzZShSMiA9IDEtbWVhbihFcnJvcl4yLCBuYS5ybSA9IFQpL21lYW4oUmVhbGl6ZWReMiwgbmEucm0gPSBUKSkgJT4lCiAgICB1bmdyb3VwKCkgJT4lCiAgICBtdXRhdGUoRGVwdGggPSBhcy5mYWN0b3IoRGVwdGgpLAogICAgICAgICAgIE1ldGhvZCA9IGFzLmZhY3RvcihNZXRob2QpKSAKcmVzICU+JQogICAgZ2dwbG90KGFlcyh4ID0gRGVwdGgsIHkgPSBSMiwgZmlsbCA9IE1ldGhvZCkpICsgZ2VvbV9jb2wocG9zaXRpb24gPSAiZG9kZ2UiKSArIAogICAgZmFjZXRfZ3JpZCguIH5MYWJlbCwgc2NhbGVzID0gImZyZWUiKSArCiAgICB5bGFiKCJPdXQtb2Ytc2FtcGxlIFJeMiIpICsgeGxhYigiU2FtcGxlIGRlcHRoIChtb250aHMpIikgKyAKICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoIiMxMzNFN0UiLCIjMEZENTgxIiksIAogICAgICAgICAgICAgICAgICAgICAgbmFtZSA9ICJNZXRob2QiLCAKICAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoIkJvb3N0ZWQgdHJlZXMiLCAiTmV1cmFsIG5ldHdvcmtzIikpICsKICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiKSAKYGBgCgpBbmQgZmluYWxseSwgdGhlIE1EIHNlcmllcy4KCmBgYHtyfQpsaWJyYXJ5KHZpcmlkaXMpCmVycm9yX2RmICU+JQogICAgc3ByZWFkKGtleSA9IE1ldGhvZCwgdmFsdWUgPSBFcnJvcikgJT4lCiAgICBncm91cF9ieShEYXRlLCBEZXB0aCwgTGFiZWwpICU+JQogICAgc3VtbWFyaXNlKE1EID0gbWVhbihCb29zdGVkX3RyZWVzXjItTmV1cmFsX25ldHdvcmteMiwgbmEucm09VCkgLyBzZChCb29zdGVkX3RyZWVzXjItTmV1cmFsX25ldHdvcmteMiwgbmEucm09VCkpICU+JQogICAgZ2dwbG90KGFlcyh4PXRfb29zW0RhdGVdLCB5ID0gTUQsIGNvbG9yID0gYXMuZmFjdG9yKERlcHRoKSkpICsgZ2VvbV9saW5lKCkgKyBmYWNldF9ncmlkKExhYmVsIH4uKSArCiAgICB5bGFiKCJEaWVib2xkLU1hcmlhbm8gc3RhdGlzdGljIikgKyB4bGFiKCJEYXRlIikgKyBzY2FsZV9jb2xvcl92aXJpZGlzKG9wdGlvbj0icGxhc21hIiwgZGlzY3JldGU9VFJVRSkgKwogICAgbGFicyhjb2xvciA9ICJEZXB0aCAobW9udGhzKSIpCmBgYA==