Sunday, July 26, 2015

[12] Portfolio Optimization: Black-Litterman Model

This section is the summary of Reference (1). Also, some modified sample codes are added so that readers can easily follow and understand it.

1. Overview
  • The Black-Litterman model's goal is to create a systematic method of specifying and then incorporating analyst / portfolio manager views into the estimation of market parameters (e.g., returns of assets or securities).
  • R = {r1, r2, ... , rn}
    • a set of returns (random variables) of n assets or securities
    • R ~ N(μ, Σ): multivariate normal distribution with mean μ and standard deviation Σ
  • μ
    • μ itself is a random variable which is normally distributed.
    • μ ~ N(π, τΣ): its dispersion is proportional to that of the market.
    • π: underlying parameters which can be determined by the analyst using some established procedure (obtained from the intercepts of the CAPM)
  • Next, the analyst forms subjective views on the actual mean of the returns for the holding period. This is the part of the model that allows the analyst or portfolio manager to include his or her views. BL proposed that views should be made on linear combinations (i.e., portfolios) of the asset return variable means μ. Each view would take the form of a "mean plus error." Thus, for example, a typical view would look like as follows:
    • pi1μ1 + pi2μ2 + ... + pinμn = q+ εi
    • εi ~ N(0, σi2)
      • The variance σi2 of each view could be taken as controlling the confidence in each view.
  • Collecting these views into a matrix we will call the "pick" matrix (a vector of long and short for each asset / security), we obtain the "general" view specification:
    • Pμ ~ N(μ, Ω)
    • Ω is the diagonal matrix diag(σ12, σ22, ..., σn2).
    • It can be shown, based on Bayes' Law, that the posterior distribution of the market mean conditional on these views is:
      • μ|q;Ω ~ N(μBL, ΣBLμ)
      • μBL    = ((τΣ)-1 + PTΩ-1 P)-1((τΣ)-1 π + PTΩ-1 q)
      • ΣBLμ = ((τΣ)-1 + PTΩ-1 P)-1
  • We can then obtain the posterior distribution of the market by taking R|q;Ω = μ|q;Ω  + Z, and Z  ~ N(0, Σ) is independent of μ. One then obtains E[R] = μBL and ΣBL = Σ + ΣBLμ (See Reference (2), page 5.).


2. Importing BLCOP

BLCOP
http://cran.r-project.org/package=BLCOP
Windows binaries: r-release: BLCOP_0.3.1.zip
OS X Snow Leopard binaries: r-oldrel: BLCOP_0.3.1.tgz
OS X Mavericks binaries: r-release: not available 

RUnit
http://cran.r-project.org/package=RUnit
Windows binaries: r-release: RUnit_0.4.28.zip
OS X Mavericks binaries: r-release: RUnit_0.4.28.tgz

timeSeriesfBasicsfMultivar, and fPortfolio
In the previous session, we have already installed these packages.


sample code(s)
# RUnit: R Unit Test Framework
install.packages("~/Google Drive/Finance/R/RUnit_0.4.28.tgz", repos = NULL, type="source")
library(RUnit)

# BLCOP: Black-Litterman and Copula Opinion Pooling Frameworks
install.packages("~/Google Drive/Finance/R/BLCOP_0.3.1.tgz", repos = NULL, type="source")
library(BLCOP)



3. BLCOP: Black-Litterman Model Optinization - a simple example

The implementation of the Black-Litterman model in BLCOP is based on objects that represent:
  • views on the market, and the
  • posterior distribution of the market after blending the views above.
Suppose that an analyst wishes to form views on 6 stocks, 2 of which are technology stocks and the other 4 of which are from the financial sector.

Initially, you believe that the average of 2 tech stocks (e.g., DELL, IBM) will outperform one of the 4 financial stocks (e.g., MS), say, (1/2)(IBM + DELL) - MS = (1/2)(IBM) - MS + (1/2)(DELL),  ~ N(0.06, 0.01), from return perspective. We have 3 stocks in total here.

We will create a BLViews class object w/ the BLViews constructor function. Its arguments are the "pick" matrix P, the vector "q" (0.06 here), and the names of the assets in one's "universe."

sample code(s)
c(1/2, -1, 1/2, rep(0,3))
[1]  0.5 -1.0  0.5  0.0  0.0  0.0

pickMatrix <- matrix(c(1/2, -1, 1/2, rep(0, 3)), nrow = 1, ncol = 6)

views <- BLViews(P=pickMatrix, q=0.06, confidences=100,
assetNames=colnames(monthlyReturns))

views

1 : 0.5*IBM+-1*MS+0.5*DELL=0.06  + eps. Confidence: 100 


Next, we need to determine the "prior" distribution of these assets. For instance, the mean is set to zero, and then calculate the variance-covariance matrix of these through some standard estimation procedure (e.g., exponentially weighted moving average with a half-life of 6-month). Here we use cov.mve from the MASS package.


sample code(s)
rep(0,6)
[1] 0 0 0 0 0 0
priorMeans <- rep(0,6)

monthlyReturns


                    IBM           MS          DELL             C          JPM          BAC
