Portafolios de inversión basados en distintos criterios

Empresas

symbols <- c("GMXT.MX", "AMXL.MX", "GFNORTEO.MX", "FEMSAUBD.MX", "KOFUBL.MX", "AGUA.MX", "SAN.MX","GRUMAB.MX", "OMAB.MX", "ASURB.MX", "BIMBOA.MX", "CMOCTEZ.MX")

t <- tibble(symbols)

Precios y retornos

prices <- quantmod::getSymbols(
  Symbols = symbols,
  src = "yahoo",
  from = "2010-12-31",
  auto.assign = TRUE,
  warnings = FALSE
) %>%
  purrr::map(.f = ~ quantmod::Ad(get(x = .x))) %>%
  purrr::reduce(.f = merge) %>%
  `colnames<-`(value = symbols)
pausing 1 second between requests for more than 5 symbols
pausing 1 second between requests for more than 5 symbols
pausing 1 second between requests for more than 5 symbols
pausing 1 second between requests for more than 5 symbols
pausing 1 second between requests for more than 5 symbols
pausing 1 second between requests for more than 5 symbols
pausing 1 second between requests for more than 5 symbols
asset_returns_xts <- xts::to.monthly(
  x = prices,
  drop.time = TRUE,
  indexAt = "lastof",
  OHLC = FALSE
) %>%
  PerformanceAnalytics::Return.calculate(method = "discrete") %>%
  stats::na.omit()

## quitas todo y solamente dejas tus retornos, diferencia entre symbols y asset returns
## opcional

rm(list = setdiff(x = ls(), y = c("symbols", "precios", "asset_returns_xts")))

Portafolio 1: Mínima varianza

# Crear un portafolio objetivo que es una lista de Portafolios
min_var_portfolio <- PortfolioAnalytics::portfolio.spec(assets = symbols)


# Agregar la restricci?n que los activos deben sumar 1
min_var_portfolio <- PortfolioAnalytics::add.constraint(
  portfolio = min_var_portfolio,
  type = "full_investment"
)



# Agreguemos una restricci?n tipo caja para asegurar que las ponderaciones est?n entre 0 y 1
min_var_portfolio <- PortfolioAnalytics::add.constraint(
  portfolio = min_var_portfolio,
  type = "box", min = 0.00, max = 1
)


# Agreguemos el objetivo para minimizar la varianza
min_var_portfolio <- PortfolioAnalytics::add.objective(
  portfolio = min_var_portfolio,
  # Minimizar riesgo
  type = "risk",
  # Correspondencia al riesgo
  name = "var"
)

# Corramos la optimizaci?n --------------------------------

global_min_portfolio <- PortfolioAnalytics::optimize.portfolio(
  R = asset_returns_xts,
  portfolio = min_var_portfolio,
  # Usar diferentes tipos de optimizaci?n, lo est?ndar es quadprog
  optimize_method = "quadprog",
  # Informaci?n adicional de los m?todos
  trace = TRUE
)

# Examinemos el resultado
global_min_portfolio
***********************************
PortfolioAnalytics Optimization
***********************************

Call:
PortfolioAnalytics::optimize.portfolio(R = asset_returns_xts, 
    portfolio = min_var_portfolio, optimize_method = "quadprog", 
    trace = TRUE)

Optimal Weights:
    GMXT.MX     AMXL.MX GFNORTEO.MX FEMSAUBD.MX   KOFUBL.MX     AGUA.MX 
     0.1238      0.0549      0.0000      0.0000      0.0456      0.0194 
     SAN.MX   GRUMAB.MX     OMAB.MX    ASURB.MX   BIMBOA.MX  CMOCTEZ.MX 
     0.0289      0.1079      0.0000      0.0000      0.0929      0.5266 

Objective Measure:
 StdDev 
0.02984 

Portafolio 2: Máximos rendimientos

# Crear otro nuevo Portafolio
max_exp_return_portfolio <- PortfolioAnalytics::portfolio.spec(assets = symbols)

# Restricci?n que la suma nos de 1
max_exp_return_portfolio <- PortfolioAnalytics::add.constraint(
  portfolio = max_exp_return_portfolio,
  type = "full_investment"
)

# Restricci?n tipo box, igualemos en 0.05 y .5

max_exp_return_portfolio <- PortfolioAnalytics::add.constraint(
  portfolio = max_exp_return_portfolio,
  type = "box", min = 0.05, max = .5
)

# Busquemos maximizar retorno
max_exp_return_portfolio <- PortfolioAnalytics::add.objective(
  portfolio = max_exp_return_portfolio,
  # Maximizar retornos esperados
  type = "return",
  # Funci?n tipo media del retorno hist?rico
  name = "mean"
)

# Riesgo rendimiento, quadratic utility

max_exp_return_portfolio <- PortfolioAnalytics::add.objective(
  portfolio = max_exp_return_portfolio,
  # Maximizar retornos esperados
  type = "return",
  # Funci?n tipo media del retorno hist?rico
  name = "mean"
)



# Correr la optimizaci?n
global_max_portfolio <- PortfolioAnalytics::optimize.portfolio(
  R = asset_returns_xts,
  portfolio = max_exp_return_portfolio,
  # En este caso usa "glpk" 
  optimize_method = "glpk",
  # Regresa con informaci?n adicional
  trace = TRUE
)
# Examina el resultado
global_max_portfolio
***********************************
PortfolioAnalytics Optimization
***********************************

Call:
PortfolioAnalytics::optimize.portfolio(R = asset_returns_xts, 
    portfolio = max_exp_return_portfolio, optimize_method = "glpk", 
    trace = TRUE)

Optimal Weights:
    GMXT.MX     AMXL.MX GFNORTEO.MX FEMSAUBD.MX   KOFUBL.MX     AGUA.MX 
       0.05        0.05        0.05        0.05        0.05        0.05 
     SAN.MX   GRUMAB.MX     OMAB.MX    ASURB.MX   BIMBOA.MX  CMOCTEZ.MX 
       0.05        0.05        0.45        0.05        0.05        0.05 

