1.0 data - etfs lists: ticker & name

filtered to remove leveraged/short/inversed etfs, price >= 10$ & mktcap >= 100m as of 2018

1.1 data - etf prices

from 2015-06-05 to 2022-06-07

etf_prices <- foreach(var = 1:nrow(etf_list), .combine = "bind_rows", .packages = "data.table") %dopar% {
  price_api(
    ticker = etf_list$ticker[var],
    from = from_date,
    to = to_date
  )
}

etf_prices <- etf_prices %>%
  rename_all(~tolower(.)) %>%
  select(ticker, everything()) %>%
  drop_na(close) %>%
  mutate(date = ymd(date)) %>%
  arrange(ticker, date)

head(etf_prices, 5)

2.0 spy - filtered for spy prices & calculated returns


daily simple returns = (last/first)-1

hold = (close/lag(close))-1

overnight = (open/lag(close))-1

intraday = (close/open)-1

the first days of all returns set to 0

head(spy_returns, 5)

2.1 spy - cumulative return timeseries

2.2 spy - return/inferential metrics

2015-06-05 to 2022-06-07

spy_returns %>%
  pivot_longer(hold:intraday, names_to = "spy") %>%
  group_by(spy) %>%
  summarise(return = port(value, total = TRUE),
            mean = mean(value),
            median = median(value),
            min = min(value),
            q10 = quantile(value, .1),
            q90 = quantile(value, .9),
            max = max(value))

2.3 spy - return/portfolio metrics

spy_returns %>%
  pivot_longer(hold:intraday, names_to = "spy") %>%
  group_by(spy) %>%
  mutate(portfolio = port(value, balance = 10000)) %>% 
  summarise(return = port(value, total = TRUE),
            cagr = cagr(portfolio, days = as.numeric(last(date)-first(date))),
            stdev = sd(value),
            annual_vol = stdev*sqrt(252),
            sharpe = cagr/annual_vol)

3.0 all etfs - return summary tbl grouped by ticker

ie. first row shows the simple returns of hold:intraday for aaxj, for 2015-06 to 2022-06. overnight was filtered for <= q95(overnight). seemed to have some bad data (possible outliers) for 5-8 tickers, quick attempt to remove them.

3.1 all etfs - summary tbl of the table from 3.0

etf_returns %>%
  summarise(across(hold:intraday, ~port(., total = TRUE)),
            trading_days = n()) %>% 
  filter(overnight <= quantile(overnight, .95)) %>% 
  summary() 
    ticker               hold            overnight           intraday         trading_days 
 Length:344         Min.   :-0.70217   Min.   :-0.76216   Min.   :-0.85631   Min.   :1106  
 Class :character   1st Qu.:-0.04497   1st Qu.:-0.04444   1st Qu.:-0.27788   1st Qu.:1765  
 Mode  :character   Median : 0.26180   Median : 0.33977   Median :-0.02531   Median :1765  
                    Mean   : 0.39452   Mean   : 0.64582   Mean   :-0.01074   Mean   :1749  
                    3rd Qu.: 0.75331   3rd Qu.: 1.11882   3rd Qu.: 0.17950   3rd Qu.:1765  
                    Max.   : 3.43673   Max.   : 4.77693   Max.   : 2.31032   Max.   :1765  

3.2 all etfs - boxplots of 3.0 data

3.3 all etfs - top3 overnight return tickers

wanted to take a closer look at the top tickers for overnight returns: gvip, ptf, & xme. graphed a cumulative return timeseries for them. question: highest overnight return doesn’t necessarily mean overnight outperforms the buyhold strat per ticker. creating a new metric next section to look into this.

3.4 all etfs - new metric: ‘excess overnight returns’ / ‘overnight premium’

created new metric called ‘premium’ = overnight returns - hold returns. arranged by descending order, top5 listed below

3.5 all etfs - graphed cumulative returns of top3 etfs from 3.4

the data for some of these might not be right

4.0 text eda - tokenized words from etf names

  1. calculated the total return by ticker
  2. split etf names into words
  3. grouped words, then aggregated ticker returns + counted how many times each word was in data (frequency)
  4. distinct’d all return data to avoid words with same return values (happens when words are shown multiple times, same value, same etf. ie. “S & P…”)
  5. filtered for word count to be between 10 & 100 (wanted to see a grouping effect/relationship, doesn’t help if one word is from one ticker or if the word is etf, index, or fund)
  6. graphed a verticle column chart by word, by return type (ie. hold/overnight/intraday), colored/filled by count
etf_returns %>%
  summarise(across(hold:intraday, ~port(., total = TRUE))) %>%
  left_join(etf_list, by = "ticker") %>%
  unnest_tokens(words, name) %>%
  group_by(words) %>%
  summarise(across(hold:intraday, ~mean(.)),
            count = n()) %>%
  distinct(hold, overnight, intraday, .keep_all = TRUE) %>%
  filter(count >= 10 & count <= 100) %>%
  pivot_longer(hold:intraday) %>%
  mutate(words = reorder_within(words, value, name)) %>%
  ggplot(aes(value, words, fill = count)) +
  geom_col() +
  scale_y_reordered() +
  facet_wrap(~name, scales = "free") +
  labs(title = "aggregated returns by tokenized words",
       subtitle = "avg total returns of 300+ etfs by words from name, 2015-2020",
       x = "avg total returns (%)", y = "")

4.1 text eda - same concept as 4.0 except for the metric: premium

5.0 date eda - boxplots of etf returns by year

how do these returns pane out in years like 2020?

etf_returns %>%
  mutate(year = year(date)) %>%
  group_by(ticker, year) %>%
  summarise(across(hold:intraday, ~port(., total = TRUE)),
            count = n()) %>%
  filter(count >= 220) %>%
  pivot_longer(hold:intraday) %>%
  ggplot(aes(name, value, fill = name)) +
  geom_boxplot(outlier.shape=NA) +
  facet_wrap(~year, scales = "free") +
  coord_cartesian(ylim = c(-.5, 1)) +
  geom_hline(yintercept = 0, linetype = 2, alpha = 0.5, color = "red") +
  labs(title = "etf returns by year",
       subtitle = "2016-2021, full years",
       x = "", y = "return (%)")
