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, ggplot2)
In that blog post we simulated 10000 portfolios of 4 iShares ETF’s (EEM, IAU, URTH, TIPS) and then selected portfolio with minamal variance and another portfolio with maximal Sharpe ratio. However, variance (or standard deviation) ain’t the only risk measures we use. Thus portfolio optimization problem can be generalized to any other risk measure \(\mathcal{R}\): \[\min_{\omega} \mathcal{R} \left( \omega^T \mu \right)\] with constraints that weights are non-negative (if short sale is forbidden) and sum of weights equals one.
In this post as risk measure we will use Value-at-Risk and Conditional Value-at-Risk (also known as Expected Shortfall). While standard approach with variance minimizes fluctuations of portfolio returns, approach with VaR and cVaR minimizes risk of huge dropdowns of the portfolio value.
Let’s denote portfolio losses \(L = -\omega^T \mu\). Distribution of random variable \(L\) is induced by distribution of asset returns \(\mu\) with density \(p_{\mu}\). Probability that loss \(L\) doesn’t exceed certain threshold \(z\) is \[\mathbb{P} ( L \leq z ) = F_L (z) \text{,}\] where \(F_L\) is cdf of \(L\). Value-at-Risk at confidence level \(\alpha \in (0,1)\) is defined as \[\text{VaR}\ (\alpha;\ \omega) := \inf \lbrace z \in \mathbb{R} \ | \ \mathbb{P} (L > z) \leq 1 - \alpha \rbrace = \inf \lbrace z \in \mathbb{R} \ | \ F_L(z) \geq \alpha \rbrace\] which is just \(1-\alpha\)-quantile of cdf \(F_L\). For instance, once one calculates VaR at \(\alpha = 95\%\), he can say “with 95% probability maximal loss won’t exceed 1000 EUR”. But remember, usually distribution of random variable \(L\) ain’t known, so estimation of this parameter is also subject to a certain margin of error.
But what happens if loss exceeds VaR? What we can say about remaining \(5\%\) of uncertainty? It’s well known fact, that time series of financial asset returns have heavy-tailed distributions, and especially events in the left tail are more frequent than events in the right tail. Conditional value-at-risk (or expected shortfall) is defined as average loss in this \(5\%\) tail, i.e. \[\text{cVaR}\ (\alpha;\ \omega) := \frac{1}{1-\alpha} \int_{\alpha}^1 q_u(F_L) \ du = \frac{1}{1-\alpha} \int_{\alpha}^1 \text{VaR}\ (u;\ \omega) \ du\] assuming random variable \(L\) is integrable, i.e. \(\mathbb{E} \ |L| < \infty\). Moreover, if cdf \(F_L\) is continuous we get more clear and intuitive definition of cVaR: \[\text{cVaR}\ (\alpha;\ \omega) = \mathbb{E} [\ L \ | \ L \geq \text{VaR}\ (\alpha;\ \omega)\ ] = \mathbb{E} [ \ L \ \pmb{1}_{ \lbrace L \geq \text{VaR}_{\alpha} \rbrace } \ ] \text{.}\]
Calculation of VaR boils down to determining the quantile of a generally unknown distribution, therefore there are different, more or less precise methods of calculating this measure. In our simulations we will use historical method which is just: \[ \widehat{\text{VaR}_{\alpha}} = \widehat{q_{\alpha}} \text{,} \] \[ \widehat{\text{ES}_{\alpha}} =
\frac{\sum_{i=1}^n L_i \pmb{1}_{ \lbrace L_i \geq \widehat{\text{VaR}_{\alpha}} \rbrace } }
{\sum_{i=1}^n \pmb{1}_{ \lbrace L_i \geq \widehat{\text{VaR}_{\alpha}} \rbrace } } \text{.} \]
Variance optimization problem is either quatratic programming problem or just system of linear equations, depending if short sale is allowed or not. I have good and bad news for the reader. Good news is that cVaR optimization can be reduced to linear programming problem, regardless of the distribution of asset return \(\mu\). Bad news is that VaR attainable portfolios don’t form convex set, what can be easily verified by vizualizing all the portfolios consisted of 2 assets.
In this post we will simulate \(10000\) portfolios and select portfolios with minimal VaR and cVaR. We also generalize Sharpe ratio to any risk measure, e.g. \[\text{Sharpe} = \frac{\mu_R (\omega) - \mu_F}{\text{VaR} \ (\alpha;\ \omega)} \text{,}\] so we will be able to select risk-reward optimal portfolios.
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))
Although in the PolishStock package there exists a function which does the steps below, for educational purposes we write it explicitly here. First we calculate ETF’s annualized average returns. Assume \(2\%\) risk-free interest rate and significance level \(\alpha = 95\%\).
mean_returns <- (colMeans(returns) + 1)^252 - 1
rf <- 0.02
alpha = 0.95
We initialize matrix where portoflios weights will be stored and data frame where portfolios return, VaR, cVaR and sharpe ratios will be stored.
num_of_portfolios <- 1e4
weights <- matrix(nrow = num_of_portfolios, ncol = ncol(returns))
portfolio_metrics <- data.frame(
Returns = rep(0, num_of_portfolios)
,VaR = rep(0, num_of_portfolios)
,VaR_Sharpe = rep(0, num_of_portfolios)
,cVaR = rep(0, num_of_portfolios)
,cVaR_Sharpe = rep(0, num_of_portfolios)
)
We simulate \(10000\) random portfolios with random weights from Uniform[0,1] distribution (which means that short sale is not allowed in our model). We calculate portfolio return, VaR, cVaR and sharpe ratios as explained above.
set.seed(2137)
for (i in 1:num_of_portfolios) {
random_weights <- runif(ncol(weights))
random_weights <- random_weights / sum(random_weights)
weights[i, ] <- random_weights
portfolio_returns <- returns %*% random_weights
portfolio_metrics$Returns[i] <- random_weights %*% mean_returns
portfolio_metrics$VaR[i] <- -quantile(portfolio_returns, 1 - alpha)
portfolio_metrics$VaR_Sharpe[i] <- (portfolio_metrics$Returns[i] - rf) / portfolio_metrics$VaR[i]
cVaR_indicator <- (portfolio_returns < -portfolio_metrics$VaR[i])
portfolio_metrics$cVaR[i] <- -sum(portfolio_returns * cVaR_indicator) / sum(cVaR_indicator)
portfolio_metrics$cVaR_Sharpe[i] <- (portfolio_metrics$Returns[i] - rf) / portfolio_metrics$cVaR[i]
}
Below we see optimal portfolios:
- Minimal VaR and cVaR portfolios have only about \(3.3\%\) expected annual return and we are \(95\%\) condident that daily loss won’t exceed \(0.46\%\). In worst case scenario expected daily dropdown is about \(0.77\%\). These portfolios mainly consist of treasury inflation-protected bonds.
- Maximal sharpe portfolios have about \(7.3\%\) expected annual return and almost two times higher risk than minimal risk portfolios. These portfolios consist of developed markets stocks and treasury inflation-protected bonds in proportion about 50:50.
In fact, pretty similar results were obtained with variance optimization).
optimal_portfolios <- data.frame(
Min_VaR = c(
round(100*weights[which.min(portfolio_metrics$VaR),], 2)
,round(100*portfolio_metrics[which.min(portfolio_metrics$VaR), ], 2) %>% as.numeric()
)
,Max_VaR_Sharpe = c(
round(100*weights[which.max(portfolio_metrics$VaR_Sharpe),], 2)
,round(100*portfolio_metrics[which.max(portfolio_metrics$VaR_Sharpe), ], 2) %>% as.numeric()
)
,Min_cVaR = c(
round(100*weights[which.min(portfolio_metrics$cVaR),], 2)
,round(100*portfolio_metrics[which.min(portfolio_metrics$cVaR), ], 2) %>% as.numeric()
)
,Max_cVaR_Sharpe = c(
round(100*weights[which.max(portfolio_metrics$cVaR_Sharpe),], 2)
,round(100*portfolio_metrics[which.max(portfolio_metrics$cVaR_Sharpe), ], 2) %>% as.numeric()
)
) %>% t() %>% as.data.frame()
colnames(optimal_portfolios) <- c(colnames(returns), 'Expected Return', 'VaR', 'VaR Sharpe', 'cVaR', 'cVaR Sharpe')
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. It’s not obvious from the vizualization, but VaR attainable portfolios ain’t convex set.
p1 <- ggplot(portfolio_metrics, aes(x = VaR, y = Returns, color = VaR_Sharpe)) +
geom_point() +
geom_point(aes(x = (optimal_portfolios$VaR[1] / 100)
,y = (optimal_portfolios$`Expected Return`)[1] / 100)
,color = '#2ca02c', size = 5, shape = 17) +
geom_point(aes(x = (optimal_portfolios$VaR[2] / 100)
,y = (optimal_portfolios$`Expected Return`)[2] / 100)
,color = '#ff7f0e', size = 5, shape = 17) +
theme_bw()
p2 <- ggplot(portfolio_metrics, aes(x = cVaR, y = Returns, color = cVaR_Sharpe)) +
geom_point() +
geom_point(aes(x = (optimal_portfolios$cVaR[3] / 100)
,y = (optimal_portfolios$`Expected Return`)[3] / 100)
,color = '#2ca02c', size = 5, shape = 17) +
geom_point(aes(x = (optimal_portfolios$cVaR[4] / 100)
,y = (optimal_portfolios$`Expected Return`)[4] / 100)
,color = '#ff7f0e', size = 5, shape = 17) +
theme_bw()
gridExtra::grid.arrange(p1, p2)