1998-02-02  0.057620253  0.195786228  0.4066773934  0.1224778047  0.157384084  0.143954576
1998-03-02 -0.005457679  0.043833262 -0.5156562768  0.0785547367  0.087215863  0.064817518
1998-04-01  0.115529027  0.082338411  0.1918819188  0.0198333333  0.027283511  0.041952290
1998-05-01  0.014067489 -0.010270065  0.0205572755  0.0009805524 -0.018908776 -0.006578947
1998-06-01 -0.022893617  0.170509864  0.1261982769 -0.0101224490 -0.444607915  0.015761589
1998-07-01  0.154080655 -0.047170844  0.1700247818  0.1091868712  0.001589404  0.039900900
1998-08-03 -0.150037736 -0.333103607 -0.0791048900 -0.3400743494 -0.305739222 -0.278996865
1998-09-01  0.141005150 -0.257147778 -0.3425000000 -0.1550247859 -0.178476190 -0.069565217
1998-10-01  0.155642023  0.507071644 -0.0038022814  0.2533333333  0.317180617  0.074766355
1998-11-02  0.111986532  0.071076923 -0.0716030534  0.0691489362  0.116704805  0.133739130
1998-12-01  0.116574820  0.019821890  0.2035849367 -0.0111442786  0.119167718 -0.077619267
1999-01-04 -0.006128647  0.220000000  0.3663068725  0.1281948078  0.083661972  0.112090471
1999-02-01 -0.073669850  0.044793350 -0.1988000000  0.0479843025  0.034832337 -0.023328847
1999-03-01  0.044182622  0.104309392 -0.4897653520  0.0873191489  0.021979402  0.081304548
1999-04-01  0.180197461 -0.007504503  0.0075831703  0.1720413275  0.013887182  0.013310677
1999-05-03 -0.445480185 -0.027119669 -0.1638747269 -0.1151328970 -0.122787879 -0.096003354
1999-06-01  0.114224138  0.063419689  0.0743321719 -0.2830188679  0.195246649  0.133250889
1999-07-01 -0.027543520 -0.120541805  0.1048648649 -0.0618947368 -0.109132948 -0.094666485
1999-08-02 -0.008990373 -0.049196676  0.1939823875 -0.0026929982  0.086036854 -0.088443574
1999-09-01 -0.028580604  0.039389349 -0.1434132350 -0.0099009901 -0.099414506 -0.079504132
1999-10-01 -0.188016529  0.236797847 -0.0401817747  0.2329545455  0.157622396  0.157119770
1999-11-01  0.048956743  0.093463875  0.0715175679 -0.0068202765 -0.114613181 -0.090471757
1999-12-01  0.046671842  0.183468745  0.1860465116  0.0335931700  0.005695793 -0.143661491
2000-01-03  0.040604431 -0.535901926 -0.2462745098  0.0235230742  0.038615008 -0.034867503
2000-02-01 -0.084632517  0.063245283  0.0616545265 -0.0921052632 -0.013260627 -0.050371594
2000-03-01  0.152019465  0.176462237  0.3217348689  0.1571014493  0.095076614  0.140000000
2000-04-03 -0.058038354 -0.073850609 -0.0706340378 -0.0146960588 -0.172840922 -0.065598780
2000-05-01 -0.037578475 -0.068403909 -0.1396369439  0.0540677966  0.035635053  0.131428571
2000-06-01  0.020967291  0.164335664  0.1432877348 -0.0311947258 -0.383317713 -0.224386724
2000-07-03  0.024552756  0.096096096 -0.1089028595  0.1701244813  0.081415545  0.101860465
2000-08-01  0.176124722  0.178958904 -0.0070550751 -0.1717730496  0.121863080  0.130856902
2000-09-01 -0.146947432 -0.150027886 -0.2938345175 -0.0741565337 -0.173407301 -0.022396417
2000-10-02 -0.125377375 -0.121719160 -0.0425186628 -0.0264520903 -0.014938298 -0.082474227
2000-11-01 -0.050761421 -0.210808119 -0.3474576271 -0.0535816074 -0.189450549 -0.168955472
2000-12-01 -0.090909091  0.250394446 -0.0940259740  0.0250953624  0.232104121  0.148723085
2001-01-02  0.317647059  0.070031546  0.4977064220  0.0961613788  0.210167254  0.173060157
2001-02-01 -0.108035714 -0.231957547 -0.1627105666 -0.1213149902 -0.151482088 -0.057971014
2001-03-01 -0.037237237 -0.178565945  0.1746684957 -0.0854005693 -0.037719674  0.079881657
2001-04-02  0.197130381  0.173644860  0.0214091086  0.0927078702  0.068596882  0.022831050
2001-05-01 -0.029008164  0.035355948 -0.0716463415  0.0427263479  0.024385160  0.058035714
2001-06-01  0.015205725 -0.011998154  0.0734811166  0.0310243902 -0.095218718  0.013164557
2001-07-02 -0.073039648 -0.068659505  0.0298279159 -0.0497728993 -0.026309872  0.059803432
2001-08-01 -0.049995248 -0.108157807 -0.2060898626 -0.0878311093 -0.090069284 -0.033322854
2001-09-04 -0.082341171 -0.131208997 -0.1333021515 -0.1157205240 -0.133248731 -0.050406504
2001-10-01  0.178259922  0.055447681  0.2941176471  0.1239506173  0.035431918  0.010102740
2001-11-01  0.069584529  0.134505315  0.1647206005  0.0522847100  0.066742081  0.040515342
2001-12-03  0.046457306  0.007927928 -0.0268528464  0.0538622129 -0.036320255  0.025578364
2002-01-02 -0.108052249 -0.016803718  0.0114054452 -0.0610142631 -0.063273728  0.001270850
2002-02-01 -0.090555195 -0.106909091 -0.1018552201 -0.0453586498 -0.140969163  0.014596224
2002-03-01  0.059926620  0.166734528  0.0575131632  0.0943646409  0.218803419  0.063643471
2002-04-01 -0.194615385 -0.167335544  0.0088088855 -0.1256058158 -0.015427770  0.065568950
2002-05-01 -0.039517670 -0.047359598  0.0193621868 -0.0027713626  0.024216524  0.045943709
2002-06-03 -0.105034183 -0.052353718 -0.0264432030 -0.1025937934 -0.056467316 -0.071890252
2002-07-01 -0.022222222 -0.063370474 -0.0462892119 -0.1344516129 -0.264150943 -0.054860716
2002-08-01  0.070738636  0.058736059  0.0677898115 -0.0235539654  0.057692308  0.053834586
2002-09-03 -0.226452640 -0.206928839 -0.1168294515 -0.0946564885 -0.280681818 -0.089611872
2002-10-01  0.353798662  0.148760331  0.2169289664  0.2462057336  0.092680358  0.094043887
2002-11-01  0.101089435  0.162384378 -0.0003495281  0.0522327470  0.213012048  0.004011461
2002-12-02 -0.108375518 -0.117595049 -0.0650349650 -0.0949074074 -0.046483909 -0.007277397
2003-01-02  0.009032258 -0.050601202 -0.1077038145 -0.0238704177 -0.027500000  0.006899526
2003-02-03 -0.003196931 -0.027704485  0.1299245599 -0.0294032023 -0.028277635 -0.011563169
2003-03-03  0.006157793  0.040705563  0.0129821958  0.0332933413  0.045414462 -0.034662045
2003-04-01  0.082493944  0.166883963  0.0611497620  0.1393323657  0.237874315  0.107869539
2003-05-01  0.036984688  0.022346369  0.0824706694  0.0450955414  0.119591141  0.002025658
2003-06-02 -0.062925943 -0.065573770  0.0149824673  0.0433934666  0.040170420  0.065094340
2003-07-01 -0.015151515  0.109707602  0.0577889447  0.0467289720  0.025453482  0.044793117
2003-08-01  0.009353846  0.028456998 -0.0314726841 -0.0323660714 -0.023680456 -0.040208308
2003-09-02  0.077063773  0.034228325  0.0245248314  0.0498269896  0.003214494 -0.015268139
2003-10-01  0.013019359  0.087395957  0.0771992819  0.0415293342  0.045732595 -0.029600205
2003-11-03  0.011846223  0.007472207 -0.0397222222 -0.0078059072 -0.013927577 -0.003961442
2003-12-01  0.023635962  0.046852388 -0.0170668209  0.0321071656  0.037570621  0.066286623
attr(,"na.action")
1998-02-02
         1
attr(,"class")
[1] "omit"
attr(,"control")
method
   "r"

priorVarcov <- cov.mve(monthlyReturns)$cov

priorVarcov

             IBM          MS        DELL           C         JPM         BAC
IBM  0.010743588 0.006899941 0.006875766 0.006333900 0.005729608 0.002179980
MS   0.006899941 0.013216783 0.010115206 0.007579162 0.008996890 0.003032132
DELL 0.006875766 0.010115206 0.022510315 0.006662863 0.008328882 0.003206410
C    0.006333900 0.007579162 0.006662863 0.007640900 0.006616338 0.003439520
JPM  0.005729608 0.008996890 0.008328882 0.006616338 0.011606268 0.004166217
BAC  0.002179980 0.003032132 0.003206410 0.003439520 0.004166217 0.004733423




"Posterior" distribution of assets

sample code(s)
# The posteriorEst takes as parameters the view object, the prior covariance and mean, and "tau (τ)." The procedure to define τ is controversial, but let's say it's 1/2 here.

marketPosterior <- posteriorEst(views = views, sigma = priorVarcov, mu = priorMeans, tau = 1/2)

marketPosterior 

Prior means:
 IBM   MS DELL    C  JPM  BAC
   0    0    0    0    0    0
