If any issues, questions or suggestions feel free to reach me out via e-mail wieczynskipawel@gmail.com or Linkedin. You can also visit my Github.
if(!require(pacman)) install.packages('pacman')
pacman::p_load(PolishStock, dplyr, tidyr, ggplot2, doParallel)
In that blog post we explained Value-at-Risk concept and simulated 10000 portfolios of 4 iShares ETF’s (EEM, IAU, URTH, TIPS) in order to select the optimal ones. However, we calculated VaR and cVaR only one day ahead. In this post we will calculate these measures \(n\) days ahead using bootstrap samples from historical returns.
We use PolishStock package to download data from the website stooq.pl. Daily data for period January 2012 - February 2022 consists of 2345 observations.
tickers <- c('EEM.US', 'IAU.US', 'URTH.US', 'TIP.US')
# for (ticker in tickers){
# stooq_download(ticker, '20120101', '20220228'
# ,destination = paste0(getwd(), '/datasets/'))
# }
close_prices <- df_prices(paste0(tickers, '.csv')
,source = 'datasets/')
returns <- sapply(select(close_prices, -Date)
,FUN = function(x) diff(log(x), lag = 1))
We will simulate \(10000\) portfolios with random weights from Uniform[0,1] distribution. Confidence level for Value-at-Risk is \(\alpha = 95\%\), risk-free interest rate (for generalized Sharpe ratio) is \(\mu_F = 2\%\). For every random portfolio we take \(10000\) samples \(21\) days ahead (approx. 1 month ahead in terms of trading days). To speed up our calculations we use doParallel package in order to utilize multiple CPU cores.
num_of_portfolios <- 1e4
bootstrap_size <- 1e4
days_ahead <- 21
alpha <- 0.95
rf <- 0.02
my.cluster <- makeCluster(detectCores() - 1, type = 'PSOCK')
registerDoParallel(cl = my.cluster)
set.seed(2137)
ahead_metrics <- foreach(
i = 1:num_of_portfolios
,.packages = 'dplyr'
) %dopar% {
portfolio_metrics <- data.frame(
day = 1:days_ahead
,Return = rep(NaN, days_ahead)
,VaR = rep(NaN, days_ahead)
,VaR_Sharpe = rep(NaN, days_ahead)
,cVaR = rep(NaN, days_ahead)
,cVaR_Sharpe = rep(NaN, days_ahead)
)
weights <- c()
random_weights <- runif(4)
random_weights <- random_weights / sum(random_weights)
weights <- random_weights
names(weights) <- colnames(returns)
portfolio_returns <- returns %*% random_weights
bootstrap_returns <- sample(portfolio_returns
,size = days_ahead * bootstrap_size
,replace = TRUE) %>%
matrix(nrow = days_ahead, ncol = bootstrap_size) %>%
apply(2, cumsum)
for (day in 1:days_ahead) {
portfolio_metrics$Return[day] <- (mean(bootstrap_returns[day,]) + 1)^(252/day) - 1
portfolio_metrics$VaR[day] <- -quantile(bootstrap_returns[day,], 1 - alpha)
portfolio_metrics$VaR_Sharpe[day] <- (portfolio_metrics$Return[day] - rf) / portfolio_metrics$VaR[day]
cVaR_indicator <- (portfolio_returns < -portfolio_metrics$VaR[day])
portfolio_metrics$cVaR[day] <- -sum(portfolio_returns * cVaR_indicator) / sum(cVaR_indicator)
portfolio_metrics$cVaR_Sharpe[day] <- (portfolio_metrics$Return[day] - rf) / portfolio_metrics$cVaR[day]
}
list(weights, portfolio_metrics)
}
stopCluster(cl = my.cluster)
Unlisting the results for data vizualization purposes.
d <- days_ahead
d_metrics <- data.frame()
for (i in 1:num_of_portfolios) {
d_metrics <- bind_rows(
d_metrics, c(ahead_metrics[[i]][[1]], ahead_metrics[[i]][[2]][d, -1]) %>%
unlist()
)
}
quantile_metrics <- data.frame(
day = 1:days_ahead
,Return_q_25 = rep(NA, days_ahead)
,Return_q_50 = rep(NA, days_ahead)
,Return_q_75 = rep(NA, days_ahead)
,VaR_q_25 = rep(NA, days_ahead)
,VaR_q_50 = rep(NA, days_ahead)
,VaR_q_75 = rep(NA, days_ahead)
,cVaR_q_25 = rep(NA, days_ahead)
,cVaR_q_50 = rep(NA, days_ahead)
,cVaR_q_75 = rep(NA, days_ahead)
)
for (d in 1:days_ahead) {
temp <- data.frame()
for (i in 1:num_of_portfolios) {
temp <- bind_rows(temp, ahead_metrics[[i]][[2]][d, c(2, 3, 5)])
}
quantile_metrics[d, 2:4] <- quantile(temp$Return, probs = c(0.25, 0.50, 0.75), na.rm = TRUE)
quantile_metrics[d, 5:7] <- quantile(temp$VaR, probs = c(0.25, 0.50, 0.75), na.rm = TRUE)
quantile_metrics[d, 8:10] <- quantile(temp$cVaR, probs = c(0.25, 0.50, 0.75), na.rm = TRUE)
}
In the below chart we see how Expected Return (green) VaR (orange) and cVaR (red) changes with time horizon. Ribbons are interquantile ranges of \(10000\) random portfolios after bootstrap, while the centers of the ribbons are medians of the respective measures. It’s obvious than risk increases with time horizon. Morever, uncertainty connected with risk (ribbon width) also increases with time.
ggplot(quantile_metrics) +
geom_line(aes(x = day, y = Return_q_50), color = 'green', size = 1) +
geom_ribbon(aes(x = day, ymin = Return_q_25, ymax = Return_q_75)
,fill = 'green' ,alpha= 0.10) +
geom_line(aes(x = day, y = VaR_q_50), color = 'orange', size = 1) +
geom_ribbon(aes(x = day, ymin = VaR_q_25, ymax = VaR_q_75)
,fill = 'orange' ,alpha= 0.25) +
geom_line(aes(x = day, y = cVaR_q_50), color = 'red', size = 1) +
geom_ribbon(aes(x = day, ymin = cVaR_q_25, ymax = cVaR_q_75)
,fill = 'red' ,alpha= 0.25) +
theme_bw() +
labs(y ='')

