Post List

2017년 8월 3일 목요일

US Sector Rotation Using SPDR ETF (+ R Code)



미국에 상장된 SPDR의 섹터 ETF 9종을 이용한
섹터 로테이션 전략을 테스트합니다.


먼저, 사용된 9개 ETF는 다음과 같습니다.

XLY: Consumer Discretionary SPDR
XLP: Consumer Staples Select Sect. SPDR
XLF: Financial Select Sector SPDR
XLE: Energy Select Sector SPDR
XLV: Health Care SPDR
XLI: The Industrial Select Sector SPDR
XLB: Materials Select Sector SPDR
XLK: Technology Select Sector SPDR
XLU: Utilities SPDR 


Cash 대용 ETF는 다음과 같습니다.

SHY: iShares Barclays 1-3 Year Treasry Bnd


9개 섹터 ETF의 누적수익률은 다음과 같습니다.



다음은 섹터 모멘텀 전략의 백테스트 입니다.

먼저 9개 섹터 중 12개월 누적 수익률 상위 5개 섹터를 선택한 후,
이 중 누적 수익률이 (+)인 섹터만 선택합니다.

선택된 섹터에 각 20%씩 비중을 부여하며,
주식 비중이 남을 경우 단기채권 혹은 현금 ETF를 매입합니다.

매매에 따른 수수료는 매수/매도 시 30bp로 가정합니다.

아래는 해당 로직을 적용한 백테스트 포트폴리오와,
미국 S&P 500을 추종하는 SPY ETF 수익률의 비교입니다.




Portfolio
SPY
Ann Ret (Arith)
6.58%
6.75%
Ann Ret (CAGR)
5.77%
4.95%
Ann Std Dev
13.90%
19.56%
Ann Sharpe
0.4154
0.2532
Win Ratio
0.5346
0.5391
MDD
27.34%
55.19%


전체적으로 수익률은 비슷하지만
단순 지수의 Buy & Hold 대비

훨씬 낮은 변동성과 MDD를 보입니다.


다음은 매 월별 투자비중과 Cash의 투자비중입니다.

2002년과 2008년 Market Crash 시절,
대부분의 금액이 Cash에 투자되어 추가적인 하락을 방어합니다.




다음은 매 월별 턴오버 입니다.


위 테스트의 코드는 다음과 같습니다.




ipak = function(pkg){
  new.pkg = pkg[!(pkg %in% installed.packages()[, "Package"])]
  if (length(new.pkg)) 
    install.packages(new.pkg, dependencies = TRUE)
  sapply(pkg, require, character.only = TRUE)
}

stat_summary = function(x) {
  x = as.matrix(x)
  stat_table = round(rbind(Return.annualized(x, geometric = FALSE), table.AnnualizedReturns(x), UpsideFrequency(x), maxDrawdown(x)), 4)
  rownames(stat_table) = c("Ann Ret (Arith)", "Ann Ret (CAGR)", "Ann Std Dev", "Ann Sharpe", "Win Ratio", "MDD")
  return(stat_table)
}

packages = c("PerformanceAnalytics", "quantmod", "PortfolioAnalytics")
ipak(packages)

symbols = c("XLY", "XLP", "XLF", "XLE", "XLV", "XLI", "XLB", "XLK", "XLU", "SHY")
symbols_bm = c("SPY")

getSymbols(c(symbols,symbols_bm), src="yahoo", from = "1998-12-31")

##################################################
### XLY: Consumer Discretionary SPDR #############
### XLP: Consumer Staples Select Sect. SPDR ######
### XLF: Financial Select Sector SPDR ############
### XLE: Energy Select Sector SPDR ###############
### XLV: Health Care SPDR ########################
### XLI: The Industrial Select Sector SPDR #######
### XLB: Materials Select Sector SPDR ############
### XLK: Technology Select Sector SPDR ###########
### XLU: Utilities SPDR ##########################

### SHY: iShares Barclays 1-3 Year Treasry Bnd ###

prices = list()
for(i in 1:length(symbols)) {
  prices[[i]] = Ad(get(symbols[[i]]))
}
prices = do.call(cbind, prices)

bm = Ad(get(symbols_bm))

rets = na.omit(Return.calculate(prices[, -ncol(prices)]))
ret_cash =na.omit(Return.calculate(prices[, ncol(prices)]))
ret_bm = na.omit(Return.calculate(bm))

num = 5 
lookback = 12
fee = 0.0030

wts = list()
ep = endpoints(rets, on = "months") 

for(i in (lookback+1) : (length(ep)) ) {
  
  sub_cum = Return.cumulative( rets[c(ep[i-12] : ep[i]) , ])
  
  K = which(rank(- sub_cum) <= num & sub_cum >= 0)
  wt = rep(0, ncol(rets))
  wt[K] = 1 / num
  names(wt) = colnames(rets)
  wt = xts(t(wt), order.by = index(rets[ep[i]]))
  wts[[i]] = wt
}

wts = round(do.call(rbind, wts), 3)
wts$zeros = 1 - rowSums(wts)
rets$zeros = ret_cash

Sector = Return.portfolio(R = rets, weights = wts, verbose = TRUE)
Sector_ret = Sector$return
Sector_Turnover = xts(rowSums(abs(Sector$BOP.Weight - lag(Sector$EOP.Weight)), na.rm = TRUE),
                      order.by = index(Sector$BOP.Weight))
Sector_ret_net = round(Sector_ret - Sector_Turnover * fee, 4)

compare = na.omit(cbind(Sector_ret_net,ret_bm))
charts.PerformanceSummary(compare, main = "")
stat_summary(compare)
chart.StackedBar(wts, col = rainbow10equal)
apply.yearly(compare, Return.cumulative)

댓글 2개:

  1. 안녕하십니까? 요즘은 금융관련 R을 공부하던 중인데 이런 code를 공개해 주심에 무한 감사드립니다. 지금은 코드만 봐서는 전부 이해할 수는 없지만, 열심히 공부해서 저도 이렇게 나눌 수 있는 사람이 되겠습니다.

    답글삭제
    답글
    1. 이정도 코드는 구글에 몇일만 검색하면 나오는거라 ㅎㅎ

      삭제