Posterior means:
          IBM            MS          DELL             C           JPM           BAC
 0.0040991717 -0.0101081324  0.0098261425 -0.0023198538 -0.0042234721 -0.0007275142
Posterior covariance:
             IBM          MS        DELL           C         JPM         BAC
IBM  0.016050145 0.010510778 0.010157271 0.009537769 0.008661627 0.003281547
MS   0.010510778 0.019428497 0.015558420 0.011277704 0.013329592 0.004519647
DELL 0.010157271 0.015558420 0.033390619 0.010082793 0.012654443 0.004837369
C    0.009537769 0.011277704 0.010082793 0.011440456 0.009886468 0.005152728
JPM  0.008661627 0.013329592 0.012654443 0.009886468 0.017340150 0.006237397
BAC  0.003281547 0.004519647 0.004837369 0.005152728 0.006237397 0.007098080




Now suppose that we wish to add another view, this time on the average of the four financial stocks. This can be done conveniently with addBLViews as in the following example:

sample code(s)
finViews <- matrix(ncol = 4, nrow = 1, dimnames = list(NULL, c("C", "JPM", "BAC", "MS" )))

finViews

      C JPM BAC MS
[1,] NA  NA  NA NA

rep(1/4,4)

[1] 0.25 0.25 0.25 0.25

finViews[,1:4] <- rep(1/4,4)

finViews[,1:4]

   C  JPM  BAC   MS
0.25 0.25 0.25 0.25

# addBLViews(finViews, Mean, Confidence, views)

# q: 0.15
# Confidence: 90
views <- addBLViews(finViews, 0.15, 90, views)

views

1 : 0.5*IBM+-1*MS+0.5*DELL=0.06  + eps. Confidence: 100
2 : 0.25*MS+0.25*C+0.25*JPM+0.25*BAC=0.15  + eps. Confidence: 90  



We will now reproduce the posterior, but this time using the CAPM to compute the "prior" means. Rather than manually computing these, it is convenient to use the BLPosterior wrapper function. It will compute these "alphas," as well as the variance-covariance matrix of a returns series, and will the call posteriorEst automatically.

sample code(s)
marketPosterior <- BLPosterior(as.matrix(monthlyReturns), views, tau = 1/2, marketIndex = as.matrix(sp500Returns), riskFree = as.matrix(US13wTB))

marketPosterior

Prior means:
        IBM          MS        DELL           C         JPM         BAC
0.020883598 0.059548398 0.017010062 0.014492325 0.027365230 0.002829908
Posterior means:
       IBM         MS       DELL          C        JPM        BAC
0.06344562 0.07195806 0.07777653 0.04030821 0.06884519 0.02592776
Posterior covariance:
             IBM          MS        DELL           C         JPM         BAC
IBM  0.021334221 0.010575532 0.012465444 0.008518356 0.010605748 0.005281807
MS   0.010575532 0.031231768 0.017034827 0.012704758 0.014532900 0.008023646
DELL 0.012465444 0.017034827 0.047250599 0.007386821 0.009352949 0.005086150
C    0.008518356 0.012704758 0.007386821 0.016267422 0.010968240 0.006365457
JPM  0.010605748 0.014532900 0.009352949 0.010968240 0.028181136 0.011716834
BAC  0.005281807 0.008023646 0.005086150 0.006365457 0.011716834 0.011199343




Both BLPosterior (2nd) and posteriorEst (1st) have a kappa parameter which may be used to replace the matrix Ω of confidences in the posterior calculation. If it is greater than 0, then Ω is set to κPTΣP rather than diag(σ12, σ22, ..., σn2). This choice of Ω is suggested by several authors, and it leads to the confidences being determined by volatilities of the asset returns.

A user may also be interested in comparing allocations that are optimal under the prior and posterior distributions. The fPortfolio package of the Rmetrics project ([RmCTWu09]), for example, has a rich set of functionality available for portfolio optimization. The helper function optimalPortfolios.fPort was created to wrap these functions for exploratory purposes.


sample code(s)

library(fPortfolio)

optPorts <- optimalPortfolios.fPort(marketPosterior, optimizer = "tangencyPortfolio")

optPorts

$priorOptimPortfolio

Title:
 MV Tangency Portfolio
 Estimator:         getPriorEstim
 Solver:            solveRquadprog
 Optimize:          minRisk
 Constraints:       LongOnly

Portfolio Weights:
   IBM     MS   DELL      C    JPM    BAC
0.0765 0.9235 0.0000 0.0000 0.0000 0.0000

Covariance Risk Budgets:
 IBM   MS DELL    C  JPM  BAC
                           

Target Returns and Risks:
  mean     mu    Cov  Sigma   CVaR    VaR
0.0000 0.0566        0.1460 0.0000 0.0000

Description:
 Fri Jul 17 16:41:29 2015 by user: xxxxxxx

$posteriorOptimPortfolio

Title:
 MV Tangency Portfolio
 Estimator:         getPosteriorEstim
 Solver:            solveRquadprog
 Optimize:          minRisk
 Constraints:       LongOnly

Portfolio Weights:
   IBM     MS   DELL      C    JPM    BAC
0.3633 0.1966 0.1622 0.0000 0.2779 0.0000

Covariance Risk Budgets:
 IBM   MS DELL    C  JPM  BAC
                           

Target Returns and Risks:
  mean     mu    Cov  Sigma   CVaR    VaR
0.0000 0.0689        0.1268 0.0000 0.0000

Description:
 Fri Jul 17 16:41:29 2015 by user: xxxxxxx

attr(,"class")
[1] "BLOptimPortfolios"

mfcol = c(2, 1)

par(mfcol = c(2, 1))

mfcol

[1] 2 1
weightsPie(optPorts$priorOptimPortfolio)
weightsPie(optPorts$posteriorOptimPortfolio)






Additional parameters may be passed into function to control the optimization process. Users are referred to the fPortfolio package documentation for details.


sample code(s)
optPorts2 <- optimalPortfolios.fPort(marketPosterior, constraints = "minW[1:6]=0.1", optimizer = "minriskPortfolio")

optPorts2

rOptimPortfolio

Title:
 MV Minimum Risk Portfolio
 Estimator:         getPriorEstim
 Solver:            solveRquadprog
 Optimize:          minRisk
 Constraints:       minW

Portfolio Weights:
   IBM     MS   DELL      C    JPM    BAC
0.1137 0.1000 0.1000 0.1098 0.1000 0.4764

Covariance Risk Budgets:
 IBM   MS DELL    C  JPM  BAC
                           

Target Returns and Risks:
  mean     mu    Cov  Sigma   CVaR    VaR
0.0000 0.0157        0.0864 0.0000 0.0000

Description:
 Fri Jul 17 17:02:23 2015 by user: xxxxxxx

$posteriorOptimPortfolio

Title:
 MV Minimum Risk Portfolio
 Estimator:         getPosteriorEstim
 Solver:            solveRquadprog
 Optimize:          minRisk
 Constraints:       minW

Portfolio Weights:
   IBM     MS   DELL      C    JPM    BAC
0.1000 0.1000 0.1000 0.1326 0.1000 0.4674

Covariance Risk Budgets:
 IBM   MS DELL    C  JPM  BAC
                           

Target Returns and Risks:
  mean     mu    Cov  Sigma   CVaR    VaR
0.0000 0.0457        0.1008 0.0000 0.0000

Description:
 Fri Jul 17 17:02:23 2015 by user: xxxxxxx

attr(,"class")
[1] "BLOptimPortfolios"