LS0tDQp0aXRsZTogIlZhbHVlLWF0LVJpc2sgUG9ydGZvbGlvIE9wdGltaXphdGlvbiINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCklmIGFueSBpc3N1ZXMsIHF1ZXN0aW9ucyBvciBzdWdnZXN0aW9ucyBmZWVsIGZyZWUgdG8gcmVhY2ggbWUgb3V0IHZpYSBlLW1haWwgPHdpZWN6eW5za2lwYXdlbEBnbWFpbC5jb20+IG9yIFtMaW5rZWRpbl0oaHR0cHM6Ly93d3cubGlua2VkaW4uY29tL2luL3Bhd2VsLXdpZWN6eW5za2kvKS4gWW91IGNhbiBhbHNvIHZpc2l0IG15IFtHaXRodWJdKGh0dHBzOi8vZ2l0aHViLmNvbS9wYXdlbC13aWVjenluc2tpKS4NCg0KYGBge3IgbGlicmFyaWVzLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQ0KaWYoIXJlcXVpcmUocGFjbWFuKSkgaW5zdGFsbC5wYWNrYWdlcygncGFjbWFuJykNCnBhY21hbjo6cF9sb2FkKFBvbGlzaFN0b2NrLCBkcGx5ciwgZ2dwbG90MikNCmBgYA0KDQpJbiBbdGhhdCBibG9nIHBvc3RdKGh0dHBzOi8vcnB1YnMuY29tL3Bhd2VsLXdpZWN6eW5za2kvODc0NDU0KSB3ZSBzaW11bGF0ZWQgMTAwMDAgcG9ydGZvbGlvcyBvZiA0IGlTaGFyZXMgRVRGJ3MgKEVFTSwgSUFVLCBVUlRILCBUSVBTKSBhbmQgdGhlbiBzZWxlY3RlZCBwb3J0Zm9saW8gd2l0aCBtaW5hbWFsIHZhcmlhbmNlIGFuZCBhbm90aGVyIHBvcnRmb2xpbyB3aXRoIG1heGltYWwgU2hhcnBlIHJhdGlvLiBIb3dldmVyLCB2YXJpYW5jZSAob3Igc3RhbmRhcmQgZGV2aWF0aW9uKSBhaW4ndCB0aGUgb25seSByaXNrIG1lYXN1cmVzIHdlIHVzZS4gVGh1cyBwb3J0Zm9saW8gb3B0aW1pemF0aW9uIHByb2JsZW0gY2FuIGJlIGdlbmVyYWxpemVkIHRvIGFueSBvdGhlciByaXNrIG1lYXN1cmUgJFxtYXRoY2Fse1J9JDogJCRcbWluX3tcb21lZ2F9IFxtYXRoY2Fse1J9IFxsZWZ0KCBcb21lZ2FeVCBcbXUgXHJpZ2h0KSQkIHdpdGggY29uc3RyYWludHMgdGhhdCB3ZWlnaHRzIGFyZSBub24tbmVnYXRpdmUgKGlmIHNob3J0IHNhbGUgaXMgZm9yYmlkZGVuKSBhbmQgc3VtIG9mIHdlaWdodHMgZXF1YWxzIG9uZS4NCg0KSW4gdGhpcyBwb3N0IGFzIHJpc2sgbWVhc3VyZSB3ZSB3aWxsIHVzZSBWYWx1ZS1hdC1SaXNrIGFuZCBDb25kaXRpb25hbCBWYWx1ZS1hdC1SaXNrIChhbHNvIGtub3duIGFzIEV4cGVjdGVkIFNob3J0ZmFsbCkuIFdoaWxlIHN0YW5kYXJkIGFwcHJvYWNoIHdpdGggdmFyaWFuY2UgbWluaW1pemVzIGZsdWN0dWF0aW9ucyBvZiBwb3J0Zm9saW8gcmV0dXJucywgYXBwcm9hY2ggd2l0aCBWYVIgYW5kIGNWYVIgbWluaW1pemVzIHJpc2sgb2YgaHVnZSBkcm9wZG93bnMgb2YgdGhlIHBvcnRmb2xpbyB2YWx1ZS4NCg0KTGV0J3MgZGVub3RlIHBvcnRmb2xpbyBsb3NzZXMgJEwgPSAtXG9tZWdhXlQgXG11JC4gRGlzdHJpYnV0aW9uIG9mIHJhbmRvbSB2YXJpYWJsZSAkTCQgaXMgaW5kdWNlZCBieSBkaXN0cmlidXRpb24gb2YgYXNzZXQgcmV0dXJucyAkXG11JCB3aXRoIGRlbnNpdHkgJHBfe1xtdX0kLiBQcm9iYWJpbGl0eSB0aGF0IGxvc3MgJEwkIGRvZXNuJ3QgZXhjZWVkIGNlcnRhaW4gdGhyZXNob2xkICR6JCBpcyAkJFxtYXRoYmJ7UH0gKCBMIFxsZXEgeiApID0gRl9MICh6KSBcdGV4dHssfSQkIHdoZXJlICRGX0wkIGlzIGNkZiBvZiAkTCQuICoqVmFsdWUtYXQtUmlzayoqIGF0IGNvbmZpZGVuY2UgbGV2ZWwgJFxhbHBoYSBcaW4gKDAsMSkkIGlzIGRlZmluZWQgYXMgJCRcdGV4dHtWYVJ9XCAoXGFscGhhO1wgXG9tZWdhKSA6PSBcaW5mIFxsYnJhY2UgeiBcaW4gXG1hdGhiYntSfSBcIHwgXCBcbWF0aGJie1B9IChMID4geikgXGxlcSAxIC0gXGFscGhhIFxyYnJhY2UgPSBcaW5mIFxsYnJhY2UgeiBcaW4gXG1hdGhiYntSfSBcIHwgXCBGX0woeikgXGdlcSBcYWxwaGEgXHJicmFjZSQkIHdoaWNoIGlzIGp1c3QgJDEtXGFscGhhJC1xdWFudGlsZSBvZiBjZGYgJEZfTCQuIEZvciBpbnN0YW5jZSwgb25jZSBvbmUgY2FsY3VsYXRlcyBWYVIgYXQgJFxhbHBoYSA9IDk1XCUkLCBoZSBjYW4gc2F5ICoid2l0aCA5NSUgcHJvYmFiaWxpdHkgbWF4aW1hbCBsb3NzIHdvbid0IGV4Y2VlZCAxMDAwIEVVUiIqLiBCdXQgcmVtZW1iZXIsIHVzdWFsbHkgZGlzdHJpYnV0aW9uIG9mIHJhbmRvbSB2YXJpYWJsZSAkTCQgYWluJ3Qga25vd24sIHNvIGVzdGltYXRpb24gb2YgdGhpcyBwYXJhbWV0ZXIgaXMgYWxzbyBzdWJqZWN0IHRvIGEgY2VydGFpbiBtYXJnaW4gb2YgZXJyb3IuDQoNCkJ1dCB3aGF0IGhhcHBlbnMgaWYgbG9zcyBleGNlZWRzIFZhUj8gV2hhdCB3ZSBjYW4gc2F5IGFib3V0IHJlbWFpbmluZyAkNVwlJCBvZiB1bmNlcnRhaW50eT8gSXQncyB3ZWxsIGtub3duIGZhY3QsIHRoYXQgdGltZSBzZXJpZXMgb2YgZmluYW5jaWFsIGFzc2V0IHJldHVybnMgaGF2ZSBoZWF2eS10YWlsZWQgZGlzdHJpYnV0aW9ucywgYW5kIGVzcGVjaWFsbHkgZXZlbnRzIGluIHRoZSBsZWZ0IHRhaWwgYXJlIG1vcmUgZnJlcXVlbnQgdGhhbiBldmVudHMgaW4gdGhlIHJpZ2h0IHRhaWwuICoqQ29uZGl0aW9uYWwgdmFsdWUtYXQtcmlzayoqIChvciBleHBlY3RlZCBzaG9ydGZhbGwpIGlzIGRlZmluZWQgYXMgYXZlcmFnZSBsb3NzIGluIHRoaXMgJDVcJSQgdGFpbCwgaS5lLiAkJFx0ZXh0e2NWYVJ9XCAoXGFscGhhO1wgXG9tZWdhKSA6PSAgXGZyYWN7MX17MS1cYWxwaGF9IFxpbnRfe1xhbHBoYX1eMSBxX3UoRl9MKSBcIGR1ID0gXGZyYWN7MX17MS1cYWxwaGF9IFxpbnRfe1xhbHBoYX1eMSBcdGV4dHtWYVJ9XCAodTtcIFxvbWVnYSkgXCBkdSQkIGFzc3VtaW5nIHJhbmRvbSB2YXJpYWJsZSAkTCQgaXMgaW50ZWdyYWJsZSwgaS5lLiAkXG1hdGhiYntFfSBcIHxMfCA8IFxpbmZ0eSQuIE1vcmVvdmVyLCBpZiBjZGYgJEZfTCQgaXMgY29udGludW91cyB3ZSBnZXQgbW9yZSBjbGVhciBhbmQgaW50dWl0aXZlIGRlZmluaXRpb24gb2YgY1ZhUjogJCRcdGV4dHtjVmFSfVwgKFxhbHBoYTtcIFxvbWVnYSkgPSBcbWF0aGJie0V9IFtcIEwgXCB8IFwgTCBcZ2VxIFx0ZXh0e1ZhUn1cIChcYWxwaGE7XCBcb21lZ2EpXCBdID0gXG1hdGhiYntFfSBbIFwgTCBcIFxwbWJ7MX1feyBcbGJyYWNlIEwgXGdlcSBcdGV4dHtWYVJ9X3tcYWxwaGF9IFxyYnJhY2UgfSBcIF0gXHRleHR7Ln0kJA0KDQpDYWxjdWxhdGlvbiBvZiBWYVIgYm9pbHMgZG93biB0byBkZXRlcm1pbmluZyB0aGUgcXVhbnRpbGUgb2YgYSBnZW5lcmFsbHkgdW5rbm93biBkaXN0cmlidXRpb24sIHRoZXJlZm9yZSB0aGVyZSBhcmUgZGlmZmVyZW50LCBtb3JlIG9yIGxlc3MgcHJlY2lzZSBtZXRob2RzIG9mIGNhbGN1bGF0aW5nIHRoaXMgbWVhc3VyZS4gSW4gb3VyIHNpbXVsYXRpb25zIHdlIHdpbGwgdXNlICoqaGlzdG9yaWNhbCBtZXRob2QqKiB3aGljaCBpcyBqdXN0OiAkJCBcd2lkZWhhdHtcdGV4dHtWYVJ9X3tcYWxwaGF9fSA9IFx3aWRlaGF0e3Ffe1xhbHBoYX19IFx0ZXh0eyx9ICQkDQokJCBcd2lkZWhhdHtcdGV4dHtFU31fe1xhbHBoYX19ID0gDQpcZnJhY3tcc3VtX3tpPTF9Xm4gTF9pIFxwbWJ7MX1feyBcbGJyYWNlIExfaSBcZ2VxIFx3aWRlaGF0e1x0ZXh0e1ZhUn1fe1xhbHBoYX19IFxyYnJhY2UgfSB9IA0Ke1xzdW1fe2k9MX1ebiBccG1iezF9X3sgXGxicmFjZSBMX2kgXGdlcSBcd2lkZWhhdHtcdGV4dHtWYVJ9X3tcYWxwaGF9fSBccmJyYWNlIH0gIH0gXHRleHR7Ln0gJCQNCg0KW1ZhcmlhbmNlIG9wdGltaXphdGlvbiBwcm9ibGVtXShodHRwczovL3JwdWJzLmNvbS9wYXdlbC13aWVjenluc2tpLzg3NDQ1NCkgaXMgZWl0aGVyIHF1YXRyYXRpYyBwcm9ncmFtbWluZyBwcm9ibGVtIG9yIGp1c3Qgc3lzdGVtIG9mIGxpbmVhciBlcXVhdGlvbnMsIGRlcGVuZGluZyBpZiBzaG9ydCBzYWxlIGlzIGFsbG93ZWQgb3Igbm90LiBJIGhhdmUgZ29vZCBhbmQgYmFkIG5ld3MgZm9yIHRoZSByZWFkZXIuIEdvb2QgbmV3cyBpcyB0aGF0IGNWYVIgb3B0aW1pemF0aW9uIGNhbiBiZSByZWR1Y2VkIHRvIGxpbmVhciBwcm9ncmFtbWluZyBwcm9ibGVtLCByZWdhcmRsZXNzIG9mIHRoZSBkaXN0cmlidXRpb24gb2YgYXNzZXQgcmV0dXJuICRcbXUkLiBCYWQgbmV3cyBpcyB0aGF0IFZhUiBhdHRhaW5hYmxlIHBvcnRmb2xpb3MgZG9uJ3QgZm9ybSBjb252ZXggc2V0LCB3aGF0IGNhbiBiZSBlYXNpbHkgdmVyaWZpZWQgYnkgdml6dWFsaXppbmcgYWxsIHRoZSBwb3J0Zm9saW9zIGNvbnNpc3RlZCBvZiAyIGFzc2V0cy4NCg0KSW4gdGhpcyBwb3N0IHdlIHdpbGwgc2ltdWxhdGUgJDEwMDAwJCBwb3J0Zm9saW9zIGFuZCBzZWxlY3QgcG9ydGZvbGlvcyB3aXRoIG1pbmltYWwgVmFSIGFuZCBjVmFSLiBXZSBhbHNvICoqZ2VuZXJhbGl6ZSBTaGFycGUgcmF0aW8qKiB0byBhbnkgcmlzayBtZWFzdXJlLCBlLmcuICQkXHRleHR7U2hhcnBlfSA9IFxmcmFje1xtdV9SIChcb21lZ2EpIC0gXG11X0Z9e1x0ZXh0e1ZhUn0gXCAoXGFscGhhO1wgXG9tZWdhKX0gXHRleHR7LH0kJCBzbyB3ZSB3aWxsIGJlIGFibGUgdG8gc2VsZWN0IHJpc2stcmV3YXJkIG9wdGltYWwgcG9ydGZvbGlvcy4NCg0KV2UgdXNlIFtQb2xpc2hTdG9jayBwYWNrYWdlXShodHRwczovL2dpdGh1Yi5jb20vcGF3ZWwtd2llY3p5bnNraS9Qb2xpc2hTdG9jaykgdG8gZG93bmxvYWQgZGF0YSBmcm9tIHRoZSB3ZWJzaXRlIFtzdG9vcS5wbF0oaHR0cHM6Ly9zdG9vcS5wbC8pLiBEYWlseSBkYXRhIGZvciBwZXJpb2QgSmFudWFyeSAyMDEyIC0gRmVicnVhcnkgMjAyMiBjb25zaXN0cyBvZiAyMzQ1IG9ic2VydmF0aW9ucy4NCmBgYHtyIGdldF9kYXRhLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KdGlja2VycyA8LSBjKCdFRU0uVVMnLCAnSUFVLlVTJywgJ1VSVEguVVMnLCAnVElQLlVTJykNCg0KZm9yICh0aWNrZXIgaW4gdGlja2Vycyl7DQogIHN0b29xX2Rvd25sb2FkKHRpY2tlciwgJzIwMTIwMTAxJywgJzIwMjIwMjI4Jw0KICAgICAgICAgICAgICAgICAsZGVzdGluYXRpb24gPSBwYXN0ZTAoZ2V0d2QoKSwgJy9kYXRhc2V0cy8nKSkNCn0NCg0KY2xvc2VfcHJpY2VzIDwtIGRmX3ByaWNlcyhwYXN0ZTAodGlja2VycywgJy5jc3YnKQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAsc291cmNlID0gJ2RhdGFzZXRzLycpDQoNCnJldHVybnMgPC0gc2FwcGx5KHNlbGVjdChjbG9zZV9wcmljZXMsIC1EYXRlKQ0KICAgICAgICAgICAgICAgICAgLEZVTiA9IGZ1bmN0aW9uKHgpIGRpZmYobG9nKHgpLCBsYWcgPSAxKSkNCg0KYGBgDQoNCkFsdGhvdWdoIGluIHRoZSBbUG9saXNoU3RvY2sgcGFja2FnZV0oaHR0cHM6Ly9naXRodWIuY29tL3Bhd2VsLXdpZWN6eW5za2kvUG9saXNoU3RvY2spIHRoZXJlIGV4aXN0cyBhIGZ1bmN0aW9uIHdoaWNoIGRvZXMgdGhlIHN0ZXBzIGJlbG93LCBmb3IgZWR1Y2F0aW9uYWwgcHVycG9zZXMgd2Ugd3JpdGUgaXQgZXhwbGljaXRseSBoZXJlLiBGaXJzdCB3ZSBjYWxjdWxhdGUgRVRGJ3MgYW5udWFsaXplZCBhdmVyYWdlIHJldHVybnMuIEFzc3VtZSAkMlwlJCByaXNrLWZyZWUgaW50ZXJlc3QgcmF0ZSBhbmQgc2lnbmlmaWNhbmNlIGxldmVsICRcYWxwaGEgPSA5NVwlJC4NCmBgYHtyIG1lYW5fYW5kX2NvdmFyaWFuY2V9DQptZWFuX3JldHVybnMgPC0gKGNvbE1lYW5zKHJldHVybnMpICsgMSleMjUyIC0gMQ0KcmYgPC0gMC4wMg0KYWxwaGEgPSAwLjk1DQpgYGANCg0KV2UgaW5pdGlhbGl6ZSBtYXRyaXggd2hlcmUgcG9ydG9mbGlvcyB3ZWlnaHRzIHdpbGwgYmUgc3RvcmVkIGFuZCBkYXRhIGZyYW1lIHdoZXJlIHBvcnRmb2xpb3MgcmV0dXJuLCBWYVIsIGNWYVIgYW5kIHNoYXJwZSByYXRpb3Mgd2lsbCBiZSBzdG9yZWQuDQpgYGB7ciBpbml0aWFsaXplX3RhYmxlc30NCm51bV9vZl9wb3J0Zm9saW9zIDwtIDFlNA0Kd2VpZ2h0cyA8LSBtYXRyaXgobnJvdyA9IG51bV9vZl9wb3J0Zm9saW9zLCBuY29sID0gbmNvbChyZXR1cm5zKSkNCg0KcG9ydGZvbGlvX21ldHJpY3MgPC0gZGF0YS5mcmFtZSgNCiAgUmV0dXJucyA9IHJlcCgwLCBudW1fb2ZfcG9ydGZvbGlvcykNCiAgLFZhUiA9IHJlcCgwLCBudW1fb2ZfcG9ydGZvbGlvcykNCiAgLFZhUl9TaGFycGUgPSByZXAoMCwgbnVtX29mX3BvcnRmb2xpb3MpDQogICxjVmFSID0gcmVwKDAsIG51bV9vZl9wb3J0Zm9saW9zKQ0KICAsY1ZhUl9TaGFycGUgPSByZXAoMCwgbnVtX29mX3BvcnRmb2xpb3MpDQopDQpgYGANCg0KV2Ugc2ltdWxhdGUgJDEwMDAwJCByYW5kb20gcG9ydGZvbGlvcyB3aXRoIHJhbmRvbSB3ZWlnaHRzIGZyb20gVW5pZm9ybVswLDFdIGRpc3RyaWJ1dGlvbiAod2hpY2ggbWVhbnMgdGhhdCBzaG9ydCBzYWxlIGlzIG5vdCBhbGxvd2VkIGluIG91ciBtb2RlbCkuIFdlIGNhbGN1bGF0ZSBwb3J0Zm9saW8gcmV0dXJuLCBWYVIsIGNWYVIgYW5kIHNoYXJwZSByYXRpb3MgYXMgZXhwbGFpbmVkIGFib3ZlLg0KYGBge3Igc2ltdWxhdGlvbn0NCnNldC5zZWVkKDIxMzcpDQpmb3IgKGkgaW4gMTpudW1fb2ZfcG9ydGZvbGlvcykgew0KICANCiAgcmFuZG9tX3dlaWdodHMgPC0gcnVuaWYobmNvbCh3ZWlnaHRzKSkNCiAgcmFuZG9tX3dlaWdodHMgPC0gcmFuZG9tX3dlaWdodHMgLyBzdW0ocmFuZG9tX3dlaWdodHMpDQogIHdlaWdodHNbaSwgXSA8LSByYW5kb21fd2VpZ2h0cw0KICANCiAgcG9ydGZvbGlvX3JldHVybnMgPC0gcmV0dXJucyAlKiUgcmFuZG9tX3dlaWdodHMNCiAgDQogIHBvcnRmb2xpb19tZXRyaWNzJFJldHVybnNbaV0gPC0gcmFuZG9tX3dlaWdodHMgJSolIG1lYW5fcmV0dXJucw0KICBwb3J0Zm9saW9fbWV0cmljcyRWYVJbaV0gPC0gLXF1YW50aWxlKHBvcnRmb2xpb19yZXR1cm5zLCAxIC0gYWxwaGEpDQogIHBvcnRmb2xpb19tZXRyaWNzJFZhUl9TaGFycGVbaV0gPC0gKHBvcnRmb2xpb19tZXRyaWNzJFJldHVybnNbaV0gLSByZikgLyBwb3J0Zm9saW9fbWV0cmljcyRWYVJbaV0NCiAgY1ZhUl9pbmRpY2F0b3IgPC0gKHBvcnRmb2xpb19yZXR1cm5zIDwgLXBvcnRmb2xpb19tZXRyaWNzJFZhUltpXSkNCiAgcG9ydGZvbGlvX21ldHJpY3MkY1ZhUltpXSA8LSAtc3VtKHBvcnRmb2xpb19yZXR1cm5zICogY1ZhUl9pbmRpY2F0b3IpIC8gc3VtKGNWYVJfaW5kaWNhdG9yKQ0KICBwb3J0Zm9saW9fbWV0cmljcyRjVmFSX1NoYXJwZVtpXSA8LSAocG9ydGZvbGlvX21ldHJpY3MkUmV0dXJuc1tpXSAtIHJmKSAvIHBvcnRmb2xpb19tZXRyaWNzJGNWYVJbaV0NCiAgDQp9DQpgYGANCg0KQmVsb3cgd2Ugc2VlIG9wdGltYWwgcG9ydGZvbGlvczogXA0KIC0gTWluaW1hbCBWYVIgYW5kIGNWYVIgcG9ydGZvbGlvcyBoYXZlIG9ubHkgYWJvdXQgJDMuM1wlJCBleHBlY3RlZCBhbm51YWwgcmV0dXJuIGFuZCB3ZSBhcmUgJDk1XCUkIGNvbmRpZGVudCB0aGF0IGRhaWx5IGxvc3Mgd29uJ3QgZXhjZWVkICQwLjQ2XCUkLiBJbiB3b3JzdCBjYXNlIHNjZW5hcmlvIGV4cGVjdGVkIGRhaWx5IGRyb3Bkb3duIGlzIGFib3V0ICQwLjc3XCUkLiBUaGVzZSBwb3J0Zm9saW9zIG1haW5seSBjb25zaXN0IG9mIHRyZWFzdXJ5IGluZmxhdGlvbi1wcm90ZWN0ZWQgYm9uZHMuIFwNCiAtIE1heGltYWwgc2hhcnBlIHBvcnRmb2xpb3MgaGF2ZSBhYm91dCAkNy4zXCUkIGV4cGVjdGVkIGFubnVhbCByZXR1cm4gYW5kIGFsbW9zdCB0d28gdGltZXMgaGlnaGVyIHJpc2sgdGhhbiBtaW5pbWFsIHJpc2sgcG9ydGZvbGlvcy4gVGhlc2UgcG9ydGZvbGlvcyBjb25zaXN0IG9mIGRldmVsb3BlZCBtYXJrZXRzIHN0b2NrcyBhbmQgdHJlYXN1cnkgaW5mbGF0aW9uLXByb3RlY3RlZCBib25kcyBpbiBwcm9wb3J0aW9uIGFib3V0IDUwOjUwLg0KIA0KSW4gZmFjdCwgcHJldHR5IHNpbWlsYXIgcmVzdWx0cyB3ZXJlIG9idGFpbmVkIHdpdGggW3ZhcmlhbmNlIG9wdGltaXphdGlvbl0oKGh0dHBzOi8vcnB1YnMuY29tL3Bhd2VsLXdpZWN6eW5za2kvODc0NDU0KSkpLiANCmBgYHtyIG9wdGltYWxfcG9ydGZvbGlvc30NCm9wdGltYWxfcG9ydGZvbGlvcyA8LSBkYXRhLmZyYW1lKA0KICBNaW5fVmFSID0gYygNCiAgICByb3VuZCgxMDAqd2VpZ2h0c1t3aGljaC5taW4ocG9ydGZvbGlvX21ldHJpY3MkVmFSKSxdLCAyKQ0KICAgICxyb3VuZCgxMDAqcG9ydGZvbGlvX21ldHJpY3Nbd2hpY2gubWluKHBvcnRmb2xpb19tZXRyaWNzJFZhUiksIF0sIDIpICU+JSBhcy5udW1lcmljKCkNCiAgKQ0KICAsTWF4X1ZhUl9TaGFycGUgPSBjKA0KICAgIHJvdW5kKDEwMCp3ZWlnaHRzW3doaWNoLm1heChwb3J0Zm9saW9fbWV0cmljcyRWYVJfU2hhcnBlKSxdLCAyKQ0KICAgICxyb3VuZCgxMDAqcG9ydGZvbGlvX21ldHJpY3Nbd2hpY2gubWF4KHBvcnRmb2xpb19tZXRyaWNzJFZhUl9TaGFycGUpLCBdLCAyKSAlPiUgYXMubnVtZXJpYygpDQogICkNCiAgLE1pbl9jVmFSID0gYygNCiAgICByb3VuZCgxMDAqd2VpZ2h0c1t3aGljaC5taW4ocG9ydGZvbGlvX21ldHJpY3MkY1ZhUiksXSwgMikNCiAgICAscm91bmQoMTAwKnBvcnRmb2xpb19tZXRyaWNzW3doaWNoLm1pbihwb3J0Zm9saW9fbWV0cmljcyRjVmFSKSwgXSwgMikgJT4lIGFzLm51bWVyaWMoKQ0KICApDQogICxNYXhfY1ZhUl9TaGFycGUgPSBjKA0KICAgIHJvdW5kKDEwMCp3ZWlnaHRzW3doaWNoLm1heChwb3J0Zm9saW9fbWV0cmljcyRjVmFSX1NoYXJwZSksXSwgMikNCiAgICAscm91bmQoMTAwKnBvcnRmb2xpb19tZXRyaWNzW3doaWNoLm1heChwb3J0Zm9saW9fbWV0cmljcyRjVmFSX1NoYXJwZSksIF0sIDIpICU+JSBhcy5udW1lcmljKCkNCiAgKQ0KKSAlPiUgdCgpICU+JSBhcy5kYXRhLmZyYW1lKCkNCg0KY29sbmFtZXMob3B0aW1hbF9wb3J0Zm9saW9zKSA8LSBjKGNvbG5hbWVzKHJldHVybnMpLCAnRXhwZWN0ZWQgUmV0dXJuJywgJ1ZhUicsICdWYVIgU2hhcnBlJywgJ2NWYVInLCAnY1ZhUiBTaGFycGUnKQ0Kb3B0aW1hbF9wb3J0Zm9saW9zDQpgYGANCg0KQmVsb3cgd2Ugc2VlIHNpbXVsYXRlZCBwb3J0Zm9saW9zIG9uIDItZGltZW5zaW9uYWwgcmlzay1yZXR1cm4gcGxhbmVzLiBNaW5pbWFsIHJpc2sgcG9ydGZvbGlvcyBhcmUgZ3JlZW4gdHJpYW5nbGVzIGFuZCBtYXhpbWFsIHNoYXJwZSBwb3J0Zm9saW9zIGFyZSB0aGUgb3JhbmdlIG9uZXMuIEl0J3Mgbm90IG9idmlvdXMgZnJvbSB0aGUgdml6dWFsaXphdGlvbiwgYnV0IFZhUiBhdHRhaW5hYmxlIHBvcnRmb2xpb3MgYWluJ3QgY29udmV4IHNldC4NCmBgYHtyIHZpenVhbGl6YXRpb259DQpwMSA8LSBnZ3Bsb3QocG9ydGZvbGlvX21ldHJpY3MsIGFlcyh4ID0gVmFSLCB5ID0gUmV0dXJucywgY29sb3IgPSBWYVJfU2hhcnBlKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX3BvaW50KGFlcyh4ID0gKG9wdGltYWxfcG9ydGZvbGlvcyRWYVJbMV0gLyAxMDApDQogICAgICAgICAgICAgICAgICx5ID0gKG9wdGltYWxfcG9ydGZvbGlvcyRgRXhwZWN0ZWQgUmV0dXJuYClbMV0gLyAxMDApDQogICAgICAgICAgICAgLGNvbG9yID0gJyMyY2EwMmMnLCBzaXplID0gNSwgc2hhcGUgPSAxNykgKw0KICBnZW9tX3BvaW50KGFlcyh4ID0gKG9wdGltYWxfcG9ydGZvbGlvcyRWYVJbMl0gLyAxMDApDQogICAgICAgICAgICAgICAgICx5ID0gKG9wdGltYWxfcG9ydGZvbGlvcyRgRXhwZWN0ZWQgUmV0dXJuYClbMl0gLyAxMDApDQogICAgICAgICAgICAgLGNvbG9yID0gJyNmZjdmMGUnLCBzaXplID0gNSwgc2hhcGUgPSAxNykgKyANCiAgdGhlbWVfYncoKQ0KDQpwMiA8LSBnZ3Bsb3QocG9ydGZvbGlvX21ldHJpY3MsIGFlcyh4ID0gY1ZhUiwgeSA9IFJldHVybnMsIGNvbG9yID0gY1ZhUl9TaGFycGUpKSArDQogIGdlb21fcG9pbnQoKSArDQogIGdlb21fcG9pbnQoYWVzKHggPSAob3B0aW1hbF9wb3J0Zm9saW9zJGNWYVJbM10gLyAxMDApDQogICAgICAgICAgICAgICAgICx5ID0gKG9wdGltYWxfcG9ydGZvbGlvcyRgRXhwZWN0ZWQgUmV0dXJuYClbM10gLyAxMDApDQogICAgICAgICAgICAgLGNvbG9yID0gJyMyY2EwMmMnLCBzaXplID0gNSwgc2hhcGUgPSAxNykgKw0KICBnZW9tX3BvaW50KGFlcyh4ID0gKG9wdGltYWxfcG9ydGZvbGlvcyRjVmFSWzRdIC8gMTAwKQ0KICAgICAgICAgICAgICAgICAseSA9IChvcHRpbWFsX3BvcnRmb2xpb3MkYEV4cGVjdGVkIFJldHVybmApWzRdIC8gMTAwKQ0KICAgICAgICAgICAgICxjb2xvciA9ICcjZmY3ZjBlJywgc2l6ZSA9IDUsIHNoYXBlID0gMTcpICsgDQogIHRoZW1lX2J3KCkNCg0KZ3JpZEV4dHJhOjpncmlkLmFycmFuZ2UocDEsIHAyKQ0KYGBg