This tutorial shows how to aggregate the value of variables in a time series by certain time frames with XTS objects.

We will take the univariate timeseries dataset co2 (from Jan 1959 to Dec 1997) as an example (already provided in library xts).

library(xts)
#load the timeseries dataset co2
data(co2)
#convert the timeseries to a XTS object named co2.xts
co2.xts <- as.xts(co2)
#show first 15 rows in co2.xts
head(co2.xts, 15)
           [,1]
Jan 1959 315.42
Feb 1959 316.31
Mar 1959 316.50
Apr 1959 317.56
May 1959 318.13
Jun 1959 318.00
Jul 1959 316.39
Aug 1959 314.65
Sep 1959 313.68
Oct 1959 313.18
Nov 1959 314.66
Dec 1959 315.43
Jan 1960 316.27
Feb 1960 316.81
Mar 1960 317.42

This is an example of a monthly timeseries. There are several ways to achieve aggregation of this timeseries.


I Using functions in xts like endpoint and period.apply to perform aggregation


Usage of endpoint()

Function endpoint takes a time series and returns the locations (indices) of the last observations in each interval. We can invoke it like this:

ep <- endpoints(co2.xts, on="quarters", k=1)
head(ep, 10)
 [1]  0  3  6  9 12 15 18 21 24 27

We can notice that endpoint() has three parameters. Valid values for the argument on include: “us” (microseconds), “microseconds”, “ms” (milliseconds), “milliseconds”, “secs” (seconds), “seconds”, “mins” (minutes), “minutes”, “hours”, “days”, “weeks”, “months”, “quarters”, and “years”. Subsecond on periods are not supported on Windows yet.

endpoint() returns a numeric vector corresponding to the last observation in each period specified by argument on, with a zero added to the beginning of the vector, and the index of the last observation in the time series at the end.

Argument k indicates the function iterates along every k-th element. We set k to 2 in the following code chunk to locate the final index of every two years.

ep2 <- endpoints(co2.xts, on="years", k=2)
head(ep2, 10)
 [1]   0  12  36  60  84 108 132 156 180 204


Usage of period.apply() on univariate timeseries

After getting the indices of the last observations of each time interval, we can use period.apply() to calculate a specified aggregation function value over non-overlapping intervals along the time series data.

period.apply() also has sevreal parameters. We can use the return value of endpoints() as the value of argument INDEX and specify the function type in argument FUN. The following code chunks aggregate the mean, standard deviation and cumulated sum values quarterly from the timeseries respectively.

quarterlymean <- period.apply(co2.xts, INDEX = ep, FUN = mean)
head(quarterlymean)
             [,1]
Mar 1959 316.0767
Jun 1959 317.8967
Sep 1959 314.9067
Dec 1959 314.4233
Mar 1960 316.8333
Jun 1960 319.3900
quarterlysd <- period.apply(co2.xts, INDEX = ep, FUN = sd)
head(quarterlysd)
              [,1]
Mar 1959 0.5765703
Jun 1959 0.2987195
Sep 1959 1.3731108
Dec 1959 1.1435180
Mar 1960 0.5753550
Jun 1960 0.5011986
quarterlycumsum <- period.apply(co2.xts, INDEX = ep, FUN = cumsum)
head(quarterlycumsum)
           [,1]   [,2]   [,3]
Mar 1959 315.42 631.73 948.23
Jun 1959 317.56 635.69 953.69
Sep 1959 316.39 631.04 944.72
Dec 1959 313.18 627.84 943.27
Mar 1960 316.27 633.08 950.50
Jun 1960 318.87 638.74 958.17

Aggregation of multivariate timeseries

We can also apply aggregation to multivariate time series using this method. The aggregation values will be calculated separately on each column (variable). Let’s take dataset sample_matrix as an example (already provided in library xts).

data(sample_matrix)
sample.xts <- as.xts(sample_matrix)
head(sample.xts)
               Open     High      Low    Close
