0. 시작하기 전에저는 딱히 전공자도 아니고 석사 출신도 아니며 그저 경제학 학부졸업생 나부랭이로 이하 본문에는 오류가 포함되어있을 확률이 높으며, 해당 오류에 대한 지적은 감사합니다.
아래 내용은 모두 R을 통하여 작성되었습니다.
1. 효율적 투자기회선이란?
- 정해진 위험수준 하에서 가장 높은 수익률을 달성하는 시장 포트폴리오를 '효율적 포트폴리오'라고 하며, 그 효율적 포트폴리오들을 위험-수익 좌표공간에 나타낸 것이 효율적 투자기회선입니다. 그리고 무위험수익률로부터 효율적 투자기회선 상의 단 한 점만 지나가도록 선을 그을 수가 있는데, 해당 선을 자본시장선이라고 하며 개인은 자신의 무차별곡선과 자본시장선 상의 접점을 보유함으로써 효용을 극대화할 수 있습니다.
<그림 1> 투자기회선은 대충 저렇게 생겼다.
2. 사전 정보수집
효율적 투자기회선을 그리려면 우선 포트폴리오를 구해야합니다. 코스피 시장을 기준으로 상위 5개 사(전체 상장사를 처리하려면 시간이 오래걸리니까)를 뽑아 포트폴리오를 구성해봅시다.
library(quantmod)
library(plotly)
library(PerformanceAnalytics)
library(timetk)
library(tidyverse)
위 다섯 개의 라이브러리를 우선 로딩해줍니다. 네이버 파이낸스로 가서 확인해보니 한국의 코스피 시가총액 기준 상위 5개 사는 "삼성전자, SK하이닉스, NAVER, 삼성바이오로직스, LG화학"이군요. 해당 5개 사의 ticker를 통해 주가 데이터를 받아봅시다. 한국거래소에서 직접 받아도 되지만 코드가 20배는 길어지므로 야후 파이낸스 자료를 사용하겠습니다.
ticker = c('005930.KS', '000660.KS', '035420.KS', '207940.KS', '051910.KS')
price_data = getSymbols(ticker, from = '2017-01-01', to = '2020-10-31')
prices = do.call(cbind,
lapply(ticker, function(x) Cl(get(x))))
rets = Return.calculate(prices, method = 'log') %>%
na.omit()
각 사의 ticker를 통해 주가데이터를 받아오고, prices에는 종가(Cl)만 남긴 후 rets에 각 날짜 종가 기준 로그수익률을 저장합니다. 이제 자료가 준비되었으니 다음 단계인 포트폴리오 구성으로 가봅시다.
3. 효율적 투자기회선 그리기
num_port = 500
all_wts = matrix(nrow = num_port, ncol = length(ticker))
port_returns = vector('numeric', length = num_port)
port_risk = vector('numeric', length = num_port)
port_sr = vector('numeric', length = num_port)
500번의 무작위 시행으로 우선 시도해보겠습니다. all_wts에는 5개 사의 비중을 담을 것이며, port_returns와 port_risk는 각 비중 별 수익과 위험, port_sr에는 샤프 비율을 담을 겁니다.
샤프 비율이란? 수익률/표준편차로 구하며, 위험 대비 수익률이 얼마나 좋았는가를 측정하는 지표입니다.
for (i in seq_along(port_returns)) {
wts = runif(n = length(ticker))
wts = wts/sum(wts)
all_wts[i,] = wts
port = Return.portfolio(R = rets, weights = wts, verbose = TRUE)
a = StdDev.annualized(port$returns)[1]
b = SharpeRatio.annualized(port$returns, Rf = 0.0155/360)[1]
c = a*b
port_returns[i] = c
port_risk[i] = a
port_sr[i] = b
}
각 5개 사에 대한 비중을 무작위로 생성하여 wts에 담고, 각 비중으로부터 수익률을 구하여 port에 담고, 그 후 연간표준편차, 연간수익률, 연간 샤프비율을 구하는 일련의 과정입니다. SharpeRatio.annualized 함수의 Rf에는 대충 국채수익률이 10월 말 기준 1.55%길래 넣어보았습니다. 0으로 진행하셔도 무방합니다.
즉 무작위로 생성된 비중의 포트폴리오가 각각 얼마만큼의 위험과 표준편차를 보유하고 있는지 계산하는 작업입니다. 이 작업을 500번 수행하여 결과를 한 번 보도록 하죠.
all_wts = tk_tbl(all_wts)
colnames(all_wts) = colnames(rets)
pf_val = tibble(ret = port_returns, risk = port_risk, sr = port_sr)
pf_val = tk_tbl(cbind(all_wts, pf_val))
all_wts를 작업하기 쉽도록 matrix에서 tibble로 변환하고, pf_val에 모든 정보를 담습니다. pf_val에 담기는 정보는 all_wts의 비중, 그리고 해당 비중으로부터 산출된 위험과 수익이 담겨있습니다. 잠시 어떤 정보가 담겨있나 확인해봅시다.
head(pf_val)
# A tibble: 6 x 8
X005930.KS.Close X000660.KS.Close X035420.KS.Close X207940.KS.Close X051910.KS.Close ret risk sr
<dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 0.312 0.0519 0.216 0.167 0.253 0.138 0.242 0.569
2 0.286 0.272 0.212 0.185 0.0454 0.128 0.248 0.515
3 0.0340 0.152 0.148 0.266 0.400 0.171 0.280 0.609
4 0.0216 0.256 0.554 0.0833 0.0844 0.109 0.248 0.440
5 0.00254 0.242 0.280 0.286 0.190 0.164 0.273 0.599
6 0.0730 0.366 0.0101 0.214 0.337 0.152 0.273 0.557
1행에 적힌 1번 포트폴리오의 경우 삼성전자 31.2%, SK하이닉스 5.19%, NAVER 21.6%, 삼성바이오로직스 16.7%, LG화학 13.8%을 보유한 포트폴리오이고, 연간수익률은 13.8%, 연간표준편차는 24.2%, 샤프비율은 56.9%네요.
min_var = pf_val[which.min(pf_val$risk),]
max_sr = pf_val[which.max(pf_val$sr),]
min_var에는 최소분산포트폴리오를, max_sr에는 최대샤프비율포트폴리오를 담았습니다. 최소분산포트폴리오의 경우 시장포트폴리오 중 가장 적은 위험을 보유한 포트폴리오이며, 최대샤프비율포트폴리오의 경우 Tangency Portfolio로 Y-절편에서 해당 점까지 이어서 효율적투자기회선을 그릴 포트폴리오입니다.
p = ggplot(aes(x = risk, y = ret, color = sr), data = pf_val) +
geom_point() + theme_classic() +
scale_y_continuous(labels = scales::percent) +
scale_x_continuous(labels = scales::percent) +
labs(x = 'risk', y = 'return') +
geom_point(aes(x = risk, y = ret, color = sr), data = min_var, color = 'red') +
geom_point(aes(x = risk, y = ret, color = sr), data = max_sr, color = 'red') +
annotate('text', x = 0.33, y = 0.22, label = "Tangency Portfolio") +
annotate('text', x = 0.24, y = 0.11, label = "Minimum variance portfolio")
ggplot 툴을 사용해 이제 포트폴리오를 그래프로 나타내봅시다. 위에 설명드린 pf_val 안의 risk와 ret을 좌표로 나타낸 모습은 다음과 같습니다.
<그림 2> 500개 무작위 포트폴리오
Minimum 부분이 살짝 잘렸네요. 어쨌건 500개의 무작위 포트폴리오를 좌표평면에 나타낸 모습은 위와 같습니다! 이제 가장자리에 있는 녀석들만 모아서 선으로 이으면 효율적 투자기회선을 완성할 수 있을 것 같네요.
foo = pf_val
foo$quantile = ntile(foo$risk, 100)
d = aggregate(ret ~ quantile, data = foo, max)
pf_line2 = pf_val[which((pf_val$ret %in% d$ret)),]
pf_val을 foo에 담고(그냥 pf_val에 진행하셔도 무방합니다만, 어쩌다보니 저렇게 되었네요.) pf_val의 risk를 100개의 블록으로 나누어 각 블록에서 가장 높은 수익률을 기록한 포트폴리오만 남깁시다. 해당 자료를 pf_line2에 담고 그리면 다음과 같은 그림이 나옵니다.
p2 = ggplot(aes(x = risk, y = ret, color = sr), data = pf_line2) +
geom_point() + theme_classic() +
scale_y_continuous(labels = scales::percent) +
scale_x_continuous(labels = scales::percent) +
labs(x = 'risk', y = 'return')
ggplotly(p2)
<그림 3> 효율적 투자기회선 밑작업
이제 해당 점들을 이어서 효율적 투자기회선을 그려봅시다.
p2 = ggplot(aes(x = risk, y = ret, color = sr), data = pf_line2) +
geom_point() + theme_classic() +
scale_y_continuous(labels = scales::percent) +
scale_x_continuous(labels = scales::percent) +
labs(x = 'risk', y = 'return') +
geom_smooth(method = 'lm', formula = y ~ log(x), alpha = 0) +
geom_smooth(method = 'loess', col = 'red', level = 0, alpha = 0)
ggplotly(p2)
아래 geom_smooth 항목이 두 개가 추가되었는데, 하나는 해당 데이터를 로그스케일 선형회귀를 사용해서 그려본 값이고, 하나는 loess 방식을 사용해서 그려본 값입니다.
<그림 4> 효율적 투자기회선
파란색이 로그 리그레션, 빨간색이 loess 리그레션인데 빨간색 선이 보기에는 더 예쁘지만 수식으로 나타낼 수 없다는 단점을 가지고 있습니다. 본 효율적 투자기회선을 수식으로 표현하고 싶다면 로그 리그레션을 사용하시면 되겠네요.
뭔가 느낌상으로는 envelope curve를 사용해야할 것 같지만, 해당 내용을 자세히 모르므로 일단 이 정도에서 넘어가겠습니다.
4. 그래서 뭐?
자, 이제 효율적 투자기회선을 그려보았습니다. 그렇다면이 투자기회선으로 무엇을 할 수 있을까요? 아까 위에서 이 투자기회선으로부터 투자자의 최적자산배분을 도출할 수 있다고 말씀드렸습니다. 이제부터 최적자산배분을 한 번 계산해보겠습니다.
우선 필요한 정보는 다음과 같습니다.
무위험자산수익률 : .0155 (위에서 언급했습니다.)
Tangency Portfolio의 {위험, 수익률} = {.3341, .2146}
위 자료를 토대로 계산하면 자본시장선의 경우 Y-절편 .0155, 기울기 .5959를 가진 직선이 됩니다.
y = .5959 * x + .0155 ------ 식 1
일반적인 무차별곡선의 형태인 위험회피성향의 투자자를 가정하여 무차별곡선을 다음과 같이 표현하겠습니다.
y = U + 2 * x^2 (2는 임의의 위험회피계수) ------ 식 2
무차별곡선이 자본시장선과 접하는 점은 미분을 통해 구할 수 있죠? 식 1과 식 2를 미분하여 x와 y값을 구하면 {x, y} = {.148975, .1063747}이며, 해당 수치를 식 2에 대입하면 U = .061876을 도출할 수 있습니다.
이제 무차별곡선 그래프를 그려보겠습니다.
indiff_curve = function(risk, U0, A) {
ret = U0 + A*risk^2
return (ret)
}
risk_grid = 0:40/100
util_level = 0.061876
risk_aversion = 2
return = indiff_curve(risk_grid, U0=util_level, A=risk_aversion)
idc = data.frame(sd = risk_grid, r = return)
0부터 40%까지의 x값에 대해 y값을 도출한 뒤, 두 값을 하나의 프레임 안에 묶는 작업입니다. indiff_curve 함수를 정의한 후 risk_grid를 0~40%까지 함수 안에 넣어서 나온 y값을 idc 안에 묶었습니다.
이제 마지막으로 그래프를 그려보겠습니다.
p3 = ggplot(aes(x = risk, y = ret), data = pf_val) +
geom_point(color = 'lightblue') + theme_classic() +
scale_y_continuous(labels = scales::percent, limits = c(0, .4)) +
scale_x_continuous(labels = scales::percent, limits = c(0, .4)) +
labs(x = 'risk', y = 'return') +
geom_point(aes(x = risk, y = ret, color = sr), data = min_var, color = 'red') +
geom_point(aes(x = risk, y = ret, color = sr), data = max_sr, color = 'red') +
annotate('text', x = 0.33, y = 0.22, label = "Tangency Portfolio") +
annotate('text', x = 0.22, y = 0.12, label = "Minimum variance portfolio") +
geom_line(aes(x = sd, y = r), data = idc) +
geom_abline(intercept = 0.0155, slope = .5959, col = 'red') +
geom_point(aes(x = .148975, y = .1063747), color = 'red') +
annotate('text', x = .14, y = .10, label = 'Choice')
자본시장선은 절편에서부터 시작하니까 scale_y와 scale_x를 0에서 40%까지로 설정했습니다. geom_abline을 통해 자본시장선을 그리고 geom_line을 통해 바로 위에서 계산한 무차별곡선을 그리며, geom_point로 그 위에서 계산한 접점을 나타냈습니다. 완성된 그래프는 다음과 같습니다.
<그림 5> 자본시장선 및 투자자의 최적자산배분
Tangency Portfolio의 구성비율은 다음과 같습니다.
> max_sr
# A tibble: 1 x 8
X005930.KS.Close X000660.KS.Close X035420.KS.Close X207940.KS.Close X051910.KS.Close ret risk sr
<dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 0.0279 0.106 0.0584 0.491 0.317 0.215 0.334 0.642
삼성전자 2.79%, SK하이닉스 10.6%, NAVER 5.84%, 삼성바이오로직스 49.1%, LG화학 31.7%를 들고 있군요! 삼바로와 LG 비중이 높은 건 제가 기간을 특수하게 잡아서 그런 것이 아닐까 싶네요.
두 점 사이의 거리를 계산하는 공식에 의하면 대충 Y절편에서 Choice까지의 거리와 Tangency까지의 거리는 0.47:1 정도가 되는군요!
5. 결론
즉 2017년 1월부터 2020년 10월까지의 최적 자산배분은
국채를 47%, 삼성전자를 1.5%, SK하이닉스를 5.6%, NAVER를 3.1%, 삼성바이오로직스를 26.0%, LG화학을 16.8%을 보유하는 것이 되는군요!
물론 딱히 현실적인 이야기는 아닙니다.
6. 마치며
교육을 받으면서 어떠한 과정이건 수많은 이론을 마주치게 되는데, 과연 그런 이론을 직접 검증해볼 수 있을까? 하는 의문이 들어 구글링을 통하 짜깁기해가며 누덕누덕 기워서 적은 내용입니다.
저는 컴공 교육을 들은 적도 없고, 경영학이나 통계학 전공 출신도 아니라 본문 중 틀린 내용이 존재할 확률이 높으니 해당 내용 지적해주시면 감사히 배우겠습니다.
사실 글은 길게 적었지만 딱히 현실에 도움 될 내용도 없고, 놀라운 통찰이 담겨있는 것도 아니라 단순 소일거리로 봐주시면 될 것 같습니다.
담원 우승 축하합니다.