`summarise()` has grouped output by 'ticker'. You can override using the `.groups` argument.

5.1 date eda - summary tbl of data from 5.0

etf returns are aggregated by year

etf_returns %>%
  mutate(year = year(date)) %>%
  group_by(ticker, year) %>%
  summarise(across(hold:intraday, ~port(., total = TRUE)),
            count = n()) %>%
  filter(count >= 220) %>%
  mutate(premium = overnight-hold) %>%
  ungroup() %>%
  group_by(year) %>%
  summarise(mean = mean(premium),
            median = median(premium),
            sd = sd(premium),
            q10 = quantile(premium, .1),
            q90 = quantile(premium, .9),
            etfs_count = n())
`summarise()` has grouped output by 'ticker'. You can override using the `.groups` argument.

5.2 date eda - boxplots of etf returns by weekday

similar concept as 5.1, except for weekday. should be noted, the day of overnight returns are of the day that they are closed, not opened. ie. overnight returns for monday are actually the returns held from friday close to monday open.

etf_returns %>%
  mutate(weekday = weekdays(date)) %>%
  group_by(ticker, weekday) %>%
  summarise(across(hold:intraday, ~port(., total = TRUE)),
            count = n()) %>%
  filter(count >= 220) %>%
  pivot_longer(hold:intraday) %>%
  ggplot(aes(name, value, fill = name)) +
  geom_boxplot(outlier.shape=NA) +
  facet_wrap(~weekday, scales = "free") +
  coord_cartesian(ylim = c(-.5, 1.2)) +
  geom_hline(yintercept = 0, linetype = 2, alpha = 0.5, color = "red") +
  labs(title = "etf returns by weekday",
       subtitle = "2016-2021, full years",
       x = "", y = "return (%)")
`summarise()` has grouped output by 'ticker'. You can override using the `.groups` argument.

5.3 date eda - boxplots of etf ‘excess overnight returns’ by year + weekdays

similar concept as above boxplots, but combined for one metric: excess overnight returns (overnight returns - hold returns)

etf_returns %>%
  mutate(year = year(date),
         weekday = weekdays(date, abbreviate = TRUE)) %>%
  group_by(ticker, year, weekday) %>%
  summarise(across(hold:overnight, ~port(., total = TRUE)),
            count = n()) %>%
  filter(count >= 45) %>%
  mutate(premium = overnight-hold) %>%
  ggplot(aes(weekday, premium, fill = weekday)) +
  geom_boxplot(outlier.shape=NA) +
  facet_wrap(~year, scales = "free") +
  coord_cartesian(ylim = c(-.3, 0.3)) +
  geom_hline(yintercept = 0, linetype = 2, alpha = 0.5, color = "red") +
  labs(title = "'excess overnight returns' by year + weekdays",
       subtitle = "2016-2021, full years",
       x = "", y = "return (%)")
`summarise()` has grouped output by 'ticker', 'year'. You can override using the `.groups` argument.

5.4 date eda - boxplots of etf ‘excess overnight returns’ by year + quarter

similar concept as above boxplots, but combined for one metric: excess overnight returns (overnight returns - hold returns)

etf_returns %>%
  mutate(year = year(date),
         quarter = as.factor(quarter(date))) %>%
  group_by(ticker, year, quarter) %>%
  summarise(across(hold:overnight, ~port(., total = TRUE)),
            count = n()) %>%
  filter(year %in% c(2016:2021)) %>%
  mutate(premium = overnight-hold) %>%
  ggplot(aes(quarter, premium, fill = quarter)) +
  geom_boxplot(outlier.shape=NA) +
  facet_wrap(~year, scales = "free") +
  coord_cartesian(ylim = c(-.25, 0.35)) +
  geom_hline(yintercept = 0, linetype = 2, alpha = 0.5, color = "red") +
  labs(title = "'excess overnight returns' by year + quarter",
       subtitle = "2016-2021, full years",
       x = "", y = "return (%)")
`summarise()` has grouped output by 'ticker', 'year'. You can override using the `.groups` argument.