2007-01-02 50.03978 50.11778 49.95041 50.11778
2007-01-03 50.23050 50.42188 50.23050 50.39767
2007-01-04 50.42096 50.42096 50.26414 50.33236
2007-01-05 50.37347 50.37347 50.22103 50.33459
2007-01-06 50.24433 50.24433 50.11121 50.18112
2007-01-07 50.13211 50.21561 49.99185 49.99185
ep3 <- endpoints(sample.xts, on="weeks")
weeklymean <- period.apply(sample.xts, INDEX = ep3, FUN = mean)
head(weeklymean)
               Open     High      Low    Close
2007-01-08 50.21096 50.27109 50.10555 50.19192
2007-01-15 50.20139 50.35916 50.14784 50.27336
2007-01-22 50.44732 50.55528 50.31774 50.40107
2007-01-29 50.05702 50.13030 49.96866 50.07504
2007-02-05 50.28181 50.41690 50.22217 50.37982
2007-02-12 50.69801 50.82414 50.62509 50.73846

Aggregation of high frequency timeseries

Even high frequency timeseries data can be conveniently aggregated. We can take a stock trading timeseries as an example.

#set the maximum number of digits to print to 6 when formatting time values 
#in seconds, since we will use microsecond time indices in this example.
options(digits.secs=6)
#restore the xts object from a RDS file.
stock_data.xts <- readRDS(file = "stockxts.rds")
head(stock_data.xts, 20)
                            Price Volume Market.VWAP
2019-07-17 10:01:27.900519 41.070     49    41.08583
2019-07-17 10:01:27.904071 41.070    100    41.08582
2019-07-17 10:01:27.904653 41.070    100    41.08582
2019-07-17 10:01:27.908002 41.070    437    41.08579
2019-07-17 10:01:27.908526 41.070    105    41.08578
2019-07-17 10:01:27.912014 41.060    473    41.08573
2019-07-17 10:01:27.912523 41.060    477    41.08568
2019-07-17 10:01:27.916008 41.060     71    41.08567
2019-07-17 10:01:27.916518 41.060     49    41.08566
2019-07-17 10:01:27.916518 41.090     65    41.08566
2019-07-17 10:01:28.016527 41.060     15    41.08566
2019-07-17 10:01:28.204061 41.090     18    41.08566
2019-07-17 10:01:28.204560 41.090    100    41.08566
2019-07-17 10:01:28.208549 41.090     34    41.08566
2019-07-17 10:01:28.467587 41.075     49    41.08566
2019-07-17 10:01:30.483712 41.075     27    41.08566
2019-07-17 10:01:31.567144 41.075     56    41.08566
2019-07-17 10:01:33.031348 41.075     52    41.08566
2019-07-17 10:01:33.031886 41.090     49    41.08566
2019-07-17 10:01:33.035198 41.090      8    41.08566

After loading the timeseries from a rds file, we can calculate the mean trading price, volume and VWAP of this stock per second and show the results.

#perform aggregation on high frequency financial time series
ep <- endpoints(stock_data.xts, 'seconds')
secmean <- period.apply(stock_data.xts, INDEX = ep, FUN = mean)

#align the timestamps to the next seconds
secmean <- align.time(secmean, 1)
head(secmean, 10)
                       Price    Volume Market.VWAP
2019-07-17 10:01:28 41.06800 192.60000    41.08574
2019-07-17 10:01:29 41.08100  43.20000    41.08566
2019-07-17 10:01:31 41.07500  27.00000    41.08566
2019-07-17 10:01:32 41.07500  56.00000    41.08566
2019-07-17 10:01:34 41.08500  33.75000    41.08566
2019-07-17 10:01:35 41.11217  76.86957    41.08575
2019-07-17 10:01:36 41.10800 141.20000    41.08591
2019-07-17 10:01:37 41.14000  34.00000    41.08600
2019-07-17 10:01:38 41.14875  58.25000    41.08602
2019-07-17 10:01:39 41.13750   8.50000    41.08607