Objective Measure:
   mean 
0.01639 

Portafolio 3: Rebalanceo mensual

# Computar retornos mensuales de la estrategia
portfolio_returns_xts_rebalanced_monthly <-
  PerformanceAnalytics::Return.portfolio(
    R = asset_returns_xts,
    weights = weights,
    # Rebalanceo Mensual, implica comprar/vender cada mes
    reblance_on = "months",
    geometric = FALSE
  ) %>%
  `colnames<-`("Monthly_portfolio_returns")
Warning: index does not have a ‘tclass’ attributeWarning: number of assets in beginning_weights is less than number of columns in returns, so subsetting returns.Warning: index does not have a ‘tclass’ attributeWarning: index does not have a ‘tclass’ attributeWarning: index does not have a ‘tclass’ attributeWarning: index does not have a ‘tclass’ attributeWarning: index does not have a ‘tclass’ attributeWarning: index does not have a ‘tclass’ attributeWarning: index does not have a ‘tclass’ attributeWarning: index does not have a ‘tclass’ attributeWarning: index does not have a ‘tclass’ attributeWarning: index does not have a ‘tclass’ attributeWarning: index does not have a ‘tclass’ attributeWarning: index does not have a ‘tclass’ attributeWarning: index does not have a ‘tclass’ attributeWarning: index does not have a ‘tclass’ attributeWarning: index does not have a ‘tclass’ attributeWarning: index does not have a ‘tclass’ attributeWarning: index does not have a ‘tclass’ attributeWarning: index does not have a ‘tclass’ attribute
# grafica los rendimientos acumulados

charts.PerformanceSummary(portfolio_returns_xts_rebalanced_monthly)

Portfolio 3 vs S&P500

#asumamos que el bmk es el S&P500 (SPY)

SPY <- quantmod::getSymbols(
  Symbols = "SPY",
  src = "yahoo",
  from = "2015-12-31",
  auto.assign = TRUE,
  warnings = FALSE
) %>%
  purrr::map(.f = ~ quantmod::Ad(get(x = .x))) %>%
  purrr::reduce(.f = merge) %>%
  `colnames<-`(value = "SPY")

SPY_returns_xts <- xts::to.monthly(
  x = SPY,
  drop.time = TRUE,
  indexAt = "lastof",
  OHLC = FALSE
) %>%
  PerformanceAnalytics::Return.calculate(method = "discrete") %>%
  stats::na.omit()



compara <- cbind(portfolio_returns_xts_rebalanced_monthly, SPY_returns_xts)

# c?mo se ve

charts.PerformanceSummary(compara)

Portafolio 4: Utilidad cuadrática


quat_ut_port <- PortfolioAnalytics::portfolio.spec(assets = symbols)

quat_ut_port <- PortfolioAnalytics::add.constraint(
  portfolio = quat_ut_port,
  type = "full_investment"
)

quat_ut_port <- PortfolioAnalytics::add.constraint(
  portfolio = quat_ut_port,
  type = "box", min = 0.00, max = 0.5
)

quat_ut_port <- PortfolioAnalytics::add.objective(quat_ut_port, type="quadratic_utility",
                                                  risk_aversion=5)

max_quat_ut_port<-  PortfolioAnalytics::optimize.portfolio(
  R = asset_returns_xts,
  portfolio = quat_ut_port,
  # En este caso usa "quadprog" 
  optimize_method = "quadprog",
  # Regresa con informaci?n adicional
  trace = TRUE
)

max_quat_ut_port
***********************************
PortfolioAnalytics Optimization
***********************************

Call:
PortfolioAnalytics::optimize.portfolio(R = asset_returns_xts, 
    portfolio = quat_ut_port, optimize_method = "quadprog", trace = TRUE)

Optimal Weights:
    GMXT.MX     AMXL.MX GFNORTEO.MX FEMSAUBD.MX   KOFUBL.MX     AGUA.MX 
     0.3131      0.0070      0.0000      0.0000      0.0000      0.0608 
     SAN.MX   GRUMAB.MX     OMAB.MX    ASURB.MX   BIMBOA.MX  CMOCTEZ.MX 
     0.0000      0.0000      0.0336      0.0266      0.3553      0.2036 

Objective Measure:
   mean 
0.01616 


 StdDev 
0.03897 
weights3 <- pluck(.x = max_quat_ut_port, "weights")

## Max mean and low ETL

meanETL_port <- PortfolioAnalytics::portfolio.spec(assets = symbols)

meanETL_port <- PortfolioAnalytics::add.constraint(
  portfolio = meanETL_port,
  type = "weight_sum",
  min_sum=0.99,
  max_sum=1.01
)

#meanETL_port <- PortfolioAnalytics::add.constraint(
#  portfolio = meanETL_port,
#  type = "full_investment"
#)

meanETL_port <- PortfolioAnalytics::add.constraint(
  portfolio = meanETL_port,
  type = "box", min = -0.05, max = 0.50
)
                                                
meanETL_port <- PortfolioAnalytics::add.objective(meanETL_port, type="return", name="mean")

meanETL_port <- PortfolioAnalytics::add.objective(meanETL_port, type="risk", name="ETL", arguments=list(p=0.95))

max_min_ETL_port<-  PortfolioAnalytics::optimize.portfolio(
  R = asset_returns_xts,
  portfolio = meanETL_port,
  # En este caso usa "quadprog" 
  optimize_method = "random",
  search_size = 10000,
  # Regresa con informaci?n adicional
  trace = TRUE
)

weights <- pluck(.x = max_min_ETL_port, "weights")

portfolio_returns_xts_rebalanced_monthly <-
  PerformanceAnalytics::Return.portfolio(
    R = asset_returns_xts,
    weights = weights,
    # Rebalanceo Mensual, implica comprar/vender cada mes
    reblance_on = "months",
    geometric = FALSE
  ) %>%
  `colnames<-`("Monthly_portfolio_returns")



