If any issues, questions or suggestions feel free to reach me out via e-mail or Linkedin. You can also visit my Github.

if(!require(pacman)) install.packages('pacman')
pacman::p_load(PolishStock, dplyr, TTR, tidyr, purrr)

We use PolishStock package to download the below data from the website stooq.pl. Initial dataset will consist of OLHC prices and volume for the following assets:
- WIG20 index (top 20 Polish stocks)
- mWIG40 index (medium-cap 40 Polish stocks)
- sWIG80 idex (small-cap 80 Polish stocks)
- S&P500 index
- DAX index
- NASDAQ index
- iShares MSCI Emerging Markets ETF.

Depending on the index there are ~3050 daily observations for the period January 2010 - February 2022.

tickers <- c('WIG20', 'mWIG40', 'sWIG80', '^SPX', '^DAX', '^NDQ', 'EEM.US')

for (ticker in tickers) {
  stooq_download(ticker
                 ,start_date = 20100101
                 ,end_date = 20222502
                 ,interval = 'd'
                 ,destination = paste0(getwd(), '/datasets/'))
}

dataset <- lapply(tickers, function(ticker) {
  read.csv(paste0('datasets/', ticker, '.csv')
           ,colClasses = c('Date', rep('numeric', 5)))
})

names(dataset) <- tickers

We derive the following technical indicators using package TTR:
- position of EMA’s (10, 20, 30, 50, 100, 200) relative to close prices (above/below)
- ADX (n=14) measuring strength and momemntum of a trend
- MACD histogram (Fast=12, Slow=26, Signal=9)
- OBV measuring money flowing in and out of a security
- RSI (n=14) measuring the velocity and magnitude of recent price changes
- Stoch %K (n=14) comparing close price to a range of its prices over a period of n
- Stoch %D (n=3) EMA of Stoch %K
- Moving volatility of close prices (n=14)
- Parkinson volatility (n=14) measuring volatility in terms of Highs and Lows.

All indicators are shifted one day ahead. The reason is we know close prices at the end of the trading day and until that we can’t calculate indicators or make investment decisions based upon them. After omitting NA’s there remains ~2800 daily observations depending on the index.

for (i in 1:length(dataset)) {
  
  temp <- dataset[[i]]
  colnames(temp) <- c('Date', 'Open', 'High', 'Low', 'Close', 'Volume')

  temp <- temp %>%
    mutate(Returns = c(NA, diff(log(Close), lag = 1))
           ,Class_Returns = if_else(Returns > 0, 'Increase', 'Deacrease')
           ,EMA15 = if_else(EMA(Close, n = 15) > Close, 'Above', 'Below')
           ,EMA20 = if_else(EMA(Close, n = 20) > Close, 'Above', 'Below')
           ,EMA30 = if_else(EMA(Close, n = 30) > Close, 'Above', 'Below')
           ,EMA50 = if_else(EMA(Close, n = 50) > Close, 'Above', 'Below')
           ,EMA100 = if_else(EMA(Close, n = 100) > Close, 'Above', 'Below')
           ,EMA200 = if_else(EMA(Close, n = 200) > Close, 'Above', 'Below')
           ,ADX = ADX(select(temp, High:Close), n = 14, maType = 'EMA')[,4] 
           ,MACD = MACD(Close, nFast = 12, nSlow = 26, nSig = 9, maType = 'EMA') 
           ,MACD_Histogram = MACD[,1] - MACD[,2]
           ,OBV = OBV(Close, Volume) 
           ,RSI = RSI(Close, n = 14, maType = 'EMA') 
           ,Stoch = stoch(select(temp, High:Close), nFastK = 14, nFastD = 3, nSlowD = 3, maType = 'EMA') 
           ,Stoch_K = Stoch[,1]
           ,Stoch_D = Stoch[,3]
           ,Close_Volatility = volatility(select(temp, Open:Close), n = 14, calc = 'close', N = 252) 
           ,Parkinson_Volatility = volatility(select(temp, Open:Close), n = 14, calc = 'parkinson', N = 252) 
    ) %>%
    select(-(Open:Returns), -MACD, -Stoch) %>%
    mutate(across(.cols = 3:16, .fns = function(x) lag(x, 1))) %>%
    na.omit()
  
  dataset[[i]] <- temp
  
}

Now we split dataset into traning set (75%) and test set (25%). In further blog posts we will try different classification algorithms in order to predict increase or decrease of the close prices using technical indicators. Our goal will be selection of the most informative technical indicators for making investment decisions.

set.seed(2137)
train_set <- lapply(dataset, function(x) sample_frac(x, 0.75))
train_set <- lapply(train_set, function(x) arrange(x, Date))
test_set <- map(seq_along(dataset), ~anti_join(dataset[[.x]], train_set[[.x]], by = 'Date'))
names(test_set) <- names(train_set)
train_set <- lapply(train_set, function(x) select(x, -Date))
test_set <- lapply(test_set, function(x) select(x, -Date))
dput(train_set, 'technical_indicators_train_set.txt')
dput(test_set, 'technical_indicators_test_set.txt')
LS0tDQp0aXRsZTogIlRlY2huaWNhbCBJbmRpY2F0b3JzIC0gRGF0YSBQcmVwYXJhdGlvbiINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCklmIGFueSBpc3N1ZXMsIHF1ZXN0aW9ucyBvciBzdWdnZXN0aW9ucyBmZWVsIGZyZWUgdG8gcmVhY2ggbWUgb3V0IHZpYSBlLW1haWwgPHdpZWN6eW5za2lwYXdlbEBnbWFpbC5jb20+IG9yIFtMaW5rZWRpbl0oaHR0cHM6Ly93d3cubGlua2VkaW4uY29tL2luL3Bhd2VsLXdpZWN6eW5za2kvKS4gWW91IGNhbiBhbHNvIHZpc2l0IG15IFtHaXRodWJdKGh0dHBzOi8vZ2l0aHViLmNvbS9wYXdlbC13aWVjenluc2tpKS4NCg0KYGBge3IgbGlicmFyaWVzLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQ0KaWYoIXJlcXVpcmUocGFjbWFuKSkgaW5zdGFsbC5wYWNrYWdlcygncGFjbWFuJykNCnBhY21hbjo6cF9sb2FkKFBvbGlzaFN0b2NrLCBkcGx5ciwgVFRSLCB0aWR5ciwgcHVycnIpDQpgYGANCg0KV2UgdXNlIFtQb2xpc2hTdG9jayBwYWNrYWdlXShodHRwczovL2dpdGh1Yi5jb20vcGF3ZWwtd2llY3p5bnNraS9Qb2xpc2hTdG9jaykgdG8gZG93bmxvYWQgdGhlIGJlbG93IGRhdGEgZnJvbSB0aGUgd2Vic2l0ZSBbc3Rvb3EucGxdKGh0dHBzOi8vc3Rvb3EucGwvKS4gSW5pdGlhbCBkYXRhc2V0IHdpbGwgY29uc2lzdCBvZiBPTEhDIHByaWNlcyBhbmQgdm9sdW1lIGZvciB0aGUgZm9sbG93aW5nIGFzc2V0czogXA0KIC0gV0lHMjAgaW5kZXggKHRvcCAyMCBQb2xpc2ggc3RvY2tzKSBcDQogLSBtV0lHNDAgaW5kZXggKG1lZGl1bS1jYXAgNDAgUG9saXNoIHN0b2NrcykgXA0KIC0gc1dJRzgwIGlkZXggKHNtYWxsLWNhcCA4MCBQb2xpc2ggc3RvY2tzKSBcDQogLSBTJlA1MDAgaW5kZXggXA0KIC0gREFYIGluZGV4IFwNCiAtIE5BU0RBUSBpbmRleCBcDQogLSBpU2hhcmVzIE1TQ0kgRW1lcmdpbmcgTWFya2V0cyBFVEYuDQogDQpEZXBlbmRpbmcgb24gdGhlIGluZGV4IHRoZXJlIGFyZSB+MzA1MCBkYWlseSBvYnNlcnZhdGlvbnMgZm9yIHRoZSBwZXJpb2QgSmFudWFyeSAyMDEwIC0gRmVicnVhcnkgMjAyMi4NCmBgYHtyIGdldF9kYXRhfQ0KdGlja2VycyA8LSBjKCdXSUcyMCcsICdtV0lHNDAnLCAnc1dJRzgwJywgJ15TUFgnLCAnXkRBWCcsICdeTkRRJywgJ0VFTS5VUycpDQoNCmZvciAodGlja2VyIGluIHRpY2tlcnMpIHsNCiAgc3Rvb3FfZG93bmxvYWQodGlja2VyDQogICAgICAgICAgICAgICAgICxzdGFydF9kYXRlID0gMjAxMDAxMDENCiAgICAgICAgICAgICAgICAgLGVuZF9kYXRlID0gMjAyMjI1MDINCiAgICAgICAgICAgICAgICAgLGludGVydmFsID0gJ2QnDQogICAgICAgICAgICAgICAgICxkZXN0aW5hdGlvbiA9IHBhc3RlMChnZXR3ZCgpLCAnL2RhdGFzZXRzLycpKQ0KfQ0KDQpkYXRhc2V0IDwtIGxhcHBseSh0aWNrZXJzLCBmdW5jdGlvbih0aWNrZXIpIHsNCiAgcmVhZC5jc3YocGFzdGUwKCdkYXRhc2V0cy8nLCB0aWNrZXIsICcuY3N2JykNCiAgICAgICAgICAgLGNvbENsYXNzZXMgPSBjKCdEYXRlJywgcmVwKCdudW1lcmljJywgNSkpKQ0KfSkNCg0KbmFtZXMoZGF0YXNldCkgPC0gdGlja2Vycw0KYGBgDQoNCldlIGRlcml2ZSB0aGUgZm9sbG93aW5nIHRlY2huaWNhbCBpbmRpY2F0b3JzIHVzaW5nIHBhY2thZ2UgVFRSOiBcDQogLSBwb3NpdGlvbiBvZiBFTUHigJlzICgxMCwgMjAsIDMwLCA1MCwgMTAwLCAyMDApIHJlbGF0aXZlIHRvIGNsb3NlIHByaWNlcyAoYWJvdmUvYmVsb3cpIFwNCiAtIEFEWCAobj0xNCkgbWVhc3VyaW5nIHN0cmVuZ3RoIGFuZCBtb21lbW50dW0gb2YgYSB0cmVuZCBcDQogLSBNQUNEIGhpc3RvZ3JhbSAoRmFzdD0xMiwgU2xvdz0yNiwgU2lnbmFsPTkpIFwNCiAtIE9CViBtZWFzdXJpbmcgbW9uZXkgZmxvd2luZyBpbiBhbmQgb3V0IG9mIGEgc2VjdXJpdHkgXA0KIC0gUlNJIChuPTE0KSBtZWFzdXJpbmcgdGhlIHZlbG9jaXR5IGFuZCBtYWduaXR1ZGUgb2YgcmVjZW50IHByaWNlIGNoYW5nZXMgXA0KIC0gU3RvY2ggJUsgKG49MTQpIGNvbXBhcmluZyBjbG9zZSBwcmljZSB0byBhIHJhbmdlIG9mIGl0cyBwcmljZXMgb3ZlciBhIHBlcmlvZCBvZiBuIFwNCiAtIFN0b2NoICVEIChuPTMpIEVNQSBvZiBTdG9jaCAlSyBcDQogLSBNb3Zpbmcgdm9sYXRpbGl0eSBvZiBjbG9zZSBwcmljZXMgKG49MTQpIFwNCiAtIFBhcmtpbnNvbiB2b2xhdGlsaXR5IChuPTE0KSBtZWFzdXJpbmcgdm9sYXRpbGl0eSBpbiB0ZXJtcyBvZiBIaWdocyBhbmQgTG93cy4NCiANCkFsbCBpbmRpY2F0b3JzIGFyZSBzaGlmdGVkIG9uZSBkYXkgYWhlYWQuIFRoZSByZWFzb24gaXMgd2Uga25vdyBjbG9zZSBwcmljZXMgYXQgdGhlIGVuZCBvZiB0aGUgdHJhZGluZyBkYXkgYW5kIHVudGlsIHRoYXQgd2UgY2Fu4oCZdCBjYWxjdWxhdGUgaW5kaWNhdG9ycyBvciBtYWtlIGludmVzdG1lbnQgZGVjaXNpb25zIGJhc2VkIHVwb24gdGhlbS4gQWZ0ZXIgb21pdHRpbmcgTkEncyB0aGVyZSByZW1haW5zIH4yODAwIGRhaWx5IG9ic2VydmF0aW9ucyBkZXBlbmRpbmcgb24gdGhlIGluZGV4Lg0KYGBge3IgY2FsY3VsYXRlX2luZGljYXRvcnN9DQpmb3IgKGkgaW4gMTpsZW5ndGgoZGF0YXNldCkpIHsNCiAgDQogIHRlbXAgPC0gZGF0YXNldFtbaV1dDQogIGNvbG5hbWVzKHRlbXApIDwtIGMoJ0RhdGUnLCAnT3BlbicsICdIaWdoJywgJ0xvdycsICdDbG9zZScsICdWb2x1bWUnKQ0KDQogIHRlbXAgPC0gdGVtcCAlPiUNCiAgICBtdXRhdGUoUmV0dXJucyA9IGMoTkEsIGRpZmYobG9nKENsb3NlKSwgbGFnID0gMSkpDQogICAgICAgICAgICxDbGFzc19SZXR1cm5zID0gaWZfZWxzZShSZXR1cm5zID4gMCwgJ0luY3JlYXNlJywgJ0RlYWNyZWFzZScpDQogICAgICAgICAgICxFTUExNSA9IGlmX2Vsc2UoRU1BKENsb3NlLCBuID0gMTUpID4gQ2xvc2UsICdBYm92ZScsICdCZWxvdycpDQogICAgICAgICAgICxFTUEyMCA9IGlmX2Vsc2UoRU1BKENsb3NlLCBuID0gMjApID4gQ2xvc2UsICdBYm92ZScsICdCZWxvdycpDQogICAgICAgICAgICxFTUEzMCA9IGlmX2Vsc2UoRU1BKENsb3NlLCBuID0gMzApID4gQ2xvc2UsICdBYm92ZScsICdCZWxvdycpDQogICAgICAgICAgICxFTUE1MCA9IGlmX2Vsc2UoRU1BKENsb3NlLCBuID0gNTApID4gQ2xvc2UsICdBYm92ZScsICdCZWxvdycpDQogICAgICAgICAgICxFTUExMDAgPSBpZl9lbHNlKEVNQShDbG9zZSwgbiA9IDEwMCkgPiBDbG9zZSwgJ0Fib3ZlJywgJ0JlbG93JykNCiAgICAgICAgICAgLEVNQTIwMCA9IGlmX2Vsc2UoRU1BKENsb3NlLCBuID0gMjAwKSA+IENsb3NlLCAnQWJvdmUnLCAnQmVsb3cnKQ0KICAgICAgICAgICAsQURYID0gQURYKHNlbGVjdCh0ZW1wLCBIaWdoOkNsb3NlKSwgbiA9IDE0LCBtYVR5cGUgPSAnRU1BJylbLDRdIA0KICAgICAgICAgICAsTUFDRCA9IE1BQ0QoQ2xvc2UsIG5GYXN0ID0gMTIsIG5TbG93ID0gMjYsIG5TaWcgPSA5LCBtYVR5cGUgPSAnRU1BJykgDQogICAgICAgICAgICxNQUNEX0hpc3RvZ3JhbSA9IE1BQ0RbLDFdIC0gTUFDRFssMl0NCiAgICAgICAgICAgLE9CViA9IE9CVihDbG9zZSwgVm9sdW1lKSANCiAgICAgICAgICAgLFJTSSA9IFJTSShDbG9zZSwgbiA9IDE0LCBtYVR5cGUgPSAnRU1BJykgDQogICAgICAgICAgICxTdG9jaCA9IHN0b2NoKHNlbGVjdCh0ZW1wLCBIaWdoOkNsb3NlKSwgbkZhc3RLID0gMTQsIG5GYXN0RCA9IDMsIG5TbG93RCA9IDMsIG1hVHlwZSA9ICdFTUEnKSANCiAgICAgICAgICAgLFN0b2NoX0sgPSBTdG9jaFssMV0NCiAgICAgICAgICAgLFN0b2NoX0QgPSBTdG9jaFssM10NCiAgICAgICAgICAgLENsb3NlX1ZvbGF0aWxpdHkgPSB2b2xhdGlsaXR5KHNlbGVjdCh0ZW1wLCBPcGVuOkNsb3NlKSwgbiA9IDE0LCBjYWxjID0gJ2Nsb3NlJywgTiA9IDI1MikgDQogICAgICAgICAgICxQYXJraW5zb25fVm9sYXRpbGl0eSA9IHZvbGF0aWxpdHkoc2VsZWN0KHRlbXAsIE9wZW46Q2xvc2UpLCBuID0gMTQsIGNhbGMgPSAncGFya2luc29uJywgTiA9IDI1MikgDQogICAgKSAlPiUNCiAgICBzZWxlY3QoLShPcGVuOlJldHVybnMpLCAtTUFDRCwgLVN0b2NoKSAlPiUNCiAgICBtdXRhdGUoYWNyb3NzKC5jb2xzID0gMzoxNiwgLmZucyA9IGZ1bmN0aW9uKHgpIGxhZyh4LCAxKSkpICU+JQ0KICAgIG5hLm9taXQoKQ0KICANCiAgZGF0YXNldFtbaV1dIDwtIHRlbXANCiAgDQp9DQpgYGANCg0KTm93IHdlIHNwbGl0IGRhdGFzZXQgaW50byB0cmFuaW5nIHNldCAoNzUlKSBhbmQgdGVzdCBzZXQgKDI1JSkuIEluIGZ1cnRoZXIgYmxvZyBwb3N0cyB3ZSB3aWxsIHRyeSBkaWZmZXJlbnQgY2xhc3NpZmljYXRpb24gYWxnb3JpdGhtcyBpbiBvcmRlciB0byBwcmVkaWN0IGluY3JlYXNlIG9yIGRlY3JlYXNlIG9mIHRoZSBjbG9zZSBwcmljZXMgdXNpbmcgdGVjaG5pY2FsIGluZGljYXRvcnMuIE91ciBnb2FsIHdpbGwgYmUgc2VsZWN0aW9uIG9mIHRoZSBtb3N0IGluZm9ybWF0aXZlIHRlY2huaWNhbCBpbmRpY2F0b3JzIGZvciBtYWtpbmcgaW52ZXN0bWVudCBkZWNpc2lvbnMuDQpgYGB7ciBkYXRhX3NwbGl0fQ0Kc2V0LnNlZWQoMjEzNykNCnRyYWluX3NldCA8LSBsYXBwbHkoZGF0YXNldCwgZnVuY3Rpb24oeCkgc2FtcGxlX2ZyYWMoeCwgMC43NSkpDQp0cmFpbl9zZXQgPC0gbGFwcGx5KHRyYWluX3NldCwgZnVuY3Rpb24oeCkgYXJyYW5nZSh4LCBEYXRlKSkNCnRlc3Rfc2V0IDwtIG1hcChzZXFfYWxvbmcoZGF0YXNldCksIH5hbnRpX2pvaW4oZGF0YXNldFtbLnhdXSwgdHJhaW5fc2V0W1sueF1dLCBieSA9ICdEYXRlJykpDQpuYW1lcyh0ZXN0X3NldCkgPC0gbmFtZXModHJhaW5fc2V0KQ0KdHJhaW5fc2V0IDwtIGxhcHBseSh0cmFpbl9zZXQsIGZ1bmN0aW9uKHgpIHNlbGVjdCh4LCAtRGF0ZSkpDQp0ZXN0X3NldCA8LSBsYXBwbHkodGVzdF9zZXQsIGZ1bmN0aW9uKHgpIHNlbGVjdCh4LCAtRGF0ZSkpDQpkcHV0KHRyYWluX3NldCwgJ3RlY2huaWNhbF9pbmRpY2F0b3JzX3RyYWluX3NldC50eHQnKQ0KZHB1dCh0ZXN0X3NldCwgJ3RlY2huaWNhbF9pbmRpY2F0b3JzX3Rlc3Rfc2V0LnR4dCcpDQpgYGANCg0K