II Using functions split and lapply to perform aggregation on intervals

The sencond way is to use split() to split your time series data into non-overlapping chunks and use lapply() to perform aggregations on these periods. The following code chunk shows how to do this on univariate time series co2.

sp.co2 <- split(co2.xts, "years")
#rbind() is used to combine the results by row into a matrix.
yearlymean <- do.call(rbind, lapply(sp.co2, mean))
head(yearlymean)
         [,1]
[1,] 315.8258
[2,] 316.7475
[3,] 317.4850
[4,] 318.2975
[5,] 318.8325
[6,] 319.4625

We can compare the result with what we get from using period.apply().

yearlymean2 <- period.apply(co2.xts, INDEX = endpoints(co2.xts, on="years"), FUN = mean)
head(yearlymean2)
             [,1]
Dec 1959 315.8258
Dec 1960 316.7475
Dec 1961 317.4850
Dec 1962 318.2975
Dec 1963 318.8325
Dec 1964 319.4625

If you want to perform aggregations on mutivariate timeseries, you can write code like this:

weeklymean2 <- do.call(rbind, lapply(split(sample.xts, "weeks"), function(w) apply(w,2,mean)))
head(weeklymean2)
         Open     High      Low    Close
[1,] 50.21096 50.27109 50.10555 50.19192
[2,] 50.20139 50.35916 50.14784 50.27336
[3,] 50.44732 50.55528 50.31774 50.40107
[4,] 50.05702 50.13030 49.96866 50.07504
[5,] 50.28181 50.41690 50.22217 50.37982
[6,] 50.69801 50.82414 50.62509 50.73846

III Using function rollapply to perform aggregation to rolling windows in time series

In function rollapply, Argument width means an integer specifying the window width (in numbers of observations) and FUN specifies the function to be applied. In the following code chunck, we calculated the weekly mean of different columns in sample.xts with a sliding window. Please note values of the first 6 rows would be NA.

samweeksum <- rollapply(sample.xts, width = 7, FUN = mean)
head(samweeksum,15)
               Open     High      Low    Close
2007-01-02       NA       NA       NA       NA
2007-01-03       NA       NA       NA       NA
2007-01-04       NA       NA       NA       NA
2007-01-05       NA       NA       NA       NA
2007-01-06       NA       NA       NA       NA
2007-01-07       NA       NA       NA       NA
2007-01-08 50.21096 50.27109 50.10555 50.19192
2007-01-09 50.20454 50.25354 50.08471 50.16271
2007-01-10 50.15908 50.21191 50.03925 50.10197
2007-01-11 50.08256 50.18594 49.98513 50.08865
2007-01-12 50.05957 50.18398 49.97809 50.08159
2007-01-13 50.07093 50.21765 50.00847 50.11470
2007-01-14 50.11829 50.27599 50.07586 50.20178
2007-01-15 50.20139 50.35916 50.14784 50.27336
2007-01-16 50.29072 50.46522 50.25666 50.38265

Exercises

Load timeseries data “AirPassengers” and convert it to a XTS object. This time series data contains monthly totals of international airline passengers between 1949 to 1960. Please try to use the methods introduced in this tutorial to calculate the mean and stand deviation of the numbers over quarterly and yearly intervals.

