Automated ARIMA
Quá trình lựa chọn các tham số tối ưu cho mô hình ARIMA có thể được
tóm tắt bằng hình dưới đây:

Hyndman
& Khandakar (2008) đề xuất một thuật toán nhằm thực hiện quá
trình lựa chọn và xây dựng mô hình ARIMA một cách tự động (gọi là Auto
ARIMA). Post này trình bày việc áp dụng Auto ARIMA cho việc dự báo trước
1 ngày 9 time series là Bitcoin, TetherUSD, E1VFN30, Gold, Oil, HNX30,
VN100, VN30 và VNIndex. Hai time series đầu tiên đại diện cho tiền ảo,
E1VFN30 là chứng chỉ quỹ ETF, Gold và Oil đại diện cho hợp đồng tương
lai về Vàng và Dầu Thô. Các time series còn lại là các chỉ số chứng
khoán tại Việt Nam. Tất các các time series này được lấy từ https://vn.investing.com/
từ ngày 01-01-2022 đến ngày 05-12-2023.
Empirical Results
Trước hết load và xử lí qua dữ liệu thô thu được:
# Clear R Environment:
rm(list = ls())
# Load R packages:
library(arrow)
library(dplyr)
library(tidyr)
library(lubridate)
library(readr)
library(stringr)
library(ggplot2)
library(scales)
library(plotly)
library(fpp2)
library(gt)
library(showtext)
my_font <- "Ubuntu Condensed" # Set font for our plot.
font_add_google(name = my_font, family = my_font)
showtext_auto()
theme_set(theme_minimal())
# Load data collected from https://vn.investing.com (01-01-2021 to 05-12-2021):
dir("E:/dataTikopProject", full.names = TRUE) -> indicatorPaths
lapply(indicatorPaths, read_csv) -> indicatorList
sapply(indicatorList, nrow) -> nRep
c(rep("Bitcoin", nRep[1]),
rep("E1VFVN30", nRep[2]),
rep("HNX30", nRep[3]),
rep("Oil", nRep[4]),
rep("Gold", nRep[5]),
rep("TetherUSD", nRep[6]),
rep("VN30", nRep[7]),
rep("VNIndex", nRep[8]),
rep("VN100", nRep[9])) -> indicators
allIndicator <- unique(indicators)
do.call("bind_rows", indicatorList) -> dfIndicators
dfIndicators %>%
select(1, 3) %>%
mutate(indicator = indicators) -> dfIndicators
names(dfIndicators) <- c("dateYMD", "open", "indicator")
dfIndicators %>% mutate(dateYMD = dmy(dateYMD)) -> dfIndicators
dfIndicators %>%
ggplot(aes(dateYMD, open, color = indicator)) +
geom_line(show.legend = FALSE) +
facet_wrap(~ indicator, scales = "free") +
theme(axis.title = element_blank()) +
theme(plot.margin = unit(rep(0.5, 4), "cm")) +
labs(title = "Figure 1: Return Trend for Some Selected Time Series",
subtitle = "",
caption = "Source: https://vn.investing.com") -> f11
Figure 1 dưới đây chỉ ra trend cho các time series từ ngày 01-01-2022
đến ngày 05-12-2023:
Dưới đây là hàm
thực hiện 1-day ahead forecasting bằng Auto ARIMA và sử dụng hàm này để
dự báo:
# Function conducts one-day ahead forecasting by Auto ARIMA:
n_ahead <- 1
n_windown <- 360
oneDayAheadForecast <- function(indicatorSelected) {
# indicatorSelected <- "Bitcoin"
dfIndicators %>%
arrange(dateYMD) %>%
filter(indicator == indicatorSelected) -> dfSelected
autoARIMA <- function(j) {
# Set up parameters for training Auto ARIMA:
# j <- 1
startTime <- j
endTime <- n_windown + j - 1
# Data for training Auto ARIMA:
dfSelected$open[startTime:endTime] %>% ts() -> train
# Train Auto ARIMA:
my_arima <- auto.arima(train)
# Use trained model for forecasting:
predicted <- forecast(my_arima, h = n_ahead)$mean %>% as.vector()
dfSelected %>%
slice(endTime + 1) %>%
mutate(predicted = predicted) %>%
mutate(err = predicted - open) %>%
mutate(perErr = predicted / open - 1) %>%
return()
}
# Forecast for all remaining days:
n_days <- nrow(dfSelected)
n_remaining <- n_days - n_windown
do.call("bind_rows", lapply(1:n_remaining, autoARIMA)) -> dfComparing
return(dfComparing)
}
# oneDayAheadForecast(indicatorSelected = allIndicator[1])
# Use the function:
do.call("bind_rows", lapply(allIndicator, oneDayAheadForecast)) -> dfForecastForAll
Kết quả dự báo cho 9 time series được trình bày ở Table 1 dưới
đây:
dfForecastForAll %>%
mutate(Under5 = case_when(abs(perErr) <= 0.05 ~ "Yes", TRUE ~ "No")) %>%
group_by(indicator, Under5) %>%
count() %>%
pivot_wider(names_from = Under5, values_from = n, values_fill = 0) %>%
mutate(Total = No + Yes) -> df_wider
dfForecastForAll %>%
group_by(indicator) %>%
summarise(MeanERR = mean(perErr), MaxERR = max(perErr), MinERR = min(perErr)) %>%
full_join(df_wider %>% select(indicator, Num.OK = Yes, Num.Fail = No, Total)) -> dfResults
dfResults %>%
mutate_if(is.numeric, function(x) {round(x, 4)}) %>%
gt() %>%
tab_header(title = "Table 1: One-day ahead forecasting by Auto ARIMA")
Table 1: One-day ahead forecasting by Auto ARIMA |
indicator |
MeanERR |
MaxERR |
MinERR |
Num.OK |
Num.Fail |
Total |
Bitcoin |
-0.0019 |
0.0785 |
-0.0924 |
326 |
18 |
344 |
E1VFVN30 |
0.0003 |
0.0532 |
-0.0293 |
119 |
1 |
120 |
Gold |
-0.0003 |
0.0165 |
-0.0307 |
147 |
0 |
147 |
HNX30 |
0.0008 |
0.0890 |
-0.0664 |
108 |
10 |
118 |
Oil |
-0.0001 |
0.0585 |
-0.0642 |
150 |
4 |
154 |
TetherUSD |
0.0000 |
0.0029 |
-0.0053 |
344 |
0 |
344 |
VN100 |
0.0000 |
0.0482 |
-0.0387 |
120 |
0 |
120 |
VN30 |
0.0003 |
0.0419 |
-0.0337 |
120 |
0 |
120 |
VNIndex |
0.0002 |
0.0471 |
-0.0333 |
120 |
0 |
120 |
Kết quả này chỉ ra rằng, với Bitcoin chẳng hạn:
- Trong tổng số 344 kết quả dự báo (trước 1 ngày), có 18 dự báo không
thỏa mãn điều kiện sai sốt tuyệt đối MAE nhỏ hơn hoặc bằng 5%. Còn lại
326 giá trị dự báo thỏa mãn điều kiện |MAE| <= 5%.
- Sai số trung bình MAE của các dự báo là 0.19%. Dự kém nhất bị sai
lệch 9.24% (giá trị âm có nghĩa là thấp hơn giá trị thực tế được thể
hiện ở cột MinERR).
Việc đọc hiểu các kêt quả cho 8 chỉ số còn lại tương tự như trên.
Figure 2 dưới đây so sánh giá trị dự báo và giá trị thực tế của 9
time series:
dfForecastForAll %>%
rename(Actual = open, Forecasted = predicted) %>%
pivot_longer(cols = c("Actual", "Forecasted"), names_to = "type", values_to = "value") -> df_long
df_long %>%
ggplot(aes(x = dateYMD, y = value, color = type)) +
geom_line() +
facet_wrap(~ indicator, scales = "free") +
theme(axis.title = element_blank()) +
theme(legend.title = element_blank()) +
theme(legend.position = "top") +
theme(plot.margin = unit(rep(0.5, 4), "cm")) +
labs(title = "Figure 2: Actual and Forecasted Values",
subtitle = "Note: One-day ahead forecasting by Auto ARIMA",
caption = "Source: https://vn.investing.com") -> fig222
fig222

