This document will create a portfolio using the Risk-Parity strategy (equal variance) and compare the performance with Equal Weight portfolio and Tangency Portfolio.
Reference:https://cran.r-project.org/web/packages/riskParityPortfolio/vignettes/RiskParityPortfolio.html
#Import Library
library(tidyquant)
## 필요한 패키지를 로딩중입니다: lubridate
##
## 다음의 패키지를 부착합니다: 'lubridate'
## The following objects are masked from 'package:base':
##
## date, intersect, setdiff, union
## 필요한 패키지를 로딩중입니다: PerformanceAnalytics
## 필요한 패키지를 로딩중입니다: xts
## 필요한 패키지를 로딩중입니다: zoo
##
## 다음의 패키지를 부착합니다: 'zoo'
## The following objects are masked from 'package:base':
##
## as.Date, as.Date.numeric
##
## 다음의 패키지를 부착합니다: 'PerformanceAnalytics'
## The following object is masked from 'package:graphics':
##
## legend
## 필요한 패키지를 로딩중입니다: quantmod
## 필요한 패키지를 로딩중입니다: TTR
## Registered S3 method overwritten by 'quantmod':
## method from
## as.zoo.data.frame zoo
library(riskParityPortfolio)
library(ggplot2)
library(portfolioBacktest)
library(PerformanceAnalytics)
library(PortfolioAnalytics)
## 필요한 패키지를 로딩중입니다: foreach
library(timetk)
library(tidyverse)
## -- Attaching core tidyverse packages ------------------------ tidyverse 2.0.0 --
## v dplyr 1.0.8 v stringr 1.5.0
## v forcats 1.0.0 v tibble 3.1.6
## v purrr 0.3.4 v tidyr 1.2.0
## v readr 2.1.4
## -- Conflicts ------------------------------------------ tidyverse_conflicts() --
## x purrr::accumulate() masks foreach::accumulate()
## x dplyr::filter() masks stats::filter()
## x dplyr::first() masks xts::first()
## x dplyr::lag() masks stats::lag()
## x dplyr::last() masks xts::last()
## x purrr::when() masks foreach::when()
## i Use the [conflicted package](http://conflicted.r-lib.org/) to force all conflicts to become errors
#Import stock prices using tidyquant
symbols <- c("NVDA", "AAPL", "MSFT", "GOOG", "SCHD")
start_date <- "2015-01-01"
end_date <- Sys.Date()
prices <- tq_get(symbols, get = "stock.prices", from = start_date, to = end_date)
# Calculate daily returns
returns <- prices %>%
group_by(symbol) %>%
tq_transmute(select = adjusted,
mutate_fun = periodReturn,
period = "daily",
type = "log",
col_rename = 'returns')
returns %>%
slice(1:2)
## # A tibble: 10 x 3
## # Groups: symbol [5]
## symbol date returns
## <chr> <date> <dbl>
## 1 AAPL 2015-01-02 0
## 2 AAPL 2015-01-05 -0.0286
## 3 GOOG 2015-01-02 0
## 4 GOOG 2015-01-05 -0.0211
## 5 MSFT 2015-01-02 0
## 6 MSFT 2015-01-05 -0.00924
## 7 NVDA 2015-01-02 0
## 8 NVDA 2015-01-05 -0.0170
## 9 SCHD 2015-01-02 0
## 10 SCHD 2015-01-05 -0.0142
# Calculate covariance matrix
cov_mat <- cov(returns %>%
spread(symbol, returns) %>%
select(-date))
cov_mat
## AAPL GOOG MSFT NVDA SCHD
## AAPL 0.0003535784 0.0002164187 0.0002368233 0.0003343175 0.0001301511
## GOOG 0.0002164187 0.0003251943 0.0002418244 0.0003123124 0.0001204959
## MSFT 0.0002368233 0.0002418244 0.0003160965 0.0003423645 0.0001334459
## NVDA 0.0003343175 0.0003123124 0.0003423645 0.0009210148 0.0001741355
## SCHD 0.0001301511 0.0001204959 0.0001334459 0.0001741355 0.0001228573
# Calculate risk-parity portfolio weights
weights_rp <- riskParityPortfolio(cov_mat)$w
#Risk Parity Allocation
V_rp <- barplotPortfolioRisk(weights_rp,cov_mat,color='cornflowerblue')+
ggtitle('Risk Parity Portfolio')+
scale_y_continuous(labels = scales::percent)
V_rp
# Calculate maximum Sharpe ratio portfolio weights
# Convert the data into xts to use PerformanceAnlytics package
returns2 <- returns %>%
select(symbol,date,returns) %>%
pivot_wider(names_from = symbol, values_from = returns) %>%
tk_xts(silent = TRUE) * 100
head(returns2)
## NVDA AAPL MSFT GOOG SCHD
## 2015-01-02 0.0000000 0.000000000 0.0000000 0.0000000 0.0000000
## 2015-01-05 -1.7034170 -2.857586856 -0.9238415 -2.1065926 -1.4166607
## 2015-01-06 -3.0788025 0.009403233 -1.4785791 -2.3449898 -0.6133327
## 2015-01-07 -0.2608865 1.392457163 1.2625065 -0.1714703 1.1722467
## 2015-01-08 3.6927330 3.770290577 2.8993596 0.3148075 1.8326230
## 2015-01-09 0.4020390 0.107193993 -0.8440510 -1.3035143 -0.7741233
#Add constraints and objective for tangency portfolio
portf <- portfolio.spec(colnames(returns2))
portf <- add.constraint(portf, type="weight_sum", min_sum=1, max_sum=1)
portf <- add.constraint(portf, type="long_only")
portf <- add.constraint(portf, type="box", min=.10,max=0.9)
portf <- add.objective(portf, type="return", name="mean")
portf <- add.objective(portf, type="risk", name="StdDev")
portf_max_sharpe <- optimize.portfolio(returns2, portf,
optimize_method = 'ROI',
trace=TRUE,maxSR=TRUE
)
## Registered S3 method overwritten by 'ROI':
## method from
## print.constraint PortfolioAnalytics
#Tangency Allocation
V_sp <- barplotPortfolioRisk(portf_max_sharpe$weights,cov_mat,col = "#A29BFE")+
ggtitle('Tangency Portfolio')+
scale_y_continuous(labels = scales::percent)
V_sp
#Equally weighted portfolio
EWP = rep(1/nrow(cov_mat),nrow(cov_mat))
V_ewp <- barplotPortfolioRisk(EWP,cov_mat,col = "lightgreen")+
ggtitle('Equally Weighted Portfolio')+
scale_y_continuous(labels = scales::percent)+
scale_x_discrete(labels = symbols)
V_ewp
#Aggregate and Visualize tghe weights and risk contribution
w_all <- cbind("RPP" = weights_rp,
"Tangency" = portf_max_sharpe$weights,
"EWP" = EWP)
barplotPortfolioRisk(w_all, cov_mat,colors = c('cornflowerblue',"#A29BFE","lightgreen")) +
scale_y_continuous(labels = scales::percent)
#Create variables to use PerformanceAnalytics Package
# Calculate daily returns
#Weights of each portfolio
w_rp <- c(weights_rp)
w_ewp <- c(EWP)
w_tan <- c(portf_max_sharpe$weights)
#Create Return columns and convert to XTS
rp_return <- prices %>%
group_by(symbol) %>%
tq_transmute(select = adjusted,
mutate_fun = periodReturn,
period = "monthly",
type = "log",
col_rename = 'returns') %>%
tq_portfolio(assets_col = symbol,
returns_col = returns,
weights = w_rp,
col_rename = 'returns',
geometric = FALSE)
## Warning in check_weights(weights, assets_col, map, x): Sum of weights must be 1.
rp_xts_return <- rp_return %>%
tk_xts(silent = TRUE)
ewp_return <- prices %>%
group_by(symbol) %>%
tq_transmute(select = adjusted,
mutate_fun = periodReturn,
period = "monthly",
type = "log",
col_rename = 'returns') %>%
tq_portfolio(assets_col = symbol,
returns_col = returns,
weights = w_ewp,
col_rename = 'returns',
geometric = FALSE)
ewp_xts_return <- ewp_return %>%
tk_xts(silent = TRUE)
tan_return <- prices %>%
group_by(symbol) %>%
tq_transmute(select = adjusted,
mutate_fun = periodReturn,
period = "monthly",
type = "log",
col_rename = 'returns') %>%
tq_portfolio(assets_col = symbol,
returns_col = returns,
weights = w_tan,
col_rename = 'returns',
geometric = FALSE)
tan_xts_return <- tan_return %>%
tk_xts(silent = TRUE)
#Aggregate and change column names
p.returns <- merge(rp_xts_return,ewp_xts_return,tan_xts_return)
names(p.returns) <- c('Risk Parity Portfolio','Equal Weight Portfolio','Tangency Portfolio')
head(p.returns)
## Risk Parity Portfolio Equal Weight Portfolio Tangency Portfolio
## 2015-01-30 -0.029883037 -0.027320634 -0.0454177586
## 2015-02-27 0.082468654 0.084490528 0.1091957018
## 2015-03-31 -0.039464993 -0.040506046 -0.0484254403
## 2015-04-30 0.045112605 0.046912228 0.0627714569
## 2015-05-29 0.003518476 0.002048018 -0.0008909818
## 2015-06-30 -0.049189419 -0.049788585 -0.0697897283
#Visualize the performance
charts.PerformanceSummary(p.returns, colorset=rich6equal,
lwd=3, cex.legend = 1, event.labels = TRUE,main='Portfolio Performance')
#Create Variables to visualize using ggplot2
rp_return$symbol <- 'RP'
ewp_return$symbol <- 'EWP'
tan_return$symbol <- 'TAN'
#Calculate Cumulative Return
rp_return<- rp_return %>%
mutate(cr=cumprod(1+returns))
ewp_return<- ewp_return %>%
mutate(cr=cumprod(1+returns))
tan_return<- tan_return %>%
mutate(cr=cumprod(1+returns))
#Merge data
p_return_df <- rbind(rp_return,ewp_return,tan_return) %>%
group_by(symbol)
p_return_df$symbol <- factor(p_return_df$symbol, levels = c("RP", "TAN", "EWP")) #To order it
p_return_df %>%
slice(1:3)
## # A tibble: 9 x 4
## # Groups: symbol [3]
## date returns symbol cr
## <date> <dbl> <fct> <dbl>
## 1 2015-01-30 -0.0299 RP 0.970
## 2 2015-02-27 0.0825 RP 1.05
## 3 2015-03-31 -0.0395 RP 1.01
## 4 2015-01-30 -0.0454 TAN 0.955
## 5 2015-02-27 0.109 TAN 1.06
## 6 2015-03-31 -0.0484 TAN 1.01
## 7 2015-01-30 -0.0273 EWP 0.973
## 8 2015-02-27 0.0845 EWP 1.05
## 9 2015-03-31 -0.0405 EWP 1.01
# Create the bar plot
ggplot(p_return_df, aes(x = date, y = returns,fill=symbol)) +
geom_bar(stat = "identity",width=20,color='black') +
scale_fill_manual(values = c("RP" = "cornflowerblue",
"TAN" = "#A29BFE",
"EWP" = "lightgreen")) +
labs(x = "Date", y = "Returns",fill='Portfolio')+
scale_y_continuous(labels = scales::percent,
breaks = seq(-0.5,0.75,0.1)) +
facet_wrap(~symbol,ncol=1) +
ggtitle('Portfolio Monthly Returns') +
theme(legend.position = "top")
## Warning: `position_stack()` requires non-overlapping x intervals
## `position_stack()` requires non-overlapping x intervals
## `position_stack()` requires non-overlapping x intervals
#Box Plot
p_return_df %>%
ggplot(aes(symbol, returns,fill=symbol)) +
geom_boxplot(width=0.2) +
scale_fill_manual(values = c("RP" = "cornflowerblue",
"TAN" = "#A29BFE",
"EWP" = "lightgreen")) +
labs(x="Portfolio", y="Monthly Returns",fill='Portfolio') +
scale_y_continuous(labels = scales::percent) +
theme(legend.position = "top") +
ggtitle('Portoflio Monthly Return Boxplot')
#Mean Return
p_return_df%>%
mutate(year=year(date)) %>%
group_by(symbol,year) %>%
summarise(mean = mean(returns),
sd = sd(returns)) %>%
ggplot(aes(x = year, y = mean, fill = symbol)) +
geom_bar(stat = "identity", position = "dodge", width = 0.7,color="black") +
scale_y_continuous(breaks = seq(-0.2,0.2,0.02),
labels = scales::percent) +
scale_x_continuous(breaks = seq(2015,2023,1)) +
scale_fill_manual(values = c("RP" = "cornflowerblue",
"TAN" = "#A29BFE",
"EWP" = "lightgreen")) +
labs(x = "Year", y = "Monthly Mean Return",fill='Portfolio') +
theme(legend.position = "top") +
ggtitle("Portfolio Monthly Mean Return")
## `summarise()` has grouped output by 'symbol'. You can override using the
## `.groups` argument.
#Standard Deviation
p_return_df%>%
mutate(year=year(date)) %>%
group_by(symbol,year) %>%
summarise(mean = mean(returns),
sd = sd(returns)) %>%
ggplot(aes(x = year, y = sd, fill = symbol)) +
geom_bar(stat = "identity", position = "dodge", width = 0.7,color="black") +
scale_y_continuous(breaks = seq(-0.1,0.4,0.02),
labels = scales::percent) +
scale_x_continuous(breaks = seq(2015,2023,1)) +
scale_fill_manual(values = c("RP" = "cornflowerblue",
"TAN" = "#A29BFE",
"EWP" = "lightgreen")) +
labs(x = "Year", y = "Monthly Risk",fill='Portfolio') +
theme(legend.position = "top") +
ggtitle("Portfolio Monthly Standard Deviation")
## `summarise()` has grouped output by 'symbol'. You can override using the
## `.groups` argument.
#Sharpe Ratio
#Annualize Return
mean_rp <- (mean(rp_return$returns)+1)^12 - 1
mean_tan <- (mean(tan_return$returns)+1)^12 - 1
mean_ewp <- (mean(ewp_return$returns)+1)^12 - 1
#Annualize Risk
sd_rp <- sd(rp_return$returns)*sqrt(12)
sd_tan <- sd(tan_return$returns)*sqrt(12)
sd_ewp <- sd(ewp_return$returns)*sqrt(12)
#Risk free rate
rf <- 0.04
sharpe_rp <- (mean_rp-rf)/sd_rp
sharpe_tan <- (mean_tan-rf)/sd_tan
sharpe_ewp <- (mean_ewp-rf)/sd_ewp
sharpe <- tibble(Portfolio=c('RP','TAN',"EWP"),SharpeRatio=c(sharpe_rp,sharpe_tan,sharpe_ewp))
sharpe$Portfolio <- factor(sharpe$Portfolio, levels = c("RP", "TAN", "EWP"))
sharpe %>%
ggplot(aes(x=Portfolio,y=SharpeRatio,fill=Portfolio))+
geom_bar(stat='identity',color="black",width=0.4)+
scale_fill_manual(values = c("RP" = "cornflowerblue",
"TAN" = "#A29BFE",
"EWP" = "lightgreen")) +
ggtitle('Portfolio Sharpe Ratio')+
geom_text(aes(label=sprintf('%.2f', SharpeRatio)), vjust=-0.2, size=4)+
theme(legend.position = "top")
#Cumulative Return
ggplot(p_return_df, aes(x = date, y = cr, color=symbol)) +
geom_line(size=1)+
geom_point(size=1.5)+
scale_color_manual(values = c("RP" = "cornflowerblue",
"TAN" = "#A29BFE",
"EWP" = "lightgreen")) +
labs(x = "Date", y = "Cumulative Return",color='Portfolio')+
ggtitle('Portfolio Cumulative Return') +
theme(legend.position = "top") +
scale_y_continuous(breaks = seq(0,15,2))
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## i Please use `linewidth` instead.
#Risk Parity - EWP
var.test(x=rp_return$returns,y=ewp_return$returns)
##
## F test to compare two variances
##
## data: rp_return$returns and ewp_return$returns
## F = 0.88523, num df = 98, denom df = 98, p-value = 0.5474
## alternative hypothesis: true ratio of variances is not equal to 1
## 95 percent confidence interval:
## 0.5944035 1.3183402
## sample estimates:
## ratio of variances
## 0.8852265
#Risk Parity - Tangency
var.test(x=rp_return$returns,y=tan_return$returns)
##
## F test to compare two variances
##
## data: rp_return$returns and tan_return$returns
## F = 0.46079, num df = 98, denom df = 98, p-value = 0.0001574
## alternative hypothesis: true ratio of variances is not equal to 1
## 95 percent confidence interval:
## 0.3094102 0.6862474
## sample estimates:
## ratio of variances
## 0.4607949
#Tangency - EWP
var.test(x=tan_return$returns,y=ewp_return$returns)
##
## F test to compare two variances
##
## data: tan_return$returns and ewp_return$returns
## F = 1.9211, num df = 98, denom df = 98, p-value = 0.001396
## alternative hypothesis: true ratio of variances is not equal to 1
## 95 percent confidence interval:
## 1.289952 2.861013
## sample estimates:
## ratio of variances
## 1.921086
Apparently, Risk Parity Portoflio and EWP portfolio had no difference in risk. However, Tangency portfolio showed a statistically higher risk than other 2 portfolios.
#Create Dummy Variables
Drp <- rep(1,99)
Drp[100:297] <- 0
Dtan <- rep(0,99)
Dtan[100:198] <- 1
Dtan[199:297] <- 0
Dewp <- rep(0,198)
Dewp[199:297] <- 1
anova <- cbind(p_return_df,Drp,Dtan,Dewp)
## New names:
## * `` -> `...5`
## * `` -> `...6`
## * `` -> `...7`
colnames(anova)[5:7] <- c('Drp','Dtan','Dewp')
library(DT)
datatable(anova)
reg <- lm(data=anova, returns~Drp+Dtan) #Don't include Dewp due to dummy trap
summary(reg)
##
## Call:
## lm(formula = returns ~ Drp + Dtan, data = anova)
##
## Residuals:
## Min 1Q Median 3Q Max
## -0.281420 -0.039193 0.008875 0.050900 0.159742
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 0.028408 0.007252 3.917 0.000111 ***
## Drp -0.009596 0.010255 -0.936 0.350197
## Dtan -0.008345 0.010255 -0.814 0.416431
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.07215 on 294 degrees of freedom
## Multiple R-squared: 0.003508, Adjusted R-squared: -0.003271
## F-statistic: 0.5175 on 2 and 294 DF, p-value: 0.5965
The F-test of the ANOVA shows that all 3 portfolio has jointly same returns