Finally, density plots of marginal prior and posterior distributions can be generated with densityPlots. As we will see in the next section, this gives more interesting results when used with copula opinion pooling.

sample code(s)
# Please close the former graph window before running the following code.
densityPlots(marketPosterior, assetsSel = "JPM")




4. Overview of Copula Opinion Pooling (COP)

Copula opinion pooling is an alternative way to blend analyst views on market distributions that was developed by Attilio Meucci towards the end of 2005. It is similar to the Black-Litterman model in that it also uses a "pick" matrix to formulate views. However, it has several advantages including the following:

  • Views are made on realization of the market, not on market parameters as in the original formulation of BL.
  • The joint distribution of the market can be any multivariate distribution.
  • Views are NOT restricted to the normal distribution.
  • The parameters in the model have clearer meanings.
  • The model can easily be generalized to incorporate the views of multiple analysts.

Nevertheless, all of this comes at a price. We can no longer use closed-form expressions for calculating the posterior distribution of the market and hence rely on simulation instead. Before proceeding to the implementation, however, let's look at the theory. Please refer to (3) for a more detailed discussion.

As before, suppose that we have a set of n assets whose returns are represented by a set of random variables R = {r1, r2, ..., rn}. As in Black-Litterman, we suppose that R has some prior joint distribution whose c.d.f we will denote by ΦA. Denote the marginals of this distributon by Φi. An analyst forms his views on linear combinations of future realizations of the values of R by assigning subjective probability distributions to these linear combinations. That is we form views of the form pi,1a+ pi,2a+...+ pi,nani, where θi is some distribution. Denote the pick matrix formed by all of these views by P once again. Now, since we have assigned some prior distribution Φto these assets, it follows that actually the product V = PA inherits a distribution as well, say
v= pi,1a1 + pi,2a2 +...+ pi,nan~θ'i
In general, θ'i≠θ'i unless one's views are identical to the market prior. Thus we must somehow resolve this contradiction. A straightforward way of doing this is to take the weighted sum of the two marginal c.d.fs, so i.e.,
, and
is a parameter representing our confidence in our subjective views. This is the actual marginal distribution that will be used to determine the market posterior.

The market posterior is actually determined by setting the marginals of distributions of V to
, while using a copula to keep the dependence structure of V intact. Let V = (v1, v2, ..., vk), where k is the number of views that the analyst has formed. Then
. Let C be the copula of V so that C is the joint distribution of
 if we now take the θ'i to be cumulative density functions. Next set  as the random variable with the joint distriburtion
 . The posterior market distribution is obtained by rotating  back into market coordinates using the orthogonal complement of P. See Reference (3), p. 5 for details.


5. BLCOP: COP

Let us now work through a brief example to see how these ideas are implemented in the BLCOP package.
First, one again works with object that hold the view specification, which in the COP case are of class COPViews. These can again be created with a constructor function of the same name. However, a significant difference is the use of mvdistribution and distribution class objects to specify the prior distribution and view distributions respectively. We will show the use of these in the following example, which is based on the example used in Reference (3), p.9. Suppose that we wish to invest in 4 market indices (S&P500, FTSE, CAC and DAX). Meucci suggests a multivariate Student-t distribution with = 5 degrees of freedom and dispersion matrix given by:
He then sets μ = δΣ weq where weq is the relative capitalization of the 4 indices and δ = 2.5. For simplicity we will simply take weq = (1/4, 1/4, 1/4, 1/4).

sample code(s)
dispersion <- c(.376,.253,.360,.333,.360,.600,.397,.396,.578,.775) / 1000
sigma <- BLCOP:::.symmetricMatrix(dispersion, dim = 4)
caps <- rep(1/4, 4)
mu <- 2.5 * sigma %*% caps
dim(mu) <- NULL
# The following function could return an error message.
marketDistribution <- mvdistribution("mt", mean = mu, S = sigma, df = 5 )
class(marketDistribution)

[1] "mvdistribution"
attr(,"package")
[1] "BLCOP"


The class mvdistribution works with R multivariate probabilty distribution "su xes". mt is the R
"name"/"su x" of the multivariate Student-t as found in the package mnormt. That is, the sampling function is given by rmt, the density by dmt, and so on. The other parameters are those required by the these functions to fully parametrize the multivariate Student-t. The distribution class works with univariate distributions in a similar way and is used to create the view distributions. We continue with the above example by creating a single view on the DAX.


sample code(s)
pick <- matrix(0, ncol = 4, nrow = 1, dimnames = list(NULL, c("SP", "FTSE", "CAC", "DAX")))
pick[1,"DAX"] <- 1
viewDist <- list(distribution("unif", min = -0.02, max = 0))
views <- COPViews(pick, viewDist = viewDist, confidences = 0.2, assetNames = c("SP", "FTSE", "CAC", "DAX"))


As can be seen, the view distributions are given as a list of distribution class objects, and the
con dences set the tau's described previously. Here we have assigned a U(􀀀0:02; 0) distribution to our view with con dence 0:2. Additional views can be added with addCOPViews.

sample code(s)
newPick <- matrix(0, 1, 2)
dimnames(newPick) <- list(NULL, c("SP", "FTSE"))
newPick[1,] <- c(1, -1) # add a relative view
views <- addCOPViews(newPick, list(distribution("norm", mean = 0.05, sd = 0.02)), 0.5, views)


The posterior is calculated with COPPosterior, and the updated marginal distributions can be visualized with densityPlots once again. The calculation is performed by simulation, based on the ideas described in Reference (4). The simulations of the posterior distribution are stored in the posteriorSims of the class COPResult that is returned by COPPosterior.

sample code(s)
# The following functions could return an error message.
marketPosterior <- COPPosterior(marketDistribution, views, numSimulations = 50000)
densityPlots(marketPosterior, assetsSel = 4)




6. Future Developments

While mostly stable, the code is currently in need of some minor cleanup work and refactoring (e.g. pick matrices are referred to as P in some places and pick in others) as well as improvements in the documentation and examples. Attilio Meucci has also very recently proposed an even more general view-blending method which he calls Entropy Pooling and its inclusion would be another obvious extension of this package's functionality in the longer term.


Reference
(1) http://cran.r-project.org/web/packages/BLCOP/vignettes/BLCOP.pdf

(2) Meucci, Attilio. The Black-Litterman Approach: Original Model and Extensions. April 2008.
http://ssrn.com/abstract=1117574

(3) Meucci, Attilio. Beyond Black-Litterman: Views on Non-Normal Markets. November 2005.
http://ssrn.com/abstract=848407

(4) Meucci, Attilio. Beyond Black-Litterman in Practice: A Five-Step Recipe
to Input Views on non-Normal Markets. May 2006, Available at SSRN:
http://papers.ssrn.com/sol3/papers.cfm?abstract id=872577

Others
http://ssrn.com/abstract=1314585
http://datalab.morningstar.com/knowledgebase/aspx/files/Step_by_Step_Guide_to_the_Black_Litterman_Model.pdf
http://brage.bibsys.no/xmlui/bitstream/id/85285/Syvertsen,

Tuesday, July 14, 2015

[11c] Portfolio Optimization and Efficient Frontier: Import CSV files

Create csv files:

er.csv
SPY,EFA,LQD,EEM
0.14473728,0.06458429,0.05929884,0.07109433



covmat.csv
,SPY,EFA,LQD,HYG
SPY,0.02141010,0.02351144,-0.00049749,0.00915216
EFA,0.02351144,0.03227444,-0.00011169,0.01105802
LQD,-0.00049749,-0.00011169,0.00323208,0.00146939
HYG,0.00915216,0.01105802,0.00146939,0.00689213




Original sample codes in [11a] or [11b] are on the left hand side. For this section, please try the sample codes of read.csv on the right hand side, and then run sample codes in [11a] and/or [11b].

sample code(s)
r.free <- 0.00973449


er <- c(
 "SPY" = 0.14473728,
 "EFA" = 0.06458429,
 "LQD" = 0.05929884,
 "EEM" = 0.07109433
)


covmat <- matrix(c(0.02141010, 0.02351144, -0.00049749, 0.00915216, 0.02351144, 0.03227444, -0.00011169, 0.01105802, -0.00049749, -0.00011169, 0.00323208, 0.00146939, 0.00915216, 0.01105802, 0.00146939, 0.00689213), nrow = 4, ncol = 4, byrow = TRUE)

rownames(covmat) <- c("SPY", "EFA", "LQD", "HYG")
colnames(covmat) <- c("SPY", "EFA", "LQD", "HYG")

r.free <- 0.00973449




sample code(s)
er
       SPY        EFA        LQD        EEM
0.14473728 0.06458429 0.05929884 0.07109433

class(er)
[1] "numeric"

names(er)
[1] "SPY" "EFA" "LQD" "EEM"

rownames(er)
NULL

colnames(er)
NULL

dim(er)
NULL

erdf <- read.csv("H:/Quantitative Research/R/Optimization/er.csv", sep=",")


erdf
        SPY        EFA        LQD        EEM
1 0.1447373 0.06458429 0.05929884 0.07109433

class(erdf)
[1] "data.frame"

names(erdf)
[1] "SPY" "EFA" "LQD" "EEM"

rownames(erdf)
[1] "1"

colnames(erdf)
[1] "SPY" "EFA" "LQD" "EEM"

dim(erdf)
[1] 1 4


ermt <- as.matrix(erdf)

ermt
           SPY        EFA        LQD        EEM
[1,] 0.1447373 0.06458429 0.05929884 0.07109433

class(ermt)
[1] "matrix"

names(ermt)
NULL

rownames(ermt)
NULL

colnames(ermt)
[1] "SPY" "EFA" "LQD" "EEM"

dim(ermt)
[1] 1 4


names(ermt) <- colnames(ermt)
names(ermt) 
[1] "SPY" "EFA" "LQD" "EEM"


er <- ermt


er
           SPY        EFA        LQD        EEM
[1,] 0.1447373 0.06458429 0.05929884 0.07109433
attr(,"names")
[1] "SPY" "EFA" "LQD" "EEM"

class(er)
[1] "matrix"

names(er)
[1] "SPY" "EFA" "LQD" "EEM"

rownames(er)
NULL

colnames(er)
[1] "SPY" "EFA" "LQD" "EEM"

dim(er)
[1] 1 4





sample code(s)
covmat
            SPY         EFA         LQD        HYG
SPY  0.02141010  0.02351144 -0.00049749 0.00915216
EFA  0.02351144  0.03227444 -0.00011169 0.01105802
LQD -0.00049749 -0.00011169  0.00323208 0.00146939
HYG  0.00915216  0.01105802  0.00146939 0.00689213

names(covmat)
NULL

rownames(covmat)
[1] "SPY" "EFA" "LQD" "HYG"

colnames(covmat)
[1] "SPY" "EFA" "LQD" "HYG"

class(covmat)
[1] "matrix"

dim(covmat)
[1] 4 4

covmatdf <- read.csv("H:/Quantitative Research/R/Optimization/covmat.csv", sep=",", row.names=1)

covmatdf
            SPY         EFA         LQD        HYG
SPY  0.02141010  0.02351144 -0.00049749 0.00915216
EFA  0.02351144  0.03227444 -0.00011169 0.01105802
LQD -0.00049749 -0.00011169  0.00323208 0.00146939
HYG  0.00915216  0.01105802  0.00146939 0.00689213

names(covmatdf)
[1] "SPY" "EFA" "LQD" "HYG"

class(covmatdf)
[1] "data.frame"

dim(covmatdf)
[1] 4 4



covmatmt <- as.matrix(covmatdf)

covmatmt
            SPY         EFA         LQD        HYG
SPY  0.02141010  0.02351144 -0.00049749 0.00915216
EFA  0.02351144  0.03227444 -0.00011169 0.01105802
LQD -0.00049749 -0.00011169  0.00323208 0.00146939
HYG  0.00915216  0.01105802  0.00146939 0.00689213

names(covmatmt)
NULL

rownames(covmatmt)
[1] "SPY" "EFA" "LQD" "HYG"

colnames(covmatmt)
[1] "SPY" "EFA" "LQD" "HYG"

class(covmatmt)
[1] "data.frame"

dim(covmatmt)
[1] 4 4



covmat <- covmatmt


covmat
            SPY         EFA         LQD        HYG
SPY  0.02141010  0.02351144 -0.00049749 0.00915216
EFA  0.02351144  0.03227444 -0.00011169 0.01105802
LQD -0.00049749 -0.00011169  0.00323208 0.00146939
HYG  0.00915216  0.01105802  0.00146939 0.00689213

class(covmat)
[1] "matrix"

names(covmat)
[1] "SPY" "EFA" "LQD" "EEM"

rownames(covmat)
[1] "SPY" "EFA" "LQD" "HYG"

colnames(covmat)
[1] "SPY" "EFA" "LQD" "EEM"

dim(covmat)
[1] 1 4




Try [11a] and/or [11b] after running the sample codes of read.csv on the right hand side.

Monday, July 13, 2015

[11b] Long-Short Portfolio Optimization (equal-weighted, tangency, minimum volatility, and target return portfolio) and Efficient Frontier

Create the following r source code files by Eric Zivot on a certain directory.
  • portfolio.r


portfolio.r
# portfolio.r

# Functions for portfolio analysis
# to be used in Introduction to Computational Finance & Financial Econometrics
# last updated: November 7, 2000 by Eric Zivot
#               Oct 15, 2003 by Tim Hesterberg
#               November 18, 2003 by Eric Zivot
#               November 9, 2004 by Eric Zivot
#             November 9, 2008 by Eric Zivot
#               August 11, 2011 by Eric Zivot
#
# Functions:
# 1. efficient.portfolio compute minimum variance portfolio
#                 subject to target return
# 2. globalMin.portfolio compute global minimum variance portfolio
# 3. tangency.portfolio   compute tangency portfolio
# 4. efficient.frontier   compute Markowitz bullet
# 5. getPortfolio     create portfolio object
#

getPortfolio <-
function(er, cov.mat, weights)
{
# contruct portfolio object
#
# inputs:
# er    N x 1 vector of expected returns
# cov.mat    N x N covariance matrix of returns
# weights  N x 1 vector of portfolio weights
#
# output is portfolio object with the following elements
# call original function call
# er   portfolio expected return
# sd   portfolio standard deviation
# weights N x 1 vector of portfolio weights
#
call <- match.call()

#
# check for valid inputs
#
asset.names <- names(er)
weights <- as.vector(weights)
names(weights) = names(er)
   er <- as.vector(er) # assign names if none exist
if(length(er) != length(weights))
stop("dimensions of er and weights do not match")
  cov.mat <- as.matrix(cov.mat)
if(length(er) != nrow(cov.mat))
stop("dimensions of er and cov.mat do not match")
if(any(diag(chol(cov.mat)) <= 0))
stop("Covariance matrix not positive definite")

#
# create portfolio
#
er.port <- crossprod(er,weights)
sd.port <- sqrt(weights %*% cov.mat %*% weights)
ans <- list("call" = call,
      "er" = as.vector(er.port),
      "sd" = as.vector(sd.port),
      "weights" = weights) 
class(ans) <- "portfolio"
ans
}

efficient.portfolio <-
function(er, cov.mat, target.return)
{
  # compute minimum variance portfolio subject to target return
  #
  # inputs:
  # er     N x 1 vector of expected returns
  # cov.mat     N x N covariance matrix of returns
  # target.return   scalar, target expected return
  #
  # output is portfolio object with the following elements
  # call     original function call
  # er     portfolio expected return
  # sd     portfolio standard deviation
  # weights     N x 1 vector of portfolio weights
  #
  call <- match.call()

  #
  # check for valid inputs
  #
  asset.names <- names(er)
  er <- as.vector(er) # assign names if none exist
  cov.mat <- as.matrix(cov.mat)
  if(length(er) != nrow(cov.mat))
    stop("invalid inputs")
  if(any(diag(chol(cov.mat)) <= 0))
    stop("Covariance matrix not positive definite")
  # remark: could use generalized inverse if cov.mat is positive semidefinite

  #
  # compute efficient portfolio
  #
  ones <- rep(1, length(er))
  top <- cbind(2*cov.mat, er, ones)
  bot <- cbind(rbind(er, ones), matrix(0,2,2))
  A <- rbind(top, bot)
  b.target <- as.matrix(c(rep(0, length(er)), target.return, 1))
  x <- solve(A, b.target)
  w <- x[1:length(er)]
  names(w) <- asset.names

  #
  # compute portfolio expected returns and variance
  #
  er.port <- crossprod(er,w)
  sd.port <- sqrt(w %*% cov.mat %*% w)
  ans <- list("call" = call,
      "er" = as.vector(er.port),
      "sd" = as.vector(sd.port),
      "weights" = w) 
  class(ans) <- "portfolio"
  ans
}

globalMin.portfolio <-
function(er, cov.mat)
{
  # Compute global minimum variance portfolio
  #
  # inputs:
  # er N x 1 vector of expected returns
  # cov.mat N x N return covariance matrix
  #
  # output is portfolio object with the following elements
  # call original function call
  # er portfolio expected return
  # sd portfolio standard deviation
  # weights N x 1 vector of portfolio weights
  call <- match.call()

  #
  # check for valid inputs
  #
  asset.names <- names(er)
  er <- as.vector(er) # assign names if none exist
  cov.mat <- as.matrix(cov.mat)
  if(length(er) != nrow(cov.mat))
    stop("invalid inputs")
  if(any(diag(chol(cov.mat)) <= 0))
    stop("Covariance matrix not positive definite")
  # remark: could use generalized inverse if cov.mat is positive semi-definite

  #
  # compute global minimum portfolio
  #
  cov.mat.inv <- solve(cov.mat)
  one.vec <- rep(1,length(er))
#  w.gmin <- cov.mat.inv %*% one.vec/as.vector(one.vec %*% cov.mat.inv %*% one.vec)
  w.gmin <- rowSums(cov.mat.inv) / sum(cov.mat.inv)
  w.gmin <- as.vector(w.gmin)
  names(w.gmin) <- asset.names
  er.gmin <- crossprod(w.gmin,er)
  sd.gmin <- sqrt(t(w.gmin) %*% cov.mat %*% w.gmin)
  gmin.port <- list("call" = call,
    "er" = as.vector(er.gmin),
    "sd" = as.vector(sd.gmin),
    "weights" = w.gmin)
  class(gmin.port) <- "portfolio"
  gmin.port
}


tangency.portfolio <- 
function(er,cov.mat,risk.free)
{
  # compute tangency portfolio
  #
  # inputs:
  # er    N x 1 vector of expected returns
  # cov.mat    N x N return covariance matrix
  # risk.free  scalar, risk-free rate
  #
  # output is portfolio object with the following elements
  # call   captures function call
  # er   portfolio expected return
  # sd   portfolio standard deviation
  # weights  N x 1 vector of portfolio weights
  call <- match.call()

  #
  # check for valid inputs
  #
  asset.names <- names(er)
  if(risk.free < 0)
    stop("Risk-free rate must be positive")
  er <- as.vector(er)
  cov.mat <- as.matrix(cov.mat)
  if(length(er) != nrow(cov.mat))
    stop("invalid inputs")
  if(any(diag(chol(cov.mat)) <= 0))
    stop("Covariance matrix not positive definite")
  # remark: could use generalized inverse if cov.mat is positive semi-definite

  #
  # compute global minimum variance portfolio
  #
  gmin.port <- globalMin.portfolio(er,cov.mat)
  if(gmin.port$er < risk.free)
    stop("Risk-free rate greater than avg return on global minimum variance portfolio")

  # 
  # compute tangency portfolio
  #
  cov.mat.inv <- solve(cov.mat)
  w.t <- cov.mat.inv %*% (er - risk.free) # tangency portfolio
  w.t <- as.vector(w.t/sum(w.t)) # normalize weights
  names(w.t) <- asset.names
  er.t <- crossprod(w.t,er)
  sd.t <- sqrt(t(w.t) %*% cov.mat %*% w.t)
  tan.port <- list("call" = call,
   "er" = as.vector(er.t),
   "sd" = as.vector(sd.t),
   "weights" = w.t)
  class(tan.port) <- "portfolio"
  tan.port
}

efficient.frontier <- 
function(er, cov.mat, nport=20, alpha.min=-0.5, alpha.max=1.5)
{
  # Compute efficient frontier with no short-sales constraints
  #
  # inputs:
  # er   N x 1 vector of expected returns
  # cov.mat   N x N return covariance matrix
  # nport   scalar, number of efficient portfolios to compute
  #
  # output is a Markowitz object with the following elements
  # call   captures function call
  # er   nport x 1 vector of expected returns on efficient porfolios
  # sd   nport x 1 vector of std deviations on efficient portfolios
  # weights  nport x N matrix of weights on efficient portfolios 
  call <- match.call()

  #
  # check for valid inputs
  #
  asset.names <- names(er)
  er <- as.vector(er)
  cov.mat <- as.matrix(cov.mat)
  if(length(er) != nrow(cov.mat))
    stop("invalid inputs")
  if(any(diag(chol(cov.mat)) <= 0))
    stop("Covariance matrix not positive definite")

  #
  # create portfolio names
  #
  port.names <- rep("port",nport)
  ns <- seq(1,nport)
  port.names <- paste(port.names,ns)

  #
  # compute global minimum variance portfolio
  #
  cov.mat.inv <- solve(cov.mat)
  one.vec <- rep(1,length(er))
  port.gmin <- globalMin.portfolio(er,cov.mat)
  w.gmin <- port.gmin$weights

  #
  # compute efficient frontier as convex combinations of two efficient portfolios
  # 1st efficient port: global min var portfolio
  # 2nd efficient port: min var port with ER = max of ER for all assets
  #
  er.max <- max(er)
  port.max <- efficient.portfolio(er,cov.mat,er.max)
  w.max <- port.max$weights    
  a <- seq(from=alpha.min,to=alpha.max,length=nport) # convex combinations
  we.mat <- a %o% w.gmin + (1-a) %o% w.max # rows are efficient portfolios
  er.e <- we.mat %*% er # expected returns of efficient portfolios
  er.e <- as.vector(er.e)
  names(er.e) <- port.names
  cov.e <- we.mat %*% cov.mat %*% t(we.mat) # cov mat of efficient portfolios
  sd.e <- sqrt(diag(cov.e)) # std devs of efficient portfolios
  sd.e <- as.vector(sd.e)
  names(sd.e) <- port.names
  dimnames(we.mat) <- list(port.names,asset.names)

  # 
  # summarize results
  #
  ans <- list("call" = call,
      "er" = er.e,
      "sd" = sd.e,
      "weights" = we.mat)
  class(ans) <- "Markowitz"
  ans
}

