Introduction

Cuốn Risk Management in Banking của Joël Bessis đã được tái bản lần thứ 4. Ở Việt Nam cuốn này được dịch với tên gọi Quản Trị Rủi Ro Trong Ngân Hàng tương ứng với lần tái bản thứ 3.

Khi làm các mô hình Credit Risk và Scorecard thì một trong những thước đo thường được sử dụng để đánh giá chất lượng mô hình phân loại - xếp hạng là Cumulative Accuracy Profile (CAP) và cuốn sách dành hẳn một mục ở chương 20 để nói về CAP như sau:

Đây là một cuốn sách hay và chất lượng cho những ai làm mảng Credit Risk trong ngân hàng và các tổ chức tài chính. Tuy vậy cuốn sách thuộc nhóm sách trình bày ý tưởng mà không hề có bất kì phần thực hành nào. Rất may là khái niệm về CAP này đã được mô tả chi tiết hơn cũng như kèm với phần thực hành bằng Excel - VBA tại trang 182 của cuốn Credit Risk Modeling using Excel and VBA như sau:

Đường CAP thường bị nhầm lẫn với ROC (Receiver Operating Characteristics) vì cả hai đường này có nhiều điểm chung về mặt ý nghĩa. Chẳng hạn, một mô hình có khả năng phân biệt càng tốt thì đường cong CAP và ROC càng lồi lên phía trên và trong tình huống một mô hình hoàn hảo thì diện tích nằm dưới CAP và ROC đều bằng 1.

CAP Curve and Accuracy Ratio (AR) Estimation

Trước hết chúng ta tái lập lại Table 8.1 của cuốn Credit Risk Modeling using Excel and VBA như sau:

# Load các package: 
library(tidyverse)
library(magrittr)

# Bộ dữ liệu minh họa được trình bày ở Table 8.1: 
df_example <- data.frame(Group = c("C", "B", "A"),
                         Bad = c(3, 1, 0), 
                         Good = c(1, 2, 3))


# Tính toán các thứ linh tinh khác và thêm vào điểm có tọa độ (0, 0): 
df_example %>% 
  mutate(Total_gr = Bad + Good, Total_obs = sum(Total_gr)) %>% 
  mutate(Per_gr = Total_gr / Total_obs) %>% 
  mutate(Per_cum = cumsum(Per_gr)) %>% 
  mutate(BadRate = Bad / sum(Bad)) %>% 
  mutate(BadRateCum = cumsum(BadRate)) -> df_cap_example


# Vẽ CAP: 
data.frame(Per_cum = c(0, df_cap_example$Per_cum), 
           BadRateCum = c(0, df_cap_example$BadRateCum)) -> df1


theme_set(theme_minimal())

df1 %>% 
  ggplot(aes(Per_cum, BadRateCum)) + 
  geom_line(color = "blue", size = 1.3) + 
  geom_point(color = "red", size = 2) + 
  labs(x = NULL, 
       title = "An Illustration of CAP Based on Data From Table 8.1") + 
  scale_y_continuous(labels = scales::percent)

Tương tự chúng ta có thể vẽ đường CAP với một bộ số liệu thực:

# Import data: 
hmeq <- read.csv("http://www.creditriskanalytics.net/uploads/1/9/5/1/19511601/hmeq.csv")

# Function replaces NA by mean: 
replace_by_mean <- function(x) {
  x[is.na(x)] <- mean(x, na.rm = TRUE)
  return(x)
}

# A function imputes NA observations for categorical variables: 

replace_na_categorical <- function(x) {
  x %>% 
    table() %>% 
    as.data.frame() %>% 
    arrange(-Freq) ->> my_df
  
  n_obs <- sum(my_df$Freq)
  pop <- my_df$. %>% as.character()
  set.seed(29)
  x[is.na(x)] <- sample(pop, sum(is.na(x)), replace = TRUE, prob = my_df$Freq)
  return(x)
}

# Use the two functions: 
df <- hmeq %>% 
  mutate_if(is.factor, as.character) %>% 
  mutate(REASON = case_when(REASON == "" ~ NA_character_, TRUE ~ REASON), 
         JOB = case_when(JOB == "" ~ NA_character_, TRUE ~ JOB)) %>%
  mutate_if(is_character, as.factor) %>% 
  mutate_if(is.numeric, replace_by_mean) %>% 
  mutate_if(is.factor, replace_na_categorical)


# Phân chia dữ liệu: 

set.seed(2)
df_train <- df %>% group_by(BAD) %>% sample_frac(0.5, replace = FALSE)
df_test <- setdiff(df, df_train)


# Thực hiện Logistic: 
my_logistic <- glm(BAD ~ ., family = "binomial", data = df_train)

# PD từ mô hình Logistic: 
pd_logistic <- predict(my_logistic, df_test %>% select(-BAD), type = "response")

Kế tiếp là vẽ CAP (tham khảo kĩ hơn một trong hai cuốn sách ở trên về CAP Curve nếu thấy cần thiết ):

df_test %>% 
  mutate(PD = pd_logistic, BAD = case_when(BAD == 1 ~ "Bad", TRUE ~ "Good")) %>% 
  select(BAD, PD) %>% 
  mutate(Group = cut_interval(PD, 10)) %>% 
  group_by(Group, BAD) %>% 
  count() %>% 
  ungroup() %>% 
  spread(key = "BAD", value = "n") %>% 
  mutate(Good = replace_na(Good, 0)) %>% 
  mutate(RankRisk = 1:nrow(.)) %>% 
  arrange(-RankRisk) %>% 
  mutate(Total_gr = Bad + Good) %>% 
  mutate(Total_obs = sum(Total_gr)) %>% 
  mutate(BadRate = Bad / sum(Bad)) %>% 
  mutate(BadRateCum = cumsum(BadRate)) %>% 
  mutate(Per_gr = Total_gr / Total_obs)  %>% 
  mutate(Per_cum = cumsum(Per_gr)) %>% 
  mutate(Rate = Bad / Total_gr) -> df_cap


df_cap %>% 
  ggplot(aes(Per_cum, BadRateCum)) + 
  geom_line(color = "blue", size = 1.3) + 
  geom_point(color = "red", size = 2) + 
  labs(x = NULL, 
       title = "CAP for Logistic Model", 
       caption = "Data Source: http://www.creditriskanalytics.net/uploads/1/9/5/1/19511601/hmeq.csv") + 
  scale_y_continuous(labels = scales::percent)

Accuracy Ratio (AR) là một chỉ tiêu được ước lượng dựa trên CAP bằng hàm ARestimate():

mini_df <- df_cap %>% 
  select(Rate, Total_gr, RankRisk) %>% 
  arrange(-Rate)

library(LDPD)
ARestimate(mini_df$Rate, mini_df$Total_gr, rating.type = "RATING")
## $AR
## [1] 0.5514627
## 
## $CT
## [1] 0.1996644

Theo thông lệ, một mô hình được gọi là chấp nhận được nếu AR cao hơn 30%. Trong trường hợp này thì mô hình Logistic là xài được vì có AR là 55%.

References

Baesens, B., Roesch, D., & Scheule, H. (2016). Credit risk analytics: Measurement techniques, applications, and examples in SAS. John Wiley & Sons.

Bessis, J. (2015). Risk management in banking. John Wiley & Sons.

Löeffler, G., & Posch, P. N. (2011). Credit risk modeling using Excel and VBA. John Wiley & Sons.

Tasche, D. (2009) Estimating discriminatory power and PD curves when the number of defaults is small. Working paper, Lloyds Banking Group. Tasche, D. (2013) The art of probability-of-default curve calibration. Journal of Credit Risk, 9:63-103.

LS0tDQp0aXRsZTogIkN1bXVsYXRpdmUgQWNjdXJhY3kgUHJvZmlsZSAoQ0FQKSBhbmQgQWNjdXJhY3kgUmF0aW8gKEFSKSBFc3RpbWF0aW9uIiANCnN1YnRpdGxlOiAiUiBmb3IgRnVuIg0KYXV0aG9yOiAiTmd1eWVuIENoaSBEdW5nIg0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50OiANCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQogICAgIyBjb2RlX2ZvbGRpbmc6IGhpZGUNCiAgICBoaWdobGlnaHQ6IHB5Z21lbnRzDQogICAgIyBudW1iZXJfc2VjdGlvbnM6IHllcw0KICAgIHRoZW1lOiAiZmxhdGx5Ig0KICAgIHRvYzogVFJVRQ0KICAgIHRvY19mbG9hdDogVFJVRQ0KLS0tDQoNCmBgYHtyIHNldHVwLGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFKQ0KYGBgDQoNCiMgSW50cm9kdWN0aW9uDQoNCkN14buRbiBbUmlzayBNYW5hZ2VtZW50IGluIEJhbmtpbmddKGh0dHBzOi8vd3d3LmFtYXpvbi5jb20vUmlzay1NYW5hZ2VtZW50LUJhbmtpbmctV2lsZXktRmluYW5jZS9kcC8xMTE4NjYwMjE4KSBj4bunYSBKb8OrbCBCZXNzaXMgxJHDoyDEkcaw4bujYyB0w6FpIGLhuqNuIGzhuqduIHRo4bupIDQuIOG7niBWaeG7h3QgTmFtIGN14buRbiBuw6B5IMSRxrDhu6NjIGThu4tjaCB24bubaSB0w6puIGfhu41pIFtRdeG6o24gVHLhu4sgUuG7p2kgUm8gVHJvbmcgTmfDom4gSMOgbmddKGh0dHA6Ly9tdWFzYWNoaGF5LnZuL3Nhbi1waGFtL3F1YW4tdHJpLXJ1aS1yby10cm9uZy1uZ2FuLWhhbmcvKSB0xrDGoW5nIOG7qW5nIHbhu5tpIGzhuqduIHTDoWkgYuG6o24gdGjhu6kgMy4gDQoNCktoaSBsw6BtIGPDoWMgbcO0IGjDrG5oIENyZWRpdCBSaXNrIHbDoCBTY29yZWNhcmQgdGjDrCBt4buZdCB0cm9uZyBuaOG7r25nIHRoxrDhu5tjIMSRbyB0aMaw4budbmcgxJHGsOG7o2Mgc+G7rSBk4bulbmcgxJHhu4MgxJHDoW5oIGdpw6EgY2jhuqV0IGzGsOG7o25nIG3DtCBow6xuaCBwaMOibiBsb+G6oWkgLSB44bq/cCBo4bqhbmcgbMOgIEN1bXVsYXRpdmUgQWNjdXJhY3kgUHJvZmlsZSAoQ0FQKSB2w6AgY3Xhu5FuIHPDoWNoIGTDoG5oIGjhurNuIG3hu5l0IG3hu6VjIOG7nyBjaMawxqFuZyAyMCDEkeG7gyBuw7NpIHbhu4EgQ0FQIG5oxrAgc2F1OiANCg0KIVtdKEM6XFxVc2Vyc1xcWmJvb2tcXERlc2t0b3BcXHBpY1xccmlzazEucG5nKQ0KDQrEkMOieSBsw6AgbeG7mXQgY3Xhu5FuIHPDoWNoIGhheSB2w6AgY2jhuqV0IGzGsOG7o25nIGNobyBuaOG7r25nIGFpIGzDoG0gbeG6o25nIENyZWRpdCBSaXNrIHRyb25nIG5nw6JuIGjDoG5nIHbDoCBjw6FjIHThu5UgY2jhu6ljIHTDoGkgY2jDrW5oLiBUdXkgduG6rXkgY3Xhu5FuIHPDoWNoIHRodeG7mWMgbmjDs20gc8OhY2ggdHLDrG5oIGLDoHkgw70gdMaw4bufbmcgbcOgIGtow7RuZyBo4buBIGPDsyBi4bqldCBrw6wgcGjhuqduIHRo4buxYyBow6BuaCBuw6BvLiBS4bqldCBtYXkgbMOgIGtow6FpIG5p4buHbSB24buBIENBUCBuw6B5IMSRw6MgxJHGsOG7o2MgbcO0IHThuqMgY2hpIHRp4bq/dCBoxqFuIGPFqW5nIG5oxrAga8OobSB24bubaSBwaOG6p24gdGjhu7FjIGjDoG5oIGLhurFuZyBFeGNlbCAtIFZCQSB04bqhaSB0cmFuZyAxODIgY+G7p2EgY3Xhu5FuIFtDcmVkaXQgUmlzayBNb2RlbGluZyB1c2luZyBFeGNlbCBhbmQgVkJBXShodHRwczovL3d3dy53aWxleS5jb20vZW4tdXMvQ3JlZGl0K1Jpc2srTW9kZWxpbmcrdXNpbmcrRXhjZWwrYW5kK1ZCQSUyQysybmQrRWRpdGlvbi1wLTk3ODA0NzA2NjA5MjgpIG5oxrAgc2F1OiANCg0KIVtdKEM6XFxVc2Vyc1xcWmJvb2tcXERlc2t0b3BcXHBpY1xccmlzazIucG5nKQ0KDQrEkMaw4budbmcgQ0FQIHRoxrDhu51uZyBi4buLIG5o4bqnbSBs4bqrbiB24bubaSBST0MgKFJlY2VpdmVyIE9wZXJhdGluZyBDaGFyYWN0ZXJpc3RpY3MpIHbDrCBj4bqjIGhhaSDEkcaw4budbmcgbsOgeSBjw7Mgbmhp4buBdSDEkWnhu4NtIGNodW5nIHbhu4EgbeG6t3Qgw70gbmdoxKlhLiBDaOG6s25nIGjhuqFuLCBt4buZdCBtw7QgaMOsbmggY8OzIGto4bqjIG7Eg25nIHBow6JuIGJp4buHdCBjw6BuZyB04buRdCB0aMOsIMSRxrDhu51uZyBjb25nIENBUCB2w6AgUk9DIGPDoG5nIGzhu5NpIGzDqm4gcGjDrWEgdHLDqm4gdsOgIHRyb25nIHTDrG5oIGh14buRbmcgbeG7mXQgbcO0IGjDrG5oIGhvw6BuIGjhuqNvIHRow6wgZGnhu4duIHTDrWNoIG7hurFtIGTGsOG7m2kgQ0FQIHbDoCBST0MgxJHhu4F1IGLhurFuZyAxLiANCg0KDQojIENBUCBDdXJ2ZSBhbmQgQWNjdXJhY3kgUmF0aW8gKEFSKSBFc3RpbWF0aW9uIA0KDQpUcsaw4bubYyBo4bq/dCBjaMO6bmcgdGEgdMOhaSBs4bqtcCBs4bqhaSBUYWJsZSA4LjEgY+G7p2EgY3Xhu5FuICBDcmVkaXQgUmlzayBNb2RlbGluZyB1c2luZyBFeGNlbCBhbmQgVkJBIG5oxrAgc2F1OiANCg0KDQpgYGB7cn0NCiMgTG9hZCBjw6FjIHBhY2thZ2U6IA0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KG1hZ3JpdHRyKQ0KDQojIELhu5kgZOG7ryBsaeG7h3UgbWluaCBo4buNYSDEkcaw4bujYyB0csOsbmggYsOgeSDhu58gVGFibGUgOC4xOiANCmRmX2V4YW1wbGUgPC0gZGF0YS5mcmFtZShHcm91cCA9IGMoIkMiLCAiQiIsICJBIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgQmFkID0gYygzLCAxLCAwKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgR29vZCA9IGMoMSwgMiwgMykpDQoNCg0KIyBUw61uaCB0b8OhbiBjw6FjIHRo4bupIGxpbmggdGluaCBraMOhYyB2w6AgdGjDqm0gdsOgbyDEkWnhu4NtIGPDsyB04buNYSDEkeG7mSAoMCwgMCk6IA0KZGZfZXhhbXBsZSAlPiUgDQogIG11dGF0ZShUb3RhbF9nciA9IEJhZCArIEdvb2QsIFRvdGFsX29icyA9IHN1bShUb3RhbF9ncikpICU+JSANCiAgbXV0YXRlKFBlcl9nciA9IFRvdGFsX2dyIC8gVG90YWxfb2JzKSAlPiUgDQogIG11dGF0ZShQZXJfY3VtID0gY3Vtc3VtKFBlcl9ncikpICU+JSANCiAgbXV0YXRlKEJhZFJhdGUgPSBCYWQgLyBzdW0oQmFkKSkgJT4lIA0KICBtdXRhdGUoQmFkUmF0ZUN1bSA9IGN1bXN1bShCYWRSYXRlKSkgLT4gZGZfY2FwX2V4YW1wbGUNCg0KDQojIFbhur0gQ0FQOiANCmRhdGEuZnJhbWUoUGVyX2N1bSA9IGMoMCwgZGZfY2FwX2V4YW1wbGUkUGVyX2N1bSksIA0KICAgICAgICAgICBCYWRSYXRlQ3VtID0gYygwLCBkZl9jYXBfZXhhbXBsZSRCYWRSYXRlQ3VtKSkgLT4gZGYxDQoNCg0KdGhlbWVfc2V0KHRoZW1lX21pbmltYWwoKSkNCg0KZGYxICU+JSANCiAgZ2dwbG90KGFlcyhQZXJfY3VtLCBCYWRSYXRlQ3VtKSkgKyANCiAgZ2VvbV9saW5lKGNvbG9yID0gImJsdWUiLCBzaXplID0gMS4zKSArIA0KICBnZW9tX3BvaW50KGNvbG9yID0gInJlZCIsIHNpemUgPSAyKSArIA0KICBsYWJzKHggPSBOVUxMLCANCiAgICAgICB0aXRsZSA9ICJBbiBJbGx1c3RyYXRpb24gb2YgQ0FQIEJhc2VkIG9uIERhdGEgRnJvbSBUYWJsZSA4LjEiKSArIA0KICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpwZXJjZW50KQ0KYGBgDQoNCg0KVMawxqFuZyB04buxIGNow7puZyB0YSBjw7MgdGjhu4MgduG6vSDEkcaw4budbmcgQ0FQIHbhu5tpIG3hu5l0IGLhu5kgc+G7kSBsaeG7h3UgdGjhu7FjOiANCg0KYGBge3J9DQojIEltcG9ydCBkYXRhOiANCmhtZXEgPC0gcmVhZC5jc3YoImh0dHA6Ly93d3cuY3JlZGl0cmlza2FuYWx5dGljcy5uZXQvdXBsb2Fkcy8xLzkvNS8xLzE5NTExNjAxL2htZXEuY3N2IikNCg0KIyBGdW5jdGlvbiByZXBsYWNlcyBOQSBieSBtZWFuOiANCnJlcGxhY2VfYnlfbWVhbiA8LSBmdW5jdGlvbih4KSB7DQogIHhbaXMubmEoeCldIDwtIG1lYW4oeCwgbmEucm0gPSBUUlVFKQ0KICByZXR1cm4oeCkNCn0NCg0KIyBBIGZ1bmN0aW9uIGltcHV0ZXMgTkEgb2JzZXJ2YXRpb25zIGZvciBjYXRlZ29yaWNhbCB2YXJpYWJsZXM6IA0KDQpyZXBsYWNlX25hX2NhdGVnb3JpY2FsIDwtIGZ1bmN0aW9uKHgpIHsNCiAgeCAlPiUgDQogICAgdGFibGUoKSAlPiUgDQogICAgYXMuZGF0YS5mcmFtZSgpICU+JSANCiAgICBhcnJhbmdlKC1GcmVxKSAtPj4gbXlfZGYNCiAgDQogIG5fb2JzIDwtIHN1bShteV9kZiRGcmVxKQ0KICBwb3AgPC0gbXlfZGYkLiAlPiUgYXMuY2hhcmFjdGVyKCkNCiAgc2V0LnNlZWQoMjkpDQogIHhbaXMubmEoeCldIDwtIHNhbXBsZShwb3AsIHN1bShpcy5uYSh4KSksIHJlcGxhY2UgPSBUUlVFLCBwcm9iID0gbXlfZGYkRnJlcSkNCiAgcmV0dXJuKHgpDQp9DQoNCiMgVXNlIHRoZSB0d28gZnVuY3Rpb25zOiANCmRmIDwtIGhtZXEgJT4lIA0KICBtdXRhdGVfaWYoaXMuZmFjdG9yLCBhcy5jaGFyYWN0ZXIpICU+JSANCiAgbXV0YXRlKFJFQVNPTiA9IGNhc2Vfd2hlbihSRUFTT04gPT0gIiIgfiBOQV9jaGFyYWN0ZXJfLCBUUlVFIH4gUkVBU09OKSwgDQogICAgICAgICBKT0IgPSBjYXNlX3doZW4oSk9CID09ICIiIH4gTkFfY2hhcmFjdGVyXywgVFJVRSB+IEpPQikpICU+JQ0KICBtdXRhdGVfaWYoaXNfY2hhcmFjdGVyLCBhcy5mYWN0b3IpICU+JSANCiAgbXV0YXRlX2lmKGlzLm51bWVyaWMsIHJlcGxhY2VfYnlfbWVhbikgJT4lIA0KICBtdXRhdGVfaWYoaXMuZmFjdG9yLCByZXBsYWNlX25hX2NhdGVnb3JpY2FsKQ0KDQoNCiMgUGjDom4gY2hpYSBk4buvIGxp4buHdTogDQoNCnNldC5zZWVkKDIpDQpkZl90cmFpbiA8LSBkZiAlPiUgZ3JvdXBfYnkoQkFEKSAlPiUgc2FtcGxlX2ZyYWMoMC41LCByZXBsYWNlID0gRkFMU0UpDQpkZl90ZXN0IDwtIHNldGRpZmYoZGYsIGRmX3RyYWluKQ0KDQoNCiMgVGjhu7FjIGhp4buHbiBMb2dpc3RpYzogDQpteV9sb2dpc3RpYyA8LSBnbG0oQkFEIH4gLiwgZmFtaWx5ID0gImJpbm9taWFsIiwgZGF0YSA9IGRmX3RyYWluKQ0KDQojIFBEIHThu6sgbcO0IGjDrG5oIExvZ2lzdGljOiANCnBkX2xvZ2lzdGljIDwtIHByZWRpY3QobXlfbG9naXN0aWMsIGRmX3Rlc3QgJT4lIHNlbGVjdCgtQkFEKSwgdHlwZSA9ICJyZXNwb25zZSIpDQpgYGANCg0KS+G6vyB0aeG6v3AgbMOgIHbhur0gQ0FQICh0aGFtIGto4bqjbyBrxKkgaMahbiBt4buZdCB0cm9uZyBoYWkgY3Xhu5FuIHPDoWNoIOG7nyB0csOqbiB24buBIENBUCBDdXJ2ZSBu4bq/dSB0aOG6pXkgY+G6p24gdGhp4bq/dCApOiANCg0KYGBge3J9DQpkZl90ZXN0ICU+JSANCiAgbXV0YXRlKFBEID0gcGRfbG9naXN0aWMsIEJBRCA9IGNhc2Vfd2hlbihCQUQgPT0gMSB+ICJCYWQiLCBUUlVFIH4gIkdvb2QiKSkgJT4lIA0KICBzZWxlY3QoQkFELCBQRCkgJT4lIA0KICBtdXRhdGUoR3JvdXAgPSBjdXRfaW50ZXJ2YWwoUEQsIDEwKSkgJT4lIA0KICBncm91cF9ieShHcm91cCwgQkFEKSAlPiUgDQogIGNvdW50KCkgJT4lIA0KICB1bmdyb3VwKCkgJT4lIA0KICBzcHJlYWQoa2V5ID0gIkJBRCIsIHZhbHVlID0gIm4iKSAlPiUgDQogIG11dGF0ZShHb29kID0gcmVwbGFjZV9uYShHb29kLCAwKSkgJT4lIA0KICBtdXRhdGUoUmFua1Jpc2sgPSAxOm5yb3coLikpICU+JSANCiAgYXJyYW5nZSgtUmFua1Jpc2spICU+JSANCiAgbXV0YXRlKFRvdGFsX2dyID0gQmFkICsgR29vZCkgJT4lIA0KICBtdXRhdGUoVG90YWxfb2JzID0gc3VtKFRvdGFsX2dyKSkgJT4lIA0KICBtdXRhdGUoQmFkUmF0ZSA9IEJhZCAvIHN1bShCYWQpKSAlPiUgDQogIG11dGF0ZShCYWRSYXRlQ3VtID0gY3Vtc3VtKEJhZFJhdGUpKSAlPiUgDQogIG11dGF0ZShQZXJfZ3IgPSBUb3RhbF9nciAvIFRvdGFsX29icykgICU+JSANCiAgbXV0YXRlKFBlcl9jdW0gPSBjdW1zdW0oUGVyX2dyKSkgJT4lIA0KICBtdXRhdGUoUmF0ZSA9IEJhZCAvIFRvdGFsX2dyKSAtPiBkZl9jYXANCg0KDQpkZl9jYXAgJT4lIA0KICBnZ3Bsb3QoYWVzKFBlcl9jdW0sIEJhZFJhdGVDdW0pKSArIA0KICBnZW9tX2xpbmUoY29sb3IgPSAiYmx1ZSIsIHNpemUgPSAxLjMpICsgDQogIGdlb21fcG9pbnQoY29sb3IgPSAicmVkIiwgc2l6ZSA9IDIpICsgDQogIGxhYnMoeCA9IE5VTEwsIA0KICAgICAgIHRpdGxlID0gIkNBUCBmb3IgTG9naXN0aWMgTW9kZWwiLCANCiAgICAgICBjYXB0aW9uID0gIkRhdGEgU291cmNlOiBodHRwOi8vd3d3LmNyZWRpdHJpc2thbmFseXRpY3MubmV0L3VwbG9hZHMvMS85LzUvMS8xOTUxMTYwMS9obWVxLmNzdiIpICsgDQogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnQpDQpgYGANCg0KQWNjdXJhY3kgUmF0aW8gKEFSKSBsw6AgbeG7mXQgY2jhu4kgdGnDqnUgxJHGsOG7o2MgxrDhu5tjIGzGsOG7o25nIGThu7FhIHRyw6puIENBUCBi4bqxbmcgaMOgbSAqQVJlc3RpbWF0ZSgpKjogDQoNCmBgYHtyfQ0KbWluaV9kZiA8LSBkZl9jYXAgJT4lIA0KICBzZWxlY3QoUmF0ZSwgVG90YWxfZ3IsIFJhbmtSaXNrKSAlPiUgDQogIGFycmFuZ2UoLVJhdGUpDQoNCmxpYnJhcnkoTERQRCkNCkFSZXN0aW1hdGUobWluaV9kZiRSYXRlLCBtaW5pX2RmJFRvdGFsX2dyLCByYXRpbmcudHlwZSA9ICJSQVRJTkciKQ0KYGBgDQoNClRoZW8gdGjDtG5nIGzhu4csIG3hu5l0IG3DtCBow6xuaCDEkcaw4bujYyBn4buNaSBsw6AgY2jhuqVwIG5o4bqtbiDEkcaw4bujYyBu4bq/dSBBUiBjYW8gaMahbiAzMCUuIFRyb25nIHRyxrDhu51uZyBo4bujcCBuw6B5IHRow6wgbcO0IGjDrG5oIExvZ2lzdGljIGzDoCB4w6BpIMSRxrDhu6NjIHbDrCBjw7MgQVIgbMOgIDU1JS4gDQoNCiMgUmVmZXJlbmNlcw0KDQpCYWVzZW5zLCBCLiwgUm9lc2NoLCBELiwgJiBTY2hldWxlLCBILiAoMjAxNikuIENyZWRpdCByaXNrIGFuYWx5dGljczogTWVhc3VyZW1lbnQgdGVjaG5pcXVlcywgYXBwbGljYXRpb25zLCBhbmQgZXhhbXBsZXMgaW4gU0FTLiBKb2huIFdpbGV5ICYgU29ucy4NCg0KQmVzc2lzLCBKLiAoMjAxNSkuIFJpc2sgbWFuYWdlbWVudCBpbiBiYW5raW5nLiBKb2huIFdpbGV5ICYgU29ucy4NCg0KTMO2ZWZmbGVyLCBHLiwgJiBQb3NjaCwgUC4gTi4gKDIwMTEpLiBDcmVkaXQgcmlzayBtb2RlbGluZyB1c2luZyBFeGNlbCBhbmQgVkJBLiBKb2huIFdpbGV5ICYgU29ucy4NCg0KVGFzY2hlLCBELiAoMjAwOSkgRXN0aW1hdGluZyBkaXNjcmltaW5hdG9yeSBwb3dlciBhbmQgUEQgY3VydmVzIHdoZW4gdGhlIG51bWJlciBvZiBkZWZhdWx0cyBpcyBzbWFsbC4gV29ya2luZyBwYXBlciwgTGxveWRzIEJhbmtpbmcgR3JvdXAuIFRhc2NoZSwgRC4gKDIwMTMpIFRoZSBhcnQgb2YgcHJvYmFiaWxpdHktb2YtZGVmYXVsdCBjdXJ2ZSBjYWxpYnJhdGlvbi4gSm91cm5hbCBvZiBDcmVkaXQgUmlzaywgOTo2My0xMDMuDQo=