Motivations
Hiện tại R có rất nhiều Packages cho phân tích chuỗi dữ liệu thời gian (Time Series) và một trong số đó là tidyquant được viết bởi Joshua Ulrich.
Đối với điều kiện của Việt Nam, để sử dụng được các hàm của gói này chẳng hạn thì khó khăn đầu tiên là việc lấy dữ liệu của các công ti niêm yết.
Giải quyết vấn đề này bạn Khánh đã viết hàm getSymbols() và hiện này đã là một bộ phận của VNDS package. Tuy nhiên package này hoạt động không ổn định và sẽ không sử dụng được trong một số tình huống. Giải quyết triệt để vấn đề này có lẽ cần phải lấy dữ liệu qua giao thức API nhưng việc này có thể cần thời gian làm việc với VNDIRECT (và có thể phải viết lại hoàn toàn package này).
Mặc dù R và R packages đều công khai mã nguồn nhưng sẽ là hợp lí hơn khi chúng ta không sử dụng nguyên các hàm mà các tác giả khác đã viết, hoặc viết một hàm mà công năng phân tích không có gì mới hoặc không bằng cái đã có. Hàm getSymbols() của package VNDS cũng là một hàm của tidyquant. Ý tưởng của hàm getSymbols() (của package tidyquant) chúng ta có thể tiếp thu nhưng lấy luôn cả tên hàm là điều cần cân nhắc vì nhiều lí do.
Vì lí do đó, tôi viết một hàm có tên get_transData_VND() để lấy thông tin giao dịch của một mã cổ phiếu với đầu vào là:
- Mã cổ phiếu được chọn,
- Ngày bắt đầu lấy dữ liệu,
- Ngày cuối cùng lấy dữ liệu.
Kết quả của hàm này là một data frame/tibble và với dữ liệu thu được chúng ta có thể áp dụng tất cả các hàm, các mô hình phân tích cho Time Series của gói tidyquant.
# Clear work space:
rm(list = ls())
# Load some R packages:
library(rvest)
library(tidyverse)
library(lubridate)
#===============================================================
# Function collect all transaction data with inputs are:
# (1) ticker selected, (2) start date, and end date.
#===============================================================
get_transData_VND <- function(symbol, from_Date, end_Date) {
#-------------------------------------------------------------------------
# Stage 1: Create HTML form, send request and collect transaction data
# and collects stock transactions for a specific page selected
#-------------------------------------------------------------------------
url <- "https://www.vndirect.com.vn/portal/thong-ke-thi-truong-chung-khoan/lich-su-gia.shtml"
page_session <- html_session(url)
pgform <- html_form(page_session) %>% .[[1]]
get_transaction_data <- function(page_index) {
filled_form <- set_values(form = pgform,
pagingInfo.indexPage = page_index,
searchMarketStatisticsView.symbol = symbol,
strFromDate = from_Date,
strToDate = end_Date)
submit_form(page_session, filled_form) %>%
read_html() -> page_content
page_content %>%
html_nodes(".lichsugia div") %>%
html_text() %>%
matrix(ncol = 10, byrow = TRUE) %>%
data.frame() %>%
slice(-1) %>%
select(-2) %>%
mutate(X1 = as.character(X1)) %>%
mutate(X1 = str_replace_all(X1, "[^0-9]", "")) %>%
mutate(X1 = ymd(X1)) %>%
mutate_if(is.factor, function(x) {as.character(x)}) %>%
mutate_if(is.character, function(x) {as.numeric(x)}) -> df_price
names(df_price) <- c("date", "open", "high", "low", "close",
"average", "adjusted", "volume", "reconcile_volume")
return(df_price)
}
#------------------------------------------------------------------
# Stage 2: Extract the last page and return all transaction data
#------------------------------------------------------------------
filled_form_for_pages <- set_values(form = pgform,
searchMarketStatisticsView.symbol = symbol,
strFromDate = from_Date,
strToDate = end_Date)
submit_form(page_session, filled_form_for_pages) %>%
read_html() -> page_content_pages
page_content_pages %>%
html_nodes(".paging") %>%
html_text() %>%
str_extract("/[0-9]{1,100}") %>%
str_replace_all("[^0-9]", "") %>%
as.numeric() -> n_pages
if (is.na(n_pages)) {
final_df <- get_transaction_data(page_index = 1) %>%
mutate(symbol = str_to_upper(symbol)) %>%
select(symbol, everything())
} else {
lapply(1:n_pages, get_transaction_data) -> all_data_list
final_df <- do.call("bind_rows", all_data_list) %>%
mutate(symbol = str_to_upper(symbol)) %>%
select(symbol, everything())
}
return(final_df)
}
# Use the function:
symbol <- "vnm"
from_Date <- "01/01/2018"
end_Date <- "01/02/2019"
get_transData_VND(symbol = symbol, from_Date = from_Date, end_Date = end_Date) -> df_vnm
# Results by data frame:
df_vnm %>% head()
## symbol date open high low close average adjusted volume
## 1 VNM 2019-02-01 135.8 135.8 133.0 135.0 134.40 133.425 731430
## 2 VNM 2019-01-31 136.5 136.5 133.5 135.0 135.56 133.425 812290
## 3 VNM 2019-01-30 136.6 136.6 135.5 135.5 135.92 133.920 279280
## 4 VNM 2019-01-29 137.0 137.0 135.5 136.5 136.09 134.908 371010
## 5 VNM 2019-01-28 136.5 136.6 135.9 136.5 136.22 134.908 487650
## 6 VNM 2019-01-25 136.0 136.3 135.6 135.9 136.06 134.315 813810
## reconcile_volume
## 1 37520
## 2 350000
## 3 108420
## 4 27000
## 5 128480
## 6 162260

Applications
Sau khi đã có data chúng ta có thể sử dụng tidyquant cho phân tích. Ví dụ:

df_vnm %>%
ggplot(aes(x = date, y = close, open = open, high = high, low = low, close = close)) +
geom_candlestick() +
geom_bbands(ma_fun = SMA, sd = 2, n = 20) +
labs(title = "VNM Candlestick Chart",
subtitle = "BBands with SMA Applied",
caption = "Data Source: https://www.vndirect.com.vn",
y = "Closing Price",
x = NULL)

Limitations
Hàm get_transData_VND() có thể được hiệu chỉnh tùy và tùy biến nếu giao diện và cấu trúc web bị thay đổi. Nhưng cách làm này có lẽ chỉ phù hợp với các dự án mang tính thời điểm. Về lâu dài chúng ta cần một giải pháp triệt để hơn: lấy dữ liệu qua giao thức API.
Tương tự chúng ta có thể viết một hàm lấy thông tin giao dịch cho cổ phiếu niêm yết từ cophieu68.
LS0tDQp0aXRsZTogIlNjcmFwaW5nIERhdGEgZnJvbSBWTkRJUkVDVCBTZWN1cml0aWVzIENvcnBvcmF0aW9uIg0KYXV0aG9yOiAiTmd1eWVuIENoaSBEdW5nIg0Kc3VidGl0bGU6ICJEYXRhIFNjcmFwaW5nIFNlcmllcyINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDoNCiAgICBjb2RlX2Rvd25sb2FkOiB5ZXMNCiAgICAjIGNvZGVfZm9sZGluZzogaGlkZQ0KICAgIGhpZ2hsaWdodDogemVuYnVybg0KICAgIHRoZW1lOiBmbGF0bHkNCiAgICB0b2M6IHllcw0KICAgIHRvY19mbG9hdDogeWVzDQogIHdvcmRfZG9jdW1lbnQ6DQogICAgdG9jOiB5ZXMNCi0tLQ0KDQpgYGB7ciBzZXR1cCxpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSwgZmlnLnJldGluYT0yKQ0KYGBgDQoNCiMgTW90aXZhdGlvbnMNCg0KSGnhu4duIHThuqFpIFIgY8OzIHLhuqV0IG5oaeG7gXUgUGFja2FnZXMgY2hvIHBow6JuIHTDrWNoIGNodeG7l2kgZOG7ryBsaeG7h3UgdGjhu51pIGdpYW4gKFRpbWUgU2VyaWVzKSB2w6AgbeG7mXQgdHJvbmcgc+G7kSDEkcOzIGzDoCBbdGlkeXF1YW50XShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvdGlkeXF1YW50L2luZGV4Lmh0bWwpIMSRxrDhu6NjIHZp4bq/dCBi4bufaSBbSm9zaHVhIFVscmljaF0oaHR0cHM6Ly93d3cuam9zaHVhdWxyaWNoLmNvbS9yZXN1bWUuaHRtbCkuIA0KDQoNCsSQ4buRaSB24bubaSDEkWnhu4F1IGtp4buHbiBj4bunYSBWaeG7h3QgTmFtLCDEkeG7gyBz4butIGThu6VuZyDEkcaw4bujYyBjw6FjIGjDoG0gY+G7p2EgZ8OzaSBuw6B5IGNo4bqzbmcgaOG6oW4gdGjDrCBraMOzIGtoxINuIMSR4bqndSB0acOqbiBsw6Agdmnhu4djIGzhuqV5IGThu68gbGnhu4d1IGPhu6dhIGPDoWMgY8O0bmcgdGkgbmnDqm0geeG6v3QuIA0KDQpHaeG6o2kgcXV54bq/dCB24bqlbiDEkeG7gSBuw6B5IGLhuqFuIEtow6FuaCDEkcOjIHZp4bq/dCBow6BtIFtnZXRTeW1ib2xzKCldKGh0dHBzOi8vZ2l0aHViLmNvbS9waGFtZGluaGtoYW5oL1ZORC9ibG9iL21hc3Rlci9SL2dldFN5bWJvbHMuUikgdsOgIGhp4buHbiBuw6B5IMSRw6MgbMOgIG3hu5l0IGLhu5kgcGjhuq1uIGPhu6dhIFtWTkRTIHBhY2thZ2VdKGh0dHA6Ly9ycHVicy5jb20vcGhhbWRpbmhraGFuaC8zODg0OTkpLiBUdXkgbmhpw6puIHBhY2thZ2UgbsOgeSBob+G6oXQgxJHhu5luZyBraMO0bmcg4buVbiDEkeG7i25oIHbDoCBz4bq9IGtow7RuZyBz4butIGThu6VuZyDEkcaw4bujYyB0cm9uZyBt4buZdCBz4buRIHTDrG5oIGh14buRbmcuIEdp4bqjaSBxdXnhur90IHRyaeG7h3QgxJHhu4MgduG6pW4gxJHhu4EgbsOgeSBjw7MgbOG6vSBj4bqnbiBwaOG6o2kgbOG6pXkgZOG7ryBsaeG7h3UgcXVhIGdpYW8gdGjhu6ljIEFQSSBuaMawbmcgdmnhu4djIG7DoHkgY8OzIHRo4buDIGPhuqduIHRo4budaSBnaWFuIGzDoG0gdmnhu4djIHbhu5tpIFtWTkRJUkVDVF0oaHR0cHM6Ly93d3cudm5kaXJlY3QuY29tLnZuLykgKHbDoCBjw7MgdGjhu4MgcGjhuqNpIHZp4bq/dCBs4bqhaSBob8OgbiB0b8OgbiBwYWNrYWdlIG7DoHkpLiANCg0KDQpN4bq3YyBkw7kgUiB2w6AgUiBwYWNrYWdlcyDEkeG7gXUgY8O0bmcga2hhaSBtw6Mgbmd14buTbiBuaMawbmcgc+G6vSBsw6AgaOG7o3AgbMOtIGjGoW4ga2hpIGNow7puZyB0YSBraMO0bmcgc+G7rSBk4bulbmcgbmd1ecOqbiBjw6FjIGjDoG0gbcOgIGPDoWMgdMOhYyBnaeG6oyBraMOhYyDEkcOjIHZp4bq/dCwgaG/hurdjIHZp4bq/dCBt4buZdCBow6BtIG3DoCBjw7RuZyBuxINuZyBwaMOibiB0w61jaCBraMO0bmcgY8OzIGfDrCBt4bubaSBob+G6t2Mga2jDtG5nIGLhurFuZyBjw6FpIMSRw6MgY8OzLiBIw6BtICoqZ2V0U3ltYm9scygpKiogY+G7p2EgcGFja2FnZSBWTkRTIGPFqW5nIGzDoCBt4buZdCBow6BtIGPhu6dhIHRpZHlxdWFudC4gw50gdMaw4bufbmcgY+G7p2EgaMOgbSBnZXRTeW1ib2xzKCkgKGPhu6dhIHBhY2thZ2UgdGlkeXF1YW50KSBjaMO6bmcgdGEgY8OzIHRo4buDIHRp4bq/cCB0aHUgbmjGsG5nIGzhuqV5IGx1w7RuIGPhuqMgdMOqbiBow6BtIGzDoCDEkWnhu4F1IGPhuqduIGPDom4gbmjhuq9jIHbDrCBuaGnhu4F1IGzDrSBkby4gDQoNClbDrCBsw60gZG8gxJHDsywgdMO0aSB2aeG6v3QgbeG7mXQgaMOgbSBjw7MgdMOqbiAqKmdldF90cmFuc0RhdGFfVk5EKCkqKiDEkeG7gyBs4bqleSB0aMO0bmcgdGluIGdpYW8gZOG7i2NoIGPhu6dhIG3hu5l0IG3DoyBj4buVIHBoaeG6v3UgduG7m2kgxJHhuqd1IHbDoG8gbMOgOiANCg0KMS4gTcOjIGPhu5UgcGhp4bq/dSDEkcaw4bujYyBjaOG7jW4sIA0KMi4gTmfDoHkgYuG6r3QgxJHhuqd1IGzhuqV5IGThu68gbGnhu4d1LCANCjMuIE5nw6B5IGN14buRaSBjw7luZyBs4bqleSBk4buvIGxp4buHdS4gDQoNCkvhur90IHF14bqjIGPhu6dhIGjDoG0gbsOgeSBsw6AgbeG7mXQgZGF0YSBmcmFtZS90aWJibGUgdsOgIHbhu5tpIGThu68gbGnhu4d1IHRodSDEkcaw4bujYyBjaMO6bmcgdGEgY8OzIHRo4buDIMOhcCBk4bulbmcgdOG6pXQgY+G6oyBjw6FjIGjDoG0sIGPDoWMgbcO0IGjDrG5oIHBow6JuIHTDrWNoIGNobyBUaW1lIFNlcmllcyBj4bunYSBnw7NpIHRpZHlxdWFudC4gDQoNCg0KDQpgYGB7cn0NCg0KIyBDbGVhciB3b3JrIHNwYWNlOiANCg0Kcm0obGlzdCA9IGxzKCkpDQoNCiMgTG9hZCBzb21lIFIgcGFja2FnZXM6IA0KDQpsaWJyYXJ5KHJ2ZXN0KQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KGx1YnJpZGF0ZSkNCg0KDQojPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09DQojICAgIEZ1bmN0aW9uIGNvbGxlY3QgYWxsIHRyYW5zYWN0aW9uIGRhdGEgd2l0aCBpbnB1dHMgYXJlOiANCiMgICAgKDEpIHRpY2tlciBzZWxlY3RlZCwgKDIpIHN0YXJ0IGRhdGUsIGFuZCBlbmQgZGF0ZS4gDQojPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09DQoNCg0KZ2V0X3RyYW5zRGF0YV9WTkQgPC0gZnVuY3Rpb24oc3ltYm9sLCBmcm9tX0RhdGUsIGVuZF9EYXRlKSB7DQogIA0KICAjLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KICAjICBTdGFnZSAxOiBDcmVhdGUgSFRNTCBmb3JtLCBzZW5kIHJlcXVlc3QgYW5kIGNvbGxlY3QgdHJhbnNhY3Rpb24gZGF0YQ0KICAjICBhbmQgY29sbGVjdHMgc3RvY2sgdHJhbnNhY3Rpb25zIGZvciBhIHNwZWNpZmljIHBhZ2Ugc2VsZWN0ZWQNCiAgIy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCiAgDQogIHVybCA8LSAiaHR0cHM6Ly93d3cudm5kaXJlY3QuY29tLnZuL3BvcnRhbC90aG9uZy1rZS10aGktdHJ1b25nLWNodW5nLWtob2FuL2xpY2gtc3UtZ2lhLnNodG1sIg0KICBwYWdlX3Nlc3Npb24gPC0gaHRtbF9zZXNzaW9uKHVybCkgICAgICAgICAgICAgICANCiAgcGdmb3JtIDwtIGh0bWxfZm9ybShwYWdlX3Nlc3Npb24pICU+JSAuW1sxXV0NCiAgDQogIA0KICBnZXRfdHJhbnNhY3Rpb25fZGF0YSA8LSBmdW5jdGlvbihwYWdlX2luZGV4KSB7DQogICAgDQogICAgZmlsbGVkX2Zvcm0gPC0gc2V0X3ZhbHVlcyhmb3JtID0gcGdmb3JtLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhZ2luZ0luZm8uaW5kZXhQYWdlID0gcGFnZV9pbmRleCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZWFyY2hNYXJrZXRTdGF0aXN0aWNzVmlldy5zeW1ib2wgPSBzeW1ib2wsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyRnJvbURhdGUgPSBmcm9tX0RhdGUsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyVG9EYXRlID0gZW5kX0RhdGUpDQogICAgDQogICAgDQogICAgc3VibWl0X2Zvcm0ocGFnZV9zZXNzaW9uLCBmaWxsZWRfZm9ybSkgJT4lIA0KICAgICAgcmVhZF9odG1sKCkgLT4gcGFnZV9jb250ZW50DQogICAgDQogICAgcGFnZV9jb250ZW50ICU+JSANCiAgICAgIGh0bWxfbm9kZXMoIi5saWNoc3VnaWEgZGl2IikgJT4lDQogICAgICBodG1sX3RleHQoKSAlPiUgDQogICAgICBtYXRyaXgobmNvbCA9IDEwLCBieXJvdyA9IFRSVUUpICU+JSANCiAgICAgIGRhdGEuZnJhbWUoKSAlPiUgDQogICAgICBzbGljZSgtMSkgJT4lIA0KICAgICAgc2VsZWN0KC0yKSAlPiUgDQogICAgICBtdXRhdGUoWDEgPSBhcy5jaGFyYWN0ZXIoWDEpKSAlPiUgDQogICAgICBtdXRhdGUoWDEgPSBzdHJfcmVwbGFjZV9hbGwoWDEsICJbXjAtOV0iLCAiIikpICU+JSANCiAgICAgIG11dGF0ZShYMSA9IHltZChYMSkpICU+JSANCiAgICAgIG11dGF0ZV9pZihpcy5mYWN0b3IsIGZ1bmN0aW9uKHgpIHthcy5jaGFyYWN0ZXIoeCl9KSAlPiUgDQogICAgICBtdXRhdGVfaWYoaXMuY2hhcmFjdGVyLCBmdW5jdGlvbih4KSB7YXMubnVtZXJpYyh4KX0pIC0+IGRmX3ByaWNlDQogICAgDQogICAgbmFtZXMoZGZfcHJpY2UpIDwtIGMoImRhdGUiLCAib3BlbiIsICJoaWdoIiwgImxvdyIsICJjbG9zZSIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICJhdmVyYWdlIiwgImFkanVzdGVkIiwgInZvbHVtZSIsICJyZWNvbmNpbGVfdm9sdW1lIikNCiAgICANCiAgICByZXR1cm4oZGZfcHJpY2UpDQogICAgDQogIH0NCiAgDQogICMtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCiAgIyAgU3RhZ2UgMjogRXh0cmFjdCB0aGUgbGFzdCBwYWdlIGFuZCByZXR1cm4gYWxsIHRyYW5zYWN0aW9uIGRhdGENCiAgIy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KICANCiAgDQogIGZpbGxlZF9mb3JtX2Zvcl9wYWdlcyA8LSBzZXRfdmFsdWVzKGZvcm0gPSBwZ2Zvcm0sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZWFyY2hNYXJrZXRTdGF0aXN0aWNzVmlldy5zeW1ib2wgPSBzeW1ib2wsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdHJGcm9tRGF0ZSA9IGZyb21fRGF0ZSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0clRvRGF0ZSA9IGVuZF9EYXRlKQ0KICANCiAgDQogIHN1Ym1pdF9mb3JtKHBhZ2Vfc2Vzc2lvbiwgZmlsbGVkX2Zvcm1fZm9yX3BhZ2VzKSAlPiUgDQogICAgcmVhZF9odG1sKCkgLT4gcGFnZV9jb250ZW50X3BhZ2VzDQogIA0KICANCiAgcGFnZV9jb250ZW50X3BhZ2VzICU+JSANCiAgICBodG1sX25vZGVzKCIucGFnaW5nIikgJT4lIA0KICAgIGh0bWxfdGV4dCgpICU+JSAgDQogICAgc3RyX2V4dHJhY3QoIi9bMC05XXsxLDEwMH0iKSAlPiUgDQogICAgc3RyX3JlcGxhY2VfYWxsKCJbXjAtOV0iLCAiIikgJT4lIA0KICAgIGFzLm51bWVyaWMoKSAtPiBuX3BhZ2VzDQoNCiAgDQogIGlmIChpcy5uYShuX3BhZ2VzKSkgew0KICAgIGZpbmFsX2RmIDwtIGdldF90cmFuc2FjdGlvbl9kYXRhKHBhZ2VfaW5kZXggPSAxKSAlPiUgDQogICAgICBtdXRhdGUoc3ltYm9sID0gc3RyX3RvX3VwcGVyKHN5bWJvbCkpICU+JSANCiAgICAgIHNlbGVjdChzeW1ib2wsIGV2ZXJ5dGhpbmcoKSkNCiAgfSBlbHNlIHsNCiAgICANCiAgICBsYXBwbHkoMTpuX3BhZ2VzLCBnZXRfdHJhbnNhY3Rpb25fZGF0YSkgLT4gYWxsX2RhdGFfbGlzdA0KICAgIA0KICAgIGZpbmFsX2RmIDwtIGRvLmNhbGwoImJpbmRfcm93cyIsIGFsbF9kYXRhX2xpc3QpICU+JSANCiAgICAgIG11dGF0ZShzeW1ib2wgPSBzdHJfdG9fdXBwZXIoc3ltYm9sKSkgJT4lIA0KICAgICAgc2VsZWN0KHN5bWJvbCwgZXZlcnl0aGluZygpKQ0KICB9DQogIA0KICByZXR1cm4oZmluYWxfZGYpDQogIA0KfQ0KDQoNCiMgVXNlIHRoZSBmdW5jdGlvbjogDQoNCnN5bWJvbCA8LSAidm5tIg0KZnJvbV9EYXRlIDwtICIwMS8wMS8yMDE4Ig0KZW5kX0RhdGUgPC0gIjAxLzAyLzIwMTkiDQoNCmdldF90cmFuc0RhdGFfVk5EKHN5bWJvbCA9IHN5bWJvbCwgZnJvbV9EYXRlID0gZnJvbV9EYXRlLCBlbmRfRGF0ZSA9IGVuZF9EYXRlKSAtPiBkZl92bm0NCg0KDQojIFJlc3VsdHMgYnkgZGF0YSBmcmFtZTogDQpkZl92bm0gJT4lIGhlYWQoKQ0KDQoNCiMgUmVzdWx0cyBieSBwbG90OiANCmRmX3ZubSAlPiUgDQogIGdncGxvdChhZXMoZGF0ZSwgb3BlbikpICsgDQogIGdlb21fbGluZSgpICsgDQogIGxhYnMoeCA9ICJQcmljZSIsIHkgPSAiRGF0ZSIsIA0KICAgICAgIHRpdGxlID0gIk9wZW4gUHJpY2UgZm9yIFZpbmFtaWxrIChWTk0pIGZyb20gSmFuLTIwMTggdG8gSmFuLTIwMTkiLCANCiAgICAgICBjYXB0aW9uID0gIkRhdGEgU291cmNlOiBodHRwczovL3d3dy52bmRpcmVjdC5jb20udm4iKQ0KDQpgYGANCg0KIyBBcHBsaWNhdGlvbnMNCg0KU2F1IGtoaSDEkcOjIGPDsyBkYXRhIGNow7puZyB0YSBjw7MgdGjhu4Mgc+G7rSBk4bulbmcgKip0aWR5cXVhbnQqKiBjaG8gcGjDom4gdMOtY2guIFbDrSBk4bulOiANCg0KYGBge3J9DQpsaWJyYXJ5KHRpZHlxdWFudCkNCnRoZW1lX3NldCh0aGVtZV90cSgpKQ0KDQoNCmRmX3ZubSAlPiUNCiAgZ2dwbG90KGFlcyhkYXRlLCBhZGp1c3RlZCkpICsNCiAgZ2VvbV9saW5lKGNvbG9yID0gcGFsZXR0ZV9saWdodCgpW1sxXV0pICsgDQogIHNjYWxlX3lfbG9nMTAoKSArDQogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIpICsNCiAgbGFicyh0aXRsZSA9ICJWTk0gTGluZSBDaGFydCIsIA0KICAgICAgIHN1YnRpdGxlID0gIkxvZyBTY2FsZSwgQXBwbHlpbmcgTGluZWFyIFRyZW5kbGluZSIsIA0KICAgICAgIGNhcHRpb24gPSAiRGF0YSBTb3VyY2U6IGh0dHBzOi8vd3d3LnZuZGlyZWN0LmNvbS52biIsIA0KICAgICAgIHkgPSAiQWRqdXN0ZWQgQ2xvc2luZyBQcmljZSIsIA0KICAgICAgIHggPSBOVUxMKQ0KDQoNCg0KZGZfdm5tICU+JQ0KICBnZ3Bsb3QoYWVzKHggPSBkYXRlLCB5ID0gY2xvc2UsIG9wZW4gPSBvcGVuLCBoaWdoID0gaGlnaCwgbG93ID0gbG93LCBjbG9zZSA9IGNsb3NlKSkgKw0KICBnZW9tX2NhbmRsZXN0aWNrKCkgKw0KICBnZW9tX2JiYW5kcyhtYV9mdW4gPSBTTUEsIHNkID0gMiwgbiA9IDIwKSArIA0KICBsYWJzKHRpdGxlID0gIlZOTSBDYW5kbGVzdGljayBDaGFydCIsIA0KICAgICAgIHN1YnRpdGxlID0gIkJCYW5kcyB3aXRoIFNNQSBBcHBsaWVkIiwgDQogICAgICAgY2FwdGlvbiA9ICJEYXRhIFNvdXJjZTogaHR0cHM6Ly93d3cudm5kaXJlY3QuY29tLnZuIiwgDQogICAgICAgeSA9ICJDbG9zaW5nIFByaWNlIiwgDQogICAgICAgeCA9IE5VTEwpDQpgYGANCg0KDQojIExpbWl0YXRpb25zDQoNCkjDoG0gKipnZXRfdHJhbnNEYXRhX1ZORCgpKiogY8OzIHRo4buDIMSRxrDhu6NjIGhp4buHdSBjaOG7iW5oIHTDuXkgdsOgIHTDuXkgYmnhur9uIG7hur91IGdpYW8gZGnhu4duIHbDoCBj4bqldSB0csO6YyB3ZWIgYuG7iyB0aGF5IMSR4buVaS4gTmjGsG5nIGPDoWNoIGzDoG0gbsOgeSBjw7MgbOG6vSBjaOG7iSBwaMO5IGjhu6NwIHbhu5tpIGPDoWMgZOG7sSDDoW4gbWFuZyB0w61uaCB0aOG7nWkgxJFp4buDbS4gVuG7gSBsw6J1IGTDoGkgY2jDum5nIHRhIGPhuqduIG3hu5l0IGdp4bqjaSBwaMOhcCB0cmnhu4d0IMSR4buDIGjGoW46IGzhuqV5IGThu68gbGnhu4d1IHF1YSBnaWFvIHRo4bupYyBBUEkuIA0KDQpUxrDGoW5nIHThu7EgY2jDum5nIHRhIGPDsyB0aOG7gyB2aeG6v3QgbeG7mXQgaMOgbSBs4bqleSB0aMO0bmcgdGluIGdpYW8gZOG7i2NoIGNobyBj4buVIHBoaeG6v3UgbmnDqm0geeG6v3QgdOG7qyBbY29waGlldTY4XShodHRwczovL3d3dy5jb3BoaWV1Njgudm4vKS4gDQoNCg0K