LS0tCnRpdGxlOiAiQWdncmVnYXRpb24gaW4gVGltZVNlcmllcyB3aXRoIFhUUyBPYmplY3RzIgpvdXRwdXQ6CiAgcGRmX2RvY3VtZW50OiBkZWZhdWx0CiAgd29yZF9kb2N1bWVudDogZGVmYXVsdAogIGh0bWxfZG9jdW1lbnQ6CiAgICBkZl9wcmludDogcGFnZWQKICBodG1sX25vdGVib29rOiBkZWZhdWx0Ci0tLQoKVGhpcyB0dXRvcmlhbCBzaG93cyBob3cgdG8gYWdncmVnYXRlIHRoZSB2YWx1ZSBvZiB2YXJpYWJsZXMgaW4gYSB0aW1lIHNlcmllcyBieSBjZXJ0YWluIHRpbWUgZnJhbWVzIHdpdGggWFRTIG9iamVjdHMuCgpXZSB3aWxsIHRha2UgdGhlIDxzcGFuIHN0eWxlPSJjb2xvcjpibHVlIj4qKnVuaXZhcmlhdGUqKjwvc3Bhbj4gdGltZXNlcmllcyBkYXRhc2V0IGNvMiAoZnJvbSBKYW4gMTk1OSB0byBEZWMgMTk5NykgYXMgYW4gZXhhbXBsZSAoYWxyZWFkeSBwcm92aWRlZCBpbiBsaWJyYXJ5IHh0cykuCgpgYGB7cn0KbGlicmFyeSh4dHMpCiNsb2FkIHRoZSB0aW1lc2VyaWVzIGRhdGFzZXQgY28yCmRhdGEoY28yKQojY29udmVydCB0aGUgdGltZXNlcmllcyB0byBhIFhUUyBvYmplY3QgbmFtZWQgY28yLnh0cwpjbzIueHRzIDwtIGFzLnh0cyhjbzIpCiNzaG93IGZpcnN0IDE1IHJvd3MgaW4gY28yLnh0cwpoZWFkKGNvMi54dHMsIDE1KQpgYGAKVGhpcyBpcyBhbiBleGFtcGxlIG9mIGEgKm1vbnRobHkqIHRpbWVzZXJpZXMuIFRoZXJlIGFyZSBzZXZlcmFsIHdheXMgdG8gYWNoaWV2ZSBhZ2dyZWdhdGlvbiBvZiB0aGlzIHRpbWVzZXJpZXMuCgoqKioKIyMjIyA8c3BhbiBzdHlsZT0iY29sb3I6Ymx1ZSI+KipJKio8L3NwYW4+ICAqKipVc2luZyBmdW5jdGlvbnMgaW4geHRzIGxpa2UgYGVuZHBvaW50YCBhbmQgYHBlcmlvZC5hcHBseWAgdG8gcGVyZm9ybSBhZ2dyZWdhdGlvbioqKgo8YnI+CioqVXNhZ2Ugb2YgYGVuZHBvaW50KClgKioKCkZ1bmN0aW9uIGBlbmRwb2ludGAgdGFrZXMgYSB0aW1lIHNlcmllcyBhbmQgcmV0dXJucyB0aGUgbG9jYXRpb25zIChpbmRpY2VzKSBvZiB0aGUgbGFzdCBvYnNlcnZhdGlvbnMgaW4gZWFjaCBpbnRlcnZhbC4gV2UgY2FuIGludm9rZSBpdCBsaWtlIHRoaXM6CgpgYGB7cn0KZXAgPC0gZW5kcG9pbnRzKGNvMi54dHMsIG9uPSJxdWFydGVycyIsIGs9MSkKaGVhZChlcCwgMTApCmBgYAoKV2UgY2FuIG5vdGljZSB0aGF0IGBlbmRwb2ludCgpYCBoYXMgdGhyZWUgcGFyYW1ldGVycy4gVmFsaWQgdmFsdWVzIGZvciB0aGUgYXJndW1lbnQgYG9uYCBpbmNsdWRlOiDigJx1c+KAnSAobWljcm9zZWNvbmRzKSwg4oCcbWljcm9zZWNvbmRz4oCdLCDigJxtc+KAnSAobWlsbGlzZWNvbmRzKSwg4oCcbWlsbGlzZWNvbmRz4oCdLCDigJxzZWNz4oCdIChzZWNvbmRzKSwg4oCcc2Vjb25kc+KAnSwg4oCcbWluc+KAnSAobWludXRlcyksIOKAnG1pbnV0ZXPigJ0sIOKAnGhvdXJz4oCdLCDigJxkYXlz4oCdLCDigJx3ZWVrc+KAnSwg4oCcbW9udGhz4oCdLCDigJxxdWFydGVyc+KAnSwgYW5kIOKAnHllYXJz4oCdLiBTdWJzZWNvbmQgb24gcGVyaW9kcyBhcmUgbm90IHN1cHBvcnRlZCBvbiBXaW5kb3dzIHlldC4KCmBlbmRwb2ludCgpYCByZXR1cm5zIGEgbnVtZXJpYyB2ZWN0b3IgY29ycmVzcG9uZGluZyB0byB0aGUgbGFzdCBvYnNlcnZhdGlvbiBpbiBlYWNoIHBlcmlvZCBzcGVjaWZpZWQgYnkgYXJndW1lbnQgYG9uYCwgd2l0aCBhIHplcm8gYWRkZWQgdG8gdGhlIGJlZ2lubmluZyBvZiB0aGUgdmVjdG9yLCBhbmQgdGhlIGluZGV4IG9mIHRoZSBsYXN0IG9ic2VydmF0aW9uIGluIHRoZSB0aW1lIHNlcmllcyBhdCB0aGUgZW5kLgoKQXJndW1lbnQgYGtgIGluZGljYXRlcyB0aGUgZnVuY3Rpb24gaXRlcmF0ZXMgYWxvbmcgZXZlcnkgay10aCBlbGVtZW50LiBXZSBzZXQgayB0byAyIGluIHRoZSBmb2xsb3dpbmcgY29kZSBjaHVuayB0byBsb2NhdGUgdGhlIGZpbmFsIGluZGV4IG9mIGV2ZXJ5IHR3byB5ZWFycy4KCmBgYHtyfQplcDIgPC0gZW5kcG9pbnRzKGNvMi54dHMsIG9uPSJ5ZWFycyIsIGs9MikKaGVhZChlcDIsIDEwKQpgYGAKPGJyPgoqKlVzYWdlIG9mIGBwZXJpb2QuYXBwbHkoKWAgb24gdW5pdmFyaWF0ZSB0aW1lc2VyaWVzKioKCkFmdGVyIGdldHRpbmcgdGhlIGluZGljZXMgb2YgdGhlIGxhc3Qgb2JzZXJ2YXRpb25zIG9mIGVhY2ggdGltZSBpbnRlcnZhbCwgd2UgY2FuIHVzZSBgcGVyaW9kLmFwcGx5KClgIHRvIGNhbGN1bGF0ZSBhIHNwZWNpZmllZCA8c3BhbiBzdHlsZT0iY29sb3I6Ymx1ZSI+YWdncmVnYXRpb248L3NwYW4+IGZ1bmN0aW9uIHZhbHVlIG92ZXIgbm9uLW92ZXJsYXBwaW5nIGludGVydmFscyBhbG9uZyB0aGUgdGltZSBzZXJpZXMgZGF0YS4KCmBwZXJpb2QuYXBwbHkoKWAgYWxzbyBoYXMgc2V2cmVhbCBwYXJhbWV0ZXJzLiBXZSBjYW4gdXNlIHRoZSByZXR1cm4gdmFsdWUgb2YgYGVuZHBvaW50cygpYCBhcyB0aGUgdmFsdWUgb2YgYXJndW1lbnQgYElOREVYYCBhbmQgc3BlY2lmeSB0aGUgZnVuY3Rpb24gdHlwZSBpbiBhcmd1bWVudCBgRlVOYC4gVGhlIGZvbGxvd2luZyBjb2RlIGNodW5rcyBhZ2dyZWdhdGUgdGhlIG1lYW4sIHN0YW5kYXJkIGRldmlhdGlvbiBhbmQgY3VtdWxhdGVkIHN1bSB2YWx1ZXMgcXVhcnRlcmx5IGZyb20gdGhlIHRpbWVzZXJpZXMgcmVzcGVjdGl2ZWx5LgpgYGB7cn0KcXVhcnRlcmx5bWVhbiA8LSBwZXJpb2QuYXBwbHkoY28yLnh0cywgSU5ERVggPSBlcCwgRlVOID0gbWVhbikKaGVhZChxdWFydGVybHltZWFuKQpxdWFydGVybHlzZCA8LSBwZXJpb2QuYXBwbHkoY28yLnh0cywgSU5ERVggPSBlcCwgRlVOID0gc2QpCmhlYWQocXVhcnRlcmx5c2QpCnF1YXJ0ZXJseWN1bXN1bSA8LSBwZXJpb2QuYXBwbHkoY28yLnh0cywgSU5ERVggPSBlcCwgRlVOID0gY3Vtc3VtKQpoZWFkKHF1YXJ0ZXJseWN1bXN1bSkKYGBgCioqQWdncmVnYXRpb24gb2YgbXVsdGl2YXJpYXRlIHRpbWVzZXJpZXMqKgoKV2UgY2FuIGFsc28gYXBwbHkgYWdncmVnYXRpb24gdG8gPHNwYW4gc3R5bGU9ImNvbG9yOmJsdWUiPioqbXVsdGl2YXJpYXRlKio8L3NwYW4+IHRpbWUgc2VyaWVzIHVzaW5nIHRoaXMgbWV0aG9kLiBUaGUgYWdncmVnYXRpb24gdmFsdWVzIHdpbGwgYmUgY2FsY3VsYXRlZCBzZXBhcmF0ZWx5IG9uIGVhY2ggY29sdW1uICh2YXJpYWJsZSkuIExldCdzIHRha2UgZGF0YXNldCBzYW1wbGVfbWF0cml4IGFzIGFuIGV4YW1wbGUgKGFscmVhZHkgcHJvdmlkZWQgaW4gbGlicmFyeSB4dHMpLgpgYGB7cn0KZGF0YShzYW1wbGVfbWF0cml4KQpzYW1wbGUueHRzIDwtIGFzLnh0cyhzYW1wbGVfbWF0cml4KQpoZWFkKHNhbXBsZS54dHMpCmBgYAoKYGBge3J9CmVwMyA8LSBlbmRwb2ludHMoc2FtcGxlLnh0cywgb249IndlZWtzIikKd2Vla2x5bWVhbiA8LSBwZXJpb2QuYXBwbHkoc2FtcGxlLnh0cywgSU5ERVggPSBlcDMsIEZVTiA9IG1lYW4pCmhlYWQod2Vla2x5bWVhbikKYGBgCioqQWdncmVnYXRpb24gb2YgaGlnaCBmcmVxdWVuY3kgdGltZXNlcmllcyoqCgpFdmVuIDxzcGFuIHN0eWxlPSJjb2xvcjpibHVlIj4qKipoaWdoIGZyZXF1ZW5jeSoqKjwvc3Bhbj4gdGltZXNlcmllcyBkYXRhIGNhbiBiZSBjb252ZW5pZW50bHkgYWdncmVnYXRlZC4gV2UgY2FuIHRha2UgYSBzdG9jayB0cmFkaW5nIHRpbWVzZXJpZXMgYXMgYW4gZXhhbXBsZS4KYGBge3J9CiNzZXQgdGhlIG1heGltdW0gbnVtYmVyIG9mIGRpZ2l0cyB0byBwcmludCB0byA2IHdoZW4gZm9ybWF0dGluZyB0aW1lIHZhbHVlcyAKI2luIHNlY29uZHMsIHNpbmNlIHdlIHdpbGwgdXNlIG1pY3Jvc2Vjb25kIHRpbWUgaW5kaWNlcyBpbiB0aGlzIGV4YW1wbGUuCm9wdGlvbnMoZGlnaXRzLnNlY3M9NikKI3Jlc3RvcmUgdGhlIHh0cyBvYmplY3QgZnJvbSBhIFJEUyBmaWxlLgpzdG9ja19kYXRhLnh0cyA8LSByZWFkUkRTKGZpbGUgPSAic3RvY2t4dHMucmRzIikKaGVhZChzdG9ja19kYXRhLnh0cywgMjApCmBgYApBZnRlciBsb2FkaW5nIHRoZSB0aW1lc2VyaWVzIGZyb20gYSByZHMgZmlsZSwgd2UgY2FuIGNhbGN1bGF0ZSB0aGUgbWVhbiB0cmFkaW5nIHByaWNlLCB2b2x1bWUgYW5kIFZXQVAgb2YgdGhpcyBzdG9jayBwZXIgc2Vjb25kIGFuZCBzaG93IHRoZSByZXN1bHRzLgpgYGB7cn0KI3BlcmZvcm0gYWdncmVnYXRpb24gb24gaGlnaCBmcmVxdWVuY3kgZmluYW5jaWFsIHRpbWVzZXJpZXMKZXAgPC0gZW5kcG9pbnRzKHN0b2NrX2RhdGEueHRzLCAnc2Vjb25kcycpCnNlY21lYW4gPC0gcGVyaW9kLmFwcGx5KHN0b2NrX2RhdGEueHRzLCBJTkRFWCA9IGVwLCBGVU4gPSBtZWFuKQoKI2FsaWduIHRoZSB0aW1lc3RhbXBzIHRvIHRoZSBuZXh0IHNlY29uZHMKc2VjbWVhbiA8LSBhbGlnbi50aW1lKHNlY21lYW4sIDEpCmhlYWQoc2VjbWVhbiwgMTApCmBgYAoKKioqCiMjIyMgPHNwYW4gc3R5bGU9ImNvbG9yOmJsdWUiPioqSUkqKjwvc3Bhbj4gKioqVXNpbmcgZnVuY3Rpb25zIGBzcGxpdGAgYW5kIGBsYXBwbHlgIHRvIHBlcmZvcm0gYWdncmVnYXRpb24gb24gaW50ZXJ2YWxzKioqCgpUaGUgc2VuY29uZCB3YXkgaXMgdG8gdXNlIGBzcGxpdCgpYCB0byBzcGxpdCB5b3VyIHRpbWUgc2VyaWVzIGRhdGEgaW50byBub24tb3ZlcmxhcHBpbmcgY2h1bmtzIGFuZCB1c2UgYGxhcHBseSgpYCB0byBwZXJmb3JtIGFnZ3JlZ2F0aW9ucyBvbiB0aGVzZSBwZXJpb2RzLiBUaGUgZm9sbG93aW5nIGNvZGUgY2h1bmsgc2hvd3MgaG93IHRvIGRvIHRoaXMgb24gPHNwYW4gc3R5bGU9ImNvbG9yOmJsdWUiPioqdW5pdmFyaWF0ZSoqPC9zcGFuPiB0aW1lIHNlcmllcyBjbzIuCmBgYHtyfQpzcC5jbzIgPC0gc3BsaXQoY28yLnh0cywgInllYXJzIikKI3JiaW5kKCkgaXMgdXNlZCB0byBjb21iaW5lIHRoZSByZXN1bHRzIGJ5IHJvdyBpbnRvIGEgbWF0cml4Lgp5ZWFybHltZWFuIDwtIGRvLmNhbGwocmJpbmQsIGxhcHBseShzcC5jbzIsIG1lYW4pKQpoZWFkKHllYXJseW1lYW4pCmBgYApXZSBjYW4gY29tcGFyZSB0aGUgcmVzdWx0IHdpdGggd2hhdCB3ZSBnZXQgZnJvbSB1c2luZyBgcGVyaW9kLmFwcGx5KClgLgpgYGB7cn0KeWVhcmx5bWVhbjIgPC0gcGVyaW9kLmFwcGx5KGNvMi54dHMsIElOREVYID0gZW5kcG9pbnRzKGNvMi54dHMsIG9uPSJ5ZWFycyIpLCBGVU4gPSBtZWFuKQpoZWFkKHllYXJseW1lYW4yKQpgYGAKSWYgeW91IHdhbnQgdG8gcGVyZm9ybSBhZ2dyZWdhdGlvbnMgb24gPHNwYW4gc3R5bGU9ImNvbG9yOmJsdWUiPioqbXV0aXZhcmlhdGUqKjwvc3Bhbj4gdGltZXNlcmllcywgeW91IGNhbiB3cml0ZSBjb2RlIGxpa2UgdGhpczoKYGBge3J9CndlZWtseW1lYW4yIDwtIGRvLmNhbGwocmJpbmQsIGxhcHBseShzcGxpdChzYW1wbGUueHRzLCAid2Vla3MiKSwgZnVuY3Rpb24odykgYXBwbHkodywyLG1lYW4pKSkKaGVhZCh3ZWVrbHltZWFuMikKYGBgCioqKgojIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjpibHVlIj4qKklJSSoqPC9zcGFuPiAqKipVc2luZyBmdW5jdGlvbiBgcm9sbGFwcGx5YCB0byBwZXJmb3JtIGFnZ3JlZ2F0aW9uIHRvIHJvbGxpbmcgd2luZG93cyBpbiB0aW1lIHNlcmllcyoqKgoKSW4gZnVuY3Rpb24gYHJvbGxhcHBseWAsIEFyZ3VtZW50IGB3aWR0aGAgbWVhbnMgYW4gaW50ZWdlciBzcGVjaWZ5aW5nIHRoZSB3aW5kb3cgd2lkdGggKGluIG51bWJlcnMgb2Ygb2JzZXJ2YXRpb25zKSBhbmQgYEZVTmAgc3BlY2lmaWVzIHRoZSBmdW5jdGlvbiB0byBiZSBhcHBsaWVkLiBJbiB0aGUgZm9sbG93aW5nIGNvZGUgY2h1bmNrLCB3ZSBjYWxjdWxhdGVkIHRoZSB3ZWVrbHkgbWVhbiBvZiBkaWZmZXJlbnQgY29sdW1ucyBpbiBgc2FtcGxlLnh0c2Agd2l0aCBhIHNsaWRpbmcgd2luZG93LiBQbGVhc2Ugbm90ZSB2YWx1ZXMgb2YgdGhlIGZpcnN0IDYgcm93cyB3b3VsZCBiZSBgTkFgLgpgYGB7cn0Kc2Ftd2Vla3N1bSA8LSByb2xsYXBwbHkoc2FtcGxlLnh0cywgd2lkdGggPSA3LCBGVU4gPSBtZWFuKQpoZWFkKHNhbXdlZWtzdW0sMTUpCmBgYAoqKioKIyMgRXhlcmNpc2VzCkxvYWQgdGltZXNlcmllcyBkYXRhICoiQWlyUGFzc2VuZ2VycyIqIGFuZCBjb252ZXJ0IGl0IHRvIGEgWFRTIG9iamVjdC4gVGhpcyB0aW1lIHNlcmllcyBkYXRhIGNvbnRhaW5zIG1vbnRobHkgdG90YWxzIG9mIGludGVybmF0aW9uYWwgYWlybGluZSBwYXNzZW5nZXJzIGJldHdlZW4gMTk0OSB0byAxOTYwLiBQbGVhc2UgdHJ5IHRvIHVzZSB0aGUgbWV0aG9kcyBpbnRyb2R1Y2VkIGluIHRoaXMgdHV0b3JpYWwgdG8gY2FsY3VsYXRlIHRoZSBtZWFuIGFuZCBzdGFuZCBkZXZpYXRpb24gb2YgdGhlIG51bWJlcnMgb3ZlciBxdWFydGVybHkgYW5kIHllYXJseSBpbnRlcnZhbHMuCg==