Diversify investment -> porfolio -> increase return’s expectation and reduce the risk. Hence, we need to test our investment strategy, it is called: backtesting. Backtesting are tested using historical data. We also need to use an online performance monitoring.
## 'getSymbols' currently uses auto.assign=TRUE by default, but will
## use auto.assign=FALSE in 0.5-0. You will still be able to use
## 'loadSymbols' to automatically load data. getOption("getSymbols.env")
## and getOption("getSymbols.auto.assign") will still be checked for
## alternate defaults.
##
## This message is shown once per session and may be disabled by setting
## options("getSymbols.warning4.0"=FALSE). See ?getSymbols for details.
## KO.Open KO.High KO.Low KO.Close KO.Volume KO.Adjusted PEP.Open
## 2003-01-02 22.075 22.495 22.025 22.425 9358800 10.38614 42.12
## 2003-01-03 22.430 22.460 22.110 22.370 6184400 10.36066 43.29
## 2003-01-06 22.300 22.575 22.170 22.460 7796000 10.40235 43.25
## 2003-01-07 22.250 22.375 22.100 22.180 7628000 10.27266 42.70
## 2003-01-08 22.300 22.375 21.950 22.035 6403600 10.20551 42.46
## 2003-01-09 21.950 22.340 21.850 22.265 9030200 10.31203 42.95
## PEP.High PEP.Low PEP.Close PEP.Volume PEP.Adjusted
## 2003-01-02 43.20 42.12 43.10 3522600 27.12391
## 2003-01-03 43.49 42.78 43.40 3153400 27.31272
## 2003-01-06 43.68 42.79 42.96 4139400 27.03581
## 2003-01-07 42.81 42.00 42.18 4796800 26.54493
## 2003-01-08 42.96 42.40 42.70 4514600 26.87218
## 2003-01-09 43.29 42.50 43.14 3762800 27.14910
## KO.Adjusted PEP.Adjusted
## 2003-01-02 10.38614 27.12391
## 2003-01-03 10.36066 27.31272
## 2003-01-06 10.40235 27.03581
## 2003-01-07 10.27266 26.54493
## 2003-01-08 10.20551 26.87218
## 2003-01-09 10.31203 27.14910
First, we define ko_pep as the ratio expressing the value of the share price of the Coca Cola company in terms of the share price of PepsiCo.
## KO.Adjusted
## 2003-01-02 0.3829144
## 2003-01-03 0.3793347
## 2003-01-06 0.3847618
## 2003-01-07 0.3869914
## 2003-01-08 0.3797796
## 2003-01-09 0.3798298
# Make a time series plot of ko_pep
plot.zoo(ko_pep)
# Add as a reference, a horizontal line at 1
abline(h=1)How to decide between this two companies? Coca-cola or Pepsi? 30% KO and 70% PEP, or just 100% PEP just because you like Pepsi more?
Several strategies available to choose: 1. Put all-in on one basket <- Noobs only! 2. Equal weighting 3. Market cap weighting 4. Mean and variance optimization <- Our goal!
For you whose interested in finance in R, you definitely should learn more and learn deeply this package: PerformanceAnalytics
Return.calculate(): calculate the individual asset return Return.portfolio(): calculate the porfolio return
library(PerformanceAnalytics)
ko_ret <- Return.calculate(ko)
pep_ret <- Return.calculate(pep)
head(cbind(ko_ret, pep_ret))## KO.Adjusted PEP.Adjusted
## 2003-01-02 NA NA
## 2003-01-03 -0.002452789 0.006960869
## 2003-01-06 0.004023199 -0.010138573
## 2003-01-07 -0.012466804 -0.018156366
## 2003-01-08 -0.006537157 0.012328077
## 2003-01-09 0.010438286 0.010304857
For portfolio weights, there are options: 1. Set initial weight at initial date and never change it. So, the weight of individual stock price in the portfolio will constantly changing because the changes of the price itself. 2. Dynamic approach -> we constantly adjusting the weight of the individual asset.
Let’s try to build both models!
# Combine both returns into one variable
rets <- cbind(ko_ret, pep_ret)
# Create the weights, we divide our money half to each of the asset
eq_weights <- c(0.5, 0.5)
# Create a portfolio using buy and hold
pf_bh <- Return.portfolio(R = rets, weights = eq_weights, verbose = TRUE)
# Create a portfolio rebalancing monthly
pf_rebal <- Return.portfolio(R = rets, weights = eq_weights, rebalance_on = "months", verbose = TRUE)
# Plot the time-series
par(mfrow = c(2, 1), mar = c(2, 4, 2, 2))
plot.zoo(pf_bh$returns)
plot.zoo(pf_rebal$returns) Can you notice any difference here?
Both pf_bh and pf_rebal contains useful information that we can extract.
# Create eop_weight_bh
eop_weight_bh <- pf_bh$EOP.Weight
# Create eop_weight_rebal
eop_weight_rebal <- pf_rebal$EOP.Weight
# Plot end of period weights
par(mfrow = c(2, 1), mar=c(2, 4, 2, 2))
plot.zoo(eop_weight_bh$KO)
plot.zoo(eop_weight_rebal$KO) We can see here that: 1. The buy and hold strategy, the weights is changes due to the fluctuaton of the prices. 2. The rebalance strategy, it always try to rebalance it to the initial set weights.
Portfolio Return Analysis is both analyse the past performance and predict the future. How to choose the best portfolio? We should always consider the risk vs reward. Portfolio management is heavily utilized math, statistics, programming skills and intuition.
How to quantify risk and reward? Reward -> portfolio mean return, it is the expectation return of a portfolio Risk -> portfolio volatility (variance and standard deviation), it is a measure how big the changes of prices againt the mean return
Return is not linear. 50% loss then followed by 50% profit, we only obtain our money 75% from the initial value. final value = initial value * 0.5 * 1.5 = 0.75 * initial value That’s why we won’t use simple arithmatic when calculate returns. Instead we will used what we called geometric return Read more here: https://corporatefinanceinstitute.com/resources/knowledge/other/what-is-geometric-mean/
Now let’s try to analyze the sp500 index.
# Fetch price
sp500 <- getSymbols("^GSPC", from = "1985-01-01", to = "2017-12-30", src = "yahoo", auto.assign = FALSE)
head(sp500)## GSPC.Open GSPC.High GSPC.Low GSPC.Close GSPC.Volume GSPC.Adjusted
## 1985-01-02 167.20 167.20 165.19 165.37 67820000 165.37
## 1985-01-03 165.37 166.11 164.38 164.57 88880000 164.57
## 1985-01-04 164.55 164.55 163.36 163.68 77480000 163.68
## 1985-01-07 163.68 164.71 163.68 164.24 86190000 164.24
## 1985-01-08 164.24 164.59 163.91 163.99 92110000 163.99
## 1985-01-09 163.99 165.57 163.99 165.18 99230000 165.18
# Convert the daily frequency of sp500 to monthly frequency: sp500_monthly
sp500_monthly <- to.monthly(sp500)
# Print the first six rows of sp500_monthly
head(sp500_monthly)## sp500.Open sp500.High sp500.Low sp500.Close sp500.Volume
## Jan 1985 167.20 180.27 163.36 179.63 2673710000
## Feb 1985 179.63 183.95 177.75 181.18 2194620000
## Mar 1985 181.18 183.89 176.53 180.66 2153090000
## Apr 1985 180.66 183.61 177.86 179.83 1981880000
## May 1985 179.83 189.98 178.35 189.55 2350340000
## Jun 1985 189.55 191.85 185.03 191.85 2117000000
## sp500.Adjusted
## Jan 1985 179.63
## Feb 1985 181.18
## Mar 1985 180.66
## Apr 1985 179.83
## May 1985 189.55
## Jun 1985 191.85
# Create sp500_returns using Return.calculate using the closing prices
sp500_returns <- Return.calculate(Cl(sp500_monthly))
# Time series plot
plot.ts(sp500_returns)# Remove first value because it is NA
sp500_returns <- sp500_returns[(-1),]
# Compute the mean monthly returns
mean(sp500_returns)## [1] 0.007780972
## sp500.Close
## Geometric Mean 0.006859586
## [1] 0.04255479
The (annualized) Sharpe ratio Here we will define risk-free asset is something like Reksadana Pasar Uang, Deposito, or simply Bank Account. It is depends on you what you want to choose. Reksadana Pasar Uang (money market fund) actually not 100% risk-free. Depends on the composition of the fund, if the risk is small enought, we can safely ignore it.
In this stage, you should already understand risk-return tradeoff and its graph representation. You will also want to learn about Modern Portfolio Theory, Excess Return Sharpe Ratio. Please read more here: https://www.wikiwand.com/en/Risk%E2%80%93return_spectrum and https://web.stanford.edu/~wfsharpe/mia/rr/mia_rr5.htm
Annualized mean and volatility
Time-variation in portfolio performance So far we only view everything as static. In reality, asset price is affected by things like: cycle, news, psychological effect, political, unexpected pandemic, etc. That’s why we need to weight more the most recent observation and weight less (or discard) the most distant, it is called: Rolling estimation Read more here: https://www.mathworks.com/help/econ/rolling-window-estimation-of-state-space-models.html The tricky part here is to choose the window length? How should we choose the window length?
Rolling annualized mean and volatility
# Plotting the 12-month rolling annualized mean
chart.RollingPerformance(R = sp500_returns, width = 12, FUN = "Return.annualized")# Plotting the 12-month rolling annualized standard deviation
chart.RollingPerformance(R = sp500_returns, width = 12, FUN = "StdDev.annualized")# Plotting the 12-month rolling annualized Sharpe ratio, assuming rf = 0
chart.RollingPerformance(R = sp500_returns, width = 12, FUN = "SharpeRatio.annualized")To see all together:
Using window to analyze specific date range
# Use daily return
sp500_ret_daily <- Return.calculate(Cl(sp500))
# Fill in window for 2008
sp500_2008 <- window(sp500_ret_daily, start = "2008-01-01", end = "2008-12-31")
# Create window for 2014
sp500_2014 <-window(sp500_ret_daily, start = "2014-01-01", end = "2014-12-31")
# Plotting settings
par(mfrow = c(1, 2) , mar=c(3, 2, 2, 2))
names(sp500_2008) <- "sp500_2008"
names(sp500_2014) <- "sp500_2014"
# Plot histogram of 2008
chart.Histogram(sp500_2008, methods = c("add.density", "add.normal"))
# Plot histogram of 2014
chart.Histogram(sp500_2014, methods = c("add.density", "add.normal")) How does it compare to normal distribution? Is it correct to assume asset return on normal distribution? Of course no. It is almost illogical to use normal distribution on asset return.
Non-normality of the return distribution Asset return tend to be skewed and fat-tailed. It is possible to see more negative retuns on the left tail of the distribution (see image above).
Two metrics key to understanding the distribution of non-normal returns are skewness and kurtosis. Here is a good resource: https://towardsdatascience.com/testing-for-normality-using-skewness-and-kurtosis-afd61be860
## [1] -0.8381007
## [1] 21.84005
Semi-Deviation, Value-at-Risk and 5% Expected Shortfall In this stage, you should already understand the terms: 5% expected shortfall and Value-at-Risk Read more here: https://www.investopedia.com/articles/04/092904.asp
Semi-deviation is a method of measuring the below-mean fluctuations in the returns on investment. Please read more here: https://www.investopedia.com/terms/s/semideviation.asp
## sp500.Close
## Semi-Deviation 0.03254367
## sp500.Close
## VaR -0.06918071
## sp500.Close
## ES -0.1167946
Drawdown We now analyzing the portfolio’s drawdowns, or peak-to-trough decline in cumulative returns. Please read more here: https://www.investopedia.com/terms/d/drawdown.asp
The future are unknown and random We will start to shift, from thinking return as past performance, to return as predictions. In this stage, you should already understand the terms: Expected Value, Variance, and Random Variable. It is a basic concept in StatProb class.
Correlation between returns We are using ko_ret and pep_ret again.
# Create a scatter plot of returns
chart.Scatter(ko_ret, pep_ret, xlab = "ko returns", ylab = "pep returns", main = "ko-pep returns")## PEP.Adjusted
## KO.Adjusted 0.6290417
# Find and visualize the correlation using chart.Correlation
chart.Correlation(cbind(ko_ret, pep_ret))# Visualize the rolling estimates using chart.RollingCorrelation
chart.RollingCorrelation(ko_ret, pep_ret, width = 22) # 22 trading days, about 1 calendar monthCovariance Matrix Just recall your linear algebra class here.
ko <- getSymbols("ko", from = "2003-01-01", to = "2016-08-30", auto.assign = FALSE)
pep <- getSymbols("pep", from = "2003-01-01", to = "2016-08-30", auto.assign = FALSE)
aapl <- getSymbols("aapl", from = "2003-01-01", to = "2016-08-30", auto.assign = FALSE)
ge <- getSymbols("ge", from = "2003-01-01", to = "2016-08-30", auto.assign = FALSE)ret_ko <- Return.calculate(Ad(ko))
ret_pep <- Return.calculate(Ad(pep))
ret_aapl <- Return.calculate(Ad(aapl))
ret_ge <- Return.calculate(Ad(ge))# Remove NA value in the first row
ret_ko <- ret_ko[(-1),]
ret_pep <- ret_pep[(-1),]
ret_aapl <- ret_aapl[(-1),]
ret_ge <- ret_ge[(-1),]return_multi <- cbind(ret_ko, ret_pep, ret_aapl, ret_ge) # you could insert as many return as you like
# Create a vector of returns
means <- apply(return_multi, 2, "mean")
# Create a vector of standard deviation
sds <- apply(return_multi, 2, "sd")
# Create a scatter plot
plot(sds, means)
text(sds, means, labels = colnames(return_multi), cex = 0.7)
abline(h = 0, lty = 3) Do you see any pattern/cluster here?
Now we will compute the covariance and correlation matrix on the daily returns of the four assets from the previous exercise. [This part is not really important, you can skip if you like to.]
# Create a matrix with variances on the diagonal
diag_cov <- diag(sds^2)
# Create a covariance matrix of returns
cov_matrix <- cov(return_multi)
# Create a correlation matrix of returns
cor_matrix <- cor(return_multi)
# Verify covariances equal the product of standard deviations and correlation
all.equal(cov_matrix[1,2], as.numeric(cor_matrix[1,2] * sds[1] * sds[2]))## [1] TRUE
Portfolio risk budget
# Create portfolio weights [arbitrary in this case, for practice purpose]
weights <- c(0.4, 0.4, 0.1, 0.1)
# Create volatility budget
vol_budget <- StdDev(return_multi, portfolio_method = "component", weights = weights)
# Make a table of weights and risk contribution
weights_percrisk <- cbind(weights, vol_budget$pct_contrib_StdDev)
colnames(weights_percrisk) <- c("weights", "perc vol contrib")
# Print the table
weights_percrisk## weights perc vol contrib
## KO.Adjusted 0.4 0.3980722
## PEP.Adjusted 0.4 0.3665127
## AAPL.Adjusted 0.1 0.1208515
## GE.Adjusted 0.1 0.1145636
Modern Portfolio Theory (MPT) Please read more here: https://www.investopedia.com/terms/m/modernportfoliotheory.asp NB: Post-modern portfolio theory (PMPT) attempts to improve on modern portfolio theory by minimizing downside risk instead of variance.
tickers <- c("AA","AAPL","AXP","BA","BAC","CAT","CVX","DD","DIS","GE","HD","HPQ","INTC","IBM","JNJ","JPM","KO","MCD","MMM","MRK","MSFT","NKE","PFE","PG","TRV","UTX","VZ","WMT","XOM","T")
first.date = "1991-01-01"
last.date = "2016-12-31"
getSymbols(tickers, from=first.date, to=last.date, periodicity = 'monthly')## pausing 1 second between requests for more than 5 symbols
## pausing 1 second between requests for more than 5 symbols
## pausing 1 second between requests for more than 5 symbols
## pausing 1 second between requests for more than 5 symbols
## pausing 1 second between requests for more than 5 symbols
## pausing 1 second between requests for more than 5 symbols
## pausing 1 second between requests for more than 5 symbols
## pausing 1 second between requests for more than 5 symbols
## pausing 1 second between requests for more than 5 symbols
## pausing 1 second between requests for more than 5 symbols
## pausing 1 second between requests for more than 5 symbols
## pausing 1 second between requests for more than 5 symbols
## pausing 1 second between requests for more than 5 symbols
## pausing 1 second between requests for more than 5 symbols
## pausing 1 second between requests for more than 5 symbols
## pausing 1 second between requests for more than 5 symbols
## pausing 1 second between requests for more than 5 symbols
## pausing 1 second between requests for more than 5 symbols
## pausing 1 second between requests for more than 5 symbols
## pausing 1 second between requests for more than 5 symbols
## pausing 1 second between requests for more than 5 symbols
## pausing 1 second between requests for more than 5 symbols
## pausing 1 second between requests for more than 5 symbols
## pausing 1 second between requests for more than 5 symbols
## pausing 1 second between requests for more than 5 symbols
## pausing 1 second between requests for more than 5 symbols
## [1] "AA" "AAPL" "AXP" "BA" "BAC" "CAT" "CVX" "DD" "DIS" "GE"
## [11] "HD" "HPQ" "INTC" "IBM" "JNJ" "JPM" "KO" "MCD" "MMM" "MRK"
## [21] "MSFT" "NKE" "PFE" "PG" "TRV" "UTX" "VZ" "WMT" "XOM" "T"
# The process to combine into one dataframe a bit messy here, there should be a better way
dim2 <- dim(ClosePrices[[1]])[1]
df_fin = data.frame(matrix(nrow = dim2))
for (i in 1:length(ClosePrices)){
df_fin <- cbind2(df_fin, ClosePrices[i])
}
df_fin <- df_fin[,(-1)]
head(df_fin)stocks_ret <- CalculateReturns(as.xts(df_fin))
# Drop first row because it is NA
stocks_ret <- stocks_ret[(-1),]
head(stocks_ret)## AA.Adjusted AAPL.Adjusted AXP.Adjusted BA.Adjusted BAC.Adjusted
## 1991-02-01 0.00738590 0.03153212 0.05494564 -0.02278561 0.039999967
## 1991-03-01 0.01935073 0.19027072 0.20312475 -0.02122358 0.183760096
## 1991-04-01 0.03061856 -0.19117656 -0.13419917 -0.02659554 0.119530766
## 1991-05-01 0.06291249 -0.14545558 0.05769139 0.07103800 0.142372765
## 1991-06-01 -0.05289805 -0.11476287 -0.12195144 -0.06133335 -0.151335316
## 1991-07-01 0.04575157 0.11445565 0.17330752 0.01366051 0.007422725
## CAT.Adjusted CVX.Adjusted DD.Adjusted DIS.Adjusted GE.Adjusted
## 1991-02-01 0.158603154 0.05428991 0.01425177 0.16119890 0.06249920
## 1991-03-01 -0.123569460 0.07077335 -0.08665108 -0.03546091 0.03148121
## 1991-04-01 -0.005221863 0.00486970 0.04455549 -0.03256343 0.01615781
## 1991-05-01 0.145250770 -0.00232642 0.10447728 0.02587683 0.09187313
## 1991-06-01 -0.045893550 -0.04737785 -0.04279196 -0.03879268 -0.03564176
## 1991-07-01 -0.005063306 0.02486708 0.05691846 0.08295923 -0.01013562
## HD.Adjusted HPQ.Adjusted INTC.Adjusted IBM.Adjusted JNJ.Adjusted
## 1991-02-01 0.12716758 0.19935667 0.04371534 0.01577872 0.07352834
## 1991-03-01 0.10769223 0.07238642 -0.02094163 -0.08045506 0.20858417
## 1991-04-01 0.06542781 0.07047107 0.05347557 -0.09549899 -0.01434144
## 1991-05-01 0.14565192 0.06112243 0.13198051 0.03033927 -0.04100555
## 1991-06-01 0.02751424 -0.06451397 -0.16591920 -0.03996591 -0.04555699
## 1991-07-01 0.07479246 0.10514026 0.01075172 0.04247150 0.09730511
## JPM.Adjusted KO.Adjusted MCD.Adjusted MMM.Adjusted MRK.Adjusted
## 1991-02-01 0.25714331 0.074359008 0.109649221 0.042709635 0.11612021
## 1991-03-01 0.05303128 0.035799678 0.098814082 0.008622144 0.03304804
## 1991-04-01 0.23195207 0.009444137 -0.033442535 0.007061691 0.03263350
## 1991-05-01 0.04268237 0.085307628 0.044775732 0.068724173 0.10324817
## 1991-06-01 0.00000000 -0.048035366 -0.058203050 -0.008270089 -0.02313331
## 1991-07-01 0.23712205 0.143210648 -0.003801928 -0.062750798 0.11926027
## MSFT.Adjusted NKE.Adjusted PFE.Adjusted PG.Adjusted TRV.Adjusted
## 1991-02-01 0.05732528 0.005223975 0.16828861 0.03166466 0.039215958
## 1991-03-01 0.02289115 -0.039064069 0.20558132 0.04769232 0.050943715
## 1991-04-01 -0.06713769 0.125404953 0.01869096 -0.01468473 0.022303033
## 1991-05-01 0.10858614 -0.163153496 0.08027551 0.02073631 -0.081559265
## 1991-06-01 -0.06890704 -0.091211718 0.01239828 -0.09104251 -0.030888528
## 1991-07-01 0.07889903 0.361568987 0.11486387 0.04361884 -0.001693571
## UTX.Adjusted VZ.Adjusted WMT.Adjusted XOM.Adjusted T.Adjusted
## 1991-02-01 0.04736770 0.021875800 0.071969009 0.06779708 0.08292756
## 1991-03-01 0.05399486 0.048469172 0.095406696 0.11977992 0.04418597
## 1991-04-01 -0.05154603 -0.026763616 0.046349060 0.01709385 -0.04899753
## 1991-05-01 0.01902147 -0.045856000 0.058640861 -0.02100799 0.02147340
## 1991-06-01 0.03427212 0.002652812 -0.002914778 0.04544256 0.03381714
## 1991-07-01 0.04775339 0.013227313 0.115147312 0.02150561 0.00934536
Finding the mean-variance efficient portfolio We will use portfolio.optim function from tseries https://www.rdocumentation.org/packages/tseries/versions/0.10-47/topics/portfolio.optim The only argument needed is the monthly return data on the portfolio components for which the weights need to be determined.
# Load tseries
library(tseries)
# Create an optimized portfolio of returns
opt <- portfolio.optim(stocks_ret)
# Create pf_weights
pf_weights <- opt$pw
# Assign asset names
names(pf_weights) <- colnames(stocks_ret)
# Select optimum weights opt_weights
opt_weights <- pf_weights[pf_weights >= 0.01]
# Bar plot of opt_weights
barplot(opt_weights)## [1] 0.01429198
## [1] 0.03492989
The argument that can be supplied into the portfolio.optim() are: pm: the desired mean portfolio return. riskless: a logical indicating whether there is a riskless lending and borrowing rate. shorts: a logical indicating whether shortsales on the risky securities are allowed. rf: the risfree interest rate. etc…..
This exercise will show the effect of increasing your target return on the volatility of your mean-variance efficient portfolio.
# Create portfolio with target return of average returns
pf_mean <- portfolio.optim(stocks_ret, pm = mean(stocks_ret))
# Create portfolio with target return 10% greater than average returns
pf_10plus <- portfolio.optim(stocks_ret, pm = 1.1 * mean(stocks_ret))
# Print the standard deviations of both portfolios
pf_mean$ps## [1] 0.03492989
## [1] 0.03706116
# Calculate the proportion increase in standard deviation
(pf_10plus$ps - pf_mean$ps) / (pf_mean$ps)## [1] 0.06101567
By increasing your target return, your portfolio’s volatility increased by 5%!
Computing the efficient frontier using a grid of target returns
# Calculate each stocks mean returns
stockmu <- colMeans(stocks_ret)
# Create a grid of target values
grid <- seq(from = 0.01, to = max(stockmu), length.out = 50)
# Create empty vectors to store means and deviations
vpm <- vpsd <- rep(NA, 50)
# Create an empty matrix to store weights
mweights <- matrix(NA, 50, 30)
# Create your for loop
for(i in 1:length(grid)) {
opt <- portfolio.optim(x = stocks_ret, pm = grid[i])
vpm[i] <- opt$pm
vpsd[i] <- opt$ps
mweights[i, ] <- opt$pw
}# Create weights_minvar as the portfolio with the least risk
weights_minvar <- mweights[vpsd == min(vpsd), ]
# Calculate the Sharpe ratio
vsr <- (vpm - 0.0075) / vpsd
# Create weights_max_sr as the portfolio with the maximum Sharpe ratio
weights_max_sr <- mweights[vsr == max(vsr)]
# Create bar plot of weights_minvar and weights_max_sr
par(mfrow = c(2, 1), mar = c(3, 2, 2, 1))
barplot(weights_minvar[weights_minvar > 0.01])
barplot(weights_max_sr[weights_max_sr > 0.01])In-sample vs. out-of-sample evaluation Split the dataset into two parts: 1. Estimation sample: used to find the optimal weights 2. Out of sample: evaluation to give a realistic view on portfolio performance
Split-sample evaluation
# Create returns_estim
returns_estim <- window(stocks_ret, end = "2003-12-31")
# Create returns_eval
returns_eval <- window(stocks_ret, start = "2004-01-01")
# Create vector of max weights
max_weights <- rep(0.1, ncol(stocks_ret))
# Create portfolio with estimation sample
pf_estim <- portfolio.optim(returns_estim, reshigh = max_weights)
# Create portfolio with evaluation sample
pf_eval <- portfolio.optim(returns_eval, reshigh = max_weights)
# Create a scatter plot with evaluation portfolio weights on the vertical axis
plot(pf_estim$pw, pf_eval$pw)
abline(a = 0, b = 1, lty = 3) By creating two samples using the
window() function you can backtest, or evaluate the performance of a portfolio on historical data!
Out of sample performance evaluation This example will illustrate how your returns can change based on the weighting created by an optimized portfolio. You will use the estimation portfolio (pf_estim) to evaluate the performance of your portfolio on the estimation sample of returns (returns_eval).
How severe is the optimality loss? Let’s compare, for the portfolio weights in pf_estim, the performance you expected using the evaluation sample (returns_estim) with the actual return on the out-of-sample period (returns_eval).
# Create returns_pf_estim
returns_pf_estim <- Return.portfolio(returns_estim, pf_estim$pw, rebalance_on = "months")
# Create returns_pf_eval
returns_pf_eval <- Return.portfolio(returns_eval, pf_estim$pw, rebalance_on = "months")A little bit more sophisticated Read the paper here: https://journal.r-project.org/archive/2011-1/RJournal_2011-1_Ardia~et~al.pdf You may also want to learn more about the package used in this section: https://cran.r-project.org/web/packages/PortfolioAnalytics/vignettes/portfolio_vignette.pdf
At this stage, you should already understand the terms: - Complex constraint and objective sets - Moments and its estimation - Optimization with periodic rebalancing (backtesting)
Also, for complete syntax and arguments of the package that will be used here, please refer to: https://rossb34.github.io/PortfolioAnalyticsPresentation2017/#42
library(PortfolioAnalytics)
# Load the data (included in the package)
data(indexes)
# Subset the data (take the first 4 columns only)
index_returns <- indexes[,1:4]
# Print the head of the data
head(index_returns)## US Bonds US Equities Int'l Equities Commodities
## 1980-01-31 -0.0272 0.0610 0.0462 0.0568
## 1980-02-29 -0.0669 0.0031 -0.0040 -0.0093
## 1980-03-31 0.0053 -0.0987 -0.1188 -0.1625
## 1980-04-30 0.0992 0.0429 0.0864 0.0357
## 1980-05-31 0.0000 0.0562 0.0446 0.0573
## 1980-06-30 0.0605 0.0296 0.0600 0.0533
Solve a simple portfolio optimization problem The portfolio problem is to form a minimum variance portfolio subject to full investment and long only constraints. The objective is to minimize portfolio variance. There are two constraints in this problem: the full investment constraint means that the weights must sum to 1, and the long only constraint means that all weights must be greater than or equal to 0 (i.e. no short positions are allowed).
## Registered S3 method overwritten by 'ROI':
## method from
## print.constraint PortfolioAnalytics
## ROI: R Optimization Infrastructure
## Registered solver plugins: nlminb, glpk, quadprog.
## Default solver: auto.
##
## Attaching package: 'ROI'
## The following objects are masked from 'package:PortfolioAnalytics':
##
## is.constraint, objective
# Create the portfolio specification
port_spec <- portfolio.spec(colnames(index_returns))
# Add a full investment constraint such that the weights sum to 1
port_spec <- add.constraint(portfolio = port_spec, type = "full_investment")
# Add a long only constraint such that the weight of an asset is between 0 and 1
port_spec <- add.constraint(portfolio = port_spec, type = "long_only")
# Add an objective to minimize portfolio standard deviation
port_spec <- add.objective(portfolio = port_spec, type = "risk", name = "StdDev")
# Solve the optimization problem
opt <- optimize.portfolio(index_returns, portfolio = port_spec, optimize_method = "ROI")## ***********************************
## PortfolioAnalytics Optimization
## ***********************************
##
## Call:
## optimize.portfolio(R = index_returns, portfolio = port_spec,
## optimize_method = "ROI")
##
## Optimal Weights:
## US Bonds US Equities Int'l Equities Commodities
## 0.8544 0.0587 0.0000 0.0869
##
## Objective Measure:
## StdDev
## 0.01668
## US Bonds US Equities Int'l Equities Commodities
## 0.85441777 0.05872385 0.00000000 0.08685838
Challenges of portfolio optimization Optimization is a quite challenging problem. One of them is to choose what solver to use. There are associated capabilities and limits of the chosen solver.
Example of solvers: - Closed-form solver, ex: quadratic programming - Global solver, ex: differential evolution optimization
You should understand the previous terms, and: - Quadratic utility, read more: https://www.d42.com/portfolio/analysis/quadratic-utility
Maximize quadratic utility function
# Create the portfolio specification
port_spec <- portfolio.spec(assets = colnames(index_returns))
# Add a full investment constraint such that the weights sum to 1
port_spec <- add.constraint(portfolio = port_spec, type = "full_investment")
# Add a long only constraint such that the weight of an asset is between 0 and 1
port_spec <- add.constraint(portfolio = port_spec, type = "long_only")
# Add an objective to maximize portfolio mean return
port_spec <- add.objective(portfolio = port_spec, type = "return", name = "mean")
# Add an objective to minimize portfolio variance
port_spec <- add.objective(portfolio = port_spec, type = "risk", name = "var", risk_aversion = 10)
# Solve the optimization problem
opt <- optimize.portfolio(R = index_returns, portfolio = port_spec, optimize_method = "ROI")Portfolio specification, constraints, and objectives General workflow in PortfolioAnalytics: 1. Portfolio specification 2. Add constraints and objectives 3. Run optimization 4. Analyze optimization results
Different ways of defining the assets argument of portfolio.spec()
# Char vector of assets
portfolio.spec(assets = c("SP500", "DJIA", "Nasdaq"))
# Initial weight
initial_weights = c("SP500" = 0.5, "DJIA" = 0.2, "Nasdaq" = 0.3)
portfolio.spec(assets = initial_weights)
# Scalar number of assets
portfolio.spec(assets = 4)add.constraint() has type argument, it can be weight_sum, box, full_investment
add.objective() has type argument, it can be return: maximize return risk: minimize risk We can also specify the arguments argument:
# Expected shortfall
add.objective(portfolio = p,
type = "risk",
name = "ES",
arguments = list(p = 0.9, method = "gaussian"))# Add the weight sum constraint
port_spec <- add.constraint(portfolio = port_spec, type = "weight_sum", min_sum = 1, max_sum = 1)
# Add the box constraint
port_spec <- add.constraint(portfolio = port_spec, type = "box", min = c(0.1, 0.1, 0.1, 0.1, 0.1, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05), max = 0.4)
# Add the group constraint
port_spec <- add.constraint(portfolio = port_spec, type = "group", groups = list(c(1, 5, 7, 9, 10, 11), c(2, 3, 4, 6, 8, 12)), group_min = 0.4, group_max = 0.6)
# Print the portfolio specification object
print(port_spec)# Add a return objective to maximize mean return
port_spec <- add.objective(portfolio = port_spec, type = "return", name = "mean")
# Add a risk objective to minimize portfolio standard deviation
port_spec <- add.objective(portfolio = port_spec, type = "risk", name = "StdDev")
# Add a risk budget objective
port_spec <- add.objective(portfolio = port_spec, type = "risk_budget", name = "StdDev", min_prisk = 0.05, max_prisk = 0.1)
# Print the portfolio specification object
print(port_spec)Running Optimizations Single period optimization: optimize.portfolio() Optimization with periodic rebalancing (backtesting): optimize.porfolio.rebalancing()
The following optimizations are supported: 1. Global Solvers: - DEoptim - random - GenSA - pso 2. LP and QP SOlvers: - ROI
Single-Period Optimization In many cases, it is useful to specify trace = TRUE to store additional information for each iteration/trial of the optimization.
The optimization method you choose should be based on the type of problem you are solving. For example, a problem that can be formulated as a quadratic programming problem should be solved using a quadratic programming solver, whereas a non-convex problem should be solved using a global solver such as DEoptim.
In this exercise, we will define the portfolio optimization problem to maximize mean return and minimize portfolio standard deviation with a standard deviation risk budget where the minimum percentage risk is 5% and the maximum percentage risk is 10%, subject to full investment and long only constraints. The risk budget objective requires a global solver so we will solve the problem using random portfolios. The set of random portfolios, rp, is generated using 500 permutations for this exercise.
# Run a single period optimization using random portfolios as the optimization method
opt <- optimize.portfolio(R = asset_returns, portfolio = port_spec, optimize_method = "random", rp = rp, trace = TRUE)Optimization with periodic rebalancing Running the optimization with periodic rebalancing and analyzing the out-of-sample results of the backtest is an important step to better understand and potentially refine the constraints and objectives.
Run the optimization with quarterly rebalancing. Set the training period and rolling window to 60 periods. The dataset is monthly data so we are using 5 years of historical data. Assign the optimization output to a variable named opt_rebal.
# Run the optimization backtest with quarterly rebalancing
opt_rebal <- optimize.portfolio.rebalancing(R = asset_returns, portfolio = port_spec, optimize_method = "random", rp = rp, trace = TRUE, search_size = 1000, rebalance_on = "quarters", training_period = 60, rolling_window = 60)Analyzing optimization results
# Create the portfolio specification
port_spec <- portfolio.spec(colnames(index_returns))
# Add a full investment constraint such that the weights sum to 1
port_spec <- add.constraint(portfolio = port_spec, type = "full_investment")
# Add a long only constraint such that the weight of an asset is between 0 and 1
port_spec <- add.constraint(portfolio = port_spec, type = "long_only")
port_spec <- add.constraint(portfolio = port_spec, type = "box", min = c(0.1, 0.1, 0.1, 0.1), max = 0.4)
# Add a return objective to maximize mean return
port_spec <- add.objective(portfolio = port_spec, type = "return", name = "mean")
# Add a risk objective to minimize portfolio standard deviation
port_spec <- add.objective(portfolio = port_spec, type = "risk", name = "StdDev")
# Add a risk budget objective
port_spec <- add.objective(portfolio = port_spec, type = "risk_budget", name = "StdDev", min_prisk = 0.05, max_prisk = 0.1)
# Solve the optimization problem
opt <- optimize.portfolio.rebalancing(index_returns, portfolio = port_spec, optimize_method = "random", trace = TRUE, search_size = 1000, rebalance_on = "quarters")The objective measures are an important component of the optimization output to analyze. An important use case looks at the result of each iteration/trial of the optimizer. This can give you insight into how the optimizer approaches the optimal solution and potentially tune parameters. We can also analyse the distribution of the in-sample objective values of the optimal portfolios and compare to out-of-sample performance.
## mean StdDev StdDev.contribution.US Bonds
## 1982-12-31 0.007779667 0.03230061 0.011002799
## 1983-03-31 0.008704236 0.03122884 0.010506644
## 1983-06-30 0.009340610 0.03030232 0.009940392
## 1983-09-30 0.008984658 0.02936981 0.009718725
## 1983-12-31 0.008858042 0.02850340 0.009395224
## 1984-03-31 0.008300694 0.02725365 0.008660010
## StdDev.contribution.US Equities StdDev.contribution.Int'l Equities
## 1982-12-31 0.012515941 0.004375616
## 1983-03-31 0.012094573 0.004256677
## 1983-06-30 0.011957465 0.004187614
## 1983-09-30 0.011630423 0.004071917
## 1983-12-31 0.011309052 0.003952631
## 1984-03-31 0.009040919 0.003821513
## StdDev.contribution.Commodities StdDev.pct_contrib_StdDev.US Bonds
## 1982-12-31 0.004406256 0.3406375
## 1983-03-31 0.004370941 0.3364405
## 1983-06-30 0.004216849 0.3280406
## 1983-09-30 0.003948744 0.3309087
## 1983-12-31 0.003846490 0.3296177
## 1984-03-31 0.005731212 0.3177559
## StdDev.pct_contrib_StdDev.US Equities
## 1982-12-31 0.3874831
## 1983-03-31 0.3872886
## 1983-06-30 0.3946056
## 1983-09-30 0.3959993
## 1983-12-31 0.3967616
## 1984-03-31 0.3317324
## StdDev.pct_contrib_StdDev.Int'l Equities
## 1982-12-31 0.1354654
## 1983-03-31 0.1363060
## 1983-06-30 0.1381945
## 1983-09-30 0.1386430
## 1983-12-31 0.1386723
## 1984-03-31 0.1402202
## StdDev.pct_contrib_StdDev.Commodities
## 1982-12-31 0.1364140
## 1983-03-31 0.1399649
## 1983-06-30 0.1391593
## 1983-09-30 0.1344491
## 1983-12-31 0.1349485
## 1984-03-31 0.2102915
Nice! Studing the objective measures for a rebalanced portfolio can provide insights into how it changed over time.
Optimal weights
## US Bonds US Equities Int'l Equities Commodities
## 1982-12-31 0.400 0.308 0.120 0.172
## 1983-03-31 0.400 0.308 0.120 0.172
## 1983-06-30 0.400 0.308 0.120 0.172
## 1983-09-30 0.400 0.308 0.120 0.172
## 1983-12-31 0.400 0.308 0.120 0.172
## 1984-03-31 0.394 0.260 0.116 0.230
Introduction to moments The input to the portfolio optimizations are: - Assets - Constraints - Objectives - Moments of assets returns Moments of assets returns need to be estimated. Better estimates -> better results.
Asset return moments are properties of the distribution of asset returns. Please read more here: https://quant.stackexchange.com/questions/54391/any-portfolio-models-not-based-on-asset-return-moments https://orfe.princeton.edu/~jqfan/fan/FinEcon/chap1.pdf
In principle, First moment: expected return vector Second moment: variance-covariance matrix Third moment: coskewness matrix Fourth moment: cokurtosis matrix Which moment needs to be estimated? It’s depends on the objective and the constraints defined before. For example, if our objective is to minimize variance and maximize mean (Mean-Variance framework), we need to estimate first moment and second moment
However, research by Ledoit and Wolf (2003) stated that we should not use sample covariance matrix for the purpose of portfolio optimization.
In set.portfolio.moments(), several methods supported (method argument): sample, boudt, black_litterman, meucci
Simple sample moments might not always be a good estimate. Let’s look at some other methods for estimating moments.
# Fit a statistical factor model to the asset returns
fit <- statistical.factor.model(R = asset_returns, k = 3)
# Estimate the portfolio moments using the "boudt" method with 3 factors
moments_boudt <- set.portfolio.moments(R = asset_returns, portfolio = port_spec, method = "boudt", k = 3)You can also dfine a custom function to estimate moments.
# Define custom moment function
moments_robust <- function(R, portfolio){
out <- list()
out$sigma <- cov.rob(R, method = "mcd")$cov
out
}
# Estimate the portfolio moments using the function you just defined
moments <- moments_robust(R = asset_returns, portfolio = port_spec)
# Check the moment estimate
cov.rob(asset_returns, method = "mcd")$cov == moments$sigma# Run the optimization with custom moment estimates
opt_custom <- optimize.portfolio(R = asset_returns, portfolio = port_spec, optimize_method = "random", rp = rp, momentFUN = "moments_robust")
# Print the results of the optimization with custom moment estimates
print(opt_custom)
# Run the optimization with sample moment estimates
opt_sample <- optimize.portfolio(R = asset_returns, portfolio = port_spec, optimize_method = "random", rp = rp)
# Print the results of the optimization with sample moment estimates
print(opt_sample)Custom objective function
# Custom annualized portfolio standard deviation
pasd <- function(R, weights, sigma, scale = 12){
sqrt(as.numeric(t(weights) %*% sigma %*% weights)) * sqrt(scale)
}# Add custom objective to portfolio specification
port_spec <- add.objective(portfolio = port_spec, type = "risk", name = "pasd")
# Print the portfolio specificaton object
print(port_spec)
# Run the optimization
opt <- optimize.portfolio(R = asset_returns, portfolio = port_spec, momentFUN = set_sigma, optimize_method = "random", rp = rp)
# Print the results of the optimization
print(opt)Application Compute benchmark returns In this exercise, we will create a benchmark to evaluate the performance of the optimization models in later exercises. An equal weight benchmark is a simple weighting scheme to construct the benchmark portfolio. The intuition of an equal weight approach is that there is no preference for any given asset. We are setting this up to answer the question, “Can optimization outperform a simple weighting scheme to construct a portfolio?”
# Load the package
library(PortfolioAnalytics)
# Load the data
data(edhec)
# Assign the data to a variable
asset_returns <- edhec
# Create a vector of equal weights
equal_weights <- rep(1 / ncol(asset_returns), ncol(asset_returns))
# Compute the benchmark returns
r_benchmark <- Return.portfolio(R = asset_returns, weights = equal_weights, rebalance_on = "quarters")
colnames(r_benchmark) <- "benchmark"
# Plot the benchmark returns
plot(r_benchmark) Great! A good benchmark is key to know how your portfolio is performing.
Define the portfolio optimization problem We define the portfolio optimization problem to minimize portfolio standard deviation subject to full investment and long only constraints. In this problem, we will set up the portfolio specification based on the defined problem. The following exercises in this chapter will build on the initial portfolio specification set up here.
# Create the portfolio specification
port_spec <- portfolio.spec(colnames(asset_returns))
# Add a full investment constraint such that the weights sum to 1
port_spec <- add.constraint(portfolio = port_spec, type = "full_investment")
# Add a long only constraint such that the weight of an asset is between 0 and 1
port_spec <- add.constraint(portfolio = port_spec, type = "long_only")
# Add an objective to minimize portfolio standard deviation
port_spec <- add.objective(portfolio = port_spec, type = "risk", name = "StdDev")
# Print the portfolio specification
print(port_spec)## **************************************************
## PortfolioAnalytics Portfolio Specification
## **************************************************
##
## Call:
## portfolio.spec(assets = colnames(asset_returns))
##
## Number of assets: 13
## Asset Names
## [1] "Convertible Arbitrage" "CTA Global" "Distressed Securities"
## [4] "Emerging Markets" "Equity Market Neutral" "Event Driven"
## [7] "Fixed Income Arbitrage" "Global Macro" "Long/Short Equity"
## [10] "Merger Arbitrage"
## More than 10 assets, only printing the first 10
##
## Constraints
## Enabled constraint types
## - full_investment
## - long_only
##
## Objectives:
## Enabled objective names
## - StdDev
Backtest with periodic rebalancing Now we will run the backtest using the portfolio specification created in the last exercise with quarterly rebalancing to evaluate out-of-sample performance. The other backtest parameters we need to set are the training period and rolling window. The training period sets the number of data points to use for the initial optimization. The rolling window sets the number of periods to use in the window. This problem can be solved with a quadratic programming solver so we will use “ROI” for the optimization method.
# Run the optimization
opt_rebal_base <- optimize.portfolio.rebalancing(R = asset_returns,
portfolio = port_spec,
optimize_method = "ROI",
rebalance_on = "quarters",
training_period = 60,
rolling_window = 60)
# Print the results
print(opt_rebal_base)## **************************************************
## PortfolioAnalytics Optimization with Rebalancing
## **************************************************
##
## Call:
## optimize.portfolio.rebalancing(R = asset_returns, portfolio = port_spec,
## optimize_method = "ROI", rebalance_on = "quarters", training_period = 60,
## rolling_window = 60)
##
## Number of rebalancing dates: 73
## First rebalance date:
## [1] "2001-12-31"
## Last rebalance date:
## [1] "2019-11-30"
##
## Annualized Portfolio Rebalancing Return:
## [1] 0.03124747
##
## Annualized Portfolio Standard Deviation:
## [1] 0.01725335
# Compute the portfolio returns
returns_base <- Return.portfolio(R = asset_returns, weights =extractWeights(opt_rebal_base))
colnames(returns_base) <- "base"
head(returns_base)## base
## 2002-01-31 0.008543610
## 2002-02-28 0.001321513
## 2002-03-31 0.003618855
## 2002-04-30 0.009467312
## 2002-05-31 0.006848921
## 2002-06-30 0.003555712
Refine constraints and objectives Here we hypothesize that refining constraints and/or objectives will improve performance. Let us add a risk budget objective to set a minimum and maximum percentage contribution to risk for each asset. We will be building on the portfolio specification we created. This is a more complex optimization problem and will require a global solver so we will use random portfolios as the optimization method.
Add a risk budget objective risk_budget to port_spec where risk is defined as standard deviation. Set the minimum percentage risk to 5% and the maximum percentage risk to 10%.
# Add a risk budge objective
port_spec <- add.objective(portfolio = port_spec,
type = "risk_budget",
name = "StdDev",
min_prisk = 0.05,
max_prisk = 0.10)
# Run the optimization
opt_rebal_rb <- optimize.portfolio.rebalancing(R = asset_returns,
portfolio = port_spec,
optimize_method = "random", rp = rp,
trace = TRUE,
rebalance_on = "quarters",
training_period = 60,
rolling_window = 60)## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
## Leverage constraint min_sum and max_sum are restrictive,
## consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
# Chart the percentage contribution to risk
chart.RiskBudget(opt_rebal_rb, match.col = "StdDev", risk.type = "percentage")# Compute the portfolio returns
returns_rb <- Return.portfolio(R = asset_returns, weights = extractWeights(opt_rebal_rb))
colnames(returns_rb) <- "risk_budget"Do improved estimates lead to improved performance? Let us hypothesize that using a robust estimate of the variance-covariance matrix will outperform the sample variance covariance matrix. In theory, better estimates should lead to better results. We will use the moments_robust() function that was defined in chapter 3 and the portfolio specification from the last exercise.
# Define custom moment function
moments_robust <- function(R, portfolio){
out <- list()
out$sigma <- cov.rob(R, method = "mcd")$cov
out
}# Run the optimization
opt_rebal_rb_robust <- optimize.portfolio.rebalancing(R = asset_returns,
momentFUN = "moments_robust",
portfolio = port_spec,
optimize_method = "random", rp = rp,
trace = TRUE,
rebalance_on = "quarters",
training_period = 60,
rolling_window = 60)# Chart the percentage contribution to risk
chart.RiskBudget(opt_rebal_rb_robust, match.col = "StdDev", risk.type = "percentage")Analyze results and compare to benchmark
# Combine the returns
ret <- cbind(r_benchmark, returns_base, returns_rb)
# Compute annualized returns
table.AnnualizedReturns(R = ret)