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.

Backtesting for Evaluating Performance

Các giá trị được dự báo từ Auto ARIMA được thực hiện bằng phương pháp Backtesting được mô tả bằng hình dưới đây:

Trong đó training là phần dữ liệu được sử dụng để chọn các tham số tối ưu cho ARIMA, testing là phần dữ liệu sử dụng để kiểm tra ngược lại mức độ chính xác của dự báo bởi mô hình ARIMA. Ở đây tôi sử dụng dữ liệu quan sát của 360 ngày trong quá khứ (xấp xỉ 1 năm) làm training data và dữ liệu của ngày thứ 361 để đánh giá mức độ chính xác kết quả dự báo trước một ngày thu được từ mô hình.

Mean Absolute Error (MAE) được lựa chọn làm tiêu chuẩn để đánh mức độ chính xác của dự báo. Những giá trị dự báo mà giá trị tuyệt đối của MAE nhỏ hơn hoặc bằng 5% sẽ được gọi là đạt chuẩn.

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 

Conclusion

Bằng chứng thực nghiêm trên 9 time series cho thấy ARIMA có khả năng dự báo trước 1 ngày rất tốt (như được chỉ ra ở Table 1 và Figure 2). Một lựa chọn có thể là sử dụng các thuật toán Deep Learning như Long Short-Term Memory - LSTM cho bài toán dự báo. Chủ đề này sẽ được trình bày trong một bài viết khác trong tương lai.

References

  1. Awan, T.M. and Aslam, F., 2020. Prediction of daily COVID-19 cases in European countries using automatic ARIMA model. Journal of public health research, 9(3), pp.jphr-2020.

  2. Melard, G. and Pasteels, J.M., 2000. Automatic ARIMA modeling including interventions, using time series expert software. International Journal of Forecasting, 16(4), pp.497-508.

  3. Khan, S. and Alghulaiakh, H., 2020. ARIMA model for accurate time series stocks forecasting. International Journal of Advanced Computer Science and Applications, 11(7).

  4. Gupta, S. and Sharma, D., 2022. Prediction of COVID-19 spread in world using pandemic dataset with application of auto ARIMA and SIR models. International Journal of Critical Infrastructures, 18(2), pp.148-158.

  5. Kovvuri, A.R., Uppalapati, P.J., Bonthu, S. and Kandula, N.R., 2022, November. Water Level Forecasting in Reservoirs Using Time Series Analysis–Auto ARIMA Model. In International Conference on Cognitive Computing and Cyber Physical Systems, (pp. 192-200). Cham: Springer Nature Switzerland.

  6. Hyndman, R.J. and Khandakar, Y., 2008. Automatic time series forecasting: the forecast package for R. Journal of statistical software, 27, pp.1-22.

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==