主要議題:預測股票的投資報酬

學習重點:

小組討論:

rm(list=ls(all=T))
Sys.setlocale("LC_ALL","C")
[1] "C"
options(digits=5, scipen=12)
library(dplyr)
library(caTools)
package 'caTools' was built under R version 3.4.4
library(caret)
package 'caret' was built under R version 3.4.4
library(flexclust)



1. 資料探索

1.1

Load StocksCluster.csv into a data frame called “stocks”.

A = read.csv('data/StocksCluster.csv')
nrow(A)
[1] 11580

How many observations are in the dataset?

11580

1.2
mean(A$PositiveDec)
[1] 0.54611

What proportion of the observations have positive returns in December?

0.54611

1.3
cor(A[1:11]) %>% sort %>% unique %>% tail %>% round(2)
[1] 0.09 0.13 0.14 0.17 0.19 1.00

What is the maximum correlation between any two return variables in the dataset?
0.19

1.3 小組討論 :

“1”意指變數自己與自己的相關係數,相關係數最大的兩個變數為ReturnNov及ReturnOct。

1.4
colMeans(A[,1:11]) %>% sort %>% barplot(las=2, cex.names=0.8, cex.axis=0.8)

Which month (from January through November) has the largest mean return across all observations in the dataset?

April

Which month (from January through November) has the smallest mean return across all observations in the dataset?

September



2. 邏輯式回歸,單一模型

分割訓練、測試資料

Run the following commands to split the data into a training set and testing set, putting 70% of the data in the training set and 30% of the data in the testing set:

set.seed(144)

spl = sample.split(stocks$PositiveDec, SplitRatio = 0.7)

stocksTrain = subset(stocks, spl == TRUE)

stocksTest = subset(stocks, spl == FALSE)

library(caTools)
set.seed(144)
spl = sample.split(A$PositiveDec,0.7)
TR = subset(A, spl)
TS = subset(A, !spl)
sapply(list(A, TR, TS), function(x) mean(x$PositiveDec))
[1] 0.54611 0.54614 0.54606
2.1 單一模型:訓練準確率,\(\text{acc}_{train}\)

Then, use the stocksTrain data frame to train a logistic regression model (name it StocksModel) to predict PositiveDec using all the other variables as independent variables. Don’t forget to add the argument family=binomial to your glm command.

glm1 = glm(PositiveDec ~ .,  TR, family=binomial)
pred = predict(glm1, type='response')
table(TR$Pos, pred > 0.5) %>% {sum(diag(.))/sum(.)} 
[1] 0.57118

What is the overall accuracy on the training set, using a threshold of 0.5?

0.57118

2.1 小組討論:

正確率為計算「False為0」及「True為1」的加總佔總數量的比例。

2.2 單一模型:測試準確率,\(\text{acc}_{test}\)
pred = predict(glm1, TS, type='response')
table(TS$Pos, pred > 0.5) %>% {sum(diag(.))/sum(.)} 
[1] 0.56707

Now obtain test set predictions from StocksModel. What is the overall accuracy of the model on the test, again using a threshold of 0.5?

0.56707

2.3 單一模型:底線準確率,\(\text{acc}_{baseline}\)
mean(TS$PositiveDec)
[1] 0.54606

What is the accuracy on the test set of a baseline model that always predicts the most common outcome (PositiveDec = 1)?

0.54606



3. 集群分析

3.1 移除目標變數

Now, let’s cluster the stocks. The first step in this process is to remove the dependent variable using the following commands:

LTR = TR[,1:11]
LTS = TS[,1:11]

Why do we need to remove the dependent variable in the clustering phase of the cluster-then-predict methodology?

Needing to know the dependent variable value to assign an observation to a cluster defeats the purpose of the methodology

3.1 小組討論:


因為我們要利用集群分析來找出資料中相似的觀察值,然後來預測未知的變數y,如果我們的原始資料
直接就有我們要用來預測的變數Y的話,R會直接根據Y來進行分群,這樣的集群分析是沒有意義的

3.2 區隔變數常態化

In the market segmentation assignment in this week’s homework, you were introduced to the preProcess command from the caret package, which normalizes variables by subtracting by the mean and dividing by the standard deviation.

In cases where we have a training and testing set, we’ll want to normalize by the mean and standard deviation of the variables in the training set. We can do this by passing just the training set to the preProcess function:

library(caret)
preproc = preProcess(LTR)
NTR = predict(preproc, LTR)
NTS = predict(preproc, LTS)
mean(NTR$ReturnJan)
[1] 2.1006e-17

What is the mean of the ReturnJan variable in normTrain?

2.1006e-17

mean(NTS$ReturnJan)
[1] -0.00041859

What is the mean of the ReturnJan variable in normTrain?

-0.00041859

3.3 測試資料的常態化結果

Why is the mean ReturnJan variable much closer to 0 in normTrain than in normTest?
The distribution of the ReturnJan variable is different in the training and testing set

3.3 小組討論:

因為在train跟test的資料中觀察值的分布不一樣,而標準化的過程中所用的都是train的平均值,所以train的觀察值在標準化之後,平均會比test的觀察值平均更接近0

3.4 K-Means集群

Set the random seed to 144 (it is important to do this again, even though we did it earlier). Run k-means clustering with 3 clusters on normTrain, storing the result in an object called km.

set.seed(144)
km <- kmeans(NTR, 3)
table(km$cluster)

   1    2    3 
3157 4696  253 

Which cluster has the largest number of observations?

Cluster 2

3.5

Recall from the recitation that we can use the flexclust package to obtain training set and testing set cluster assignments for our observations (note that the call to as.kcca may take a while to complete):

library(flexclust)
km.kcca = as.kcca(km, NTR)
Found more than one class "kcca" in cache; using the first, from namespace 'flexclust'
Also defined by 'kernlab'
Found more than one class "kcca" in cache; using the first, from namespace 'flexclust'
Also defined by 'kernlab'
CTR = predict(km.kcca)
Found more than one class "kcca" in cache; using the first, from namespace 'flexclust'
Also defined by 'kernlab'
CTS = predict(km.kcca, newdata=NTS)
table(CTS)
CTS
   1    2    3 
1298 2080   96 

How many test-set observations were assigned to Cluster 2?

2080

3.5 小組討論

這個code是將分群的資訊做成模型,然後運用這個模型來對test資料中的觀測值進行分群,CTS就是對test資料中的觀測值以訓練資料中分群的方式進行分類


4. 邏輯式回歸,分群模型

4.1 依集群分析的結果切割資料

Using the subset function, build data frames stocksTrain1, stocksTrain2, and stocksTrain3, containing the elements in the stocksTrain data frame assigned to clusters 1, 2, and 3, respectively (be careful to take subsets of stocksTrain, not of normTrain). Similarly build stocksTest1, stocksTest2, and stocksTest3 from the stocksTest data frame.

tapply(TR$PositiveDec, CTR, mean)
      1       2       3 
0.60247 0.51405 0.43874 

Which training set data frame has the highest average value of the dependent variable?

第一群的training set data frame

4.2 分群模型,模型係數

Build logistic regression models StocksModel1, StocksModel2, and StocksModel3, which predict PositiveDec using all the other variables as independent variables. StocksModel1 should be trained on stocksTrain1, StocksModel2 should be trained on stocksTrain2, and StocksModel3 should be trained on stocksTrain3.

M = lapply(split(TR, CTR), function(x) 
  glm(PositiveDec~., data=x, family=binomial) )
sapply(M, function(x) coef(summary(x))[,1])
                    1        2          3
(Intercept)  0.172240  0.10293 -0.1818958
ReturnJan    0.024984  0.88451 -0.0097893
ReturnFeb   -0.372074  0.31762 -0.0468833
ReturnMar    0.595550 -0.37978  0.6741795
ReturnApr    1.190478  0.49291  1.2814662
ReturnMay    0.304209  0.89655  0.7625116
ReturnJune  -0.011654  1.50088  0.3294339
ReturnJuly   0.197692  0.78315  0.7741644
ReturnAug    0.512729 -0.24486  0.9826054
ReturnSep    0.588327  0.73685  0.3638068
ReturnOct   -1.022535 -0.27756  0.7822421
ReturnNov   -0.748472 -0.78747 -0.8737521

Which variables have a positive sign for the coefficient in at least one model and a negative sign for the coefficient in at least one model? Select all that apply.

ReturnJan ,ReturnFeb,ReturnMar ,ReturnJune,ReturnAug,ReturnOct

4.2 小組討論

M是把TR根據CTR分成三個部分,然後將每一部份的資料依序拿去建立一個邏輯式回歸模型
下面的sapply則是把這三個模型依序拿去做summary,再透過coef[,1]來取出各個模型的係數評估值

4.3 分群模型:分群測試準確率,\(\text{acc}_{test}^{1,2,3}\)

Using StocksModel1, make test-set predictions called PredictTest1 on the data frame stocksTest1. Using StocksModel2, make test-set predictions called PredictTest2 on the data frame stocksTest2. Using StocksModel3, make test-set predictions called PredictTest3 on the data frame stocksTest3.

Pred = lapply(1:3, function(i) 
  predict(M[[i]], TS[CTS==i,], type='response') )
sapply(1:3, function(i) 
  table(TS$Pos[CTS==i], Pred[[i]] > 0.5) %>% {sum(diag(.))/sum(.)}  )
[1] 0.61941 0.55048 0.64583

What is the overall accuracy of StocksModel1 on the test set stocksTest1, using a threshold of 0.5?

0.61941

What is the overall accuracy of StocksModel2 on the test set stocksTest3, using a threshold of 0.5?

0.55048

What is the overall accuracy of StocksModel3 on the test set stocksTest3, using a threshold of 0.5?

0.64583

4.3 小組討論

上面的Pred是把TS依照CTS分成三個部分,再把這三群的資料依序跟M的三個模型中對照的拿去做預測
下面的sapply則是把實際的結果依照CTS分成三個部分,再把每個部分跟Pred預測的結果對照的部分做成混淆矩陣,然後計算準確率

4.4 分群模型:整體測試準確率,\(\text{acc}_{test}^{1+2+3}\)

To compute the overall test-set accuracy of the cluster-then-predict approach, we can combine all the test-set predictions into a single vector and all the true outcomes into a single vector:

table( do.call(c, split(TS$Pos,CTS)), do.call(c, Pred) > 0.5 ) %>%
  {sum(diag(.))/sum(.)}
[1] 0.57887

What is the overall test-set accuracy of the cluster-then-predict approach, again using a threshold of 0.5?

0.57887

4.4 小組討論

這裡的do.call是把模型預測出來的三個部分的觀察值,變成一整個的向量,另一方面,TS$Pos的值也先依照CTS分割成三個部分,來後再變成向量

最後再把這兩筆資料變成混淆矩陣,並計算整體的準確度

We see a modest improvement over the original logistic regression model. Since predicting stock returns is a notoriously hard problem, this is a good increase in accuracy. By investing in stocks for which we are more confident that they will have positive returns (by selecting the ones with higher predicted probabilities), this cluster-then-predict model can give us an edge over the original logistic regression model.








LS0tDQp0aXRsZTogIkFTNi0zIFByZWRpY3RpbmcgU3RvY2sgUmV0dXJucyB3aXRoIENsdXN0ZXItVGhlbi1QcmVkaWN0Ig0KYXV0aG9yOiAiR3J1b3AxIDIwMTgvMDcvMzAgIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KPGJyPg0KDQoqKuS4u+imgeitsOmhjO+8mumgkOa4rOiCoeelqOeahOaKleizh+WgsemFrCoqDQoNCioq5a2457+S6YeN6bue77yaKioNCg0KKyDlhYjliIbnvqTku6XlvozjgIHlho3lgZrpoJDmuKzmgKfmqKHlnosNCisg6ZuG576k5YiG5p6Q55qE5qih5Z6L6IiH6aCQ5ris5pa55rOVDQoNCioq5bCP57WE6KiO6KuW77yaKioNCjxicj4gDQoNCisgPGEgaHJlZj0nI0QxLjMnPjEuMzwvYT4NCisgPGEgaHJlZj0nI0QyLjEnPjIuMTwvYT4NCisgPGEgaHJlZj0nI0QzLjEnPjMuMTwvYT4NCisgPGEgaHJlZj0nI0QzLjMnPjMuMzwvYT4NCisgPGEgaHJlZj0nI0QzLjUnPjMuNTwvYT4NCisgPGEgaHJlZj0nI0Q0LjInPjQuMjwvYT4NCisgPGEgaHJlZj0nI0Q0LjMnPjQuMzwvYT4NCisgPGEgaHJlZj0nI0Q0LjQnPjQuNDwvYT4NCg0KDQpgYGB7ciBlY2hvPVQsIG1lc3NhZ2U9RiwgY2FjaGU9Riwgd2FybmluZz1GfQ0Kcm0obGlzdD1scyhhbGw9VCkpDQpTeXMuc2V0bG9jYWxlKCJMQ19BTEwiLCJDIikNCm9wdGlvbnMoZGlnaXRzPTUsIHNjaXBlbj0xMikNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KGNhVG9vbHMpDQpsaWJyYXJ5KGNhcmV0KQ0KbGlicmFyeShmbGV4Y2x1c3QpDQpgYGANCjxicj4NCg0KDQotIC0gLQ0KDQojIyMgMS4g6LOH5paZ5o6i57SiDQoNCiMjIyMjIDEuMSANCkxvYWQgU3RvY2tzQ2x1c3Rlci5jc3YgaW50byBhIGRhdGEgZnJhbWUgY2FsbGVkICJzdG9ja3MiLg0KYGBge3J9DQpBID0gcmVhZC5jc3YoJ2RhdGEvU3RvY2tzQ2x1c3Rlci5jc3YnKQ0KbnJvdyhBKQ0KYGBgDQpfSG93IG1hbnkgb2JzZXJ2YXRpb25zIGFyZSBpbiB0aGUgZGF0YXNldD9fDQoNCjExNTgwDQoNCiMjIyMjIDEuMiANCmBgYHtyfQ0KbWVhbihBJFBvc2l0aXZlRGVjKQ0KYGBgDQpfV2hhdCBwcm9wb3J0aW9uIG9mIHRoZSBvYnNlcnZhdGlvbnMgaGF2ZSBwb3NpdGl2ZSByZXR1cm5zIGluIERlY2VtYmVyP18NCg0KMC41NDYxMQ0KDQoNCiMjIyMjIDEuMw0KYGBge3J9DQpjb3IoQVsxOjExXSkgJT4lIHNvcnQgJT4lIHVuaXF1ZSAlPiUgdGFpbCAlPiUgcm91bmQoMikNCmBgYA0KX1doYXQgaXMgdGhlIG1heGltdW0gY29ycmVsYXRpb24gYmV0d2VlbiBhbnkgdHdvIHJldHVybiB2YXJpYWJsZXMgaW4gdGhlIGRhdGFzZXQ/XyANCjxicj4NCjAuMTkNCg0KDQojIyMjIyMgPHNwYW4gaWQ9J0QxLjMnPjEuMyDlsI/ntYToqI7oq5YgOiA8L3NwYW4+DQoiMSLmhI/mjIforormlbjoh6rlt7HoiIfoh6rlt7HnmoTnm7jpl5zkv4LmlbjvvIznm7jpl5zkv4LmlbjmnIDlpKfnmoTlhanlgIvorormlbjngrpSZXR1cm5Ob3blj4pSZXR1cm5PY3TjgIINCg0KDQojIyMjIyAxLjQNCmBgYHtyIGZpZy5oZWlnaHQ9MywgZmlnLndpZHRoPTYuNH0NCmNvbE1lYW5zKEFbLDE6MTFdKSAlPiUgc29ydCAlPiUgYmFycGxvdChsYXM9MiwgY2V4Lm5hbWVzPTAuOCwgY2V4LmF4aXM9MC44KQ0KYGBgDQpfV2hpY2ggbW9udGggKGZyb20gSmFudWFyeSB0aHJvdWdoIE5vdmVtYmVyKSBoYXMgdGhlIGxhcmdlc3QgbWVhbiByZXR1cm4gYWNyb3NzIGFsbCBvYnNlcnZhdGlvbnMgaW4gdGhlIGRhdGFzZXQ/Xw0KDQpBcHJpbA0KDQoNCl9XaGljaCBtb250aCAoZnJvbSBKYW51YXJ5IHRocm91Z2ggTm92ZW1iZXIpIGhhcyB0aGUgc21hbGxlc3QgbWVhbiByZXR1cm4gYWNyb3NzIGFsbCBvYnNlcnZhdGlvbnMgaW4gdGhlIGRhdGFzZXQ/Xw0KDQpTZXB0ZW1iZXINCg0KDQo8YnI+DQoNCi0gLSAtDQoNCiMjIyAyLiDpgo/ovK/lvI/lm57mrbjvvIzllq7kuIDmqKHlnosNCg0KIyMjIyMg5YiG5Ymy6KiT57e044CB5ris6Kmm6LOH5paZDQpSdW4gdGhlIGZvbGxvd2luZyBjb21tYW5kcyB0byBzcGxpdCB0aGUgZGF0YSBpbnRvIGEgdHJhaW5pbmcgc2V0IGFuZCB0ZXN0aW5nIHNldCwgcHV0dGluZyA3MCUgb2YgdGhlIGRhdGEgaW4gdGhlIHRyYWluaW5nIHNldCBhbmQgMzAlIG9mIHRoZSBkYXRhIGluIHRoZSB0ZXN0aW5nIHNldDoNCg0Kc2V0LnNlZWQoMTQ0KQ0KDQpzcGwgPSBzYW1wbGUuc3BsaXQoc3RvY2tzJFBvc2l0aXZlRGVjLCBTcGxpdFJhdGlvID0gMC43KQ0KDQpzdG9ja3NUcmFpbiA9IHN1YnNldChzdG9ja3MsIHNwbCA9PSBUUlVFKQ0KDQpzdG9ja3NUZXN0ID0gc3Vic2V0KHN0b2Nrcywgc3BsID09IEZBTFNFKQ0KDQpgYGB7cn0NCmxpYnJhcnkoY2FUb29scykNCnNldC5zZWVkKDE0NCkNCnNwbCA9IHNhbXBsZS5zcGxpdChBJFBvc2l0aXZlRGVjLDAuNykNClRSID0gc3Vic2V0KEEsIHNwbCkNClRTID0gc3Vic2V0KEEsICFzcGwpDQpzYXBwbHkobGlzdChBLCBUUiwgVFMpLCBmdW5jdGlvbih4KSBtZWFuKHgkUG9zaXRpdmVEZWMpKQ0KYGBgDQoNCiMjIyMjIDIuMSDllq7kuIDmqKHlnovvvJroqJPnt7TmupbnorrnjofvvIwkXHRleHR7YWNjfV97dHJhaW59JA0KVGhlbiwgdXNlIHRoZSBzdG9ja3NUcmFpbiBkYXRhIGZyYW1lIHRvIHRyYWluIGEgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbCAobmFtZSBpdCBTdG9ja3NNb2RlbCkgdG8gcHJlZGljdCBQb3NpdGl2ZURlYyB1c2luZyBhbGwgdGhlIG90aGVyIHZhcmlhYmxlcyBhcyBpbmRlcGVuZGVudCB2YXJpYWJsZXMuIERvbid0IGZvcmdldCB0byBhZGQgdGhlIGFyZ3VtZW50IGZhbWlseT1iaW5vbWlhbCB0byB5b3VyIGdsbSBjb21tYW5kLg0KDQpgYGB7cn0NCmdsbTEgPSBnbG0oUG9zaXRpdmVEZWMgfiAuLCAgVFIsIGZhbWlseT1iaW5vbWlhbCkNCnByZWQgPSBwcmVkaWN0KGdsbTEsIHR5cGU9J3Jlc3BvbnNlJykNCnRhYmxlKFRSJFBvcywgcHJlZCA+IDAuNSkgJT4lIHtzdW0oZGlhZyguKSkvc3VtKC4pfSANCmBgYA0KX1doYXQgaXMgdGhlIG92ZXJhbGwgYWNjdXJhY3kgb24gdGhlIHRyYWluaW5nIHNldCwgdXNpbmcgYSB0aHJlc2hvbGQgb2YgMC41P18NCg0KMC41NzExOA0KDQoNCiMjIyMjIyA8c3BhbiBpZD0nRDIuMSc+Mi4xIOWwj+e1hOiojuirlu+8mjwvc3Bhbj4NCuato+eiuueOh+eCuuioiOeul+OAjEZhbHNl54K6MOOAjeWPiuOAjFRydWXngrox44CN55qE5Yqg57i95L2U57i95pW46YeP55qE5q+U5L6L44CCDQoNCiMjIyMjIDIuMiDllq7kuIDmqKHlnovvvJrmuKzoqabmupbnorrnjofvvIwkXHRleHR7YWNjfV97dGVzdH0kDQpgYGB7cn0NCnByZWQgPSBwcmVkaWN0KGdsbTEsIFRTLCB0eXBlPSdyZXNwb25zZScpDQp0YWJsZShUUyRQb3MsIHByZWQgPiAwLjUpICU+JSB7c3VtKGRpYWcoLikpL3N1bSguKX0gDQpgYGANCl9Ob3cgb2J0YWluIHRlc3Qgc2V0IHByZWRpY3Rpb25zIGZyb20gU3RvY2tzTW9kZWwuIFdoYXQgaXMgdGhlIG92ZXJhbGwgYWNjdXJhY3kgb2YgdGhlIG1vZGVsIG9uIHRoZSB0ZXN0LCBhZ2FpbiB1c2luZyBhIHRocmVzaG9sZCBvZiAwLjU/Xw0KDQowLjU2NzA3DQoNCg0KIyMjIyMgMi4zIOWWruS4gOaooeWei++8muW6lee3mua6lueiuueOh++8jCRcdGV4dHthY2N9X3tiYXNlbGluZX0kDQpgYGB7cn0NCm1lYW4oVFMkUG9zaXRpdmVEZWMpDQpgYGANCl9XaGF0IGlzIHRoZSBhY2N1cmFjeSBvbiB0aGUgdGVzdCBzZXQgb2YgYSBiYXNlbGluZSBtb2RlbCB0aGF0IGFsd2F5cyBwcmVkaWN0cyB0aGUgbW9zdCBjb21tb24gb3V0Y29tZSAoUG9zaXRpdmVEZWMgPSAxKT9fDQoNCjAuNTQ2MDYNCg0KDQo8YnI+DQoNCi0gLSAtDQoNCiMjIyAzLiDpm4bnvqTliIbmnpANCg0KIyMjIyMgMy4xIOenu+mZpOebruaomeiuiuaVuA0KTm93LCBsZXQncyBjbHVzdGVyIHRoZSBzdG9ja3MuIFRoZSBmaXJzdCBzdGVwIGluIHRoaXMgcHJvY2VzcyBpcyB0byByZW1vdmUgdGhlIGRlcGVuZGVudCB2YXJpYWJsZSB1c2luZyB0aGUgZm9sbG93aW5nIGNvbW1hbmRzOg0KYGBge3J9DQpMVFIgPSBUUlssMToxMV0NCkxUUyA9IFRTWywxOjExXQ0KYGBgDQpfV2h5IGRvIHdlIG5lZWQgdG8gcmVtb3ZlIHRoZSBkZXBlbmRlbnQgdmFyaWFibGUgaW4gdGhlIGNsdXN0ZXJpbmcgcGhhc2Ugb2YgdGhlIGNsdXN0ZXItdGhlbi1wcmVkaWN0IG1ldGhvZG9sb2d5P18NCg0KTmVlZGluZyB0byBrbm93IHRoZSBkZXBlbmRlbnQgdmFyaWFibGUgdmFsdWUgdG8gYXNzaWduIGFuIG9ic2VydmF0aW9uIHRvIGEgY2x1c3RlciBkZWZlYXRzIHRoZSBwdXJwb3NlIG9mIHRoZSBtZXRob2RvbG9neQ0KDQojIyMjIyMgPHNwYW4gaWQ9J0QzLjEnPjMuMSDlsI/ntYToqI7oq5bvvJo8L3NwYW4+DQo8YnI+DQrlm6DngrrmiJHlgJHopoHliKnnlKjpm4bnvqTliIbmnpDkvobmib7lh7ros4fmlpnkuK3nm7jkvLznmoTop4Dlr5/lgLzvvIznhLblvozkvobpoJDmuKzmnKrnn6XnmoTorormlbh577yM5aaC5p6c5oiR5YCR55qE5Y6f5aeL6LOH5paZDQo8YnI+DQrnm7TmjqXlsLHmnInmiJHlgJHopoHnlKjkvobpoJDmuKznmoTorormlbhZ55qE6Kmx77yMUuacg+ebtOaOpeagueaTmlnkvobpgLLooYzliIbnvqTvvIzpgJnmqKPnmoTpm4bnvqTliIbmnpDmmK/mspLmnInmhI/nvqnnmoQNCg0KIyMjIyMgMy4yIOWNgOmalOiuiuaVuOW4uOaFi+WMlg0KSW4gdGhlIG1hcmtldCBzZWdtZW50YXRpb24gYXNzaWdubWVudCBpbiB0aGlzIHdlZWsncyBob21ld29yaywgeW91IHdlcmUgaW50cm9kdWNlZCB0byB0aGUgcHJlUHJvY2VzcyBjb21tYW5kIGZyb20gdGhlIGNhcmV0IHBhY2thZ2UsIHdoaWNoIG5vcm1hbGl6ZXMgdmFyaWFibGVzIGJ5IHN1YnRyYWN0aW5nIGJ5IHRoZSBtZWFuIGFuZCBkaXZpZGluZyBieSB0aGUgc3RhbmRhcmQgZGV2aWF0aW9uLg0KDQpJbiBjYXNlcyB3aGVyZSB3ZSBoYXZlIGEgdHJhaW5pbmcgYW5kIHRlc3Rpbmcgc2V0LCB3ZSdsbCB3YW50IHRvIG5vcm1hbGl6ZSBieSB0aGUgbWVhbiBhbmQgc3RhbmRhcmQgZGV2aWF0aW9uIG9mIHRoZSB2YXJpYWJsZXMgaW4gdGhlIHRyYWluaW5nIHNldC4gV2UgY2FuIGRvIHRoaXMgYnkgcGFzc2luZyBqdXN0IHRoZSB0cmFpbmluZyBzZXQgdG8gdGhlIHByZVByb2Nlc3MgZnVuY3Rpb246DQpgYGB7cn0NCmxpYnJhcnkoY2FyZXQpDQpwcmVwcm9jID0gcHJlUHJvY2VzcyhMVFIpDQpOVFIgPSBwcmVkaWN0KHByZXByb2MsIExUUikNCk5UUyA9IHByZWRpY3QocHJlcHJvYywgTFRTKQ0KYGBgDQoNCmBgYHtyfQ0KbWVhbihOVFIkUmV0dXJuSmFuKQ0KYGBgDQpfV2hhdCBpcyB0aGUgbWVhbiBvZiB0aGUgUmV0dXJuSmFuIHZhcmlhYmxlIGluIG5vcm1UcmFpbj9fDQoNCjIuMTAwNmUtMTcNCg0KDQpgYGB7cn0NCm1lYW4oTlRTJFJldHVybkphbikNCmBgYA0KX1doYXQgaXMgdGhlIG1lYW4gb2YgdGhlIFJldHVybkphbiB2YXJpYWJsZSBpbiBub3JtVHJhaW4/Xw0KDQotMC4wMDA0MTg1OQ0KDQoNCiMjIyMjIDMuMyDmuKzoqabos4fmlpnnmoTluLjmhYvljJbntZDmnpwNCl9XaHkgaXMgdGhlIG1lYW4gUmV0dXJuSmFuIHZhcmlhYmxlIG11Y2ggY2xvc2VyIHRvIDAgaW4gbm9ybVRyYWluIHRoYW4gaW4gbm9ybVRlc3Q/Xw0KPGJyPg0KVGhlIGRpc3RyaWJ1dGlvbiBvZiB0aGUgUmV0dXJuSmFuIHZhcmlhYmxlIGlzIGRpZmZlcmVudCBpbiB0aGUgdHJhaW5pbmcgYW5kIHRlc3Rpbmcgc2V0DQoNCiMjIyMjIyA8c3BhbiBpZD0nRDMuMyc+My4zIOWwj+e1hOiojuirlu+8mjwvc3Bhbj4NCuWboOeCuuWcqHRyYWlu6LefdGVzdOeahOizh+aWmeS4reingOWvn+WAvOeahOWIhuW4g+S4jeS4gOaoo++8jOiAjOaomea6luWMlueahOmBjueoi+S4reaJgOeUqOeahOmDveaYr3RyYWlu55qE5bmz5Z2H5YC877yM5omA5LuldHJhaW7nmoTop4Dlr5/lgLzlnKjmqJnmupbljJbkuYvlvozvvIzlubPlnYfmnIPmr5R0ZXN055qE6KeA5a+f5YC85bmz5Z2H5pu05o6l6L+RMA0KDQoNCiMjIyMjIDMuNCBLLU1lYW5z6ZuG576kDQpTZXQgdGhlIHJhbmRvbSBzZWVkIHRvIDE0NCAoaXQgaXMgaW1wb3J0YW50IHRvIGRvIHRoaXMgYWdhaW4sIGV2ZW4gdGhvdWdoIHdlIGRpZCBpdCBlYXJsaWVyKS4gUnVuIGstbWVhbnMgY2x1c3RlcmluZyB3aXRoIDMgY2x1c3RlcnMgb24gbm9ybVRyYWluLCBzdG9yaW5nIHRoZSByZXN1bHQgaW4gYW4gb2JqZWN0IGNhbGxlZCBrbS4NCmBgYHtyfQ0Kc2V0LnNlZWQoMTQ0KQ0Ka20gPC0ga21lYW5zKE5UUiwgMykNCmBgYA0KDQpgYGB7cn0NCnRhYmxlKGttJGNsdXN0ZXIpDQpgYGANCl9XaGljaCBjbHVzdGVyIGhhcyB0aGUgbGFyZ2VzdCBudW1iZXIgb2Ygb2JzZXJ2YXRpb25zP18NCg0KQ2x1c3RlciAyDQoNCiMjIyMjIDMuNQ0KUmVjYWxsIGZyb20gdGhlIHJlY2l0YXRpb24gdGhhdCB3ZSBjYW4gdXNlIHRoZSBmbGV4Y2x1c3QgcGFja2FnZSB0byBvYnRhaW4gdHJhaW5pbmcgc2V0IGFuZCB0ZXN0aW5nIHNldCBjbHVzdGVyIGFzc2lnbm1lbnRzIGZvciBvdXIgb2JzZXJ2YXRpb25zIChub3RlIHRoYXQgdGhlIGNhbGwgdG8gYXMua2NjYSBtYXkgdGFrZSBhIHdoaWxlIHRvIGNvbXBsZXRlKToNCmBgYHtyfQ0KbGlicmFyeShmbGV4Y2x1c3QpDQprbS5rY2NhID0gYXMua2NjYShrbSwgTlRSKQ0KQ1RSID0gcHJlZGljdChrbS5rY2NhKQ0KQ1RTID0gcHJlZGljdChrbS5rY2NhLCBuZXdkYXRhPU5UUykNCmBgYA0KDQpgYGB7cn0NCnRhYmxlKENUUykNCmBgYA0KX0hvdyBtYW55IHRlc3Qtc2V0IG9ic2VydmF0aW9ucyB3ZXJlIGFzc2lnbmVkIHRvIENsdXN0ZXIgMj9fDQoNCjIwODANCg0KIyMjIyMjIDxzcGFuIGlkPSdEMy41Jz4zLjUg5bCP57WE6KiO6KuWPC9zcGFuPg0K6YCZ5YCLY29kZeaYr+Wwh+WIhue+pOeahOizh+ioiuWBmuaIkOaooeWei++8jOeEtuW+jOmBi+eUqOmAmeWAi+aooeWei+S+huWwjXRlc3Tos4fmlpnkuK3nmoTop4DmuKzlgLzpgLLooYzliIbnvqTvvIxDVFPlsLHmmK/lsI10ZXN06LOH5paZ5Lit55qE6KeA5ris5YC85Lul6KiT57e06LOH5paZ5Lit5YiG576k55qE5pa55byP6YCy6KGM5YiG6aGeDQoNCi0gLSAtDQoNCiMjIyA0LiDpgo/ovK/lvI/lm57mrbjvvIzliIbnvqTmqKHlnosNCg0KIyMjIyMgNC4xIOS+nembhue+pOWIhuaekOeahOe1kOaenOWIh+WJsuizh+aWmQ0KVXNpbmcgdGhlIHN1YnNldCBmdW5jdGlvbiwgYnVpbGQgZGF0YSBmcmFtZXMgc3RvY2tzVHJhaW4xLCBzdG9ja3NUcmFpbjIsIGFuZCBzdG9ja3NUcmFpbjMsIGNvbnRhaW5pbmcgdGhlIGVsZW1lbnRzIGluIHRoZSBzdG9ja3NUcmFpbiBkYXRhIGZyYW1lIGFzc2lnbmVkIHRvIGNsdXN0ZXJzIDEsIDIsIGFuZCAzLCByZXNwZWN0aXZlbHkgKGJlIGNhcmVmdWwgdG8gdGFrZSBzdWJzZXRzIG9mIHN0b2Nrc1RyYWluLCBub3Qgb2Ygbm9ybVRyYWluKS4gU2ltaWxhcmx5IGJ1aWxkIHN0b2Nrc1Rlc3QxLCBzdG9ja3NUZXN0MiwgYW5kIHN0b2Nrc1Rlc3QzIGZyb20gdGhlIHN0b2Nrc1Rlc3QgZGF0YSBmcmFtZS4NCg0KYGBge3J9DQp0YXBwbHkoVFIkUG9zaXRpdmVEZWMsIENUUiwgbWVhbikNCmBgYA0KX1doaWNoIHRyYWluaW5nIHNldCBkYXRhIGZyYW1lIGhhcyB0aGUgaGlnaGVzdCBhdmVyYWdlIHZhbHVlIG9mIHRoZSBkZXBlbmRlbnQgdmFyaWFibGU/Xw0KDQrnrKzkuIDnvqTnmoR0cmFpbmluZyBzZXQgZGF0YSBmcmFtZQ0KDQoNCiMjIyMjIDQuMiDliIbnvqTmqKHlnovvvIzmqKHlnovkv4LmlbgNCkJ1aWxkIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWxzIFN0b2Nrc01vZGVsMSwgU3RvY2tzTW9kZWwyLCBhbmQgU3RvY2tzTW9kZWwzLCB3aGljaCBwcmVkaWN0IFBvc2l0aXZlRGVjIHVzaW5nIGFsbCB0aGUgb3RoZXIgdmFyaWFibGVzIGFzIGluZGVwZW5kZW50IHZhcmlhYmxlcy4gU3RvY2tzTW9kZWwxIHNob3VsZCBiZSB0cmFpbmVkIG9uIHN0b2Nrc1RyYWluMSwgU3RvY2tzTW9kZWwyIHNob3VsZCBiZSB0cmFpbmVkIG9uIHN0b2Nrc1RyYWluMiwgYW5kIFN0b2Nrc01vZGVsMyBzaG91bGQgYmUgdHJhaW5lZCBvbiBzdG9ja3NUcmFpbjMuDQpgYGB7cn0NCk0gPSBsYXBwbHkoc3BsaXQoVFIsIENUUiksIGZ1bmN0aW9uKHgpIA0KICBnbG0oUG9zaXRpdmVEZWN+LiwgZGF0YT14LCBmYW1pbHk9Ymlub21pYWwpICkNCnNhcHBseShNLCBmdW5jdGlvbih4KSBjb2VmKHN1bW1hcnkoeCkpWywxXSkNCmBgYA0KDQpfV2hpY2ggdmFyaWFibGVzIGhhdmUgYSBwb3NpdGl2ZSBzaWduIGZvciB0aGUgY29lZmZpY2llbnQgaW4gYXQgbGVhc3Qgb25lIG1vZGVsIGFuZCBhIG5lZ2F0aXZlIHNpZ24gZm9yIHRoZSBjb2VmZmljaWVudCBpbiBhdCBsZWFzdCBvbmUgbW9kZWw/XyBTZWxlY3QgYWxsIHRoYXQgYXBwbHkuDQoNClJldHVybkphbiAsUmV0dXJuRmViLFJldHVybk1hciAsUmV0dXJuSnVuZSxSZXR1cm5BdWcsUmV0dXJuT2N0DQoNCiMjIyMjIyA8c3BhbiBpZD0nRDQuMic+NC4yIOWwj+e1hOiojuirljwvc3Bhbj4NCk3mmK/miopUUuagueaTmkNUUuWIhuaIkOS4ieWAi+mDqOWIhu+8jOeEtuW+jOWwh+avj+S4gOmDqOS7veeahOizh+aWmeS+neW6j+aLv+WOu+W7uueri+S4gOWAi+mCj+i8r+W8j+WbnuatuOaooeWeiw0KPGJyPg0K5LiL6Z2i55qEc2FwcGx55YmH5piv5oqK6YCZ5LiJ5YCL5qih5Z6L5L6d5bqP5ou/5Y675YGac3VtbWFyee+8jOWGjemAj+mBjmNvZWZb77yMMV3kvoblj5blh7rlkITlgIvmqKHlnovnmoTkv4LmlbjoqZXkvLDlgLwNCg0KDQojIyMjIyA0LjMg5YiG576k5qih5Z6L77ya5YiG576k5ris6Kmm5rqW56K6546H77yMJFx0ZXh0e2FjY31fe3Rlc3R9XnsxLDIsM30kDQpVc2luZyBTdG9ja3NNb2RlbDEsIG1ha2UgdGVzdC1zZXQgcHJlZGljdGlvbnMgY2FsbGVkIFByZWRpY3RUZXN0MSBvbiB0aGUgZGF0YSBmcmFtZSBzdG9ja3NUZXN0MS4gVXNpbmcgU3RvY2tzTW9kZWwyLCBtYWtlIHRlc3Qtc2V0IHByZWRpY3Rpb25zIGNhbGxlZCBQcmVkaWN0VGVzdDIgb24gdGhlIGRhdGEgZnJhbWUgc3RvY2tzVGVzdDIuIFVzaW5nIFN0b2Nrc01vZGVsMywgbWFrZSB0ZXN0LXNldCBwcmVkaWN0aW9ucyBjYWxsZWQgUHJlZGljdFRlc3QzIG9uIHRoZSBkYXRhIGZyYW1lIHN0b2Nrc1Rlc3QzLg0KYGBge3J9DQpQcmVkID0gbGFwcGx5KDE6MywgZnVuY3Rpb24oaSkgDQogIHByZWRpY3QoTVtbaV1dLCBUU1tDVFM9PWksXSwgdHlwZT0ncmVzcG9uc2UnKSApDQpzYXBwbHkoMTozLCBmdW5jdGlvbihpKSANCiAgdGFibGUoVFMkUG9zW0NUUz09aV0sIFByZWRbW2ldXSA+IDAuNSkgJT4lIHtzdW0oZGlhZyguKSkvc3VtKC4pfSAgKQ0KYGBgDQpfV2hhdCBpcyB0aGUgb3ZlcmFsbCBhY2N1cmFjeSBvZiBTdG9ja3NNb2RlbDEgb24gdGhlIHRlc3Qgc2V0IHN0b2Nrc1Rlc3QxLCB1c2luZyBhIHRocmVzaG9sZCBvZiAwLjU/Xw0KDQowLjYxOTQxDQoNCg0KX1doYXQgaXMgdGhlIG92ZXJhbGwgYWNjdXJhY3kgb2YgU3RvY2tzTW9kZWwyIG9uIHRoZSB0ZXN0IHNldCBzdG9ja3NUZXN0MywgdXNpbmcgYSB0aHJlc2hvbGQgb2YgMC41P18NCg0KMC41NTA0OA0KDQoNCl9XaGF0IGlzIHRoZSBvdmVyYWxsIGFjY3VyYWN5IG9mIFN0b2Nrc01vZGVsMyBvbiB0aGUgdGVzdCBzZXQgc3RvY2tzVGVzdDMsIHVzaW5nIGEgdGhyZXNob2xkIG9mIDAuNT9fDQoNCjAuNjQ1ODMNCg0KDQojIyMjIyMgPHNwYW4gaWQ9J0Q0LjMnPjQuMyDlsI/ntYToqI7oq5Y8L3NwYW4+DQrkuIrpnaLnmoRQcmVk5piv5oqKVFPkvp3nhadDVFPliIbmiJDkuInlgIvpg6jliIbvvIzlho3miorpgJnkuInnvqTnmoTos4fmlpnkvp3luo/ot59N55qE5LiJ5YCL5qih5Z6L5Lit5bCN54Wn55qE5ou/5Y675YGa6aCQ5risDQo8YnI+DQrkuIvpnaLnmoRzYXBwbHnliYfmmK/miorlr6bpmpvnmoTntZDmnpzkvp3nhadDVFPliIbmiJDkuInlgIvpg6jliIbvvIzlho3miormr4/lgIvpg6jliIbot59QcmVk6aCQ5ris55qE57WQ5p6c5bCN54Wn55qE6YOo5YiG5YGa5oiQ5re35reG55+p6Zmj77yM54S25b6M6KiI566X5rqW56K6546HDQoNCg0KDQojIyMjIyA0LjQg5YiG576k5qih5Z6L77ya5pW06auU5ris6Kmm5rqW56K6546H77yMJFx0ZXh0e2FjY31fe3Rlc3R9XnsxKzIrM30kDQpUbyBjb21wdXRlIHRoZSBvdmVyYWxsIHRlc3Qtc2V0IGFjY3VyYWN5IG9mIHRoZSBjbHVzdGVyLXRoZW4tcHJlZGljdCBhcHByb2FjaCwgd2UgY2FuIGNvbWJpbmUgYWxsIHRoZSB0ZXN0LXNldCBwcmVkaWN0aW9ucyBpbnRvIGEgc2luZ2xlIHZlY3RvciBhbmQgYWxsIHRoZSB0cnVlIG91dGNvbWVzIGludG8gYSBzaW5nbGUgdmVjdG9yOg0KYGBge3J9DQp0YWJsZSggZG8uY2FsbChjLCBzcGxpdChUUyRQb3MsQ1RTKSksIGRvLmNhbGwoYywgUHJlZCkgPiAwLjUgKSAlPiUNCiAge3N1bShkaWFnKC4pKS9zdW0oLil9DQpgYGANCg0KX1doYXQgaXMgdGhlIG92ZXJhbGwgdGVzdC1zZXQgYWNjdXJhY3kgb2YgdGhlIGNsdXN0ZXItdGhlbi1wcmVkaWN0IGFwcHJvYWNoLCBhZ2FpbiB1c2luZyBhIHRocmVzaG9sZCBvZiAwLjU/Xw0KDQowLjU3ODg3DQoNCg0KIyMjIyMjIDxzcGFuIGlkPSdENC40Jz40LjQg5bCP57WE6KiO6KuWPC9zcGFuPg0K6YCZ6KOh55qEZG8uY2FsbOaYr+aKiuaooeWei+mgkOa4rOWHuuS+hueahOS4ieWAi+mDqOWIhueahOingOWvn+WAvO+8jOiuiuaIkOS4gOaVtOWAi+eahOWQkemHj++8jOWPpuS4gOaWuemdou+8jFRTJFBvc+eahOWAvOS5n+WFiOS+neeFp0NUU+WIhuWJsuaIkOS4ieWAi+mDqOWIhu+8jOS+huW+jOWGjeiuiuaIkOWQkemHjw0KDQrmnIDlvozlho3miorpgJnlhannrYbos4fmlpnorormiJDmt7fmt4bnn6npmaPvvIzkuKboqIjnrpfmlbTpq5TnmoTmupbnorrluqYNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCldlIHNlZSBhIG1vZGVzdCBpbXByb3ZlbWVudCBvdmVyIHRoZSBvcmlnaW5hbCBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsLiBTaW5jZSBwcmVkaWN0aW5nIHN0b2NrIHJldHVybnMgaXMgYSBub3RvcmlvdXNseSBoYXJkIHByb2JsZW0sIHRoaXMgaXMgYSBnb29kIGluY3JlYXNlIGluIGFjY3VyYWN5LiBCeSBpbnZlc3RpbmcgaW4gc3RvY2tzIGZvciB3aGljaCB3ZSBhcmUgbW9yZSBjb25maWRlbnQgdGhhdCB0aGV5IHdpbGwgaGF2ZSBwb3NpdGl2ZSByZXR1cm5zIChieSBzZWxlY3RpbmcgdGhlIG9uZXMgd2l0aCBoaWdoZXIgcHJlZGljdGVkIHByb2JhYmlsaXRpZXMpLCB0aGlzIGNsdXN0ZXItdGhlbi1wcmVkaWN0IG1vZGVsIGNhbiBnaXZlIHVzIGFuIGVkZ2Ugb3ZlciB0aGUgb3JpZ2luYWwgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbC4NCg0KPGJyPg0KDQotIC0gLQ0KDQo8YnI+PGJyPjxicj48YnI+PGJyPg0KDQo8c3R5bGU+DQouY2FwdGlvbiB7DQogIGNvbG9yOiAjNzc3Ow0KICBtYXJnaW4tdG9wOiAxMHB4Ow0KfQ0KcCBjb2RlIHsNCiAgd2hpdGUtc3BhY2U6IGluaGVyaXQ7DQp9DQpwcmUgew0KICB3b3JkLWJyZWFrOiBub3JtYWw7DQogIHdvcmQtd3JhcDogbm9ybWFsOw0KICBsaW5lLWhlaWdodDogMTsNCn0NCnByZSBjb2RlIHsNCiAgd2hpdGUtc3BhY2U6IGluaGVyaXQ7DQp9DQpwLGxpIHsNCiAgZm9udC1mYW1pbHk6ICJUcmVidWNoZXQgTVMiLCAi5b6u6Luf5q2j6buR6auUIiwgIk1pY3Jvc29mdCBKaGVuZ0hlaSI7DQp9DQoNCi5yew0KICBsaW5lLWhlaWdodDogMS4yOw0KfQ0KDQp0aXRsZXsNCiAgY29sb3I6ICNjYzAwMDA7DQogIGZvbnQtZmFtaWx5OiAiVHJlYnVjaGV0IE1TIiwgIuW+rui7n+ato+m7kemrlCIsICJNaWNyb3NvZnQgSmhlbmdIZWkiOw0KfQ0KDQpib2R5ew0KICBmb250LWZhbWlseTogIlRyZWJ1Y2hldCBNUyIsICLlvq7ou5/mraPpu5Hpq5QiLCAiTWljcm9zb2Z0IEpoZW5nSGVpIjsNCn0NCg0KaDEsaDIsaDMsaDQsaDV7DQogIGNvbG9yOiAjMDA4ODAwOw0KICBmb250LWZhbWlseTogIlRyZWJ1Y2hldCBNUyIsICLlvq7ou5/mraPpu5Hpq5QiLCAiTWljcm9zb2Z0IEpoZW5nSGVpIjsNCn0NCg0KaDN7DQogIGNvbG9yOiAjYjM2YjAwOw0KICBiYWNrZ3JvdW5kOiAjZmZlMGIzOw0KICBsaW5lLWhlaWdodDogMjsNCiAgZm9udC13ZWlnaHQ6IGJvbGQ7DQp9DQoNCmg1ew0KICBjb2xvcjogIzAwNjAwMDsNCiAgYmFja2dyb3VuZDogI2ZmZmZlMDsNCiAgbGluZS1oZWlnaHQ6IDI7DQogIGZvbnQtd2VpZ2h0OiBib2xkOw0KfQ0KDQpoNnsNCiAgY29sb3I6ICMwMDYwMDA7DQogIGJhY2tncm91bmQ6ICMwMGZmZmY7DQogIGxpbmUtaGVpZ2h0OiAyOw0KICBmb250LXdlaWdodDogYm9sZDsNCn0NCg0KZW17DQogIGNvbG9yOiAjMDAwMGMwOw0KICBiYWNrZ3JvdW5kOiAjZjBmMGYwOw0KICB9DQo8L3N0eWxlPg0KDQo=