LS0tDQp0aXRsZTogIm92ZXJuaWdodCBlZGEiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQpgYGB7ciwgaW5jbHVkZT1GQUxTRX0NCiMgbGlicmFyaWVzL3V0aWxpdGllcw0Kc291cmNlKCJ1dGlscy9saWJyYXJpZXMuUiIpDQpzb3VyY2UoInV0aWxzL2hlbHBlcl9mdW5jdGlvbnMuUiIpDQoNCiMgb3B0aW9ucw0Kb3B0aW9ucyhzY2lwZW4gPSAxMDApDQpyZWdpc3RlckRvUGFyYWxsZWwoY29yZXMgPSAxMCkNCmtuaXRyOjpvcHRzX2NodW5rJHNldChmaWcud2lkdGggPSAxMiwgZmlnLmhlaWdodCA9IDUpDQpgYGANCg0KIyAxLjAgZGF0YSAtIGV0ZnMgbGlzdHM6IHRpY2tlciAmIG5hbWUNCmZpbHRlcmVkIHRvIHJlbW92ZSBsZXZlcmFnZWQvc2hvcnQvaW52ZXJzZWQgZXRmcywgcHJpY2UgPj0gMTAkICYgbWt0Y2FwID49IDEwMG0gYXMgb2YgMjAxOA0KDQpgYGB7cn0NCmV0Zl9saXN0ICU+JQ0KICBzZWxlY3QodGlja2VyLCBuYW1lKQ0KYGBgDQoNCiMgMS4xIGRhdGEgLSBldGYgcHJpY2VzDQpmcm9tIDIwMTUtMDYtMDUgdG8gMjAyMi0wNi0wNw0KDQpgYGB7cn0NCmV0Zl9wcmljZXMgPC0gZXRmX3ByaWNlcyAlPiUNCiAgcmVuYW1lX2FsbCh+dG9sb3dlciguKSkgJT4lDQogIHNlbGVjdCh0aWNrZXIsIGV2ZXJ5dGhpbmcoKSkgJT4lDQogIGRyb3BfbmEoY2xvc2UpICU+JQ0KICBtdXRhdGUoZGF0ZSA9IHltZChkYXRlKSkgJT4lDQogIGFycmFuZ2UodGlja2VyLCBkYXRlKQ0KDQpoZWFkKGV0Zl9wcmljZXMsIDUpDQpgYGANCg0KYGBge3IsIGluY2x1ZGU9RkFMU0V9DQpzcHlfcmV0dXJucyA8LSBldGZfcHJpY2VzICU+JQ0KICBmaWx0ZXIodGlja2VyID09ICJTUFkiKSAlPiUNCiAgbXV0YXRlKGhvbGQgPSBjbG9zZS9sYWcoY2xvc2UpLTEsDQogICAgICAgICBvdmVybmlnaHQgPSBvcGVuL2xhZyhjbG9zZSktMSwNCiAgICAgICAgIGludHJhZGF5ID0gY2xvc2Uvb3Blbi0xLA0KICAgICAgICAgYWNyb3NzKGhvbGQ6aW50cmFkYXksIH5yZXBsYWNlKC4sIDEsIDApKSkNCmBgYA0KDQojIDIuMCBzcHkgLSBmaWx0ZXJlZCBmb3Igc3B5IHByaWNlcyAmIGNhbGN1bGF0ZWQgcmV0dXJucw0KPGJyPmRhaWx5IHNpbXBsZSByZXR1cm5zID0gKGxhc3QvZmlyc3QpLTE8L2JyPg0KPGJyPmhvbGQgPSAoY2xvc2UvbGFnKGNsb3NlKSktMTwvYnI+DQo8YnI+b3Zlcm5pZ2h0ID0gKG9wZW4vbGFnKGNsb3NlKSktMTwvYnI+DQo8YnI+aW50cmFkYXkgPSAoY2xvc2Uvb3BlbiktMTwvYnI+DQo8YnI+dGhlIGZpcnN0IGRheXMgb2YgYWxsIHJldHVybnMgc2V0IHRvIDA8L2JyPg0KDQpgYGB7cn0NCmhlYWQoc3B5X3JldHVybnMsIDUpDQpgYGANCg0KIyAyLjEgc3B5IC0gY3VtdWxhdGl2ZSByZXR1cm4gdGltZXNlcmllcw0KDQpgYGB7cn0NCnNweV9yZXR1cm5zICU+JQ0KICBtdXRhdGUoYWNyb3NzKGhvbGQ6aW50cmFkYXksIH5wb3J0KC4pKSkgJT4lICAjIHN1bW1hcnkoKQ0KICBwaXZvdF9sb25nZXIoaG9sZDppbnRyYWRheSkgJT4lDQogIGdncGxvdChhZXMoZGF0ZSwgdmFsdWUsIGNvbG9yID0gbmFtZSkpICsNCiAgZ2VvbV9saW5lKCkgKw0KICBsYWJzKHRpdGxlID0gInNweSBjdW11bGF0aXZlIHJldHVybnMiLA0KICAgICAgIHN1YnRpdGxlID0gIjIwMTUtMDYtMDUgdG8gMjAyMi0wNi0wNyIsDQogICAgICAgeCA9ICIiLCB5ID0gInJldHVybnMgKCUpIikNCmBgYA0KDQojIDIuMiBzcHkgLSByZXR1cm4vaW5mZXJlbnRpYWwgbWV0cmljcw0KMjAxNS0wNi0wNSB0byAyMDIyLTA2LTA3DQoNCmBgYHtyfQ0Kc3B5X3JldHVybnMgJT4lDQogIHBpdm90X2xvbmdlcihob2xkOmludHJhZGF5LCBuYW1lc190byA9ICJzcHkiKSAlPiUNCiAgZ3JvdXBfYnkoc3B5KSAlPiUNCiAgc3VtbWFyaXNlKHJldHVybiA9IHBvcnQodmFsdWUsIHRvdGFsID0gVFJVRSksDQogICAgICAgICAgICBtZWFuID0gbWVhbih2YWx1ZSksDQogICAgICAgICAgICBtZWRpYW4gPSBtZWRpYW4odmFsdWUpLA0KICAgICAgICAgICAgbWluID0gbWluKHZhbHVlKSwNCiAgICAgICAgICAgIHExMCA9IHF1YW50aWxlKHZhbHVlLCAuMSksDQogICAgICAgICAgICBxOTAgPSBxdWFudGlsZSh2YWx1ZSwgLjkpLA0KICAgICAgICAgICAgbWF4ID0gbWF4KHZhbHVlKSkNCmBgYA0KDQojIDIuMyBzcHkgLSByZXR1cm4vcG9ydGZvbGlvIG1ldHJpY3MNCg0KYGBge3J9DQpzcHlfcmV0dXJucyAlPiUNCiAgcGl2b3RfbG9uZ2VyKGhvbGQ6aW50cmFkYXksIG5hbWVzX3RvID0gInNweSIpICU+JQ0KICBncm91cF9ieShzcHkpICU+JQ0KICBtdXRhdGUocG9ydGZvbGlvID0gcG9ydCh2YWx1ZSwgYmFsYW5jZSA9IDEwMDAwKSkgJT4lIA0KICBzdW1tYXJpc2UocmV0dXJuID0gcG9ydCh2YWx1ZSwgdG90YWwgPSBUUlVFKSwNCiAgICAgICAgICAgIGNhZ3IgPSBjYWdyKHBvcnRmb2xpbywgZGF5cyA9IGFzLm51bWVyaWMobGFzdChkYXRlKS1maXJzdChkYXRlKSkpLA0KICAgICAgICAgICAgc3RkZXYgPSBzZCh2YWx1ZSksDQogICAgICAgICAgICBhbm51YWxfdm9sID0gc3RkZXYqc3FydCgyNTIpLA0KICAgICAgICAgICAgc2hhcnBlID0gY2Fnci9hbm51YWxfdm9sKQ0KYGBgDQoNCiMgMy4wIGFsbCBldGZzIC0gcmV0dXJuIHN1bW1hcnkgdGJsIGdyb3VwZWQgYnkgdGlja2VyDQppZS4gZmlyc3Qgcm93IHNob3dzIHRoZSBzaW1wbGUgcmV0dXJucyBvZiBob2xkOmludHJhZGF5IGZvciBhYXhqLCBmb3IgMjAxNS0wNiB0byAyMDIyLTA2Lg0Kb3Zlcm5pZ2h0IHdhcyBmaWx0ZXJlZCBmb3IgPD0gcTk1KG92ZXJuaWdodCkuIHNlZW1lZCB0byBoYXZlIHNvbWUgYmFkIGRhdGEgKHBvc3NpYmxlIG91dGxpZXJzKSBmb3IgNS04IHRpY2tlcnMsIHF1aWNrIGF0dGVtcHQgdG8gcmVtb3ZlIHRoZW0uDQoNCmBgYHtyfQ0KIyBldGZfcmV0dXJucyA8LSBldGZfcHJpY2VzICU+JQ0KIyAgIGdyb3VwX2J5KHRpY2tlcikgJT4lDQojICAgbXV0YXRlKGhvbGQgPSBjbG9zZS9sYWcoY2xvc2UpLTEsDQojICAgICAgICAgIG92ZXJuaWdodCA9IG9wZW4vbGFnKGNsb3NlKS0xLA0KIyAgICAgICAgICBpbnRyYWRheSA9IGNsb3NlL29wZW4tMSwNCiMgICAgICAgICAgYWNyb3NzKGhvbGQ6aW50cmFkYXksIH5yZXBsYWNlKC4sIDEsIDApKSkgJT4lDQojICAgc2VsZWN0KHRpY2tlciwgZGF0ZSwgaG9sZDppbnRyYWRheSkNCg0KZXRmX3JldHVybnMgJT4lDQogIHN1bW1hcmlzZShhY3Jvc3MoaG9sZDppbnRyYWRheSwgfnBvcnQoLiwgdG90YWwgPSBUUlVFKSksDQogICAgICAgICAgICB0cmFkaW5nX2RheXMgPSBuKCkpICU+JSANCiAgZmlsdGVyKG92ZXJuaWdodCA8PSBxdWFudGlsZShvdmVybmlnaHQsIC45NSkpICU+JQ0KICBoZWFkKDUpDQpgYGANCg0KIyAzLjEgYWxsIGV0ZnMgLSBzdW1tYXJ5IHRibCBvZiB0aGUgdGFibGUgZnJvbSAzLjANCg0KYGBge3J9DQpldGZfcmV0dXJucyAlPiUNCiAgc3VtbWFyaXNlKGFjcm9zcyhob2xkOmludHJhZGF5LCB+cG9ydCguLCB0b3RhbCA9IFRSVUUpKSwNCiAgICAgICAgICAgIHRyYWRpbmdfZGF5cyA9IG4oKSkgJT4lIA0KICBmaWx0ZXIob3Zlcm5pZ2h0IDw9IHF1YW50aWxlKG92ZXJuaWdodCwgLjk1KSkgJT4lIA0KICBzdW1tYXJ5KCkgDQpgYGANCg0KIyAzLjIgYWxsIGV0ZnMgLSBib3hwbG90cyBvZiAzLjAgZGF0YQ0KDQpgYGB7cn0NCmV0Zl9yZXR1cm5zICU+JQ0KICBzdW1tYXJpc2UoYWNyb3NzKGhvbGQ6aW50cmFkYXksIH5wb3J0KC4sIHRvdGFsID0gVFJVRSkpKSAlPiUgDQogIGZpbHRlcihvdmVybmlnaHQgPD0gcXVhbnRpbGUob3Zlcm5pZ2h0LCAuOTUpKSAlPiUgDQogIHBpdm90X2xvbmdlcihob2xkOmludHJhZGF5KSAlPiUNCiAgZ2dwbG90KGFlcyhuYW1lLCB2YWx1ZSkpICsNCiAgZ2VvbV9ib3hwbG90KG91dGxpZXIuc2hhcGU9TkEpICsNCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgbGluZXR5cGUgPSAyLCBhbHBoYSA9IDAuNSwgY29sb3IgPSAicmVkIikgKw0KICBjb29yZF9jYXJ0ZXNpYW4oeWxpbSA9IGMoLTEsMykpICsNCiAgbGFicyh0aXRsZSA9ICJldGYgcmV0dXJucyIsDQogICAgICAgc3VidGl0bGUgPSAiMjAxNS0wNi0wNSB0byAyMDIyLTA2LTA3IiwNCiAgICAgICB4ID0gIiIsIHkgPSAicmV0dXJucyAoJSkiKQ0KYGBgDQoNCiMgMy4zIGFsbCBldGZzIC0gdG9wMyBvdmVybmlnaHQgcmV0dXJuIHRpY2tlcnMNCndhbnRlZCB0byB0YWtlIGEgY2xvc2VyIGxvb2sgYXQgdGhlIHRvcCB0aWNrZXJzIGZvciBvdmVybmlnaHQgcmV0dXJuczogZ3ZpcCwgcHRmLCAmIHhtZS4gZ3JhcGhlZCBhIGN1bXVsYXRpdmUgcmV0dXJuIHRpbWVzZXJpZXMgZm9yIHRoZW0uDQpxdWVzdGlvbjogaGlnaGVzdCBvdmVybmlnaHQgcmV0dXJuIGRvZXNuJ3QgbmVjZXNzYXJpbHkgbWVhbiBvdmVybmlnaHQgb3V0cGVyZm9ybXMgdGhlIGJ1eWhvbGQgc3RyYXQgcGVyIHRpY2tlci4gY3JlYXRpbmcgYSBuZXcgbWV0cmljIG5leHQgc2VjdGlvbiB0byBsb29rIGludG8gdGhpcy4NCg0KYGBge3IsIGZpZy53aWR0aD0xMH0NCmV0Zl9yZXR1cm5zICU+JQ0KICBmaWx0ZXIodGlja2VyICVpbiUgYygiR1ZJUCIsICJQVEYiLCAiWE1FIikpICU+JQ0KICBtdXRhdGUoYWNyb3NzKGhvbGQ6aW50cmFkYXksIH5wb3J0KC4pKSkgJT4lDQogIHBpdm90X2xvbmdlcihob2xkOmludHJhZGF5KSAlPiUNCiAgZ2dwbG90KGFlcyhkYXRlLCB2YWx1ZSwgY29sb3IgPSBuYW1lKSkgKw0KICBnZW9tX2xpbmUoKSArDQogIGZhY2V0X3dyYXAofnRpY2tlciwgc2NhbGVzID0gImZyZWUiKSArDQogIGxhYnModGl0bGUgPSAidG9wMyBvdmVybmlnaHQgY3VtdWxhdGl2ZSByZXR1cm5zIiwNCiAgICAgICBzdWJ0aXRsZSA9ICIyMDE1LTA2LTA1IHRvIDIwMjItMDYtMDciLA0KICAgICAgIHggPSAiIiwgeSA9ICJyZXR1cm5zICglKSIpDQpgYGANCg0KIyAzLjQgYWxsIGV0ZnMgLSBuZXcgbWV0cmljOiAnZXhjZXNzIG92ZXJuaWdodCByZXR1cm5zJyAvICdvdmVybmlnaHQgcHJlbWl1bScNCmNyZWF0ZWQgbmV3IG1ldHJpYyBjYWxsZWQgJ3ByZW1pdW0nID0gb3Zlcm5pZ2h0IHJldHVybnMgLSBob2xkIHJldHVybnMuDQphcnJhbmdlZCBieSBkZXNjZW5kaW5nIG9yZGVyLCB0b3A1IGxpc3RlZCBiZWxvdw0KDQpgYGB7cn0NCmV0Zl9yZXR1cm5zICU+JQ0KICBzdW1tYXJpc2UoYWNyb3NzKGhvbGQ6b3Zlcm5pZ2h0LCB+cG9ydCguLCB0b3RhbCA9IFRSVUUpKSkgJT4lDQogIG11dGF0ZShwcmVtaXVtID0gb3Zlcm5pZ2h0LWhvbGQpICU+JQ0KICBhcnJhbmdlKGRlc2MocHJlbWl1bSkpICU+JQ0KICBmaWx0ZXIob3Zlcm5pZ2h0IDw9IHF1YW50aWxlKG92ZXJuaWdodCwgLjk1KSkgJT4lDQogIGhlYWQoNSkNCmBgYA0KDQojIDMuNSBhbGwgZXRmcyAtIGdyYXBoZWQgY3VtdWxhdGl2ZSByZXR1cm5zIG9mIHRvcDMgZXRmcyBmcm9tIDMuNA0KdGhlIGRhdGEgZm9yIHNvbWUgb2YgdGhlc2UgbWlnaHQgbm90IGJlIHJpZ2h0DQoNCmBgYHtyLCBmaWcud2lkdGg9MTB9DQpldGZfcmV0dXJucyAlPiUNCiAgZmlsdGVyKHRpY2tlciAlaW4lIGMoIktXRUIiLCAiRkVOWSIsICJHVklQIikpICU+JQ0KICBtdXRhdGUoYWNyb3NzKGhvbGQ6aW50cmFkYXksIH5wb3J0KC4pKSkgJT4lDQogIHBpdm90X2xvbmdlcihob2xkOmludHJhZGF5KSAlPiUNCiAgZ2dwbG90KGFlcyhkYXRlLCB2YWx1ZSwgY29sb3IgPSBuYW1lKSkgKw0KICBnZW9tX2xpbmUoKSArDQogIGZhY2V0X3dyYXAofnRpY2tlciwgc2NhbGVzID0gImZyZWUiKSArDQogIGxhYnModGl0bGUgPSAidG9wMyBvdmVybmlnaHQgY3VtdWxhdGl2ZSByZXR1cm5zIiwNCiAgICAgICBzdWJ0aXRsZSA9ICIyMDE1LTA2LTA1IHRvIDIwMjItMDYtMDciLA0KICAgICAgIHggPSAiIiwgeSA9ICJyZXR1cm5zICglKSIpDQpgYGANCg0KDQojIDQuMCB0ZXh0IGVkYSAtIHRva2VuaXplZCB3b3JkcyBmcm9tIGV0ZiBuYW1lcw0KMS4gY2FsY3VsYXRlZCB0aGUgdG90YWwgcmV0dXJuIGJ5IHRpY2tlcg0KMi4gc3BsaXQgZXRmIG5hbWVzIGludG8gd29yZHMNCjMuIGdyb3VwZWQgd29yZHMsIHRoZW4gYWdncmVnYXRlZCB0aWNrZXIgcmV0dXJucyArIGNvdW50ZWQgaG93IG1hbnkgdGltZXMgZWFjaCB3b3JkIHdhcyBpbiBkYXRhIChmcmVxdWVuY3kpDQo0LiBkaXN0aW5jdCdkIGFsbCByZXR1cm4gZGF0YSB0byBhdm9pZCB3b3JkcyB3aXRoIHNhbWUgcmV0dXJuIHZhbHVlcyAoaGFwcGVucyB3aGVuIHdvcmRzIGFyZSBzaG93biBtdWx0aXBsZSB0aW1lcywgc2FtZSB2YWx1ZSwgc2FtZSBldGYuIGllLiAiUyAmIFAuLi4iKQ0KNS4gZmlsdGVyZWQgZm9yIHdvcmQgY291bnQgdG8gYmUgYmV0d2VlbiAxMCAmIDEwMCAod2FudGVkIHRvIHNlZSBhIGdyb3VwaW5nIGVmZmVjdC9yZWxhdGlvbnNoaXAsIGRvZXNuJ3QgaGVscCBpZiBvbmUgd29yZCBpcyBmcm9tIG9uZSB0aWNrZXIgb3IgaWYgdGhlIHdvcmQgaXMgZXRmLCBpbmRleCwgb3IgZnVuZCkNCjYuIGdyYXBoZWQgYSB2ZXJ0aWNsZSBjb2x1bW4gY2hhcnQgYnkgd29yZCwgYnkgcmV0dXJuIHR5cGUgKGllLiBob2xkL292ZXJuaWdodC9pbnRyYWRheSksIGNvbG9yZWQvZmlsbGVkIGJ5IGNvdW50DQoNCmBgYHtyLCBmaWcuaGVpZ2h0PTEyfQ0KZXRmX3JldHVybnMgJT4lDQogIHN1bW1hcmlzZShhY3Jvc3MoaG9sZDppbnRyYWRheSwgfnBvcnQoLiwgdG90YWwgPSBUUlVFKSkpICU+JQ0KICBsZWZ0X2pvaW4oZXRmX2xpc3QsIGJ5ID0gInRpY2tlciIpICU+JQ0KICB1bm5lc3RfdG9rZW5zKHdvcmRzLCBuYW1lKSAlPiUNCiAgZ3JvdXBfYnkod29yZHMpICU+JQ0KICBzdW1tYXJpc2UoYWNyb3NzKGhvbGQ6aW50cmFkYXksIH5tZWFuKC4pKSwNCiAgICAgICAgICAgIGNvdW50ID0gbigpKSAlPiUNCiAgZGlzdGluY3QoaG9sZCwgb3Zlcm5pZ2h0LCBpbnRyYWRheSwgLmtlZXBfYWxsID0gVFJVRSkgJT4lDQogIGZpbHRlcihjb3VudCA+PSAxMCAmIGNvdW50IDw9IDEwMCkgJT4lDQogIHBpdm90X2xvbmdlcihob2xkOmludHJhZGF5KSAlPiUNCiAgbXV0YXRlKHdvcmRzID0gcmVvcmRlcl93aXRoaW4od29yZHMsIHZhbHVlLCBuYW1lKSkgJT4lDQogIGdncGxvdChhZXModmFsdWUsIHdvcmRzLCBmaWxsID0gY291bnQpKSArDQogIGdlb21fY29sKCkgKw0KICBzY2FsZV95X3Jlb3JkZXJlZCgpICsNCiAgZmFjZXRfd3JhcCh+bmFtZSwgc2NhbGVzID0gImZyZWUiKSArDQogIGxhYnModGl0bGUgPSAiYWdncmVnYXRlZCByZXR1cm5zIGJ5IHRva2VuaXplZCB3b3JkcyIsDQogICAgICAgc3VidGl0bGUgPSAiYXZnIHRvdGFsIHJldHVybnMgb2YgMzAwKyBldGZzIGJ5IHdvcmRzIGZyb20gbmFtZSwgMjAxNS0yMDIwIiwNCiAgICAgICB4ID0gImF2ZyB0b3RhbCByZXR1cm5zICglKSIsIHkgPSAiIikNCmBgYA0KDQojIDQuMSB0ZXh0IGVkYSAtIHNhbWUgY29uY2VwdCBhcyA0LjAgZXhjZXB0IGZvciB0aGUgbWV0cmljOiBwcmVtaXVtDQoNCmBgYHtyLCBmaWcuaGVpZ2h0PTh9DQpldGZfcmV0dXJucyAlPiUNCiAgc3VtbWFyaXNlKGFjcm9zcyhob2xkOm92ZXJuaWdodCwgfnBvcnQoLiwgdG90YWwgPSBUUlVFKSkpICU+JQ0KICBtdXRhdGUocHJlbWl1bSA9IG92ZXJuaWdodC1ob2xkKSAlPiUNCiAgbGVmdF9qb2luKGV0Zl9saXN0LCBieSA9ICJ0aWNrZXIiKSAlPiUNCiAgdW5uZXN0X3Rva2Vucyh3b3JkcywgbmFtZSkgJT4lDQogIGdyb3VwX2J5KHdvcmRzKSAlPiUNCiAgc3VtbWFyaXNlKHByZW1pdW0gPSBtZWFuKHByZW1pdW0pLA0KICAgICAgICAgICAgY291bnQgPSBuKCkpICU+JQ0KICBkaXN0aW5jdChwcmVtaXVtLCAua2VlcF9hbGwgPSBUUlVFKSAlPiUNCiAgZmlsdGVyKGNvdW50ID49IDEwICYgY291bnQgPD0gMTAwKSAlPiUgDQogIGdncGxvdChhZXMocHJlbWl1bSwgZmN0X3Jlb3JkZXIod29yZHMsIHByZW1pdW0pLCBmaWxsID0gY291bnQpKSArDQogIGdlb21fY29sKCkgKw0KICBsYWJzKHRpdGxlID0gImFnZ3JlZ2F0ZWQgb3Zlcm5pZ2h0IGV4Y2VzcyByZXR1cm5zIGJ5IHRva2VuaXplZCB3b3JkcyIsDQogICAgICAgc3VidGl0bGUgPSAiYXZnIHRvdGFsICdvdmVybmlnaHQgZXhjZXNzIHJldHVybnMnIChvdmVybmlnaHQtaG9sZCkgb2YgMzAwKyBldGZzIGJ5IHdvcmRzIGZyb20gbmFtZSwgMjAxNS0yMDIyIiwNCiAgICAgICB4ID0gImF2ZyBvdmVybmlnaHQgZXhjZXNzIHJldHVybnMgKCUpIiwgeSA9ICIiKQ0KYGBgDQoNCiMgNS4wIGRhdGUgZWRhIC0gYm94cGxvdHMgb2YgZXRmIHJldHVybnMgYnkgeWVhcg0KaG93IGRvIHRoZXNlIHJldHVybnMgcGFuZSBvdXQgaW4geWVhcnMgbGlrZSAyMDIwPw0KDQpgYGB7cn0NCmV0Zl9yZXR1cm5zICU+JQ0KICBtdXRhdGUoeWVhciA9IHllYXIoZGF0ZSkpICU+JQ0KICBncm91cF9ieSh0aWNrZXIsIHllYXIpICU+JQ0KICBzdW1tYXJpc2UoYWNyb3NzKGhvbGQ6aW50cmFkYXksIH5wb3J0KC4sIHRvdGFsID0gVFJVRSkpLA0KICAgICAgICAgICAgY291bnQgPSBuKCkpICU+JQ0KICBmaWx0ZXIoY291bnQgPj0gMjIwKSAlPiUNCiAgcGl2b3RfbG9uZ2VyKGhvbGQ6aW50cmFkYXkpICU+JQ0KICBnZ3Bsb3QoYWVzKG5hbWUsIHZhbHVlLCBmaWxsID0gbmFtZSkpICsNCiAgZ2VvbV9ib3hwbG90KG91dGxpZXIuc2hhcGU9TkEpICsNCiAgZmFjZXRfd3JhcCh+eWVhciwgc2NhbGVzID0gImZyZWUiKSArDQogIGNvb3JkX2NhcnRlc2lhbih5bGltID0gYygtLjUsIDEpKSArDQogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGxpbmV0eXBlID0gMiwgYWxwaGEgPSAwLjUsIGNvbG9yID0gInJlZCIpICsNCiAgbGFicyh0aXRsZSA9ICJldGYgcmV0dXJucyBieSB5ZWFyIiwNCiAgICAgICBzdWJ0aXRsZSA9ICIyMDE2LTIwMjEsIGZ1bGwgeWVhcnMiLA0KICAgICAgIHggPSAiIiwgeSA9ICJyZXR1cm4gKCUpIikNCmBgYA0KDQojIDUuMSBkYXRlIGVkYSAtIHN1bW1hcnkgdGJsIG9mIGRhdGEgZnJvbSA1LjANCmV0ZiByZXR1cm5zIGFyZSBhZ2dyZWdhdGVkIGJ5IHllYXINCg0KYGBge3J9DQpldGZfcmV0dXJucyAlPiUNCiAgbXV0YXRlKHllYXIgPSB5ZWFyKGRhdGUpKSAlPiUNCiAgZ3JvdXBfYnkodGlja2VyLCB5ZWFyKSAlPiUNCiAgc3VtbWFyaXNlKGFjcm9zcyhob2xkOmludHJhZGF5LCB+cG9ydCguLCB0b3RhbCA9IFRSVUUpKSwNCiAgICAgICAgICAgIGNvdW50ID0gbigpKSAlPiUNCiAgZmlsdGVyKGNvdW50ID49IDIyMCkgJT4lDQogIG11dGF0ZShwcmVtaXVtID0gb3Zlcm5pZ2h0LWhvbGQpICU+JQ0KICB1bmdyb3VwKCkgJT4lDQogIGdyb3VwX2J5KHllYXIpICU+JQ0KICBzdW1tYXJpc2UobWVhbiA9IG1lYW4ocHJlbWl1bSksDQogICAgICAgICAgICBtZWRpYW4gPSBtZWRpYW4ocHJlbWl1bSksDQogICAgICAgICAgICBzZCA9IHNkKHByZW1pdW0pLA0KICAgICAgICAgICAgcTEwID0gcXVhbnRpbGUocHJlbWl1bSwgLjEpLA0KICAgICAgICAgICAgcTkwID0gcXVhbnRpbGUocHJlbWl1bSwgLjkpLA0KICAgICAgICAgICAgZXRmc19jb3VudCA9IG4oKSkNCmBgYA0KDQojIDUuMiBkYXRlIGVkYSAtIGJveHBsb3RzIG9mIGV0ZiByZXR1cm5zIGJ5IHdlZWtkYXkNCnNpbWlsYXIgY29uY2VwdCBhcyA1LjEsIGV4Y2VwdCBmb3Igd2Vla2RheS4gc2hvdWxkIGJlIG5vdGVkLCB0aGUgZGF5IG9mIG92ZXJuaWdodCByZXR1cm5zIGFyZSBvZiB0aGUgZGF5IHRoYXQgdGhleSBhcmUgY2xvc2VkLCBub3Qgb3BlbmVkLiBpZS4gb3Zlcm5pZ2h0IHJldHVybnMgZm9yIG1vbmRheSBhcmUgYWN0dWFsbHkgdGhlIHJldHVybnMgaGVsZCBmcm9tIGZyaWRheSBjbG9zZSB0byBtb25kYXkgb3Blbi4NCg0KYGBge3J9DQpldGZfcmV0dXJucyAlPiUNCiAgbXV0YXRlKHdlZWtkYXkgPSB3ZWVrZGF5cyhkYXRlKSkgJT4lDQogIGdyb3VwX2J5KHRpY2tlciwgd2Vla2RheSkgJT4lDQogIHN1bW1hcmlzZShhY3Jvc3MoaG9sZDppbnRyYWRheSwgfnBvcnQoLiwgdG90YWwgPSBUUlVFKSksDQogICAgICAgICAgICBjb3VudCA9IG4oKSkgJT4lDQogIGZpbHRlcihjb3VudCA+PSAyMjApICU+JQ0KICBwaXZvdF9sb25nZXIoaG9sZDppbnRyYWRheSkgJT4lDQogIGdncGxvdChhZXMobmFtZSwgdmFsdWUsIGZpbGwgPSBuYW1lKSkgKw0KICBnZW9tX2JveHBsb3Qob3V0bGllci5zaGFwZT1OQSkgKw0KICBmYWNldF93cmFwKH53ZWVrZGF5LCBzY2FsZXMgPSAiZnJlZSIpICsNCiAgY29vcmRfY2FydGVzaWFuKHlsaW0gPSBjKC0uNSwgMS4yKSkgKw0KICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBsaW5ldHlwZSA9IDIsIGFscGhhID0gMC41LCBjb2xvciA9ICJyZWQiKSArDQogIGxhYnModGl0bGUgPSAiZXRmIHJldHVybnMgYnkgd2Vla2RheSIsDQogICAgICAgc3VidGl0bGUgPSAiMjAxNi0yMDIxLCBmdWxsIHllYXJzIiwNCiAgICAgICB4ID0gIiIsIHkgPSAicmV0dXJuICglKSIpDQpgYGANCg0KIyA1LjMgZGF0ZSBlZGEgLSBib3hwbG90cyBvZiBldGYgJ2V4Y2VzcyBvdmVybmlnaHQgcmV0dXJucycgYnkgeWVhciArIHdlZWtkYXlzDQpzaW1pbGFyIGNvbmNlcHQgYXMgYWJvdmUgYm94cGxvdHMsIGJ1dCBjb21iaW5lZCBmb3Igb25lIG1ldHJpYzogZXhjZXNzIG92ZXJuaWdodCByZXR1cm5zIChvdmVybmlnaHQgcmV0dXJucyAtIGhvbGQgcmV0dXJucykNCg0KYGBge3J9DQpldGZfcmV0dXJucyAlPiUNCiAgbXV0YXRlKHllYXIgPSB5ZWFyKGRhdGUpLA0KICAgICAgICAgd2Vla2RheSA9IHdlZWtkYXlzKGRhdGUsIGFiYnJldmlhdGUgPSBUUlVFKSkgJT4lDQogIGdyb3VwX2J5KHRpY2tlciwgeWVhciwgd2Vla2RheSkgJT4lDQogIHN1bW1hcmlzZShhY3Jvc3MoaG9sZDpvdmVybmlnaHQsIH5wb3J0KC4sIHRvdGFsID0gVFJVRSkpLA0KICAgICAgICAgICAgY291bnQgPSBuKCkpICU+JQ0KICBmaWx0ZXIoY291bnQgPj0gNDUpICU+JQ0KICBtdXRhdGUocHJlbWl1bSA9IG92ZXJuaWdodC1ob2xkKSAlPiUNCiAgZ2dwbG90KGFlcyh3ZWVrZGF5LCBwcmVtaXVtLCBmaWxsID0gd2Vla2RheSkpICsNCiAgZ2VvbV9ib3hwbG90KG91dGxpZXIuc2hhcGU9TkEpICsNCiAgZmFjZXRfd3JhcCh+eWVhciwgc2NhbGVzID0gImZyZWUiKSArDQogIGNvb3JkX2NhcnRlc2lhbih5bGltID0gYygtLjMsIDAuMykpICsNCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgbGluZXR5cGUgPSAyLCBhbHBoYSA9IDAuNSwgY29sb3IgPSAicmVkIikgKw0KICBsYWJzKHRpdGxlID0gIidleGNlc3Mgb3Zlcm5pZ2h0IHJldHVybnMnIGJ5IHllYXIgKyB3ZWVrZGF5cyIsDQogICAgICAgc3VidGl0bGUgPSAiMjAxNi0yMDIxLCBmdWxsIHllYXJzIiwNCiAgICAgICB4ID0gIiIsIHkgPSAicmV0dXJuICglKSIpDQpgYGANCg0KIyA1LjQgZGF0ZSBlZGEgLSBib3hwbG90cyBvZiBldGYgJ2V4Y2VzcyBvdmVybmlnaHQgcmV0dXJucycgYnkgeWVhciArIHF1YXJ0ZXINCnNpbWlsYXIgY29uY2VwdCBhcyBhYm92ZSBib3hwbG90cywgYnV0IGNvbWJpbmVkIGZvciBvbmUgbWV0cmljOiBleGNlc3Mgb3Zlcm5pZ2h0IHJldHVybnMgKG92ZXJuaWdodCByZXR1cm5zIC0gaG9sZCByZXR1cm5zKQ0KDQpgYGB7cn0NCmV0Zl9yZXR1cm5zICU+JQ0KICBtdXRhdGUoeWVhciA9IHllYXIoZGF0ZSksDQogICAgICAgICBxdWFydGVyID0gYXMuZmFjdG9yKHF1YXJ0ZXIoZGF0ZSkpKSAlPiUNCiAgZ3JvdXBfYnkodGlja2VyLCB5ZWFyLCBxdWFydGVyKSAlPiUNCiAgc3VtbWFyaXNlKGFjcm9zcyhob2xkOm92ZXJuaWdodCwgfnBvcnQoLiwgdG90YWwgPSBUUlVFKSksDQogICAgICAgICAgICBjb3VudCA9IG4oKSkgJT4lDQogIGZpbHRlcih5ZWFyICVpbiUgYygyMDE2OjIwMjEpKSAlPiUNCiAgbXV0YXRlKHByZW1pdW0gPSBvdmVybmlnaHQtaG9sZCkgJT4lDQogIGdncGxvdChhZXMocXVhcnRlciwgcHJlbWl1bSwgZmlsbCA9IHF1YXJ0ZXIpKSArDQogIGdlb21fYm94cGxvdChvdXRsaWVyLnNoYXBlPU5BKSArDQogIGZhY2V0X3dyYXAofnllYXIsIHNjYWxlcyA9ICJmcmVlIikgKw0KICBjb29yZF9jYXJ0ZXNpYW4oeWxpbSA9IGMoLS4yNSwgMC4zNSkpICsNCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgbGluZXR5cGUgPSAyLCBhbHBoYSA9IDAuNSwgY29sb3IgPSAicmVkIikgKw0KICBsYWJzKHRpdGxlID0gIidleGNlc3Mgb3Zlcm5pZ2h0IHJldHVybnMnIGJ5IHllYXIgKyBxdWFydGVyIiwNCiAgICAgICBzdWJ0aXRsZSA9ICIyMDE2LTIwMjEsIGZ1bGwgeWVhcnMiLA0KICAgICAgIHggPSAiIiwgeSA9ICJyZXR1cm4gKCUpIikNCmBgYA0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg==