output: word_document: toc: yes html_document: toc: yes —
The code below is written to identify the portfolio (of two stocks: Apple and General Electric) weight that yields the hightest Sharp Ratio.
Suppose, instead, that you have four stocks you would like to invest: Apple, Tesla, Facebook and Netflix. To identify the optimal portfolio weight that would yield the highest Sharp Ratio, you analyze the data that covers the period of 2013-01-01 to 2017-12-31.
Hint: Nothing else is required for this question, other than revising the code below.
Hint: Nothing else is required for this question, other than revising the code below.
library(tidyquant)
library(ggplot2)
# Import stock prices
stock_prices <- c("AAPL", "TSLA", "FB", "NFLX") %>%
tq_get(get = "stock.prices",
from = "2013-01-01",
to = "2017-12-31") %>%
group_by(symbol)
stock_prices
## # A tibble: 5,036 x 8
## # Groups: symbol [4]
## symbol date open high low close volume adjusted
## <chr> <date> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 AAPL 2013-01-02 79.1 79.3 77.4 78.4 140129500 56.3
## 2 AAPL 2013-01-03 78.3 78.5 77.3 77.4 88241300 55.6
## 3 AAPL 2013-01-04 76.7 76.9 75.1 75.3 148583400 54.1
## 4 AAPL 2013-01-07 74.6 75.6 73.6 74.8 121039100 53.7
## 5 AAPL 2013-01-08 75.6 76.0 74.5 75.0 114676800 53.9
## 6 AAPL 2013-01-09 74.6 75.0 73.7 73.9 101901100 53.0
## 7 AAPL 2013-01-10 75.5 75.5 73.6 74.8 150286500 53.7
## 8 AAPL 2013-01-11 74.4 75.0 74.1 74.3 87626700 53.4
## 9 AAPL 2013-01-14 71.8 72.5 71.2 71.7 183551900 51.5
## 10 AAPL 2013-01-15 71.2 71.3 69.1 69.4 219193100 49.8
## # ... with 5,026 more rows
# Calculate monthly returns
returns <- stock_prices %>%
tq_transmute(select = adjusted,
mutate_fun = periodReturn,
period = "monthly",
col_rename = "Ra") %>%
spread(symbol, Ra)
returns <- returns[,-1] #returns[,-1] b/c 1st column is date, which we don't need
returns
## # A tibble: 60 x 4
## AAPL FB NFLX TSLA
## <dbl> <dbl> <dbl> <dbl>
## 1 -0.170 0.106 0.796 0.0608
## 2 0.0100 -0.120 0.138 -0.0714
## 3 0.00285 -0.0613 0.00638 0.0879
## 4 0.000271 0.0856 0.142 0.425
## 5 0.0647 -0.123 0.0471 0.811
## 6 -0.118 0.0218 -0.0670 0.0982
## 7 0.141 0.479 0.158 0.251
## 8 0.128 0.122 0.161 0.259
## 9 -0.0215 0.217 0.0891 0.144
## 10 0.0964 -0.000398 0.0429 -0.173
## # ... with 50 more rows
The efficient frontier first finds a grid of target returns and then a portfolio that has the lowest variance per each target return. But what is a reasonable grid of target returns?
Hint: Nothing else is required for this question, other than revising the code below.
# Compute the efficient frontier using a grid of target returns
# Calculate each stock's mean returns
stockmu <- colMeans(returns)
stockmu
## AAPL FB NFLX TSLA
## 0.02103526 0.03476665 0.05537655 0.04816872
# Create a grid of target values
grid <- seq(from = 0.03, to = max(stockmu), length.out = 50) #from=0.03: must be >= min(stockmu)
grid
## [1] 0.03000000 0.03051789 0.03103578 0.03155367 0.03207156 0.03258944
## [7] 0.03310733 0.03362522 0.03414311 0.03466100 0.03517889 0.03569678
## [13] 0.03621467 0.03673255 0.03725044 0.03776833 0.03828622 0.03880411
## [19] 0.03932200 0.03983989 0.04035778 0.04087566 0.04139355 0.04191144
## [25] 0.04242933 0.04294722 0.04346511 0.04398300 0.04450089 0.04501878
## [31] 0.04553666 0.04605455 0.04657244 0.04709033 0.04760822 0.04812611
## [37] 0.04864400 0.04916189 0.04967977 0.05019766 0.05071555 0.05123344
## [43] 0.05175133 0.05226922 0.05278711 0.05330500 0.05382289 0.05434077
## [49] 0.05485866 0.05537655
# Create empty vectors to store means and deviations
vpm <- vpsd <- rep(NA, times = 50) # 50 b/c 50 target returns were created in grid
vpm
## [1] NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
## [24] NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
## [47] NA NA NA NA
vpsd
## [1] NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
## [24] NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
## [47] NA NA NA NA
# Create an empty matrix to store weights
mweights <- matrix(NA, 50, ncol(returns)) # 50 weights/target returns (rows) and ncol(returns) is # of stocks (columns)
mweights
## [,1] [,2] [,3] [,4]
## [1,] NA NA NA NA
## [2,] NA NA NA NA
## [3,] NA NA NA NA
## [4,] NA NA NA NA
## [5,] NA NA NA NA
## [6,] NA NA NA NA
## [7,] NA NA NA NA
## [8,] NA NA NA NA
## [9,] NA NA NA NA
## [10,] NA NA NA NA
## [11,] NA NA NA NA
## [12,] NA NA NA NA
## [13,] NA NA NA NA
## [14,] NA NA NA NA
## [15,] NA NA NA NA
## [16,] NA NA NA NA
## [17,] NA NA NA NA
## [18,] NA NA NA NA
## [19,] NA NA NA NA
## [20,] NA NA NA NA
## [21,] NA NA NA NA
## [22,] NA NA NA NA
## [23,] NA NA NA NA
## [24,] NA NA NA NA
## [25,] NA NA NA NA
## [26,] NA NA NA NA
## [27,] NA NA NA NA
## [28,] NA NA NA NA
## [29,] NA NA NA NA
## [30,] NA NA NA NA
## [31,] NA NA NA NA
## [32,] NA NA NA NA
## [33,] NA NA NA NA
## [34,] NA NA NA NA
## [35,] NA NA NA NA
## [36,] NA NA NA NA
## [37,] NA NA NA NA
## [38,] NA NA NA NA
## [39,] NA NA NA NA
## [40,] NA NA NA NA
## [41,] NA NA NA NA
## [42,] NA NA NA NA
## [43,] NA NA NA NA
## [44,] NA NA NA NA
## [45,] NA NA NA NA
## [46,] NA NA NA NA
## [47,] NA NA NA NA
## [48,] NA NA NA NA
## [49,] NA NA NA NA
## [50,] NA NA NA NA
# Load tseries package to use portfolio.optim() function to calculate the efficient frontier
library(tseries)
# Create your for loop
for(i in 1:length(grid)) {
opt <- portfolio.optim(x = as.matrix(returns), pm = grid[i])
vpm[i] <- opt$pm
vpsd[i] <- opt$ps
mweights[i, ] <- opt$pw
}
vpm
## [1] 0.03000000 0.03051789 0.03103578 0.03155367 0.03207156 0.03258944
## [7] 0.03310733 0.03362522 0.03414311 0.03466100 0.03517889 0.03569678
## [13] 0.03621467 0.03673255 0.03725044 0.03776833 0.03828622 0.03880411
## [19] 0.03932200 0.03983989 0.04035778 0.04087566 0.04139355 0.04191144
## [25] 0.04242933 0.04294722 0.04346511 0.04398300 0.04450089 0.04501878
## [31] 0.04553666 0.04605455 0.04657244 0.04709033 0.04760822 0.04812611
## [37] 0.04864400 0.04916189 0.04967977 0.05019766 0.05071555 0.05123344
## [43] 0.05175133 0.05226922 0.05278711 0.05330500 0.05382289 0.05434077
## [49] 0.05485866 0.05537655
vpsd
## [1] 0.05807033 0.05821638 0.05845001 0.05877019 0.05917550 0.05966420
## [7] 0.06023428 0.06088344 0.06160919 0.06240885 0.06327962 0.06421860
## [13] 0.06522286 0.06628943 0.06741534 0.06859768 0.06983358 0.07112024
## [19] 0.07245497 0.07383516 0.07525830 0.07672200 0.07822400 0.07976212
## [25] 0.08133432 0.08293865 0.08457329 0.08623652 0.08792672 0.08964235
## [31] 0.09138198 0.09314428 0.09496278 0.09688912 0.09891772 0.10104241
## [37] 0.10325727 0.10555661 0.10793505 0.11038746 0.11290902 0.11549522
## [43] 0.11814180 0.12084479 0.12360050 0.12676397 0.13139550 0.13742230
## [49] 0.14467009 0.15296541
mweights
## [,1] [,2] [,3] [,4]
## [1,] 5.459343e-01 0.302647347 0.09718191 5.423644e-02
## [2,] 5.283957e-01 0.304697983 0.10682260 6.008372e-02
## [3,] 5.108571e-01 0.306748619 0.11646329 6.593100e-02
## [4,] 4.933185e-01 0.308799255 0.12610398 7.177827e-02
## [5,] 4.757799e-01 0.310849892 0.13574467 7.762555e-02
## [6,] 4.582413e-01 0.312900528 0.14538537 8.347283e-02
## [7,] 4.407027e-01 0.314951164 0.15502606 8.932010e-02
## [8,] 4.231641e-01 0.317001800 0.16466675 9.516738e-02
## [9,] 4.056255e-01 0.319052436 0.17430744 1.010147e-01
## [10,] 3.880869e-01 0.321103072 0.18394813 1.068619e-01
## [11,] 3.705483e-01 0.323153708 0.19358882 1.127092e-01
## [12,] 3.530097e-01 0.325204345 0.20322952 1.185565e-01
## [13,] 3.354711e-01 0.327254981 0.21287021 1.244038e-01
## [14,] 3.179325e-01 0.329305617 0.22251090 1.302510e-01
## [15,] 3.003938e-01 0.331356253 0.23215159 1.360983e-01
## [16,] 2.828552e-01 0.333406889 0.24179228 1.419456e-01
## [17,] 2.653166e-01 0.335457525 0.25143297 1.477929e-01
## [18,] 2.477780e-01 0.337508161 0.26107366 1.536401e-01
## [19,] 2.302394e-01 0.339558798 0.27071436 1.594874e-01
## [20,] 2.127008e-01 0.341609434 0.28035505 1.653347e-01
## [21,] 1.951622e-01 0.343660070 0.28999574 1.711820e-01
## [22,] 1.776236e-01 0.345710706 0.29963643 1.770292e-01
## [23,] 1.600850e-01 0.347761342 0.30927712 1.828765e-01
## [24,] 1.425464e-01 0.349811978 0.31891781 1.887238e-01
## [25,] 1.250078e-01 0.351862614 0.32855851 1.945711e-01
## [26,] 1.074692e-01 0.353913251 0.33819920 2.004183e-01
## [27,] 8.993060e-02 0.355963887 0.34783989 2.062656e-01
## [28,] 7.239200e-02 0.358014523 0.35748058 2.121129e-01
## [29,] 5.485339e-02 0.360065159 0.36712127 2.179602e-01
## [30,] 3.731479e-02 0.362115795 0.37676196 2.238075e-01
## [31,] 1.977619e-02 0.364166431 0.38640266 2.296547e-01
## [32,] 2.237582e-03 0.366217068 0.39604335 2.355020e-01
## [33,] 0.000000e+00 0.342250008 0.41490715 2.428428e-01
## [34,] 0.000000e+00 0.314478187 0.43511972 2.504021e-01
## [35,] 0.000000e+00 0.286706367 0.45533229 2.579613e-01
## [36,] 0.000000e+00 0.258934547 0.47554486 2.655206e-01
## [37,] 0.000000e+00 0.231162727 0.49575743 2.730798e-01
## [38,] 0.000000e+00 0.203390907 0.51597000 2.806391e-01
## [39,] 0.000000e+00 0.175619087 0.53618257 2.881983e-01
## [40,] 0.000000e+00 0.147847266 0.55639514 2.957576e-01
## [41,] 0.000000e+00 0.120075446 0.57660771 3.033168e-01
## [42,] 0.000000e+00 0.092303626 0.59682028 3.108761e-01
## [43,] 0.000000e+00 0.064531806 0.61703285 3.184353e-01
## [44,] 0.000000e+00 0.036759986 0.63724543 3.259946e-01
## [45,] 0.000000e+00 0.008988166 0.65745800 3.335538e-01
## [46,] -2.115360e-18 0.000000000 0.71259648 2.874035e-01
## [47,] -5.242940e-18 0.000000000 0.78444736 2.155526e-01
## [48,] 4.714063e-17 0.000000000 0.85629824 1.437018e-01
## [49,] -1.149810e-17 0.000000000 0.92814912 7.185088e-02
## [50,] -1.462568e-17 0.000000000 1.00000000 4.996004e-16
Hint: Recall how the grid is created above. The highest target monthly return is also the largest value in the grid. This highest target return is only attainable when you go all in with the stock that yielded the highest average monthly return! Buy mostly lower prices stocks. If you pay $600 to buy 600 stocks and each stock goes up one cent you will make $6 as opposed to buying one stock for $600 and only making 1 cent.
Hint: You learned this at DataCamp. See the section, “Imposing weight constraints”.
# Convert portfolio weights to data frame, and add column and row names
names(stockmu)
## [1] "AAPL" "FB" "NFLX" "TSLA"
colnames(mweights) <- names(stockmu)
mweights <- data.frame(mweights)
mweights$index <- seq(1, nrow(mweights))
mweights
## AAPL FB NFLX TSLA index
## 1 5.459343e-01 0.302647347 0.09718191 5.423644e-02 1
## 2 5.283957e-01 0.304697983 0.10682260 6.008372e-02 2
## 3 5.108571e-01 0.306748619 0.11646329 6.593100e-02 3
## 4 4.933185e-01 0.308799255 0.12610398 7.177827e-02 4
## 5 4.757799e-01 0.310849892 0.13574467 7.762555e-02 5
## 6 4.582413e-01 0.312900528 0.14538537 8.347283e-02 6
## 7 4.407027e-01 0.314951164 0.15502606 8.932010e-02 7
## 8 4.231641e-01 0.317001800 0.16466675 9.516738e-02 8
## 9 4.056255e-01 0.319052436 0.17430744 1.010147e-01 9
## 10 3.880869e-01 0.321103072 0.18394813 1.068619e-01 10
## 11 3.705483e-01 0.323153708 0.19358882 1.127092e-01 11
## 12 3.530097e-01 0.325204345 0.20322952 1.185565e-01 12
## 13 3.354711e-01 0.327254981 0.21287021 1.244038e-01 13
## 14 3.179325e-01 0.329305617 0.22251090 1.302510e-01 14
## 15 3.003938e-01 0.331356253 0.23215159 1.360983e-01 15
## 16 2.828552e-01 0.333406889 0.24179228 1.419456e-01 16
## 17 2.653166e-01 0.335457525 0.25143297 1.477929e-01 17
## 18 2.477780e-01 0.337508161 0.26107366 1.536401e-01 18
## 19 2.302394e-01 0.339558798 0.27071436 1.594874e-01 19
## 20 2.127008e-01 0.341609434 0.28035505 1.653347e-01 20
## 21 1.951622e-01 0.343660070 0.28999574 1.711820e-01 21
## 22 1.776236e-01 0.345710706 0.29963643 1.770292e-01 22
## 23 1.600850e-01 0.347761342 0.30927712 1.828765e-01 23
## 24 1.425464e-01 0.349811978 0.31891781 1.887238e-01 24
## 25 1.250078e-01 0.351862614 0.32855851 1.945711e-01 25
## 26 1.074692e-01 0.353913251 0.33819920 2.004183e-01 26
## 27 8.993060e-02 0.355963887 0.34783989 2.062656e-01 27
## 28 7.239200e-02 0.358014523 0.35748058 2.121129e-01 28
## 29 5.485339e-02 0.360065159 0.36712127 2.179602e-01 29
## 30 3.731479e-02 0.362115795 0.37676196 2.238075e-01 30
## 31 1.977619e-02 0.364166431 0.38640266 2.296547e-01 31
## 32 2.237582e-03 0.366217068 0.39604335 2.355020e-01 32
## 33 0.000000e+00 0.342250008 0.41490715 2.428428e-01 33
## 34 0.000000e+00 0.314478187 0.43511972 2.504021e-01 34
## 35 0.000000e+00 0.286706367 0.45533229 2.579613e-01 35
## 36 0.000000e+00 0.258934547 0.47554486 2.655206e-01 36
## 37 0.000000e+00 0.231162727 0.49575743 2.730798e-01 37
## 38 0.000000e+00 0.203390907 0.51597000 2.806391e-01 38
## 39 0.000000e+00 0.175619087 0.53618257 2.881983e-01 39
## 40 0.000000e+00 0.147847266 0.55639514 2.957576e-01 40
## 41 0.000000e+00 0.120075446 0.57660771 3.033168e-01 41
## 42 0.000000e+00 0.092303626 0.59682028 3.108761e-01 42
## 43 0.000000e+00 0.064531806 0.61703285 3.184353e-01 43
## 44 0.000000e+00 0.036759986 0.63724543 3.259946e-01 44
## 45 0.000000e+00 0.008988166 0.65745800 3.335538e-01 45
## 46 -2.115360e-18 0.000000000 0.71259648 2.874035e-01 46
## 47 -5.242940e-18 0.000000000 0.78444736 2.155526e-01 47
## 48 4.714063e-17 0.000000000 0.85629824 1.437018e-01 48
## 49 -1.149810e-17 0.000000000 0.92814912 7.185088e-02 49
## 50 -1.462568e-17 0.000000000 1.00000000 4.996004e-16 50
# Visualize minimum variance portfolio weights per target returns
mweights %>%
gather(var, value, 1:ncol(returns)) %>%
ggplot(aes(x = index, y = value, fill = var)) +
geom_col()
Hint: The Sharp Ratio measures returns adjusted for risk.
# Identify the minimum variance portfolio
mweights_minvar <- mweights[vpsd == min(vpsd), ]
mweights_minvar
## AAPL FB NFLX TSLA index
## 1 0.5459343 0.3026473 0.09718191 0.05423644 1
# Identify the highest Sharp Ratio portfolio
vsr <- (vpm - 0.0075)/vpsd
mweights_maxsr <- mweights[vsr == max(vsr), ]
mweights_maxsr
## AAPL FB NFLX TSLA index
## 15 0.3003938 0.3313563 0.2321516 0.1360983 15
Create three portfolios with different maximum weight constraints.
An advantage of a maximum weight constraint is the subsequent portfolio will be less concentrated in certain assets.
A disadvantage of a maximum weight constraint is the same target return may no longer be possible or will be obtained at the expense of a higher volatility.
The function portfolio.optim() allows you to set weight constraints within the reshigh argument. reshigh requires a vector of maximum weights for each asset.
Assigning any random number for the reshigh argument may give an error. This is because some target returns are not attainable with weight constraints.