LS0tDQp0aXRsZTogJ04tZGF5cyBBaGVhZCBGb3JlY2FzdGluZyB1c2luZyBBdXRvbWF0ZWQgQVJJTUEnDQphdXRob3I6ICdBdXRob3I6IE5ndXllbiBDaGkgRHVuZycNCnN1YnRpdGxlOiAiUiBEYXRhIFNjaWVuY2UgU2VyaWVzIg0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50OiANCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQogICAgIyBjb2RlX2ZvbGRpbmc6IGhpZGUNCiAgICBoaWdobGlnaHQ6IHplbmJ1cm4NCiAgICAjIG51bWJlcl9zZWN0aW9uczogeWVzDQogICAgdGhlbWU6ICJmbGF0bHkiDQogICAgdG9jOiBUUlVFDQogICAgdG9jX2Zsb2F0OiBUUlVFDQotLS0NCg0KYGBge3Igc2V0dXAsaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UsIGNhY2hlID0gVFJVRSwgZXZhbCA9IFRSVUUpDQoNCmBgYA0KDQohW10oQzpcXFVzZXJzXFxBZG1pblxcRG9jdW1lbnRzXFxib29rLmpwZykNCg0KIyBBdXRvbWF0ZWQgQVJJTUENCg0KUXXDoSB0csOsbmggbOG7sWEgY2jhu41uIGPDoWMgdGhhbSBz4buRIHThu5FpIMawdSBjaG8gbcO0IGjDrG5oIEFSSU1BIGPDsyB0aOG7gyDEkcaw4bujYyB0w7NtIHThuq90IGLhurFuZyBow6xuaCBkxrDhu5tpIMSRw6J5OiANCg0KIVtdKEM6XFxVc2Vyc1xcQWRtaW5cXERvY3VtZW50c1xcYXJpbWEuanBnKSANCg0KW0h5bmRtYW4gJiBLaGFuZGFrYXIgKDIwMDgpXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvZm9yZWNhc3QvdmlnbmV0dGVzL0pTUzIwMDgucGRmKSDEkeG7gSB4deG6pXQgbeG7mXQgdGh14bqtdCB0b8OhbiBuaOG6sW0gdGjhu7FjIGhp4buHbiBxdcOhIHRyw6xuaCBs4buxYSBjaOG7jW4gdsOgIHjDonkgZOG7sW5nIG3DtCBow6xuaCBBUklNQSBt4buZdCBjw6FjaCB04buxIMSR4buZbmcgKGfhu41pIGzDoCBBdXRvIEFSSU1BKS4gUG9zdCBuw6B5IHRyw6xuaCBiw6B5IHZp4buHYyDDoXAgZOG7pW5nIEF1dG8gQVJJTUEgY2hvIHZp4buHYyBk4buxIGLDoW8gdHLGsOG7m2MgMSBuZ8OgeSA5IHRpbWUgc2VyaWVzIGzDoCBCaXRjb2luLCBUZXRoZXJVU0QsIEUxVkZOMzAsIEdvbGQsIE9pbCwgSE5YMzAsIFZOMTAwLCBWTjMwIHbDoCBWTkluZGV4LiBIYWkgdGltZSBzZXJpZXMgxJHhuqd1IHRpw6puIMSR4bqhaSBkaeG7h24gY2hvIHRp4buBbiDhuqNvLCBFMVZGTjMwIGzDoCBjaOG7qW5nIGNo4buJIHF14bu5IEVURiwgR29sZCB2w6AgT2lsIMSR4bqhaSBkaeG7h24gY2hvIGjhu6NwIMSR4buTbmcgdMawxqFuZyBsYWkgduG7gSBWw6BuZyB2w6AgROG6p3UgVGjDtC4gQ8OhYyB0aW1lIHNlcmllcyBjw7JuIGzhuqFpIGzDoCBjw6FjIGNo4buJIHPhu5EgY2jhu6luZyBraG/DoW4gdOG6oWkgVmnhu4d0IE5hbS4gVOG6pXQgY8OhYyBjw6FjIHRpbWUgc2VyaWVzIG7DoHkgxJHGsOG7o2MgbOG6pXkgdOG7qyBbaHR0cHM6Ly92bi5pbnZlc3RpbmcuY29tL10oaHR0cHM6Ly92bi5pbnZlc3RpbmcuY29tL2NvbW1vZGl0aWVzL2dvbGQpIHThu6sgbmfDoHkgMDEtMDEtMjAyMiDEkeG6v24gbmfDoHkgMDUtMTItMjAyMy4gDQoNCg0KIyBCYWNrdGVzdGluZyBmb3IgRXZhbHVhdGluZyBQZXJmb3JtYW5jZQ0KDQpDw6FjIGdpw6EgdHLhu4sgxJHGsOG7o2MgZOG7sSBiw6FvIHThu6sgQXV0byBBUklNQSDEkcaw4bujYyB0aOG7sWMgaGnhu4duIGLhurFuZyBwaMawxqFuZyBwaMOhcCBbQmFja3Rlc3RpbmddKGh0dHBzOi8vam9hcXVpbmFtYXRyb2RyaWdvLmdpdGh1Yi5pby9za2ZvcmVjYXN0LzAuOC4xL3VzZXJfZ3VpZGVzL2JhY2t0ZXN0aW5nLmh0bWwjOn46dGV4dD1CYWNrdGVzdGluZyUyMGlzJTIwYSUyMGZvcm0lMjBvZixpc3N1ZXMlMjBvciUyMGFyZWFzJTIwb2YlMjBpbXByb3ZlbWVudC4pIMSRxrDhu6NjIG3DtCB04bqjIGLhurFuZyBow6xuaCBkxrDhu5tpIMSRw6J5Og0KDQoNCiFbXShDOlxcVXNlcnNcXEFkbWluXFxEb2N1bWVudHNcXHNsaWRpbmcuanBnKQ0KDQpUcm9uZyDEkcOzIHRyYWluaW5nIGzDoCBwaOG6p24gZOG7ryBsaeG7h3UgxJHGsOG7o2Mgc+G7rSBk4bulbmcgxJHhu4MgY2jhu41uIGPDoWMgdGhhbSBz4buRIHThu5FpIMawdSBjaG8gQVJJTUEsIHRlc3RpbmcgbMOgIHBo4bqnbiBk4buvIGxp4buHdSBz4butIGThu6VuZyDEkeG7gyBraeG7g20gdHJhIG5nxrDhu6NjIGzhuqFpIG3hu6ljIMSR4buZIGNow61uaCB4w6FjIGPhu6dhIGThu7EgYsOhbyBi4bufaSBtw7QgaMOsbmggQVJJTUEuIOG7niDEkcOieSB0w7RpIHPhu60gZOG7pW5nIGThu68gbGnhu4d1IHF1YW4gc8OhdCBj4bunYSAzNjAgbmfDoHkgdHJvbmcgcXXDoSBraOG7qSAoeOG6pXAgeOG7iSAxIG7Eg20pIGzDoG0gdHJhaW5pbmcgZGF0YSB2w6AgZOG7ryBsaeG7h3UgY+G7p2EgbmfDoHkgdGjhu6kgMzYxIMSR4buDIMSRw6FuaCBnacOhIG3hu6ljIMSR4buZIGNow61uaCB4w6FjIGvhur90IHF14bqjIGThu7EgYsOhbyB0csaw4bubYyBt4buZdCBuZ8OgeSB0aHUgxJHGsOG7o2MgdOG7qyBtw7QgaMOsbmguIA0KDQpbTWVhbiBBYnNvbHV0ZSBFcnJvciAoTUFFKV0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvTWVhbl9hYnNvbHV0ZV9lcnJvcikgxJHGsOG7o2MgbOG7sWEgY2jhu41uIGzDoG0gdGnDqnUgY2h14bqpbiDEkeG7gyDEkcOhbmggbeG7qWMgxJHhu5kgY2jDrW5oIHjDoWMgY+G7p2EgZOG7sSBiw6FvLiBOaOG7r25nIGdpw6EgdHLhu4sgZOG7sSBiw6FvIG3DoCBnacOhIHRy4buLIHR1eeG7h3QgxJHhu5FpIGPhu6dhIE1BRSBuaOG7jyBoxqFuIGhv4bq3YyBi4bqxbmcgNSUgc+G6vSDEkcaw4bujYyBn4buNaSBsw6AgxJHhuqF0IGNodeG6qW4uIA0KDQojIEVtcGlyaWNhbCBSZXN1bHRzDQoNClRyxrDhu5tjIGjhur90IGxvYWQgdsOgIHjhu60gbMOtIHF1YSBk4buvIGxp4buHdSB0aMO0IHRodSDEkcaw4bujYzogDQoNCmBgYHtyfQ0KDQojIENsZWFyIFIgRW52aXJvbm1lbnQ6IA0KDQpybShsaXN0ID0gbHMoKSkNCg0KIyBMb2FkIFIgcGFja2FnZXM6IA0KDQpsaWJyYXJ5KGFycm93KQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkodGlkeXIpDQpsaWJyYXJ5KGx1YnJpZGF0ZSkNCmxpYnJhcnkocmVhZHIpDQpsaWJyYXJ5KHN0cmluZ3IpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KHNjYWxlcykNCmxpYnJhcnkocGxvdGx5KQ0KbGlicmFyeShmcHAyKQ0KbGlicmFyeShndCkNCmxpYnJhcnkoc2hvd3RleHQpDQoNCm15X2ZvbnQgPC0gIlVidW50dSBDb25kZW5zZWQiICAjIFNldCBmb250IGZvciBvdXIgcGxvdC4gDQoNCmZvbnRfYWRkX2dvb2dsZShuYW1lID0gbXlfZm9udCwgZmFtaWx5ID0gbXlfZm9udCkNCg0Kc2hvd3RleHRfYXV0bygpDQoNCnRoZW1lX3NldCh0aGVtZV9taW5pbWFsKCkpDQpgYGANCg0KDQpgYGB7ciwgZXZhbD1GQUxTRX0NCiMgTG9hZCBkYXRhIGNvbGxlY3RlZCBmcm9tIGh0dHBzOi8vdm4uaW52ZXN0aW5nLmNvbSAoMDEtMDEtMjAyMSB0byAwNS0xMi0yMDIxKTogDQoNCmRpcigiRTovZGF0YVRpa29wUHJvamVjdCIsIGZ1bGwubmFtZXMgPSBUUlVFKSAtPiBpbmRpY2F0b3JQYXRocw0KDQpsYXBwbHkoaW5kaWNhdG9yUGF0aHMsIHJlYWRfY3N2KSAtPiBpbmRpY2F0b3JMaXN0DQoNCnNhcHBseShpbmRpY2F0b3JMaXN0LCBucm93KSAtPiBuUmVwDQoNCg0KYyhyZXAoIkJpdGNvaW4iLCBuUmVwWzFdKSwgDQogIHJlcCgiRTFWRlZOMzAiLCBuUmVwWzJdKSwgDQogIHJlcCgiSE5YMzAiLCBuUmVwWzNdKSwgDQogIHJlcCgiT2lsIiwgblJlcFs0XSksIA0KICByZXAoIkdvbGQiLCBuUmVwWzVdKSwgDQogIHJlcCgiVGV0aGVyVVNEIiwgblJlcFs2XSksDQogIHJlcCgiVk4zMCIsIG5SZXBbN10pLCANCiAgcmVwKCJWTkluZGV4IiwgblJlcFs4XSksIA0KICByZXAoIlZOMTAwIiwgblJlcFs5XSkpIC0+IGluZGljYXRvcnMNCg0KYWxsSW5kaWNhdG9yIDwtIHVuaXF1ZShpbmRpY2F0b3JzKQ0KDQpkby5jYWxsKCJiaW5kX3Jvd3MiLCBpbmRpY2F0b3JMaXN0KSAtPiBkZkluZGljYXRvcnMNCg0KZGZJbmRpY2F0b3JzICU+JSANCiAgc2VsZWN0KDEsIDMpICU+JSANCiAgbXV0YXRlKGluZGljYXRvciA9IGluZGljYXRvcnMpIC0+IGRmSW5kaWNhdG9ycw0KDQpuYW1lcyhkZkluZGljYXRvcnMpIDwtIGMoImRhdGVZTUQiLCAib3BlbiIsICJpbmRpY2F0b3IiKQ0KDQpkZkluZGljYXRvcnMgJT4lIG11dGF0ZShkYXRlWU1EID0gZG15KGRhdGVZTUQpKSAtPiBkZkluZGljYXRvcnMNCg0KDQpkZkluZGljYXRvcnMgJT4lIA0KICBnZ3Bsb3QoYWVzKGRhdGVZTUQsIG9wZW4sIGNvbG9yID0gaW5kaWNhdG9yKSkgKyANCiAgZ2VvbV9saW5lKHNob3cubGVnZW5kID0gRkFMU0UpICsgDQogIGZhY2V0X3dyYXAofiBpbmRpY2F0b3IsIHNjYWxlcyA9ICJmcmVlIikgKyANCiAgdGhlbWUoYXhpcy50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkgKyANCiAgdGhlbWUocGxvdC5tYXJnaW4gPSB1bml0KHJlcCgwLjUsIDQpLCAiY20iKSkgKyANCiAgbGFicyh0aXRsZSA9ICJGaWd1cmUgMTogUmV0dXJuIFRyZW5kIGZvciBTb21lIFNlbGVjdGVkIFRpbWUgU2VyaWVzIiwgDQogICAgICAgc3VidGl0bGUgPSAiIiwgDQogICAgICAgY2FwdGlvbiA9ICJTb3VyY2U6IGh0dHBzOi8vdm4uaW52ZXN0aW5nLmNvbSIpIC0+IGYxMSANCmBgYA0KDQpGaWd1cmUgMSBkxrDhu5tpIMSRw6J5IGNo4buJIHJhIHRyZW5kIGNobyBjw6FjIHRpbWUgc2VyaWVzIHThu6sgbmfDoHkgMDEtMDEtMjAyMiDEkeG6v24gbmfDoHkgMDUtMTItMjAyMzogDQoNCiFbXShDOlxcVXNlcnNcXEFkbWluXFxEb2N1bWVudHNcXHRpbWVmaWcxLmpwZykNCkTGsOG7m2kgxJHDonkgbMOgIGjDoG0gdGjhu7FjIGhp4buHbiAxLWRheSBhaGVhZCBmb3JlY2FzdGluZyBi4bqxbmcgQXV0byBBUklNQSB2w6Agc+G7rSBk4bulbmcgaMOgbSBuw6B5IMSR4buDIGThu7EgYsOhbzogDQoNCmBgYHtyLCBldmFsPUZBTFNFfQ0KIyBGdW5jdGlvbiBjb25kdWN0cyBvbmUtZGF5IGFoZWFkIGZvcmVjYXN0aW5nIGJ5IEF1dG8gQVJJTUE6IA0KDQpuX2FoZWFkIDwtIDENCg0Kbl93aW5kb3duIDwtIDM2MA0KDQpvbmVEYXlBaGVhZEZvcmVjYXN0IDwtIGZ1bmN0aW9uKGluZGljYXRvclNlbGVjdGVkKSB7DQogIA0KICAjIGluZGljYXRvclNlbGVjdGVkIDwtICJCaXRjb2luIg0KICANCiAgZGZJbmRpY2F0b3JzICU+JSANCiAgICBhcnJhbmdlKGRhdGVZTUQpICU+JSANCiAgICBmaWx0ZXIoaW5kaWNhdG9yID09IGluZGljYXRvclNlbGVjdGVkKSAtPiBkZlNlbGVjdGVkDQogIA0KICANCiAgYXV0b0FSSU1BIDwtIGZ1bmN0aW9uKGopIHsNCiAgICANCiAgICAjIFNldCB1cCBwYXJhbWV0ZXJzIGZvciB0cmFpbmluZyBBdXRvIEFSSU1BOiANCiAgICANCiAgICAjIGogPC0gMQ0KICAgIA0KICAgIHN0YXJ0VGltZSA8LSBqDQogICAgDQogICAgZW5kVGltZSA8LSBuX3dpbmRvd24gKyBqIC0gMQ0KICAgIA0KICAgICMgRGF0YSBmb3IgdHJhaW5pbmcgQXV0byBBUklNQTogDQogICAgDQogICAgZGZTZWxlY3RlZCRvcGVuW3N0YXJ0VGltZTplbmRUaW1lXSAlPiUgdHMoKSAtPiB0cmFpbg0KICAgIA0KICAgICMgVHJhaW4gQXV0byBBUklNQTogDQogICAgDQogICAgbXlfYXJpbWEgPC0gYXV0by5hcmltYSh0cmFpbikNCiAgICANCiAgICAjIFVzZSB0cmFpbmVkIG1vZGVsIGZvciBmb3JlY2FzdGluZzogDQogICAgDQogICAgcHJlZGljdGVkIDwtIGZvcmVjYXN0KG15X2FyaW1hLCBoID0gbl9haGVhZCkkbWVhbiAlPiUgYXMudmVjdG9yKCkNCiAgICANCiAgICBkZlNlbGVjdGVkICU+JSANCiAgICAgIHNsaWNlKGVuZFRpbWUgKyAxKSAlPiUgDQogICAgICBtdXRhdGUocHJlZGljdGVkID0gcHJlZGljdGVkKSAlPiUgDQogICAgICBtdXRhdGUoZXJyID0gcHJlZGljdGVkIC0gb3BlbikgJT4lIA0KICAgICAgbXV0YXRlKHBlckVyciA9IHByZWRpY3RlZCAvIG9wZW4gLSAxKSAlPiUgDQogICAgICByZXR1cm4oKQ0KICB9DQogIA0KICAjIEZvcmVjYXN0IGZvciBhbGwgcmVtYWluaW5nIGRheXM6IA0KICANCiAgbl9kYXlzIDwtIG5yb3coZGZTZWxlY3RlZCkNCiAgDQogIG5fcmVtYWluaW5nIDwtIG5fZGF5cyAtIG5fd2luZG93bg0KICANCiAgZG8uY2FsbCgiYmluZF9yb3dzIiwgIGxhcHBseSgxOm5fcmVtYWluaW5nLCBhdXRvQVJJTUEpKSAtPiBkZkNvbXBhcmluZw0KICANCiAgcmV0dXJuKGRmQ29tcGFyaW5nKQ0KICANCiAgDQp9DQoNCg0KDQojIG9uZURheUFoZWFkRm9yZWNhc3QoaW5kaWNhdG9yU2VsZWN0ZWQgPSBhbGxJbmRpY2F0b3JbMV0pDQoNCiMgVXNlIHRoZSBmdW5jdGlvbjogDQoNCmRvLmNhbGwoImJpbmRfcm93cyIsIGxhcHBseShhbGxJbmRpY2F0b3IsIG9uZURheUFoZWFkRm9yZWNhc3QpKSAtPiBkZkZvcmVjYXN0Rm9yQWxsDQoNCmBgYA0KDQoNCmBgYHtyLCBldmFsPVRSVUUsIGVjaG89RkFMU0V9DQojIExvYWQgcmVzdWx0czogDQpyZWFkUkRTKCJkZkZvcmVjYXN0Rm9yQWxsLnJkcyIpIC0+IGRmRm9yZWNhc3RGb3JBbGwNCmBgYA0KDQpL4bq/dCBxdeG6oyBk4buxIGLDoW8gY2hvIDkgdGltZSBzZXJpZXMgxJHGsOG7o2MgdHLDrG5oIGLDoHkg4bufIFRhYmxlIDEgZMaw4bubaSDEkcOieTogDQoNCmBgYHtyLCBldmFsPVRSVUV9DQpkZkZvcmVjYXN0Rm9yQWxsICU+JSANCiAgbXV0YXRlKFVuZGVyNSA9IGNhc2Vfd2hlbihhYnMocGVyRXJyKSA8PSAwLjA1IH4gIlllcyIsIFRSVUUgfiAiTm8iKSkgJT4lIA0KICBncm91cF9ieShpbmRpY2F0b3IsIFVuZGVyNSkgJT4lIA0KICBjb3VudCgpICU+JSANCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IFVuZGVyNSwgdmFsdWVzX2Zyb20gPSBuLCB2YWx1ZXNfZmlsbCA9IDApICU+JSANCiAgbXV0YXRlKFRvdGFsID0gTm8gKyBZZXMpIC0+IGRmX3dpZGVyDQoNCg0KZGZGb3JlY2FzdEZvckFsbCAlPiUgDQogIGdyb3VwX2J5KGluZGljYXRvcikgJT4lIA0KICBzdW1tYXJpc2UoTWVhbkVSUiA9IG1lYW4ocGVyRXJyKSwgTWF4RVJSID0gbWF4KHBlckVyciksIE1pbkVSUiA9IG1pbihwZXJFcnIpKSAlPiUgDQogIGZ1bGxfam9pbihkZl93aWRlciAlPiUgc2VsZWN0KGluZGljYXRvciwgTnVtLk9LID0gWWVzLCBOdW0uRmFpbCA9IE5vLCBUb3RhbCkpIC0+IGRmUmVzdWx0cw0KDQoNCmRmUmVzdWx0cyAlPiUgDQogIG11dGF0ZV9pZihpcy5udW1lcmljLCBmdW5jdGlvbih4KSB7cm91bmQoeCwgNCl9KSAlPiUgDQogIGd0KCkgJT4lDQogIHRhYl9oZWFkZXIodGl0bGUgPSAiVGFibGUgMTogT25lLWRheSBhaGVhZCBmb3JlY2FzdGluZyBieSBBdXRvIEFSSU1BIikNCg0KYGBgDQoNCkvhur90IHF14bqjIG7DoHkgY2jhu4kgcmEgcuG6sW5nLCB24bubaSBCaXRjb2luIGNo4bqzbmcgaOG6oW46IA0KDQotIFRyb25nIHThu5VuZyBz4buRIDM0NCBr4bq/dCBxdeG6oyBk4buxIGLDoW8gKHRyxrDhu5tjIDEgbmfDoHkpLCBjw7MgMTggZOG7sSBiw6FvIGtow7RuZyB0aOG7j2EgbcOjbiDEkWnhu4F1IGtp4buHbiBzYWkgc+G7kXQgdHV54buHdCDEkeG7kWkgTUFFIG5o4buPIGjGoW4gaG/hurdjIGLhurFuZyA1JS4gQ8OybiBs4bqhaSAzMjYgZ2nDoSB0cuG7iyBk4buxIGLDoW8gdGjhu49hIG3Do24gxJFp4buBdSBraeG7h24gfE1BRXwgPD0gNSUuIA0KLSBTYWkgc+G7kSB0cnVuZyBiw6xuaCBNQUUgY+G7p2EgY8OhYyBk4buxIGLDoW8gbMOgIDAuMTklLiBE4buxIGvDqW0gbmjhuqV0IGLhu4sgc2FpIGzhu4djaCA5LjI0JSAoZ2nDoSB0cuG7iyDDom0gY8OzIG5naMSpYSBsw6AgdGjhuqVwIGjGoW4gZ2nDoSB0cuG7iyB0aOG7sWMgdOG6vyDEkcaw4bujYyB0aOG7gyBoaeG7h24g4bufIGPhu5l0IE1pbkVSUikuIA0KDQpWaeG7h2MgxJHhu41jIGhp4buDdSBjw6FjIGvDqnQgcXXhuqMgY2hvIDggY2jhu4kgc+G7kSBjw7JuIGzhuqFpIHTGsMahbmcgdOG7sSBuaMawIHRyw6puLiANCg0KRmlndXJlIDIgZMaw4bubaSDEkcOieSBzbyBzw6FuaCBnacOhIHRy4buLIGThu7EgYsOhbyB2w6AgZ2nDoSB0cuG7iyB0aOG7sWMgdOG6vyBj4bunYSA5IHRpbWUgc2VyaWVzOiANCg0KYGBge3IsIGV2YWw9RkFMU0V9DQoNCmRmRm9yZWNhc3RGb3JBbGwgJT4lIA0KICByZW5hbWUoQWN0dWFsID0gb3BlbiwgRm9yZWNhc3RlZCA9IHByZWRpY3RlZCkgJT4lIA0KICBwaXZvdF9sb25nZXIoY29scyA9IGMoIkFjdHVhbCIsICJGb3JlY2FzdGVkIiksIG5hbWVzX3RvID0gInR5cGUiLCB2YWx1ZXNfdG8gPSAidmFsdWUiKSAtPiBkZl9sb25nIA0KDQoNCmRmX2xvbmcgJT4lIA0KICBnZ3Bsb3QoYWVzKHggPSBkYXRlWU1ELCB5ID0gdmFsdWUsIGNvbG9yID0gdHlwZSkpICsgDQogIGdlb21fbGluZSgpICsgDQogIGZhY2V0X3dyYXAofiBpbmRpY2F0b3IsIHNjYWxlcyA9ICJmcmVlIikgKyANCiAgdGhlbWUoYXhpcy50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkgKyANCiAgdGhlbWUobGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKSArIA0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAidG9wIikgKyANCiAgdGhlbWUocGxvdC5tYXJnaW4gPSB1bml0KHJlcCgwLjUsIDQpLCAiY20iKSkgKyANCiAgbGFicyh0aXRsZSA9ICJGaWd1cmUgMjogQWN0dWFsIGFuZCBGb3JlY2FzdGVkIFZhbHVlcyIsIA0KICAgICAgIHN1YnRpdGxlID0gIk5vdGU6IE9uZS1kYXkgYWhlYWQgZm9yZWNhc3RpbmcgYnkgQXV0byBBUklNQSIsIA0KICAgICAgIGNhcHRpb24gPSAiU291cmNlOiBodHRwczovL3ZuLmludmVzdGluZy5jb20iKSAtPiBmaWcyMjIgDQoNCmZpZzIyMiANCmBgYA0KDQoNCiFbXShDOlxcVXNlcnNcXEFkbWluXFxEb2N1bWVudHNcXHRpbWVmaWcyLmpwZykNCg0KDQoNCg0KDQoNCg0KIyBDb25jbHVzaW9uDQoNCkLhurFuZyBjaOG7qW5nIHRo4buxYyBuZ2hpw6ptIHRyw6puIDkgdGltZSBzZXJpZXMgY2hvIHRo4bqleSBbQVJJTUFdKGh0dHBzOi8vd3d3LmludmVzdG9wZWRpYS5jb20vdGVybXMvYS9hdXRvcmVncmVzc2l2ZS1pbnRlZ3JhdGVkLW1vdmluZy1hdmVyYWdlLWFyaW1hLmFzcCkgY8OzIGto4bqjIG7Eg25nIGThu7EgYsOhbyB0csaw4bubYyAxIG5nw6B5IHLhuqV0IHThu5F0IChuaMawIMSRxrDhu6NjIGNo4buJIHJhIOG7nyBUYWJsZSAxIHbDoCBGaWd1cmUgMikuIE3hu5l0IGzhu7FhIGNo4buNbiBjw7MgdGjhu4MgbMOgIHPhu60gZOG7pW5nIGPDoWMgdGh14bqtdCB0b8OhbiBEZWVwIExlYXJuaW5nIG5oxrAgW0xvbmcgU2hvcnQtVGVybSBNZW1vcnkgLSBMU1RNXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9Mb25nX3Nob3J0LXRlcm1fbWVtb3J5KSBjaG8gYsOgaSB0b8OhbiBk4buxIGLDoW8uIENo4bunIMSR4buBIG7DoHkgc+G6vSDEkcaw4bujYyB0csOsbmggYsOgeSB0cm9uZyBt4buZdCBiw6BpIHZp4bq/dCBraMOhYyB0cm9uZyB0xrDGoW5nIGxhaS4gDQoNCg0KDQojIFJlZmVyZW5jZXMNCg0KMS4gQXdhbiwgVC5NLiBhbmQgQXNsYW0sIEYuLCAyMDIwLiBQcmVkaWN0aW9uIG9mIGRhaWx5IENPVklELTE5IGNhc2VzIGluIEV1cm9wZWFuIGNvdW50cmllcyB1c2luZyBhdXRvbWF0aWMgQVJJTUEgbW9kZWwuICpKb3VybmFsIG9mIHB1YmxpYyBoZWFsdGggcmVzZWFyY2gqLCA5KDMpLCBwcC5qcGhyLTIwMjAuDQoNCjIuIE1lbGFyZCwgRy4gYW5kIFBhc3RlZWxzLCBKLk0uLCAyMDAwLiBBdXRvbWF0aWMgQVJJTUEgbW9kZWxpbmcgaW5jbHVkaW5nIGludGVydmVudGlvbnMsIHVzaW5nIHRpbWUgc2VyaWVzIGV4cGVydCBzb2Z0d2FyZS4gKkludGVybmF0aW9uYWwgSm91cm5hbCBvZiBGb3JlY2FzdGluZyosIDE2KDQpLCBwcC40OTctNTA4Lg0KDQozLiBLaGFuLCBTLiBhbmQgQWxnaHVsYWlha2gsIEguLCAyMDIwLiBBUklNQSBtb2RlbCBmb3IgYWNjdXJhdGUgdGltZSBzZXJpZXMgc3RvY2tzIGZvcmVjYXN0aW5nLiAqSW50ZXJuYXRpb25hbCBKb3VybmFsIG9mIEFkdmFuY2VkIENvbXB1dGVyIFNjaWVuY2UgYW5kIEFwcGxpY2F0aW9ucyosIDExKDcpLg0KDQo0LiBHdXB0YSwgUy4gYW5kIFNoYXJtYSwgRC4sIDIwMjIuIFByZWRpY3Rpb24gb2YgQ09WSUQtMTkgc3ByZWFkIGluIHdvcmxkIHVzaW5nIHBhbmRlbWljIGRhdGFzZXQgd2l0aCBhcHBsaWNhdGlvbiBvZiBhdXRvIEFSSU1BIGFuZCBTSVIgbW9kZWxzLiAqSW50ZXJuYXRpb25hbCBKb3VybmFsIG9mIENyaXRpY2FsIEluZnJhc3RydWN0dXJlcyosIDE4KDIpLCBwcC4xNDgtMTU4Lg0KDQo2LiBLb3Z2dXJpLCBBLlIuLCBVcHBhbGFwYXRpLCBQLkouLCBCb250aHUsIFMuIGFuZCBLYW5kdWxhLCBOLlIuLCAyMDIyLCBOb3ZlbWJlci4gV2F0ZXIgTGV2ZWwgRm9yZWNhc3RpbmcgaW4gUmVzZXJ2b2lycyBVc2luZyBUaW1lIFNlcmllcyBBbmFseXNpc+KAk0F1dG8gQVJJTUEgTW9kZWwuICpJbiBJbnRlcm5hdGlvbmFsIENvbmZlcmVuY2Ugb24gQ29nbml0aXZlIENvbXB1dGluZyBhbmQgQ3liZXIgUGh5c2ljYWwgU3lzdGVtcyosIChwcC4gMTkyLTIwMCkuIENoYW06IFNwcmluZ2VyIE5hdHVyZSBTd2l0emVybGFuZC4NCg0KNy4gSHluZG1hbiwgUi5KLiBhbmQgS2hhbmRha2FyLCBZLiwgMjAwOC4gQXV0b21hdGljIHRpbWUgc2VyaWVzIGZvcmVjYXN0aW5nOiB0aGUgZm9yZWNhc3QgcGFja2FnZSBmb3IgUi4gKkpvdXJuYWwgb2Ygc3RhdGlzdGljYWwgc29mdHdhcmUqLCAyNywgcHAuMS0yMi4NCg0KDQoNCg0KDQoNCg==