Below we see optimal portfolios:
- Minimal VaR and cVaR portfolios have only about \(2.7\%\) - \(3.5\%\) expected annual return and we are 95% confident that monthly (21-sessions) loss won’t exceed \(2.2\%\). In worst case scenario expected monthly dropdown is about \(2.33\%\) - \(2.73\%\). These portfolios mainly consist of treasury inflation-protected bonds.
- Maximal sharpe portfolios have \(8.4\%\) expected annual return and almost two times higher risk than minimal risk portfolios. These portfolios consist of developed markets stocks (\(54\%\)), treasury inflation-protected bonds (\(30\%\)) and gold (\(16\%\)).
optimal_portfolios <- data.frame(
Min_VaR = round(100*d_metrics[which.min(d_metrics$VaR), ], 2) %>% as.numeric()
,Max_VaR_Sharpe = round(100*d_metrics[which.max(d_metrics$VaR_Sharpe), ], 2) %>% as.numeric()
,Min_cVaR = round(100*d_metrics[which.min(d_metrics$cVaR), ], 2) %>% as.numeric()
,Max_cVaR_Sharpe = round(100*d_metrics[which.max(d_metrics$cVaR_Sharpe), ], 2) %>% as.numeric()
) %>% t() %>% as.data.frame()
colnames(optimal_portfolios) <- colnames(d_metrics)
optimal_portfolios
Below we see simulated portfolios on 2-dimensional risk-return planes. Minimal risk portfolios are green triangles and maximal sharpe portfolios are the orange ones.
p1 <- ggplot(d_metrics, aes(x = VaR, y = Return, color = VaR_Sharpe)) +
geom_point() +
geom_point(aes(x = (optimal_portfolios$VaR[1] / 100)
,y = (optimal_portfolios$Return)[1] / 100)
,color = '#2ca02c', size = 5, shape = 17) +
geom_point(aes(x = (optimal_portfolios$VaR[2] / 100)
,y = (optimal_portfolios$Return)[2] / 100)
,color = '#ff7f0e', size = 5, shape = 17) +
theme_bw()
p2 <- ggplot(d_metrics, aes(x = cVaR, y = Return, color = cVaR_Sharpe)) +
geom_point() +
geom_point(aes(x = (optimal_portfolios$cVaR[3] / 100)
,y = (optimal_portfolios$Return)[3] / 100)
,color = '#2ca02c', size = 5, shape = 17) +
geom_point(aes(x = (optimal_portfolios$cVaR[4] / 100)
,y = (optimal_portfolios$Return)[4] / 100)
,color = '#ff7f0e', size = 5, shape = 17) +
theme_bw()
gridExtra::grid.arrange(p1, p2)