bmk_returns <-
  PerformanceAnalytics::Return.portfolio(
    R = SPY_returns_xts,
    # Rebalanceo Mensual, implica comprar/vender cada mes
    reblance_on = "months",
    geometric = FALSE
  ) %>%
  `colnames<-`("Benchmark_Returns")

quad_returns <-
  PerformanceAnalytics::Return.portfolio(
    R = asset_returns_xts,
    weights = weights3,
    # Rebalanceo Mensual, implica comprar/vender cada mes
    reblance_on = "months",
    geometric = FALSE
  ) %>%
  `colnames<-`("Quad_Returns")

Portafolio 4 vs S&P500


compara2 <- cbind(portfolio_returns_xts_rebalanced_monthly, bmk_returns, quad_returns)

charts.PerformanceSummary(compara2)

Back test



opt_quad <- optimize.portfolio.rebalancing(asset_returns_xts,
                                           quat_ut_port,
                                           optimize_method = "ROI",
                                           rebalance_on="months",
                                           training_period = 12,
                                           rolling_window=24)

backtest_quad_ret <- Return.portfolio(asset_returns_xts, extractWeights((opt_quad)))

colnames(backtest_quad_ret) <- "Bkt_Quad"


#################3 Ejercicios sobre restricciones

quat_ut_35 <- PortfolioAnalytics::portfolio.spec(assets = symbols)

quat_ut_35 <- PortfolioAnalytics::add.constraint(
  portfolio = quat_ut_35,
  type = "full_investment"
)

# no puedes invertir m?s de 25%

quat_ut_35 <- PortfolioAnalytics::add.constraint(
  portfolio = quat_ut_35,
  type = "box", min = 0.05, max = .17
)

## Trabajar m?s a detalle

quat_ut_35 <- add.constraint(portfolio=quat_ut_35,
                             type="group",
                             groups=list(c(2, 3, 4, 5, 6, 7, 8, 11), c(1, 9, 10)), 
                             group_min=c(0.0, 0.0),
                             group_max=c(.60, 0.4),
                             group_labels=c("ESG", "NON-ESG"),
                             group_pos=c(1, 2)) #se arman grupos dependiendo de las acciones

quat_ut_35 <- PortfolioAnalytics::add.constraint(
  portfolio = quat_ut_35,
  type = "weight_sum",
  min_sum=1.00,
  max_sum=1.00
)

quat_ut_35 <- PortfolioAnalytics::add.objective(quat_ut_35, type="quadratic_utility",
                                                risk_aversion=2)

opt_quad_35 <- optimize.portfolio.rebalancing(asset_returns_xts,
                                              quat_ut_35,
                                              optimize_method = "ROI",
                                              rebalance_on="months",
                                              training_period = 12,
                                              rolling_window=24)

opt_quad_35_last<-  PortfolioAnalytics::optimize.portfolio(
  R = asset_returns_xts,
  portfolio = quat_ut_35,
  optimize_method = "quadprog",
  #momentargs=momentargs,
  # Regresa con informaci?n adicional
  trace = TRUE
)

Gráfica

#descriptores
chart.Weights(opt_quad_35, colorset=brewer.pal(11, "Paired"))

Grupos

(extractGroups(opt_quad_35_last))
$weights
    GMXT.MX     AMXL.MX GFNORTEO.MX FEMSAUBD.MX   KOFUBL.MX     AGUA.MX 
       0.17        0.05        0.05        0.05        0.05        0.13 
     SAN.MX   GRUMAB.MX     OMAB.MX    ASURB.MX   BIMBOA.MX 
       0.05        0.05        0.17        0.06        0.17 

$category_weights
NULL

$group_weights
    ESG NON-ESG 
    0.6     0.4 

Backtest mensual a 3 años

(backtest_quad_35 <- Return.portfolio(asset_returns_xts, extractWeights((opt_quad_35))))
           portfolio.returns
2020-05-31      0.0295510038
2020-06-30      0.0755937911
2020-07-31     -0.0264516199
2020-08-31      0.0332697816
2020-09-30      0.0005103903
2020-10-31     -0.0501127872
2020-11-30      0.1503769424
2020-12-31      0.1036271160
2021-01-31     -0.0701811189
2021-02-28      0.0612357988
2021-03-31      0.0558464459
2021-04-30      0.0209400657
2021-05-31      0.0304515441
2021-06-30      0.0047933374
2021-07-31     -0.0030034452
2021-08-31      0.0195284299
2021-09-30     -0.0075355692
2021-10-31      0.0484338686
2021-11-30     -0.0183897471
2021-12-31      0.0660781313
2022-01-31      0.0128704653
2022-02-28      0.0125992889
2022-03-31     -0.0087178671
2022-04-30     -0.0019757165
2022-05-31      0.0182226734
2022-06-30     -0.0747057758
2022-07-31      0.0358193358
2022-08-31     -0.0283194459
2022-09-30      0.0023575899
2022-10-31      0.1382212346
2022-11-30      0.0459817287
2022-12-31     -0.0304830893
2023-01-31      0.0976427856
2023-02-28     -0.0027840794
2023-03-31      0.0131105226
# Rendimiento vs S&P500
comp <- cbind(backtest_quad_35, SPY_returns_xts)

charts.PerformanceSummary(comp)