#
# print method for portfolio object
print.portfolio <- function(x, ...)
{
  cat("Call:\n")
  print(x$call, ...)
  cat("\nPortfolio expected return:    ", format(x$er, ...), "\n")
  cat("Portfolio standard deviation: ", format(x$sd, ...), "\n")
  cat("Portfolio weights:\n")
  print(round(x$weights,4), ...)
  invisible(x)
}

#
# summary method for portfolio object
summary.portfolio <- function(object, risk.free=NULL, ...)
# risk.free risk-free rate. If not null then
# compute and print Sharpe ratio

{
  cat("Call:\n")
  print(object$call)
  cat("\nPortfolio expected return:    ", format(object$er, ...), "\n")
  cat(  "Portfolio standard deviation: ", format(object$sd, ...), "\n")
  if(!is.null(risk.free)) {
    SharpeRatio <- (object$er - risk.free)/object$sd
    cat("Portfolio Sharpe Ratio:       ", format(SharpeRatio), "\n")
  }
  cat("Portfolio weights:\n")
  print(round(object$weights,4), ...)
  invisible(object)
}
# hard-coded 4 digits; prefer to let user control, via ... or other argument

#
# plot method for portfolio object
plot.portfolio <- function(object, ...)
{
  asset.names <- names(object$weights)
  barplot(object$weights, names=asset.names,
  xlab="Assets", ylab="Weight", main="Portfolio Weights", ...)
  invisible()
}

#
# print method for Markowitz object
print.Markowitz <- function(x, ...)
{
  cat("Call:\n")
  print(x$call)
  xx <- rbind(x$er,x$sd)
  dimnames(xx)[[1]] <- c("ER","SD")
  cat("\nFrontier portfolios' expected returns and standard deviations\n")
  print(round(xx,4), ...)
  invisible(x)
}
# hard-coded 4, should let user control

#
# summary method for Markowitz object
summary.Markowitz <- function(object, risk.free=NULL)
{
  call <- object$call
  asset.names <- colnames(object$weights)
  port.names <- rownames(object$weights)
  if(!is.null(risk.free)) {
    # compute efficient portfolios with a risk-free asset
    nport <- length(object$er)
    sd.max <- object$sd[1]
    sd.e <- seq(from=0,to=sd.max,length=nport)
    names(sd.e) <- port.names

    #
    # get original er and cov.mat data from call 
    er <- eval(object$call$er)
    cov.mat <- eval(object$call$cov.mat)

    #
    # compute tangency portfolio
    tan.port <- tangency.portfolio(er,cov.mat,risk.free)
    x.t <- sd.e/tan.port$sd # weights in tangency port
    rf <- 1 - x.t # weights in t-bills
    er.e <- risk.free + x.t*(tan.port$er - risk.free)
    names(er.e) <- port.names
    we.mat <- x.t %o% tan.port$weights # rows are efficient portfolios
    dimnames(we.mat) <- list(port.names, asset.names)
    we.mat <- cbind(rf,we.mat) 
  }
  else {
    er.e <- object$er
    sd.e <- object$sd
    we.mat <- object$weights
  }
  ans <- list("call" = call,
      "er"=er.e,
      "sd"=sd.e,
      "weights"=we.mat)
  class(ans) <- "summary.Markowitz"
  ans
}

print.summary.Markowitz <- function(x, ...)
{
xx <- rbind(x$er,x$sd)
port.names <- names(x$er)
asset.names <- colnames(x$weights)
dimnames(xx)[[1]] <- c("ER","SD")
cat("Frontier portfolios' expected returns and standard deviations\n")
print(round(xx,4), ...)
cat("\nPortfolio weights:\n")
print(round(x$weights,4), ...)
invisible(x)
}
# hard-coded 4, should let user control

#
# plot efficient frontier
#
# things to add: plot original assets with names
# tangency portfolio
# global min portfolio
# risk free asset and line connecting rf to tangency portfolio
#
plot.Markowitz <- function(object, plot.assets=FALSE, ...)
# plot.assets logical. If true then plot asset sd and er
{
  if (!plot.assets) {
     y.lim=c(0,max(object$er))
     x.lim=c(0,max(object$sd))
     plot(object$sd,object$er,type="b",xlim=x.lim, ylim=y.lim,
          xlab="Portfolio SD", ylab="Portfolio ER", 
          main="Efficient Frontier", ...)
     }
  else {
  call = object$call
  mu.vals = eval(call$er)
  sd.vals = sqrt( diag( eval(call$cov.mat) ) )
  y.lim = range(c(0,mu.vals,object$er))
  x.lim = range(c(0,sd.vals,object$sd))
  plot(object$sd,object$er,type="b", xlim=x.lim, ylim=y.lim,
          xlab="Portfolio SD", ylab="Portfolio ER", 
          main="Efficient Frontier", ...)
        text(sd.vals, mu.vals, labels=names(mu.vals))
  }
  invisible()
}



Source the r source codes above.

sample code(s)
source("H:/Quantitative Research/R/Optimization/portfolio.r")






Sample ticker code:

SPY SPDR S&P 500 ETF TRUST
EFA ISHARES MSCI EAFE ETF
LQD ISHARES IBOXX INVESTMENT GRA
HYG ISHARES IBOXX HIGH YIELD COR


Weekly basis stats (in decimal numbers):

(1) Risk-free return

Risk-free 0.00018631 

(2) Average Return and Standard Deviation of Returns

Weekly SPY EFA LQD HYG
Average 0.00260290 0.00120427 0.00110844 0.00132166
SD 0.02029120 0.02491309 0.00788386 0.01151264


(3) Covariance matrix

Cov SPY EFA LQD HYG
SPY 0.00041173 0.00045214 -0.00000957 0.00017600
EFA 0.00045214 0.00062066 -0.00000215 0.00021265
LQD -0.00000957 -0.00000215 0.00006216 0.00002826
HYG -0.00000617 -0.00000570 0.00000461 -0.00000108


Annual basis stats (in decimal numbers):