LS0tDQp0aXRsZTogIlZhbHVlLWF0LVJpc2sgSGlzdG9yaWNhbCBCb290c3RyYXAiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQpJZiBhbnkgaXNzdWVzLCBxdWVzdGlvbnMgb3Igc3VnZ2VzdGlvbnMgZmVlbCBmcmVlIHRvIHJlYWNoIG1lIG91dCB2aWEgZS1tYWlsIDx3aWVjenluc2tpcGF3ZWxAZ21haWwuY29tPiBvciBbTGlua2VkaW5dKGh0dHBzOi8vd3d3LmxpbmtlZGluLmNvbS9pbi9wYXdlbC13aWVjenluc2tpLykuIFlvdSBjYW4gYWxzbyB2aXNpdCBteSBbR2l0aHViXShodHRwczovL2dpdGh1Yi5jb20vcGF3ZWwtd2llY3p5bnNraSkuDQoNCmBgYHtyIGxpYnJhcmllcywgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0NCmlmKCFyZXF1aXJlKHBhY21hbikpIGluc3RhbGwucGFja2FnZXMoJ3BhY21hbicpDQpwYWNtYW46OnBfbG9hZChQb2xpc2hTdG9jaywgZHBseXIsIHRpZHlyLCBnZ3Bsb3QyLCBkb1BhcmFsbGVsKQ0KYGBgDQoNCkluIFt0aGF0IGJsb2cgcG9zdF0oaHR0cHM6Ly9ycHVicy5jb20vcGF3ZWwtd2llY3p5bnNraS84NzU3MjkpIHdlIGV4cGxhaW5lZCBWYWx1ZS1hdC1SaXNrIGNvbmNlcHQgYW5kIHNpbXVsYXRlZCAxMDAwMCBwb3J0Zm9saW9zIG9mIDQgaVNoYXJlcyBFVEbigJlzIChFRU0sIElBVSwgVVJUSCwgVElQUykgaW4gb3JkZXIgdG8gc2VsZWN0IHRoZSBvcHRpbWFsIG9uZXMuIEhvd2V2ZXIsIHdlIGNhbGN1bGF0ZWQgVmFSIGFuZCBjVmFSIG9ubHkgb25lIGRheSBhaGVhZC4gSW4gdGhpcyBwb3N0IHdlIHdpbGwgY2FsY3VsYXRlIHRoZXNlIG1lYXN1cmVzICRuJCBkYXlzIGFoZWFkIHVzaW5nIGJvb3RzdHJhcCBzYW1wbGVzIGZyb20gaGlzdG9yaWNhbCByZXR1cm5zLiANCg0KV2UgdXNlIFtQb2xpc2hTdG9jayBwYWNrYWdlXShodHRwczovL2dpdGh1Yi5jb20vcGF3ZWwtd2llY3p5bnNraS9Qb2xpc2hTdG9jaykgdG8gZG93bmxvYWQgZGF0YSBmcm9tIHRoZSB3ZWJzaXRlIFtzdG9vcS5wbF0oaHR0cHM6Ly9zdG9vcS5wbC8pLiBEYWlseSBkYXRhIGZvciBwZXJpb2QgSmFudWFyeSAyMDEyIC0gRmVicnVhcnkgMjAyMiBjb25zaXN0cyBvZiAyMzQ1IG9ic2VydmF0aW9ucy4NCmBgYHtyIGdldF9kYXRhLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KdGlja2VycyA8LSBjKCdFRU0uVVMnLCAnSUFVLlVTJywgJ1VSVEguVVMnLCAnVElQLlVTJykNCg0KIyBmb3IgKHRpY2tlciBpbiB0aWNrZXJzKXsNCiMgICBzdG9vcV9kb3dubG9hZCh0aWNrZXIsICcyMDEyMDEwMScsICcyMDIyMDIyOCcNCiMgICAgICAgICAgICAgICAgICAsZGVzdGluYXRpb24gPSBwYXN0ZTAoZ2V0d2QoKSwgJy9kYXRhc2V0cy8nKSkNCiMgfQ0KDQpjbG9zZV9wcmljZXMgPC0gZGZfcHJpY2VzKHBhc3RlMCh0aWNrZXJzLCAnLmNzdicpDQogICAgICAgICAgICAgICAgICAgICAgICAgICxzb3VyY2UgPSAnZGF0YXNldHMvJykNCg0KcmV0dXJucyA8LSBzYXBwbHkoc2VsZWN0KGNsb3NlX3ByaWNlcywgLURhdGUpDQogICAgICAgICAgICAgICAgICAsRlVOID0gZnVuY3Rpb24oeCkgZGlmZihsb2coeCksIGxhZyA9IDEpKQ0KYGBgDQoNCldlIHdpbGwgc2ltdWxhdGUgJDEwMDAwJCBwb3J0Zm9saW9zIHdpdGggcmFuZG9tIHdlaWdodHMgZnJvbSBVbmlmb3JtWzAsMV0gZGlzdHJpYnV0aW9uLiBDb25maWRlbmNlIGxldmVsIGZvciBWYWx1ZS1hdC1SaXNrIGlzICRcYWxwaGEgPSA5NVwlJCwgcmlzay1mcmVlIGludGVyZXN0IHJhdGUgKGZvciBnZW5lcmFsaXplZCBTaGFycGUgcmF0aW8pIGlzICRcbXVfRiA9IDJcJSQuIEZvciBldmVyeSByYW5kb20gcG9ydGZvbGlvIHdlIHRha2UgJDEwMDAwJCBzYW1wbGVzICQyMSQgZGF5cyBhaGVhZCAoYXBwcm94LiAxIG1vbnRoIGFoZWFkIGluIHRlcm1zIG9mIHRyYWRpbmcgZGF5cykuIFRvIHNwZWVkIHVwIG91ciBjYWxjdWxhdGlvbnMgd2UgdXNlICpkb1BhcmFsbGVsKiBwYWNrYWdlIGluIG9yZGVyIHRvIHV0aWxpemUgbXVsdGlwbGUgQ1BVIGNvcmVzLg0KYGBge3IgcGFyYW1ldGVyc30NCm51bV9vZl9wb3J0Zm9saW9zIDwtIDFlNA0KYm9vdHN0cmFwX3NpemUgPC0gMWU0DQpkYXlzX2FoZWFkIDwtIDIxDQphbHBoYSA8LSAwLjk1DQpyZiA8LSAwLjAyDQoNCm15LmNsdXN0ZXIgPC0gbWFrZUNsdXN0ZXIoZGV0ZWN0Q29yZXMoKSAtIDEsIHR5cGUgPSAnUFNPQ0snKQ0KcmVnaXN0ZXJEb1BhcmFsbGVsKGNsID0gbXkuY2x1c3RlcikNCg0KYGBgDQoNCmBgYHtyIGJvb3RzdHJhcH0NCnNldC5zZWVkKDIxMzcpDQphaGVhZF9tZXRyaWNzIDwtIGZvcmVhY2goDQogIGkgPSAxOm51bV9vZl9wb3J0Zm9saW9zDQogICwucGFja2FnZXMgPSAnZHBseXInDQogICkgJWRvcGFyJSB7DQoNCiAgcG9ydGZvbGlvX21ldHJpY3MgPC0gZGF0YS5mcmFtZSgNCiAgICBkYXkgPSAxOmRheXNfYWhlYWQNCiAgICAsUmV0dXJuID0gcmVwKE5hTiwgZGF5c19haGVhZCkNCiAgICAsVmFSID0gcmVwKE5hTiwgZGF5c19haGVhZCkNCiAgICAsVmFSX1NoYXJwZSA9IHJlcChOYU4sIGRheXNfYWhlYWQpDQogICAgLGNWYVIgPSByZXAoTmFOLCBkYXlzX2FoZWFkKQ0KICAgICxjVmFSX1NoYXJwZSA9IHJlcChOYU4sIGRheXNfYWhlYWQpDQogICkNCiAgDQogIHdlaWdodHMgPC0gYygpDQogIHJhbmRvbV93ZWlnaHRzIDwtIHJ1bmlmKDQpDQogIHJhbmRvbV93ZWlnaHRzIDwtIHJhbmRvbV93ZWlnaHRzIC8gc3VtKHJhbmRvbV93ZWlnaHRzKQ0KICB3ZWlnaHRzIDwtIHJhbmRvbV93ZWlnaHRzDQogIG5hbWVzKHdlaWdodHMpIDwtIGNvbG5hbWVzKHJldHVybnMpDQogIA0KICBwb3J0Zm9saW9fcmV0dXJucyA8LSByZXR1cm5zICUqJSByYW5kb21fd2VpZ2h0cw0KICANCiAgYm9vdHN0cmFwX3JldHVybnMgPC0gc2FtcGxlKHBvcnRmb2xpb19yZXR1cm5zDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAsc2l6ZSA9IGRheXNfYWhlYWQgKiBib290c3RyYXBfc2l6ZQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLHJlcGxhY2UgPSBUUlVFKSAlPiUNCiAgbWF0cml4KG5yb3cgPSBkYXlzX2FoZWFkLCBuY29sID0gYm9vdHN0cmFwX3NpemUpICU+JQ0KICBhcHBseSgyLCBjdW1zdW0pDQogIA0KICBmb3IgKGRheSBpbiAxOmRheXNfYWhlYWQpIHsNCiAgICBwb3J0Zm9saW9fbWV0cmljcyRSZXR1cm5bZGF5XSA8LSAobWVhbihib290c3RyYXBfcmV0dXJuc1tkYXksXSkgKyAxKV4oMjUyL2RheSkgLSAxDQogICAgcG9ydGZvbGlvX21ldHJpY3MkVmFSW2RheV0gPC0gLXF1YW50aWxlKGJvb3RzdHJhcF9yZXR1cm5zW2RheSxdLCAxIC0gYWxwaGEpDQogICAgcG9ydGZvbGlvX21ldHJpY3MkVmFSX1NoYXJwZVtkYXldIDwtIChwb3J0Zm9saW9fbWV0cmljcyRSZXR1cm5bZGF5XSAtIHJmKSAvIHBvcnRmb2xpb19tZXRyaWNzJFZhUltkYXldDQogICAgY1ZhUl9pbmRpY2F0b3IgPC0gKHBvcnRmb2xpb19yZXR1cm5zIDwgLXBvcnRmb2xpb19tZXRyaWNzJFZhUltkYXldKQ0KICAgIHBvcnRmb2xpb19tZXRyaWNzJGNWYVJbZGF5XSA8LSAtc3VtKHBvcnRmb2xpb19yZXR1cm5zICogY1ZhUl9pbmRpY2F0b3IpIC8gc3VtKGNWYVJfaW5kaWNhdG9yKQ0KICAgIHBvcnRmb2xpb19tZXRyaWNzJGNWYVJfU2hhcnBlW2RheV0gPC0gKHBvcnRmb2xpb19tZXRyaWNzJFJldHVybltkYXldIC0gcmYpIC8gcG9ydGZvbGlvX21ldHJpY3MkY1ZhUltkYXldDQogIH0NCiAgDQogIGxpc3Qod2VpZ2h0cywgcG9ydGZvbGlvX21ldHJpY3MpDQogIA0KfQ0KDQpzdG9wQ2x1c3RlcihjbCA9IG15LmNsdXN0ZXIpDQpgYGANCg0KVW5saXN0aW5nIHRoZSByZXN1bHRzIGZvciBkYXRhIHZpenVhbGl6YXRpb24gcHVycG9zZXMuDQpgYGB7ciB1bmxpc3R9DQpkIDwtIGRheXNfYWhlYWQNCmRfbWV0cmljcyA8LSBkYXRhLmZyYW1lKCkNCmZvciAoaSBpbiAxOm51bV9vZl9wb3J0Zm9saW9zKSB7DQogIGRfbWV0cmljcyA8LSBiaW5kX3Jvd3MoDQogICAgZF9tZXRyaWNzLCBjKGFoZWFkX21ldHJpY3NbW2ldXVtbMV1dLCBhaGVhZF9tZXRyaWNzW1tpXV1bWzJdXVtkLCAtMV0pICU+JQ0KICAgICAgdW5saXN0KCkNCiAgKQ0KfQ0KDQpxdWFudGlsZV9tZXRyaWNzIDwtIGRhdGEuZnJhbWUoDQogIGRheSA9IDE6ZGF5c19haGVhZA0KICAsUmV0dXJuX3FfMjUgPSByZXAoTkEsIGRheXNfYWhlYWQpDQogICxSZXR1cm5fcV81MCA9IHJlcChOQSwgZGF5c19haGVhZCkNCiAgLFJldHVybl9xXzc1ID0gcmVwKE5BLCBkYXlzX2FoZWFkKQ0KICAsVmFSX3FfMjUgPSByZXAoTkEsIGRheXNfYWhlYWQpDQogICxWYVJfcV81MCA9IHJlcChOQSwgZGF5c19haGVhZCkNCiAgLFZhUl9xXzc1ID0gcmVwKE5BLCBkYXlzX2FoZWFkKQ0KICAsY1ZhUl9xXzI1ID0gcmVwKE5BLCBkYXlzX2FoZWFkKQ0KICAsY1ZhUl9xXzUwID0gcmVwKE5BLCBkYXlzX2FoZWFkKQ0KICAsY1ZhUl9xXzc1ID0gcmVwKE5BLCBkYXlzX2FoZWFkKQ0KKQ0KDQpmb3IgKGQgaW4gMTpkYXlzX2FoZWFkKSB7DQogIHRlbXAgPC0gZGF0YS5mcmFtZSgpDQogIGZvciAoaSBpbiAxOm51bV9vZl9wb3J0Zm9saW9zKSB7DQogICAgdGVtcCA8LSBiaW5kX3Jvd3ModGVtcCwgYWhlYWRfbWV0cmljc1tbaV1dW1syXV1bZCwgYygyLCAzLCA1KV0pDQogIH0NCiAgcXVhbnRpbGVfbWV0cmljc1tkLCAyOjRdIDwtIHF1YW50aWxlKHRlbXAkUmV0dXJuLCBwcm9icyA9IGMoMC4yNSwgMC41MCwgMC43NSksIG5hLnJtID0gVFJVRSkNCiAgcXVhbnRpbGVfbWV0cmljc1tkLCA1OjddIDwtIHF1YW50aWxlKHRlbXAkVmFSLCBwcm9icyA9IGMoMC4yNSwgMC41MCwgMC43NSksIG5hLnJtID0gVFJVRSkNCiAgcXVhbnRpbGVfbWV0cmljc1tkLCA4OjEwXSA8LSBxdWFudGlsZSh0ZW1wJGNWYVIsIHByb2JzID0gYygwLjI1LCAwLjUwLCAwLjc1KSwgbmEucm0gPSBUUlVFKQ0KfQ0KYGBgDQoNCkluIHRoZSBiZWxvdyBjaGFydCB3ZSBzZWUgaG93IEV4cGVjdGVkIFJldHVybiAoZ3JlZW4pIFZhUiAob3JhbmdlKSBhbmQgY1ZhUiAocmVkKSBjaGFuZ2VzIHdpdGggdGltZSBob3Jpem9uLiBSaWJib25zIGFyZSBpbnRlcnF1YW50aWxlIHJhbmdlcyBvZiAkMTAwMDAkIHJhbmRvbSBwb3J0Zm9saW9zIGFmdGVyIGJvb3RzdHJhcCwgd2hpbGUgdGhlIGNlbnRlcnMgb2YgdGhlIHJpYmJvbnMgYXJlIG1lZGlhbnMgb2YgdGhlIHJlc3BlY3RpdmUgbWVhc3VyZXMuIEl0J3Mgb2J2aW91cyB0aGFuIHJpc2sgaW5jcmVhc2VzIHdpdGggdGltZSBob3Jpem9uLiBNb3JldmVyLCB1bmNlcnRhaW50eSBjb25uZWN0ZWQgd2l0aCByaXNrIChyaWJib24gd2lkdGgpIGFsc28gaW5jcmVhc2VzIHdpdGggdGltZS4NCmBgYHtyIHJpYmJvbnN9DQpnZ3Bsb3QocXVhbnRpbGVfbWV0cmljcykgKw0KICBnZW9tX2xpbmUoYWVzKHggPSBkYXksIHkgPSBSZXR1cm5fcV81MCksIGNvbG9yID0gJ2dyZWVuJywgc2l6ZSA9IDEpICsNCiAgZ2VvbV9yaWJib24oYWVzKHggPSBkYXksIHltaW4gPSBSZXR1cm5fcV8yNSwgeW1heCA9IFJldHVybl9xXzc1KQ0KICAgICAgICAgICAgICAsZmlsbCA9ICdncmVlbicgLGFscGhhPSAwLjEwKSArDQogIGdlb21fbGluZShhZXMoeCA9IGRheSwgeSA9IFZhUl9xXzUwKSwgY29sb3IgPSAnb3JhbmdlJywgc2l6ZSA9IDEpICsNCiAgZ2VvbV9yaWJib24oYWVzKHggPSBkYXksIHltaW4gPSBWYVJfcV8yNSwgeW1heCA9IFZhUl9xXzc1KQ0KICAgICAgICAgICAgICAsZmlsbCA9ICdvcmFuZ2UnICxhbHBoYT0gMC4yNSkgKw0KICBnZW9tX2xpbmUoYWVzKHggPSBkYXksIHkgPSBjVmFSX3FfNTApLCBjb2xvciA9ICdyZWQnLCBzaXplID0gMSkgKw0KICBnZW9tX3JpYmJvbihhZXMoeCA9IGRheSwgeW1pbiA9IGNWYVJfcV8yNSwgeW1heCA9IGNWYVJfcV83NSkNCiAgICAgICAgICAgICAgLGZpbGwgPSAncmVkJyAsYWxwaGE9IDAuMjUpICsNCiAgdGhlbWVfYncoKSArDQogIGxhYnMoeSA9JycpDQpgYGANCkJlbG93IHdlIHNlZSBvcHRpbWFsIHBvcnRmb2xpb3M6XA0KLSBNaW5pbWFsIFZhUiBhbmQgY1ZhUiBwb3J0Zm9saW9zIGhhdmUgb25seSBhYm91dCAkMi43XCUkIC0gJDMuNVwlJCBleHBlY3RlZCBhbm51YWwgcmV0dXJuIGFuZCB3ZSBhcmUgOTUlIGNvbmZpZGVudCB0aGF0IG1vbnRobHkgKDIxLXNlc3Npb25zKSBsb3NzIHdvbuKAmXQgZXhjZWVkICQyLjJcJSQuIEluIHdvcnN0IGNhc2Ugc2NlbmFyaW8gZXhwZWN0ZWQgbW9udGhseSBkcm9wZG93biBpcyBhYm91dCAkMi4zM1wlJCAtICQyLjczXCUkLiBUaGVzZSBwb3J0Zm9saW9zIG1haW5seSBjb25zaXN0IG9mIHRyZWFzdXJ5IGluZmxhdGlvbi1wcm90ZWN0ZWQgYm9uZHMuXA0KLSBNYXhpbWFsIHNoYXJwZSBwb3J0Zm9saW9zIGhhdmUgJDguNFwlJCBleHBlY3RlZCBhbm51YWwgcmV0dXJuIGFuZCBhbG1vc3QgdHdvIHRpbWVzIGhpZ2hlciByaXNrIHRoYW4gbWluaW1hbCByaXNrIHBvcnRmb2xpb3MuIFRoZXNlIHBvcnRmb2xpb3MgY29uc2lzdCBvZiBkZXZlbG9wZWQgbWFya2V0cyBzdG9ja3MgKCQ1NFwlJCksIHRyZWFzdXJ5IGluZmxhdGlvbi1wcm90ZWN0ZWQgYm9uZHMgKCQzMFwlJCkgYW5kIGdvbGQgKCQxNlwlJCkuDQpgYGB7ciBvcHRpbWFsfQ0Kb3B0aW1hbF9wb3J0Zm9saW9zIDwtIGRhdGEuZnJhbWUoDQogIE1pbl9WYVIgPSByb3VuZCgxMDAqZF9tZXRyaWNzW3doaWNoLm1pbihkX21ldHJpY3MkVmFSKSwgXSwgMikgJT4lIGFzLm51bWVyaWMoKQ0KICAsTWF4X1ZhUl9TaGFycGUgPSByb3VuZCgxMDAqZF9tZXRyaWNzW3doaWNoLm1heChkX21ldHJpY3MkVmFSX1NoYXJwZSksIF0sIDIpICU+JSBhcy5udW1lcmljKCkNCiAgLE1pbl9jVmFSID0gcm91bmQoMTAwKmRfbWV0cmljc1t3aGljaC5taW4oZF9tZXRyaWNzJGNWYVIpLCBdLCAyKSAlPiUgYXMubnVtZXJpYygpDQogICxNYXhfY1ZhUl9TaGFycGUgPSByb3VuZCgxMDAqZF9tZXRyaWNzW3doaWNoLm1heChkX21ldHJpY3MkY1ZhUl9TaGFycGUpLCBdLCAyKSAlPiUgYXMubnVtZXJpYygpDQogICkgJT4lIHQoKSAlPiUgYXMuZGF0YS5mcmFtZSgpDQoNCmNvbG5hbWVzKG9wdGltYWxfcG9ydGZvbGlvcykgPC0gY29sbmFtZXMoZF9tZXRyaWNzKQ0Kb3B0aW1hbF9wb3J0Zm9saW9zDQpgYGANCg0KQmVsb3cgd2Ugc2VlIHNpbXVsYXRlZCBwb3J0Zm9saW9zIG9uIDItZGltZW5zaW9uYWwgcmlzay1yZXR1cm4gcGxhbmVzLiBNaW5pbWFsIHJpc2sgcG9ydGZvbGlvcyBhcmUgZ3JlZW4gdHJpYW5nbGVzIGFuZCBtYXhpbWFsIHNoYXJwZSBwb3J0Zm9saW9zIGFyZSB0aGUgb3JhbmdlIG9uZXMuIA0KYGBge3Igcmlza19yZXR1cm59DQpwMSA8LSBnZ3Bsb3QoZF9tZXRyaWNzLCBhZXMoeCA9IFZhUiwgeSA9IFJldHVybiwgY29sb3IgPSBWYVJfU2hhcnBlKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX3BvaW50KGFlcyh4ID0gKG9wdGltYWxfcG9ydGZvbGlvcyRWYVJbMV0gLyAxMDApDQogICAgICAgICAgICAgICAgICx5ID0gKG9wdGltYWxfcG9ydGZvbGlvcyRSZXR1cm4pWzFdIC8gMTAwKQ0KICAgICAgICAgICAgICxjb2xvciA9ICcjMmNhMDJjJywgc2l6ZSA9IDUsIHNoYXBlID0gMTcpICsNCiAgZ2VvbV9wb2ludChhZXMoeCA9IChvcHRpbWFsX3BvcnRmb2xpb3MkVmFSWzJdIC8gMTAwKQ0KICAgICAgICAgICAgICAgICAseSA9IChvcHRpbWFsX3BvcnRmb2xpb3MkUmV0dXJuKVsyXSAvIDEwMCkNCiAgICAgICAgICAgICAsY29sb3IgPSAnI2ZmN2YwZScsIHNpemUgPSA1LCBzaGFwZSA9IDE3KSArIA0KICB0aGVtZV9idygpDQoNCnAyIDwtIGdncGxvdChkX21ldHJpY3MsIGFlcyh4ID0gY1ZhUiwgeSA9IFJldHVybiwgY29sb3IgPSBjVmFSX1NoYXJwZSkpICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgZ2VvbV9wb2ludChhZXMoeCA9IChvcHRpbWFsX3BvcnRmb2xpb3MkY1ZhUlszXSAvIDEwMCkNCiAgICAgICAgICAgICAgICAgLHkgPSAob3B0aW1hbF9wb3J0Zm9saW9zJFJldHVybilbM10gLyAxMDApDQogICAgICAgICAgICAgLGNvbG9yID0gJyMyY2EwMmMnLCBzaXplID0gNSwgc2hhcGUgPSAxNykgKw0KICBnZW9tX3BvaW50KGFlcyh4ID0gKG9wdGltYWxfcG9ydGZvbGlvcyRjVmFSWzRdIC8gMTAwKQ0KICAgICAgICAgICAgICAgICAseSA9IChvcHRpbWFsX3BvcnRmb2xpb3MkUmV0dXJuKVs0XSAvIDEwMCkNCiAgICAgICAgICAgICAsY29sb3IgPSAnI2ZmN2YwZScsIHNpemUgPSA1LCBzaGFwZSA9IDE3KSArIA0KICB0aGVtZV9idygpDQoNCmdyaWRFeHRyYTo6Z3JpZC5hcnJhbmdlKHAxLCBwMikNCmBgYA==