LS0tCnRpdGxlOiAiUG9ydGFmb2xpbyBkZSBpbnZlcnNpw7NuIG5hY2lvbmFsIgphdXRob3I6ICJBTEZBIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6IHllcwogICAgZGZfcHJpbnQ6IHBhZ2VkCiAgaHRtbF9kb2N1bWVudDoKICAgIHRvYzogeWVzCiAgICBkZl9wcmludDogcGFnZWQKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0KbGlicmFyeShST0kpCmxpYnJhcnkoUk9JLnBsdWdpbi5nbHBrKQpsaWJyYXJ5KFJPSS5wbHVnaW4ucXVhZHByb2cpCmxpYnJhcnkoUk9JLnBsdWdpbi5zeW1waG9ueSkKbGlicmFyeShxdWFudG1vZCkKbGlicmFyeShwdXJycikKbGlicmFyeShQZXJmb3JtYW5jZUFuYWx5dGljcykKbGlicmFyeShQb3J0Zm9saW9BbmFseXRpY3MpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KERFb3B0aW1SKQpgYGAKCiMgUG9ydGFmb2xpb3MgZGUgaW52ZXJzacOzbiBiYXNhZG9zIGVuIGRpc3RpbnRvcyBjcml0ZXJpb3MKCiMjIyBFbXByZXNhcwpgYGB7cn0Kc3ltYm9scyA8LSBjKCJHTVhULk1YIiwgIkFNWEwuTVgiLCAiR0ZOT1JURU8uTVgiLCAiRkVNU0FVQkQuTVgiLCAiS09GVUJMLk1YIiwgIkFHVUEuTVgiLCAiU0FOLk1YIiwiR1JVTUFCLk1YIiwgIk9NQUIuTVgiLCAiQVNVUkIuTVgiLCAiQklNQk9BLk1YIiwgIkNNT0NURVouTVgiKQoKYGBgCgojIyMgUHJlY2lvcyB5IHJldG9ybm9zCgpgYGB7ciwgd2FybmluZz1GQUxTRX0KcHJpY2VzIDwtIHF1YW50bW9kOjpnZXRTeW1ib2xzKAogIFN5bWJvbHMgPSBzeW1ib2xzLAogIHNyYyA9ICJ5YWhvbyIsCiAgZnJvbSA9ICIyMDEwLTEyLTMxIiwKICBhdXRvLmFzc2lnbiA9IFRSVUUsCiAgd2FybmluZ3MgPSBGQUxTRQopICU+JQogIHB1cnJyOjptYXAoLmYgPSB+IHF1YW50bW9kOjpBZChnZXQoeCA9IC54KSkpICU+JQogIHB1cnJyOjpyZWR1Y2UoLmYgPSBtZXJnZSkgJT4lCiAgYGNvbG5hbWVzPC1gKHZhbHVlID0gc3ltYm9scykKCgphc3NldF9yZXR1cm5zX3h0cyA8LSB4dHM6OnRvLm1vbnRobHkoCiAgeCA9IHByaWNlcywKICBkcm9wLnRpbWUgPSBUUlVFLAogIGluZGV4QXQgPSAibGFzdG9mIiwKICBPSExDID0gRkFMU0UKKSAlPiUKICBQZXJmb3JtYW5jZUFuYWx5dGljczo6UmV0dXJuLmNhbGN1bGF0ZShtZXRob2QgPSAiZGlzY3JldGUiKSAlPiUKICBzdGF0czo6bmEub21pdCgpCgojIyBxdWl0YXMgdG9kbyB5IHNvbGFtZW50ZSBkZWphcyB0dXMgcmV0b3Jub3MsIGRpZmVyZW5jaWEgZW50cmUgc3ltYm9scyB5IGFzc2V0IHJldHVybnMKIyMgb3BjaW9uYWwKCnJtKGxpc3QgPSBzZXRkaWZmKHggPSBscygpLCB5ID0gYygic3ltYm9scyIsICJwcmVjaW9zIiwgImFzc2V0X3JldHVybnNfeHRzIikpKQoKCmBgYAoKIyMgUG9ydGFmb2xpbyAxOiBNw61uaW1hIHZhcmlhbnphCmBgYHtyfQojIENyZWFyIHVuIHBvcnRhZm9saW8gb2JqZXRpdm8gcXVlIGVzIHVuYSBsaXN0YSBkZSBQb3J0YWZvbGlvcwptaW5fdmFyX3BvcnRmb2xpbyA8LSBQb3J0Zm9saW9BbmFseXRpY3M6OnBvcnRmb2xpby5zcGVjKGFzc2V0cyA9IHN5bWJvbHMpCgoKIyBBZ3JlZ2FyIGxhIHJlc3RyaWNjaT9uIHF1ZSBsb3MgYWN0aXZvcyBkZWJlbiBzdW1hciAxCm1pbl92YXJfcG9ydGZvbGlvIDwtIFBvcnRmb2xpb0FuYWx5dGljczo6YWRkLmNvbnN0cmFpbnQoCiAgcG9ydGZvbGlvID0gbWluX3Zhcl9wb3J0Zm9saW8sCiAgdHlwZSA9ICJmdWxsX2ludmVzdG1lbnQiCikKCgoKIyBBZ3JlZ3VlbW9zIHVuYSByZXN0cmljY2k/biB0aXBvIGNhamEgcGFyYSBhc2VndXJhciBxdWUgbGFzIHBvbmRlcmFjaW9uZXMgZXN0P24gZW50cmUgMCB5IDEKbWluX3Zhcl9wb3J0Zm9saW8gPC0gUG9ydGZvbGlvQW5hbHl0aWNzOjphZGQuY29uc3RyYWludCgKICBwb3J0Zm9saW8gPSBtaW5fdmFyX3BvcnRmb2xpbywKICB0eXBlID0gImJveCIsIG1pbiA9IDAuMDAsIG1heCA9IDEKKQoKCiMgQWdyZWd1ZW1vcyBlbCBvYmpldGl2byBwYXJhIG1pbmltaXphciBsYSB2YXJpYW56YQptaW5fdmFyX3BvcnRmb2xpbyA8LSBQb3J0Zm9saW9BbmFseXRpY3M6OmFkZC5vYmplY3RpdmUoCiAgcG9ydGZvbGlvID0gbWluX3Zhcl9wb3J0Zm9saW8sCiAgIyBNaW5pbWl6YXIgcmllc2dvCiAgdHlwZSA9ICJyaXNrIiwKICAjIENvcnJlc3BvbmRlbmNpYSBhbCByaWVzZ28KICBuYW1lID0gInZhciIKKQoKIyBDb3JyYW1vcyBsYSBvcHRpbWl6YWNpP24gLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCmdsb2JhbF9taW5fcG9ydGZvbGlvIDwtIFBvcnRmb2xpb0FuYWx5dGljczo6b3B0aW1pemUucG9ydGZvbGlvKAogIFIgPSBhc3NldF9yZXR1cm5zX3h0cywKICBwb3J0Zm9saW8gPSBtaW5fdmFyX3BvcnRmb2xpbywKICAjIFVzYXIgZGlmZXJlbnRlcyB0aXBvcyBkZSBvcHRpbWl6YWNpP24sIGxvIGVzdD9uZGFyIGVzIHF1YWRwcm9nCiAgb3B0aW1pemVfbWV0aG9kID0gInF1YWRwcm9nIiwKICAjIEluZm9ybWFjaT9uIGFkaWNpb25hbCBkZSBsb3MgbT90b2RvcwogIHRyYWNlID0gVFJVRQopCgojIEV4YW1pbmVtb3MgZWwgcmVzdWx0YWRvCmdsb2JhbF9taW5fcG9ydGZvbGlvCmBgYAoKIyMgUG9ydGFmb2xpbyAyOiBNw6F4aW1vcyByZW5kaW1pZW50b3MKYGBge3J9CiMgQ3JlYXIgb3RybyBudWV2byBQb3J0YWZvbGlvCm1heF9leHBfcmV0dXJuX3BvcnRmb2xpbyA8LSBQb3J0Zm9saW9BbmFseXRpY3M6OnBvcnRmb2xpby5zcGVjKGFzc2V0cyA9IHN5bWJvbHMpCgojIFJlc3RyaWNjaT9uIHF1ZSBsYSBzdW1hIG5vcyBkZSAxCm1heF9leHBfcmV0dXJuX3BvcnRmb2xpbyA8LSBQb3J0Zm9saW9BbmFseXRpY3M6OmFkZC5jb25zdHJhaW50KAogIHBvcnRmb2xpbyA9IG1heF9leHBfcmV0dXJuX3BvcnRmb2xpbywKICB0eXBlID0gImZ1bGxfaW52ZXN0bWVudCIKKQoKIyBSZXN0cmljY2k/biB0aXBvIGJveCwgaWd1YWxlbW9zIGVuIDAuMDUgeSAuNQoKbWF4X2V4cF9yZXR1cm5fcG9ydGZvbGlvIDwtIFBvcnRmb2xpb0FuYWx5dGljczo6YWRkLmNvbnN0cmFpbnQoCiAgcG9ydGZvbGlvID0gbWF4X2V4cF9yZXR1cm5fcG9ydGZvbGlvLAogIHR5cGUgPSAiYm94IiwgbWluID0gMC4wNSwgbWF4ID0gLjUKKQoKIyBCdXNxdWVtb3MgbWF4aW1pemFyIHJldG9ybm8KbWF4X2V4cF9yZXR1cm5fcG9ydGZvbGlvIDwtIFBvcnRmb2xpb0FuYWx5dGljczo6YWRkLm9iamVjdGl2ZSgKICBwb3J0Zm9saW8gPSBtYXhfZXhwX3JldHVybl9wb3J0Zm9saW8sCiAgIyBNYXhpbWl6YXIgcmV0b3Jub3MgZXNwZXJhZG9zCiAgdHlwZSA9ICJyZXR1cm4iLAogICMgRnVuY2k/biB0aXBvIG1lZGlhIGRlbCByZXRvcm5vIGhpc3Q/cmljbwogIG5hbWUgPSAibWVhbiIKKQoKIyBSaWVzZ28gcmVuZGltaWVudG8sIHF1YWRyYXRpYyB1dGlsaXR5CgptYXhfZXhwX3JldHVybl9wb3J0Zm9saW8gPC0gUG9ydGZvbGlvQW5hbHl0aWNzOjphZGQub2JqZWN0aXZlKAogIHBvcnRmb2xpbyA9IG1heF9leHBfcmV0dXJuX3BvcnRmb2xpbywKICAjIE1heGltaXphciByZXRvcm5vcyBlc3BlcmFkb3MKICB0eXBlID0gInJldHVybiIsCiAgIyBGdW5jaT9uIHRpcG8gbWVkaWEgZGVsIHJldG9ybm8gaGlzdD9yaWNvCiAgbmFtZSA9ICJtZWFuIgopCgoKCiMgQ29ycmVyIGxhIG9wdGltaXphY2k/bgpnbG9iYWxfbWF4X3BvcnRmb2xpbyA8LSBQb3J0Zm9saW9BbmFseXRpY3M6Om9wdGltaXplLnBvcnRmb2xpbygKICBSID0gYXNzZXRfcmV0dXJuc194dHMsCiAgcG9ydGZvbGlvID0gbWF4X2V4cF9yZXR1cm5fcG9ydGZvbGlvLAogICMgRW4gZXN0ZSBjYXNvIHVzYSAiZ2xwayIgCiAgb3B0aW1pemVfbWV0aG9kID0gImdscGsiLAogICMgUmVncmVzYSBjb24gaW5mb3JtYWNpP24gYWRpY2lvbmFsCiAgdHJhY2UgPSBUUlVFCikKIyBFeGFtaW5hIGVsIHJlc3VsdGFkbwpnbG9iYWxfbWF4X3BvcnRmb2xpbwpgYGAKCiMjIFBvcnRhZm9saW8gMzogUmViYWxhbmNlbyBtZW5zdWFsCmBgYHtyfQojIENvbXB1dGFyIHJldG9ybm9zIG1lbnN1YWxlcyBkZSBsYSBlc3RyYXRlZ2lhCnBvcnRmb2xpb19yZXR1cm5zX3h0c19yZWJhbGFuY2VkX21vbnRobHkgPC0KICBQZXJmb3JtYW5jZUFuYWx5dGljczo6UmV0dXJuLnBvcnRmb2xpbygKICAgIFIgPSBhc3NldF9yZXR1cm5zX3h0cywKICAgIHdlaWdodHMgPSB3ZWlnaHRzLAogICAgIyBSZWJhbGFuY2VvIE1lbnN1YWwsIGltcGxpY2EgY29tcHJhci92ZW5kZXIgY2FkYSBtZXMKICAgIHJlYmxhbmNlX29uID0gIm1vbnRocyIsCiAgICBnZW9tZXRyaWMgPSBGQUxTRQogICkgJT4lCiAgYGNvbG5hbWVzPC1gKCJNb250aGx5X3BvcnRmb2xpb19yZXR1cm5zIikKCgojIGdyYWZpY2EgbG9zIHJlbmRpbWllbnRvcyBhY3VtdWxhZG9zCgpjaGFydHMuUGVyZm9ybWFuY2VTdW1tYXJ5KHBvcnRmb2xpb19yZXR1cm5zX3h0c19yZWJhbGFuY2VkX21vbnRobHkpCgpgYGAKCiMjIyBQb3J0Zm9saW8gMyB2cyBTJlA1MDAKYGBge3J9CiNhc3VtYW1vcyBxdWUgZWwgYm1rIGVzIGVsIFMmUDUwMCAoU1BZKQoKU1BZIDwtIHF1YW50bW9kOjpnZXRTeW1ib2xzKAogIFN5bWJvbHMgPSAiU1BZIiwKICBzcmMgPSAieWFob28iLAogIGZyb20gPSAiMjAxNS0xMi0zMSIsCiAgYXV0by5hc3NpZ24gPSBUUlVFLAogIHdhcm5pbmdzID0gRkFMU0UKKSAlPiUKICBwdXJycjo6bWFwKC5mID0gfiBxdWFudG1vZDo6QWQoZ2V0KHggPSAueCkpKSAlPiUKICBwdXJycjo6cmVkdWNlKC5mID0gbWVyZ2UpICU+JQogIGBjb2xuYW1lczwtYCh2YWx1ZSA9ICJTUFkiKQoKU1BZX3JldHVybnNfeHRzIDwtIHh0czo6dG8ubW9udGhseSgKICB4ID0gU1BZLAogIGRyb3AudGltZSA9IFRSVUUsCiAgaW5kZXhBdCA9ICJsYXN0b2YiLAogIE9ITEMgPSBGQUxTRQopICU+JQogIFBlcmZvcm1hbmNlQW5hbHl0aWNzOjpSZXR1cm4uY2FsY3VsYXRlKG1ldGhvZCA9ICJkaXNjcmV0ZSIpICU+JQogIHN0YXRzOjpuYS5vbWl0KCkKCgoKY29tcGFyYSA8LSBjYmluZChwb3J0Zm9saW9fcmV0dXJuc194dHNfcmViYWxhbmNlZF9tb250aGx5LCBTUFlfcmV0dXJuc194dHMpCgojIGM/bW8gc2UgdmUKCmNoYXJ0cy5QZXJmb3JtYW5jZVN1bW1hcnkoY29tcGFyYSkKYGBgCgojIyBQb3J0YWZvbGlvIDQ6IFV0aWxpZGFkIGN1YWRyw6F0aWNhCmBgYHtyfQoKcXVhdF91dF9wb3J0IDwtIFBvcnRmb2xpb0FuYWx5dGljczo6cG9ydGZvbGlvLnNwZWMoYXNzZXRzID0gc3ltYm9scykKCnF1YXRfdXRfcG9ydCA8LSBQb3J0Zm9saW9BbmFseXRpY3M6OmFkZC5jb25zdHJhaW50KAogIHBvcnRmb2xpbyA9IHF1YXRfdXRfcG9ydCwKICB0eXBlID0gImZ1bGxfaW52ZXN0bWVudCIKKQoKcXVhdF91dF9wb3J0IDwtIFBvcnRmb2xpb0FuYWx5dGljczo6YWRkLmNvbnN0cmFpbnQoCiAgcG9ydGZvbGlvID0gcXVhdF91dF9wb3J0LAogIHR5cGUgPSAiYm94IiwgbWluID0gMC4wMCwgbWF4ID0gMC41CikKCnF1YXRfdXRfcG9ydCA8LSBQb3J0Zm9saW9BbmFseXRpY3M6OmFkZC5vYmplY3RpdmUocXVhdF91dF9wb3J0LCB0eXBlPSJxdWFkcmF0aWNfdXRpbGl0eSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmlza19hdmVyc2lvbj01KQoKbWF4X3F1YXRfdXRfcG9ydDwtICBQb3J0Zm9saW9BbmFseXRpY3M6Om9wdGltaXplLnBvcnRmb2xpbygKICBSID0gYXNzZXRfcmV0dXJuc194dHMsCiAgcG9ydGZvbGlvID0gcXVhdF91dF9wb3J0LAogICMgRW4gZXN0ZSBjYXNvIHVzYSAicXVhZHByb2ciIAogIG9wdGltaXplX21ldGhvZCA9ICJxdWFkcHJvZyIsCiAgIyBSZWdyZXNhIGNvbiBpbmZvcm1hY2k/biBhZGljaW9uYWwKICB0cmFjZSA9IFRSVUUKKQoKbWF4X3F1YXRfdXRfcG9ydAoKd2VpZ2h0czMgPC0gcGx1Y2soLnggPSBtYXhfcXVhdF91dF9wb3J0LCAid2VpZ2h0cyIpCgojIyBNYXggbWVhbiBhbmQgbG93IEVUTAoKbWVhbkVUTF9wb3J0IDwtIFBvcnRmb2xpb0FuYWx5dGljczo6cG9ydGZvbGlvLnNwZWMoYXNzZXRzID0gc3ltYm9scykKCm1lYW5FVExfcG9ydCA8LSBQb3J0Zm9saW9BbmFseXRpY3M6OmFkZC5jb25zdHJhaW50KAogIHBvcnRmb2xpbyA9IG1lYW5FVExfcG9ydCwKICB0eXBlID0gIndlaWdodF9zdW0iLAogIG1pbl9zdW09MC45OSwKICBtYXhfc3VtPTEuMDEKKQoKI21lYW5FVExfcG9ydCA8LSBQb3J0Zm9saW9BbmFseXRpY3M6OmFkZC5jb25zdHJhaW50KAojICBwb3J0Zm9saW8gPSBtZWFuRVRMX3BvcnQsCiMgIHR5cGUgPSAiZnVsbF9pbnZlc3RtZW50IgojKQoKbWVhbkVUTF9wb3J0IDwtIFBvcnRmb2xpb0FuYWx5dGljczo6YWRkLmNvbnN0cmFpbnQoCiAgcG9ydGZvbGlvID0gbWVhbkVUTF9wb3J0LAogIHR5cGUgPSAiYm94IiwgbWluID0gLTAuMDUsIG1heCA9IDAuNTAKKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKbWVhbkVUTF9wb3J0IDwtIFBvcnRmb2xpb0FuYWx5dGljczo6YWRkLm9iamVjdGl2ZShtZWFuRVRMX3BvcnQsIHR5cGU9InJldHVybiIsIG5hbWU9Im1lYW4iKQoKbWVhbkVUTF9wb3J0IDwtIFBvcnRmb2xpb0FuYWx5dGljczo6YWRkLm9iamVjdGl2ZShtZWFuRVRMX3BvcnQsIHR5cGU9InJpc2siLCBuYW1lPSJFVEwiLCBhcmd1bWVudHM9bGlzdChwPTAuOTUpKQoKbWF4X21pbl9FVExfcG9ydDwtICBQb3J0Zm9saW9BbmFseXRpY3M6Om9wdGltaXplLnBvcnRmb2xpbygKICBSID0gYXNzZXRfcmV0dXJuc194dHMsCiAgcG9ydGZvbGlvID0gbWVhbkVUTF9wb3J0LAogICMgRW4gZXN0ZSBjYXNvIHVzYSAicXVhZHByb2ciIAogIG9wdGltaXplX21ldGhvZCA9ICJyYW5kb20iLAogIHNlYXJjaF9zaXplID0gMTAwMDAsCiAgIyBSZWdyZXNhIGNvbiBpbmZvcm1hY2k/biBhZGljaW9uYWwKICB0cmFjZSA9IFRSVUUKKQoKd2VpZ2h0cyA8LSBwbHVjaygueCA9IG1heF9taW5fRVRMX3BvcnQsICJ3ZWlnaHRzIikKCnBvcnRmb2xpb19yZXR1cm5zX3h0c19yZWJhbGFuY2VkX21vbnRobHkgPC0KICBQZXJmb3JtYW5jZUFuYWx5dGljczo6UmV0dXJuLnBvcnRmb2xpbygKICAgIFIgPSBhc3NldF9yZXR1cm5zX3h0cywKICAgIHdlaWdodHMgPSB3ZWlnaHRzLAogICAgIyBSZWJhbGFuY2VvIE1lbnN1YWwsIGltcGxpY2EgY29tcHJhci92ZW5kZXIgY2FkYSBtZXMKICAgIHJlYmxhbmNlX29uID0gIm1vbnRocyIsCiAgICBnZW9tZXRyaWMgPSBGQUxTRQogICkgJT4lCiAgYGNvbG5hbWVzPC1gKCJNb250aGx5X3BvcnRmb2xpb19yZXR1cm5zIikKCgoKYm1rX3JldHVybnMgPC0KICBQZXJmb3JtYW5jZUFuYWx5dGljczo6UmV0dXJuLnBvcnRmb2xpbygKICAgIFIgPSBTUFlfcmV0dXJuc194dHMsCiAgICAjIFJlYmFsYW5jZW8gTWVuc3VhbCwgaW1wbGljYSBjb21wcmFyL3ZlbmRlciBjYWRhIG1lcwogICAgcmVibGFuY2Vfb24gPSAibW9udGhzIiwKICAgIGdlb21ldHJpYyA9IEZBTFNFCiAgKSAlPiUKICBgY29sbmFtZXM8LWAoIkJlbmNobWFya19SZXR1cm5zIikKCnF1YWRfcmV0dXJucyA8LQogIFBlcmZvcm1hbmNlQW5hbHl0aWNzOjpSZXR1cm4ucG9ydGZvbGlvKAogICAgUiA9IGFzc2V0X3JldHVybnNfeHRzLAogICAgd2VpZ2h0cyA9IHdlaWdodHMzLAogICAgIyBSZWJhbGFuY2VvIE1lbnN1YWwsIGltcGxpY2EgY29tcHJhci92ZW5kZXIgY2FkYSBtZXMKICAgIHJlYmxhbmNlX29uID0gIm1vbnRocyIsCiAgICBnZW9tZXRyaWMgPSBGQUxTRQogICkgJT4lCiAgYGNvbG5hbWVzPC1gKCJRdWFkX1JldHVybnMiKQoKCgoKCmBgYAoKIyMjIFBvcnRhZm9saW8gNCB2cyBTJlA1MDAKYGBge3J9Cgpjb21wYXJhMiA8LSBjYmluZChwb3J0Zm9saW9fcmV0dXJuc194dHNfcmViYWxhbmNlZF9tb250aGx5LCBibWtfcmV0dXJucywgcXVhZF9yZXR1cm5zKQoKY2hhcnRzLlBlcmZvcm1hbmNlU3VtbWFyeShjb21wYXJhMikKCmBgYAoKCiMgQmFjayB0ZXN0CgpgYGB7cn0KCgpvcHRfcXVhZCA8LSBvcHRpbWl6ZS5wb3J0Zm9saW8ucmViYWxhbmNpbmcoYXNzZXRfcmV0dXJuc194dHMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBxdWF0X3V0X3BvcnQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvcHRpbWl6ZV9tZXRob2QgPSAiUk9JIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlYmFsYW5jZV9vbj0ibW9udGhzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRyYWluaW5nX3BlcmlvZCA9IDEyLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcm9sbGluZ193aW5kb3c9MjQpCgpiYWNrdGVzdF9xdWFkX3JldCA8LSBSZXR1cm4ucG9ydGZvbGlvKGFzc2V0X3JldHVybnNfeHRzLCBleHRyYWN0V2VpZ2h0cygob3B0X3F1YWQpKSkKCmNvbG5hbWVzKGJhY2t0ZXN0X3F1YWRfcmV0KSA8LSAiQmt0X1F1YWQiCgoKIyMjIyMjIyMjIyMjIyMjIyMzIEVqZXJjaWNpb3Mgc29icmUgcmVzdHJpY2Npb25lcwoKcXVhdF91dF8zNSA8LSBQb3J0Zm9saW9BbmFseXRpY3M6OnBvcnRmb2xpby5zcGVjKGFzc2V0cyA9IHN5bWJvbHMpCgpxdWF0X3V0XzM1IDwtIFBvcnRmb2xpb0FuYWx5dGljczo6YWRkLmNvbnN0cmFpbnQoCiAgcG9ydGZvbGlvID0gcXVhdF91dF8zNSwKICB0eXBlID0gImZ1bGxfaW52ZXN0bWVudCIKKQoKIyBubyBwdWVkZXMgaW52ZXJ0aXIgbT9zIGRlIDI1JQoKcXVhdF91dF8zNSA8LSBQb3J0Zm9saW9BbmFseXRpY3M6OmFkZC5jb25zdHJhaW50KAogIHBvcnRmb2xpbyA9IHF1YXRfdXRfMzUsCiAgdHlwZSA9ICJib3giLCBtaW4gPSAwLjA1LCBtYXggPSAuMTcKKQoKIyMgVHJhYmFqYXIgbT9zIGEgZGV0YWxsZQoKcXVhdF91dF8zNSA8LSBhZGQuY29uc3RyYWludChwb3J0Zm9saW89cXVhdF91dF8zNSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0eXBlPSJncm91cCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3JvdXBzPWxpc3QoYygyLCAzLCA0LCA1LCA2LCA3LCA4LCAxMSksIGMoMSwgOSwgMTApKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3JvdXBfbWluPWMoMC4wLCAwLjApLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdyb3VwX21heD1jKC42MCwgMC40KSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBncm91cF9sYWJlbHM9YygiRVNHIiwgIk5PTi1FU0ciKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBncm91cF9wb3M9YygxLCAyKSkgI3NlIGFybWFuIGdydXBvcyBkZXBlbmRpZW5kbyBkZSBsYXMgYWNjaW9uZXMKCnF1YXRfdXRfMzUgPC0gUG9ydGZvbGlvQW5hbHl0aWNzOjphZGQuY29uc3RyYWludCgKICBwb3J0Zm9saW8gPSBxdWF0X3V0XzM1LAogIHR5cGUgPSAid2VpZ2h0X3N1bSIsCiAgbWluX3N1bT0xLjAwLAogIG1heF9zdW09MS4wMAopCgpxdWF0X3V0XzM1IDwtIFBvcnRmb2xpb0FuYWx5dGljczo6YWRkLm9iamVjdGl2ZShxdWF0X3V0XzM1LCB0eXBlPSJxdWFkcmF0aWNfdXRpbGl0eSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJpc2tfYXZlcnNpb249MikKCm9wdF9xdWFkXzM1IDwtIG9wdGltaXplLnBvcnRmb2xpby5yZWJhbGFuY2luZyhhc3NldF9yZXR1cm5zX3h0cywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHF1YXRfdXRfMzUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvcHRpbWl6ZV9tZXRob2QgPSAiUk9JIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlYmFsYW5jZV9vbj0ibW9udGhzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRyYWluaW5nX3BlcmlvZCA9IDEyLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcm9sbGluZ193aW5kb3c9MjQpCgpvcHRfcXVhZF8zNV9sYXN0PC0gIFBvcnRmb2xpb0FuYWx5dGljczo6b3B0aW1pemUucG9ydGZvbGlvKAogIFIgPSBhc3NldF9yZXR1cm5zX3h0cywKICBwb3J0Zm9saW8gPSBxdWF0X3V0XzM1LAogIG9wdGltaXplX21ldGhvZCA9ICJxdWFkcHJvZyIsCiAgI21vbWVudGFyZ3M9bW9tZW50YXJncywKICAjIFJlZ3Jlc2EgY29uIGluZm9ybWFjaT9uIGFkaWNpb25hbAogIHRyYWNlID0gVFJVRQopCgoKYGBgCgojIyBHcsOhZmljYQpgYGB7cn0KI2Rlc2NyaXB0b3JlcwpjaGFydC5XZWlnaHRzKG9wdF9xdWFkXzM1LCBjb2xvcnNldD1icmV3ZXIucGFsKDExLCAiUGFpcmVkIikpCgpgYGAKCiMjIEdydXBvcwpgYGB7cn0KKGV4dHJhY3RHcm91cHMob3B0X3F1YWRfMzVfbGFzdCkpCmBgYAoKIyMgQmFja3Rlc3QgbWVuc3VhbCBhIDMgYcOxb3MKYGBge3J9CihiYWNrdGVzdF9xdWFkXzM1IDwtIFJldHVybi5wb3J0Zm9saW8oYXNzZXRfcmV0dXJuc194dHMsIGV4dHJhY3RXZWlnaHRzKChvcHRfcXVhZF8zNSkpKSkKCgojIFJlbmRpbWllbnRvIHZzIFMmUDUwMApjb21wIDwtIGNiaW5kKGJhY2t0ZXN0X3F1YWRfMzUsIFNQWV9yZXR1cm5zX3h0cykKCmNoYXJ0cy5QZXJmb3JtYW5jZVN1bW1hcnkoY29tcCkKYGBgCgoK