require(tidyverse)
require(tidyquant)
require(fPortfolio)
require(PerformanceAnalytics)
require(tbl2xts)
require(ggthemes)
require(timetk)
require(PortfolioAnalytics)
require(kableExtra)
require(jtools)
require(FinTS)
require(rugarch)
require(readxl)
In this project we will be applying Modern Portfolio Theory to cryptocurrencies. We will be using the Markowitz Optimal Portfolio solution to determine portfolio weights for various cryptocurrencies. These portfolios will take returns and risk into consideration. We read in the data from a Kagel Dataset. The data for each of the coins used in this research can be downloaded here link
BTC_ETH <- read_excel("C:/Users/mikem/OneDrive/Desktop/Crypto RMD/BTC-ETH.xlsx")
To simplify the Markowitz optimal portfolio solution we will look at two coins Bitcoin (BTC) and Ethereum (ETH) to start. We first will observe the mean and standard deviation for each coins monthly returns, using the logged closed price for each coin. We will then calculate the correlation matrix between the two coins to see how they are correlated.
BE_returns <- BTC_ETH %>%
group_by(Symbol) %>%
tq_transmute(
select = Close,
mutate_fun = periodReturn,
period = "monthly",
type = "log",
col_rename = "monthly.returns"
)
BE_returns %>%
group_by(Symbol) %>%
summarise(mean = mean(monthly.returns),
st.dev = sd(monthly.returns)) %>%
kable(format = "html",
table.attr = "style='width:30%;'",
caption = "Mean and standard deviation of returns") %>%
kableExtra::kable_styling()
Symbol | mean | st.dev |
---|---|---|
BTC | 0.0677292 | 0.2173964 |
ETH | 0.1115916 | 0.3853444 |
BE_Crypto <- BE_returns %>%
tbl_xts(cols_to_xts = monthly.returns, spread_by = Symbol)
round(cor(BE_Crypto,BE_Crypto),digits=3) %>%
kable(format= "html",
table.attr = "style='width:30%;'",
caption = "Correlation matrix bewtween BTC and ETH") %>%
kableExtra::kable_styling()
BTC | ETH | |
---|---|---|
BTC | 1.000 | 0.455 |
ETH | 0.455 | 1.000 |
Now we plot the Markowitz frontier for these two assets where we have assumed that the annualized 1-month risk-free rate is 0.05%. In both plots, the minimum variance portfolio is shown as the red dot. The optimal portfolio in each plot has the highest expected excess return per unit of risk that we can obtain.
In the first plot, BTC is the dark blue diamond, ETH is the cyan diamond, and the optimal portfolio is the blue triangle. All of the Monte Carlo generated portfolios with \(w \in [0,1]\) lie on the frontier.
BE_Return <- as.timeSeries(BE_Crypto)
tgSpec <- portfolioSpec()
setRiskFreeRate(tgSpec) <- .05 / 1200
tgPortfolio <- tangencyPortfolio(data = BE_Return,
spec = tgSpec,
constraints = "LongOnly")
print(tgPortfolio)
##
## Title:
## MV Tangency Portfolio
## Estimator: covEstimator
## Solver: solveRquadprog
## Optimize: minRisk
## Constraints: LongOnly
##
## Portfolio Weights:
## BTC ETH
## 0.683 0.317
##
## Covariance Risk Budgets:
## BTC ETH
## 0.5666 0.4334
##
## Target Returns and Risks:
## mean Cov CVaR VaR
## 0.0816 0.2313 0.4279 0.3047
##
## Description:
## Wed Aug 10 19:09:46 2022 by user: mikem
plot(frontier, which = c(1, 2, 3, 4, 7)) # select 1 and 3
In this plot we allow for short-selling in our portfolio. We can not hedge one coin for the other. IN this plot BTC is the solid red dot, and ETH is the open yellow dot, and the optimal portfolio is the open cyan dot.
shortSpec <- portfolioSpec()
setNFrontierPoints(shortSpec) <- 20
setSolver(shortSpec) <- "solveRshortExact"
shortFrontier <- portfolioFrontier(data = BE_Return,
spec = shortSpec,
constraints = "Short")
print(shortFrontier)
##
## Title:
## MV Portfolio Frontier
## Estimator: covEstimator
## Solver: solveRshortExact
## Optimize: minRisk
## Constraints: Short
## Portfolio Points: 5 of 20
##
## Portfolio Weights:
## BTC ETH
## 1 1.0000 0.0000
## 5 0.7895 0.2105
## 10 0.5263 0.4737
## 15 0.2632 0.7368
## 20 0.0000 1.0000
##
## Covariance Risk Budgets:
## BTC ETH
## 1 1.0000 0.0000
## 5 0.7348 0.2652
## 10 0.3454 0.6546
## 15 0.1081 0.8919
## 20 0.0000 1.0000
##
## Target Returns and Risks:
## mean Cov CVaR VaR
## 1 0.0677 0.2174 0.4122 0.3257
## 5 0.0770 0.2207 0.4168 0.3333
## 10 0.0885 0.2558 0.4526 0.2750
## 15 0.1000 0.3141 0.5303 0.4421
## 20 0.1116 0.3853 0.6203 0.4981
##
## Description:
## Wed Aug 10 19:09:47 2022 by user: mikem
tailoredFrontierPlot(object = shortFrontier,
mText = "MV Portfolio - Short Constraints",
risk = "Cov")
getWeights(tp_BE) %>%
kable(format= "html",
table.attr = "style='width:30%;'",
caption = "Optimal Portflio Weights") %>%
kableExtra::kable_styling()
x | |
---|---|
BTC | 0.6829908 |
ETH | 0.3170092 |
From the first graph we find interesting results. If we go all in on BTC (dark blue diamond), the return is lower than the minimum variance portfolio (red dot), but the risk is only slightly higher. Where as going all in on ETH is 10-20 percentage points more risky and the returns are only .4 percentage points higher. The blue triangle is the optimal portfolio it says go about 70% in BTC and about 30% in ETH. This is daily data and the returns are calculated monthly, the data we have is form January 2018 to now. Due to the volatility of cryptocurrencies results from this analysis changes slightly after each iteration.
Lets see what an optimal portfolio would look like with a lot of coins. Some popular coins such as (Solana (SOL), or Polkadot (DOT)) don’t have a lot of historical data from this Kaggle dataset. We have 9 coins in the analysis and the descriptions of each coin or below. We have daily data for these coins from January 2018 to now. We will use the given close price of each coin for the analysis.
BNB <- read_xlsx("C:/Users/mikem/OneDrive/Desktop/Crypto RMD/ALT_coins.xlsx", sheet = 1)
ADA <- read_xlsx("C:/Users/mikem/OneDrive/Desktop/Crypto RMD/ALT_coins.xlsx",sheet =2)
LINK <- read_xlsx("C:/Users/mikem/OneDrive/Desktop/Crypto RMD/ALT_coins.xlsx",sheet =3)
LTC <- read_xlsx("C:/Users/mikem/OneDrive/Desktop/Crypto RMD/ALT_coins.xlsx",sheet =4)
XRP <- read_xlsx("C:/Users/mikem/OneDrive/Desktop/Crypto RMD/ALT_coins.xlsx",sheet =5)
XLM <- read_xlsx("C:/Users/mikem/OneDrive/Desktop/Crypto RMD/ALT_coins.xlsx",sheet =6)
TRX <- read_xlsx("C:/Users/mikem/OneDrive/Desktop/Crypto RMD/ALT_coins.xlsx",sheet =7)
BTC <- read_xlsx("C:/Users/mikem/OneDrive/Desktop/Crypto RMD/ALT_coins.xlsx",sheet =8)
ETH <- read_xlsx("C:/Users/mikem/OneDrive/Desktop/Crypto RMD/ALT_coins.xlsx",sheet =9)
Alt_Coins <-BTC %>% full_join(BNB)
Alt_Coins <-Alt_Coins %>% full_join(ADA)
Alt_Coins <-Alt_Coins %>% full_join(LINK)
Alt_Coins <-Alt_Coins %>% full_join(LTC)
Alt_Coins <-Alt_Coins %>% full_join(XRP)
Alt_Coins <-Alt_Coins %>% full_join(XLM)
Alt_Coins <-Alt_Coins %>% full_join(TRX)
Alt_Coins <-Alt_Coins %>% full_join(ETH)
Alt_Coins <- subset(Alt_Coins, Date > "2018-02-02")
Alt_Coins %>%
ggplot(aes(x = Date, y = log(Close), color = Symbol)) +
geom_line(size = 1) +
labs(
title = "Logged Daily Crypto Prices",
x = "",
y = "Logged Close Prices",
color = ""
)
Alt_returns_monthly <- Alt_Coins %>%
group_by(Symbol) %>%
tq_transmute(
select = Close,
mutate_fun = periodReturn,
period = "monthly",
type = "log",
col_rename = "Rm"
)
Alt_returns_wide <- Alt_returns_monthly %>%
spread(key = Symbol, value = Rm) %>%
tk_xts(date_var = Date)
round(cor(Returns_Alt_Coins,Returns_Alt_Coins),digits=3) %>%
kable(format= "html",
table.attr = "style='width:30%;'",
caption = "Correlation Matrix") %>%
kableExtra::kable_styling()
BTC | BNB | ADA | LINK | LTC | XRP | XLM | TRX | ETH | |
---|---|---|---|---|---|---|---|---|---|
BTC | 1.000 | 0.641 | 0.607 | 0.646 | 0.848 | 0.435 | 0.625 | 0.627 | 0.795 |
BNB | 0.641 | 1.000 | 0.654 | 0.374 | 0.680 | 0.378 | 0.472 | 0.602 | 0.573 |
ADA | 0.607 | 0.654 | 1.000 | 0.542 | 0.659 | 0.502 | 0.759 | 0.617 | 0.721 |
LINK | 0.646 | 0.374 | 0.542 | 1.000 | 0.653 | 0.558 | 0.618 | 0.608 | 0.712 |
LTC | 0.848 | 0.680 | 0.659 | 0.653 | 1.000 | 0.559 | 0.657 | 0.653 | 0.815 |
XRP | 0.435 | 0.378 | 0.502 | 0.558 | 0.559 | 1.000 | 0.810 | 0.592 | 0.661 |
XLM | 0.625 | 0.472 | 0.759 | 0.618 | 0.657 | 0.810 | 1.000 | 0.627 | 0.759 |
TRX | 0.627 | 0.602 | 0.617 | 0.608 | 0.653 | 0.592 | 0.627 | 1.000 | 0.678 |
ETH | 0.795 | 0.573 | 0.721 | 0.712 | 0.815 | 0.661 | 0.759 | 0.678 | 1.000 |
SharpeRatio(Alt_returns_wide, Rf = .005, p = 0.95, digits = 4, FUN = "StdDev") %>%
kable(format = "html", table.attr = "style='width:50%;'",
caption = "Asset Sharpe Ratios") %>%
kableExtra::kable_styling()
ADA | BNB | BTC | ETH | LINK | LTC | TRX | XLM | XRP | |
---|---|---|---|---|---|---|---|---|---|
StdDev Sharpe (Rf=0.5%, p=95%): | -0.0121942 | 0.1600751 | 0.0477923 | -0.0043282 | 0.1062672 | -0.095632 | 0.0089347 | -0.0946198 | -0.0661072 |
Very interesting Sharp Ratios. Binance Coin and ChainLink stands out.
# get optimal portfolio
rm(opt,port_spec)
data <- Alt_returns_wide
port_spec <- portfolio.spec(colnames(data))
port_spec <- add.constraint(portfolio = port_spec, type= "long_only")
port_spec <- add.constraint(portfolio = port_spec, type= "full_investment")
port_spec <- add.objective(portfolio = port_spec,
type = "risk",
name = "StdDev")
port_spec <- add.objective(portfolio = port_spec,
type = "return",
name = "mean")
opt <- optimize.portfolio(data, portfolio = port_spec,
optimize_method = "random",
search_size = 10000,
trace=TRUE)
chart.RiskReward(opt, risk.col = "StdDev", return.col = "mean",
chart.assets = TRUE)
extractWeights(opt) %>%
kable(format = "html", table.attr = "style='width:30%;'",
caption = "Optimal Long-Only Portfolio Weights") %>%
kableExtra::kable_styling()
x | |
---|---|
ADA | 0.020 |
BNB | 0.166 |
BTC | 0.752 |
ETH | 0.000 |
LINK | 0.002 |
LTC | 0.002 |
TRX | 0.050 |
XLM | 0.000 |
XRP | 0.008 |
Right off the bat the interpretation of this output is, regardless of investment amount: put ~69% in BTC, 16% in BNB, 10% in ETH, etc.
A lack of ETH in the portfolio doesn’t surprise me. The coins are the two leaders in the market and their returns and risks are definitely correlated/coupled. The program chooses one or the other and the lower risk of BTC with higher predicted returns (marginally higher Sharpe Ratio) says if you have to chose one (and apparently you should) chose BTC. If you place equal weight in each coin a crash in one coin will lead to a crash in the other. Markowitz theorized that we should be off-setting risk with other assets that are not correlated with the returns of other assets in your portfolio. The return may be lower, however, the loss of risk outweighs this issue. Binance is fairly stable so some inclusion is expected but the amount is still shocking. This is probably due to its higher Sharpe Ratio than ETH, however it should be correlated with ETH which is correlated with BTC. But math is math and if we go on the daily data based on “close” prices this is what the optimal portfolio looks like. XRP and ADA show up as well which is interesting since XRP is currently in a lawsuit and ADA is a coin with a lot of “meme” propulsion around it.
Lets see what happens when we leave BTC out of the portfolio. The Sharpe Ratios don’t change but the optimal weights for the portfolio will change.
Alt_Coins_2 <- subset(Alt_Coins, Symbol != "BTC")
CryptoALT_returns_monthly <- Alt_Coins_2 %>%
group_by(Symbol) %>%
tq_transmute(
select = Close,
mutate_fun = periodReturn,
period = "monthly",
type = "log",
col_rename = "Rm"
)
CryptoALT_returns_wide <- CryptoALT_returns_monthly %>%
spread(key = Symbol, value = Rm) %>%
tk_xts(date_var = date)
# get optimal portfolio
rm(opt,port_spec)
data <- CryptoALT_returns_wide
port_spec <- portfolio.spec(colnames(data))
port_spec <- add.constraint(portfolio = port_spec, type= "long_only")
port_spec <- add.constraint(portfolio = port_spec, type= "full_investment")
port_spec <- add.objective(portfolio = port_spec,
type = "risk",
name = "StdDev")
port_spec <- add.objective(portfolio = port_spec,
type = "return",
name = "mean")
optALT <- optimize.portfolio(data, portfolio = port_spec,
optimize_method = "random",
search_size = 10000,
trace=TRUE)
chart.RiskReward(optALT, risk.col = "StdDev", return.col = "mean",
chart.assets = TRUE)
extractWeights(optALT) %>%
kable(format = "html", table.attr = "style='width:30%;'",
caption = "Optimal Long-Only Portfolio Weights") %>%
kableExtra::kable_styling()
x | |
---|---|
ADA | 0.012 |
BNB | 0.444 |
ETH | 0.098 |
LINK | 0.104 |
LTC | 0.114 |
TRX | 0.214 |
XLM | 0.010 |
XRP | 0.004 |
With the behemoth that is BTC out of the way we see other coins start to flourish. Binance Coin (BNB), Ethereum (ETH) and ChainLink (LINK) take the spot light. A optimal Markowitz portfolio will opt to not include two coins that have highly correlated returns due to correlated risk. If two coins move up together they also fall together. We would opt to include coins with lower returns to hedge risk of choosing coins that have correlated risk.The results of this analysis will change slightly as more daily data comes in and the market these coins operate in is highly volatile.