(1') Geometrical-Average Risk-free return

Annual
Risk-free 0.00973449


(2') Geometrical-Average Return and Standard Deviation of Returns (SD figures of (2) are multiplied by 52^0.5.)

Annual SPY EFA LQD HYG
Average 0.14473728 0.06458429 0.05929884 0.07109433
SD 0.14632190 0.17965088 0.05685135 0.08301885

(3') Covariance matrix --- all the numbers of the covariance of (3) are multiplied by 52 (weeks).

Cov SPY EFA LQD HYG
SPY 0.02141010 0.02351144 -0.00049749 0.00915216
EFA 0.02351144 0.03227444 -0.00011169 0.01105802
LQD -0.00049749 -0.00011169 0.00323208 0.00146939
HYG 0.00915216 0.01105802 0.00146939 0.00689213



Stats on a annual basis:

sample code(s)
# annual

r.free <- 0.00973449

r.free
[1] 0.00973449


er <- c(
 "SPY" = 0.14473728,
 "EFA" = 0.06458429,
 "LQD" = 0.05929884,
 "EEM" = 0.07109433
)

er
       SPY        EFA        LQD        EEM
0.14473728 0.06458429 0.05929884 0.07109433



covmat <- matrix(c(0.02141010, 0.02351144, -0.00049749, 0.00915216, 0.02351144, 0.03227444, -0.00011169, 0.01105802, -0.00049749, -0.00011169, 0.00323208, 0.00146939, 0.00915216, 0.01105802, 0.00146939, 0.00689213), nrow = 4, ncol = 4, byrow = TRUE)


rownames(covmat) <- c("SPY", "EFA", "LQD", "HYG")
colnames(covmat) <- c("SPY", "EFA", "LQD", "HYG")


covmat
            SPY         EFA         LQD        HYG
SPY  0.02141010  0.02351144 -0.00049749 0.00915216
EFA  0.02351144  0.03227444 -0.00011169 0.01105802
LQD -0.00049749 -0.00011169  0.00323208 0.00146939
HYG  0.00915216  0.01105802  0.00146939 0.00689213





Equal-weighted Portfolio

sample code(s)
# [1 1 1 1]
rep(1,4)


# equally-weighted
ew = rep(1,4)/4
ew

[1] 0.25 0.25 0.25 0.25


equalWeight.portfolio = getPortfolio(er=er,cov.mat=covmat,weights=ew)


equalWeight.portfolio
Call:
getPortfolio(er = er, cov.mat = covmat, weights = ew)

Portfolio expected return:     0.08492869
Portfolio standard deviation:  0.09777922
Portfolio weights:
 SPY  EFA  LQD  EEM
0.25 0.25 0.25 0.25


plot(equalWeight.portfolio)
# If you want to change the color, then try:
# plot(equalWeight.portfolio, col="blue")





Tangency Portfolio (long-short)


sample code(s)

tan.port <- tangency.portfolio(er, covmat, r.free)
# You can also run without a "shorts" option.
# tan.port <- tangency.portfolio(er, covmat, r.free)


tan.port
Call:
tangency.portfolio(er = er, cov.mat = covmat, risk.free = r.free,
    shorts = TRUE)

Portfolio expected return:     0.1371406
Portfolio standard deviation:  0.06950249
Portfolio weights:
    SPY     EFA     LQD     EEM
 0.9766 -0.5690  0.8118 -0.2194


print(tan.port)
Call:
tangency.portfolio(er = er, cov.mat = covmat, risk.free = r.free,
    shorts = TRUE)

Portfolio expected return:     0.1371406
Portfolio standard deviation:  0.06950249
Portfolio weights:
    SPY     EFA     LQD     EEM
 0.9766 -0.5690  0.8118 -0.2194


plot(tan.port)






Minimum Volatility Portfolio (long-short)


sample code(s)

gmin.port <- globalMin.portfolio(er, covmat)

gmin.port
Call:
globalMin.portfolio(er = er, cov.mat = covmat, shorts = TRUE)

Portfolio expected return:     0.07863945
Portfolio standard deviation:  0.05111289
Portfolio weights:
    SPY     EFA     LQD     EEM
 0.2210 -0.1071  0.7990  0.0872


attributes(gmin.port)
$names
[1] "call"    "er"      "sd"      "weights"

$class
[1] "portfolio"


print(gmin.port)
Call:
globalMin.portfolio(er = er, cov.mat = covmat, shorts = TRUE)

Portfolio expected return:     0.07863945
Portfolio standard deviation:  0.05111289
Portfolio weights:
    SPY     EFA     LQD     EEM
 0.2210 -0.1071  0.7990  0.0872


plot(gmin.port)






Target Return Portfolio (long-short)


sample code(s)

target.return = er["SPY"]


target.return
      SPY
0.1447373


e.port.SPY = efficient.portfolio(er, covmat, target.return)

e.port.SPY
Call:
efficient.portfolio(er = er, cov.mat = covmat, target.return = target.return,
    shorts = TRUE)

Portfolio expected return:     0.1447373
Portfolio standard deviation:  0.0737838
Portfolio weights:
    SPY     EFA     LQD     EEM
 1.0747 -0.6290  0.8135 -0.2592

plot(e.port.SPY)





Efficient Frontier (long-short)

sample code(s)

ef = efficient.frontier(er, covmat, alpha.min = -2, alpha.max = 1.5, nport = 20)


attributes(ef)
$names
[1] "call"    "er"      "sd"      "weights"

$class
[1] "Markowitz"


summary(ef)
Frontier portfolios' expected returns and standard deviations
   port 1 port 2 port 3 port 4 port 5 port 6 port 7 port 8 port 9 port 10
ER 0.2769 0.2648 0.2526 0.2404 0.2282 0.2161 0.2039 0.1917 0.1795  0.1673
SD 0.1676 0.1583 0.1491 0.1399 0.1308 0.1219 0.1130 0.1044 0.0960  0.0878
   port 11 port 12 port 13 port 14 port 15 port 16 port 17 port 18 port 19
ER  0.1552  0.1430  0.1308  0.1186  0.1065  0.0943  0.0821  0.0699  0.0578
SD  0.0801  0.0728  0.0662  0.0604  0.0558  0.0526  0.0512  0.0516  0.0538
   port 20
ER  0.0456
SD  0.0576

Portfolio weights:
            SPY     EFA    LQD     EEM
port 1   2.7822 -1.6727 0.8425 -0.9520
port 2   2.6249 -1.5766 0.8399 -0.8882
port 3   2.4676 -1.4804 0.8372 -0.8244
port 4   2.3104 -1.3843 0.8345 -0.7606
port 5   2.1531 -1.2882 0.8318 -0.6968
port 6   1.9958 -1.1920 0.8292 -0.6330
port 7   1.8386 -1.0959 0.8265 -0.5692
port 8   1.6813 -0.9998 0.8238 -0.5053
port 9   1.5240 -0.9036 0.8211 -0.4415
port 10  1.3668 -0.8075 0.8185 -0.3777
port 11  1.2095 -0.7114 0.8158 -0.3139
port 12  1.0522 -0.6152 0.8131 -0.2501
port 13  0.8950 -0.5191 0.8104 -0.1863
port 14  0.7377 -0.4230 0.8078 -0.1225
port 15  0.5804 -0.3268 0.8051 -0.0587
port 16  0.4232 -0.2307 0.8024  0.0051
port 17  0.2659 -0.1346 0.7998  0.0689
port 18  0.1086 -0.0385 0.7971  0.1327
port 19 -0.0486  0.0577 0.7944  0.1966
port 20 -0.2059  0.1538 0.7917  0.2604


plot(ef, plot.assets=T, col="blue", pch=16)


points(gmin.port$sd, gmin.port$er,
col="green", pch=16, cex=2)


points(tan.port$sd, tan.port$er, col="red",
pch=16, cex=2)


text(gmin.port$sd, gmin.port$er,
labels="GLOBAL MIN", pos=2)


text(tan.port$sd, tan.port$er,
labels="TANGENCY", pos=2)


sr.tan = (tan.port$er - r.free)/tan.port$sd


abline(a=r.free, b=sr.tan, col="green", lwd=2)