Estimate Efficient Portfolios

There are two ways to look at a portfolio optimization problem that depends on what you are trying to do. One goal is to estimate efficient portfolios and the other is to estimate efficient frontiers. This section focuses on the former goal and Estimate Efficient Frontiers focuses on the latter goal. For information on the workflow when using PortfolioCVaR objects, see PortfolioCVaR Object Workflow.

Obtaining Portfolios Along the Entire Efficient Frontier

The most basic way to obtain optimal portfolios is to obtain points over the entire range of the efficient frontier. Given a portfolio optimization problem in a PortfolioCVaR object, the estimateFrontier method computes efficient portfolios spaced evenly according to the return proxy from the minimum to maximum return efficient portfolios. The number of portfolios estimated is controlled by the hidden property defaultNumPorts which is set to 10. A different value for the number of portfolios estimated is specified as input to estimateFrontier. This example shows the default number of efficient portfolios over the entire range of the efficient frontier:

m = [ 0.05; 0.1; 0.12; 0.18 ];
C = [ 0.0064 0.00408 0.00192 0; 
    0.00408 0.0289 0.0204 0.0119;
    0.00192 0.0204 0.0576 0.0336;
    0 0.0119 0.0336 0.1225 ];
m = m/12;
C = C/12;

AssetScenarios = mvnrnd(m, C, 20000);

p = PortfolioCVaR;
p = setScenarios(p, AssetScenarios);
p = setDefaultConstraints(p);
p = setProbabilityLevel(p, 0.95);

pwgt = estimateFrontier(p);
disp(pwgt);
Columns 1 through 8

    0.8670    0.7046    0.5421    0.3825    0.2236    0.0570    0.0000    0.0000
    0.0413    0.1193    0.1963    0.2667    0.3392    0.4159    0.3392    0.1753
    0.0488    0.0640    0.0811    0.1012    0.1169    0.1427    0.1568    0.1754
    0.0429    0.1120    0.1806    0.2496    0.3203    0.3844    0.5040    0.6493

  Columns 9 through 10

    0.0000    0.0000
    0.0230    0.0000
    0.1777    0.0000
    0.7993    1.0000 

If you want only four portfolios in the previous example:

pwgt = estimateFrontier(p, 4);

disp(pwgt);
0.8670    0.3825    0.0000    0.0000
0.0413    0.2667    0.3392    0.0000
0.0488    0.1012    0.1568    0.0000
0.0429    0.2496    0.5040    1.0000

Starting from the initial portfolio, estimateFrontier also returns purchases and sales to get from your initial portfolio to each efficient portfolio on the efficient frontier. For example, given an initial portfolio in pwgt0, you can obtain purchases and sales:

pwgt0 = [ 0.3; 0.3; 0.2; 0.1 ];
p = setInitPort(p, pwgt0);
[pwgt, pbuy, psell] = estimateFrontier(p);

display(pwgt);
display(pbuy);
display(psell);
pwgt =

  Columns 1 through 8

    0.8670    0.7046    0.5421    0.3825    0.2236    0.0570    0.0000    0.0000
    0.0413    0.1193    0.1963    0.2667    0.3392    0.4159    0.3392    0.1753
    0.0488    0.0640    0.0811    0.1012    0.1169    0.1427    0.1568    0.1754
    0.0429    0.1120    0.1806    0.2496    0.3203    0.3844    0.5040    0.6493

  Columns 9 through 10

    0.0000    0.0000
    0.0230    0.0000
    0.1777    0.0000
    0.7993    1.0000


pbuy =

  Columns 1 through 8

    0.5670    0.4046    0.2421    0.0825         0         0         0         0
         0         0         0         0    0.0392    0.1159    0.0392         0
         0         0         0         0         0         0         0         0
         0    0.0120    0.0806    0.1496    0.2203    0.2844    0.4040    0.5493

  Columns 9 through 10

         0         0
         0         0
         0         0
    0.6993    0.9000


psell =

  Columns 1 through 8

         0         0         0         0    0.0764    0.2430    0.3000    0.3000
    0.2587    0.1807    0.1037    0.0333         0         0         0    0.1247
    0.1512    0.1360    0.1189    0.0988    0.0831    0.0573    0.0432    0.0246
    0.0571         0         0         0         0         0         0         0

  Columns 9 through 10

    0.3000    0.3000
    0.2770    0.3000
    0.0223    0.2000
         0         0

If you do not specify an initial portfolio, the purchase and sale weights assume that your initial portfolio is 0.

Obtaining Endpoints of the Efficient Frontier

In many cases, you might be interested in the endpoint portfolios for the efficient frontier. Suppose you want to determine the range of returns from minimum to maximum to refine a search for a portfolio with a specific target return. Use the estimateFrontierLimits method to obtain the endpoint portfolios:

m = [ 0.05; 0.1; 0.12; 0.18 ];
C = [ 0.0064 0.00408 0.00192 0; 
    0.00408 0.0289 0.0204 0.0119;
    0.00192 0.0204 0.0576 0.0336;
    0 0.0119 0.0336 0.1225 ];
m = m/12;
C = C/12;

AssetScenarios = mvnrnd(m, C, 20000);

p = PortfolioCVaR;
p = setScenarios(p, AssetScenarios);
p = setDefaultConstraints(p);
p = setProbabilityLevel(p, 0.95);
pwgt = estimateFrontierLimits(p);

disp(pwgt);
0.8646    0.0000
0.0470    0.0000
0.0414    0.0000
0.0470    1.0000

    Note:   The endpoints of the efficient frontier depend upon the Scenarios in the PortfolioCVaR object. If you change the Scenarios, you are likely to obtain different endpoints.

Starting from an initial portfolio, estimateFrontierLimits also returns purchases and sales to get from the initial portfolio to the endpoint portfolios on the efficient frontier. For example, given an initial portfolio in pwgt0, you can obtain purchases and sales:

m = [ 0.05; 0.1; 0.12; 0.18 ];
C = [ 0.0064 0.00408 0.00192 0; 
    0.00408 0.0289 0.0204 0.0119;
    0.00192 0.0204 0.0576 0.0336;
    0 0.0119 0.0336 0.1225 ];
m = m/12;
C = C/12;

AssetScenarios = mvnrnd(m, C, 20000);

p = PortfolioCVaR;
p = setScenarios(p, AssetScenarios);
p = setDefaultConstraints(p);
p = setProbabilityLevel(p, 0.95);

pwgt0 = [ 0.3; 0.3; 0.2; 0.1 ];
p = setInitPort(p, pwgt0);
[pwgt, pbuy, psell] = estimateFrontierLimits(p);
	
display(pwgt);
display(pbuy);
display(psell);
pwgt =

    0.8624    0.0000
    0.0513    0.0000
    0.0452    0.0000
    0.0411    1.0000


pbuy =

    0.5624         0
         0         0
         0         0
         0    0.9000


psell =

         0    0.3000
    0.2487    0.3000
    0.1548    0.2000
    0.0589         0

If you do not specify an initial portfolio, the purchase and sale weights assume that your initial portfolio is 0.

Obtaining Efficient Portfolios for Target Returns

To obtain efficient portfolios that have targeted portfolio returns, the estimateFrontierByReturn method accepts one or more target portfolio returns and obtains efficient portfolios with the specified returns. For example, assume that you have a universe of four assets where you want to obtain efficient portfolios with target portfolio returns of 7%, 10%, and 12%:

m = [ 0.05; 0.1; 0.12; 0.18 ];
C = [ 0.0064 0.00408 0.00192 0; 
    0.00408 0.0289 0.0204 0.0119;
    0.00192 0.0204 0.0576 0.0336;
    0 0.0119 0.0336 0.1225 ];

AssetScenarios = mvnrnd(m, C, 20000);

p = PortfolioCVaR;
p = setScenarios(p, AssetScenarios);
p = setDefaultConstraints(p);
p = setProbabilityLevel(p, 0.95);

pwgt = estimateFrontierByReturn(p, [0.07,  0.10, .12]);
display(pwgt);
pwgt =

    0.7526    0.3773    0.1306
    0.1047    0.3079    0.4348
    0.0662    0.1097    0.1426
    0.0765    0.2051    0.2920

In some cases, you can request a return for which no efficient portfolio exists. Based on the previous example, suppose you want a portfolio with a 4% return (which is the return of the first asset). A portfolio that is fully invested in the first asset, however, is inefficient. estimateFrontierByReturn warns if your target returns are outside the range of efficient portfolio returns and replaces it with the endpoint portfolio of the efficient frontier closest to your target return:

 pwgt = estimateFrontierByReturn(p, [0.04]);
Warning: One or more target return values are outside the feasible range [
0.066388, 0.178834 ].
	Will return portfolios associated with endpoints of the range for these values. 
> In PortfolioCVaR.estimateFrontierByReturn at 93 

The best way to avoid this situation is to bracket your target portfolio returns with estimateFrontierLimits and estimatePortReturn (see Obtaining Endpoints of the Efficient Frontier and Obtaining CVaR Portfolio Risks and Returns).

pret = estimatePortReturn(p, p.estimateFrontierLimits);

display(pret);
pret =

    0.0664
    0.1788

This result indicates that efficient portfolios have returns that range from 6.5 to 17.8%. Note, your results for these examples may be different due to the random generation of scenarios.

If you have an initial portfolio, estimateFrontierByReturn also returns purchases and sales to get from your initial portfolio to the target portfolios on the efficient frontier. For example, given an initial portfolio in pwgt0, to obtain purchases and sales with target returns of 7%, 10%, and 12%:

pwgt0 = [ 0.3; 0.3; 0.2; 0.1 ];
p = setInitPort(p, pwgt0);
[pwgt, pbuy, psell] = estimateFrontierByReturn(p, [0.07,  0.10, .12]);

display(pwgt);
display(pbuy);
display(psell);
pwgt =

    0.7526    0.3773    0.1306
    0.1047    0.3079    0.4348
    0.0662    0.1097    0.1426
    0.0765    0.2051    0.2920


pbuy =

    0.4526    0.0773         0
         0    0.0079    0.1348
         0         0         0
         0    0.1051    0.1920


psell =

         0         0    0.1694
    0.1953         0         0
    0.1338    0.0903    0.0574
    0.0235         0         0

If you do not have an initial portfolio, the purchase and sale weights assume that your initial portfolio is 0.

Obtaining Efficient Portfolios for Target Risks

To obtain efficient portfolios that have targeted portfolio risks, the estimateFrontierByRisk method accepts one or more target portfolio risks and obtains efficient portfolios with the specified risks. Suppose you have a universe of four assets where you want to obtain efficient portfolios with target portfolio risks of 12%, 14%, and 16%.

m = [ 0.05; 0.1; 0.12; 0.18 ];
C = [ 0.0064 0.00408 0.00192 0; 
    0.00408 0.0289 0.0204 0.0119;
    0.00192 0.0204 0.0576 0.0336;
    0 0.0119 0.0336 0.1225 ];

AssetScenarios = mvnrnd(m, C, 20000);

p = PortfolioCVaR;
p = setScenarios(p, AssetScenarios);
p = setDefaultConstraints(p);
p = setProbabilityLevel(p, 0.9);

pwgt = estimateFrontierByRisk(p, [0.12, 0.14, 0.16]);

display(pwgt);
pwgt =

    0.3594    0.2524    0.1543
    0.3164    0.3721    0.4248
    0.1044    0.1193    0.1298
    0.2199    0.2563    0.2910

In some cases, you can request a risk for which no efficient portfolio exists. Based on the previous example, suppose you want a portfolio with 6% risk (individual assets in this universe have risks ranging from 7 to 42.5%). It turns out that a portfolio with 6% risk cannot be formed with these four assets. estimateFrontierByRisk warns if your target risks are outside the range of efficient portfolio risks and replaces it with the endpoint of the efficient frontier closest to your target risk:

pwgt = estimateFrontierByRisk(p, 0.06)
Warning: One or more target risk values are outside the feasible range [
0.0735749, 0.436667 ].
	Will return portfolios associated with endpoints of the range for these values. 
> In PortfolioCVaR.estimateFrontierByRisk at 80 

pwgt =

    0.7899
    0.0856
    0.0545
    0.0700

The best way to avoid this situation is to bracket your target portfolio risks with estimateFrontierLimits and estimatePortRisk (see Obtaining Endpoints of the Efficient Frontier and Obtaining CVaR Portfolio Risks and Returns).

prsk = estimatePortRisk(p, p.estimateFrontierLimits);

display(prsk);
prsk =

    0.0736
    0.4367

This result indicates that efficient portfolios have risks that range from 7 to 42.5%. Note, your results for these examples may be different due to the random generation of scenarios.

Starting with an initial portfolio, estimateFrontierByRisk also returns purchases and sales to get from your initial portfolio to the target portfolios on the efficient frontier. For example, given an initial portfolio in pwgt0, you can obtain purchases and sales from the example with target risks of 12%, 14%, and 16%:

pwgt0 = [ 0.3; 0.3; 0.2; 0.1 ];
p = setInitPort(p, pwgt0);
[pwgt, pbuy, psell] = estimateFrontierByRisk(p, [0.12, 0.14, 0.16]);

display(pwgt);
display(pbuy);
display(psell);
pwgt =

    0.3594    0.2524    0.1543
    0.3164    0.3721    0.4248
    0.1044    0.1193    0.1298
    0.2199    0.2563    0.2910


pbuy =

    0.0594         0         0
    0.0164    0.0721    0.1248
         0         0         0
    0.1199    0.1563    0.1910


psell =

         0    0.0476    0.1457
         0         0         0
    0.0956    0.0807    0.0702
         0         0         0

If you do not specify an initial portfolio, the purchase and sale weights assume that your initial portfolio is 0.

Choosing and Controlling the Solver

When solving portfolio optimizations for a PortfolioCVaR object, all variations of fmincon from Optimization Toolbox™ are supported. Alternatively, you can use'cuttingplane', a solver that implements Kelley's cutting plane method (see Kelley [45] at Portfolio Optimization).

Unlike Optimization Toolbox which uses the interior-point algorithm as the default algorithm for fmincon, the portfolio optimization for a PortfolioCVaR object uses the sqp algorithm. For details about fmincon and constrained nonlinear optimization algorithms and options, see Constrained Nonlinear Optimization Algorithms.

To modify fmincon options for CVaR portfolio optimizations, use setSolver to set the hidden properties solverType and solverOptions to specify and control the solver. (Note that you can see the default options by creating a dummy PortfolioCVaR object, using p = PortfolioCVaR and then type p.solverOptions.) Since these solver properties are hidden, you cannot set them using the PortfolioCVaR constructor. The default solver is fmincon with the sqp algorithm objective function, gradients turned on, and no displayed output, so you do not need to use setSolver to specify this.

If you want to specify additional options associated with the fmincon solver, setSolver accepts these options as name-value pair arguments. For example, if you want to use fmincon with the trust-region-reflective algorithm and with no displayed output, use setSolver with:

p = PortfolioCVaR;
p = setSolver(p, 'fmincon', 'Algorithm', 'trust-region-reflective', 'Display', 'on');
display(p.solverOptions.Algorithm);
display(p.solverOptions.Display);
trust-region-reflective
on

Alternatively, setSolver accepts an optimoptions object as the second argument. For example, you can change the algorithm to trust-region-reflective with no displayed output as follows:

p = PortfolioCVaR;
options = optimoptions('fmincon','Algorithm', 'trust-region-reflective', 'Display', 'off');
p = setSolver(p, 'fmincon', options);
display(p.solverOptions.Algorithm);
display(p.solverOptions.Display);
trust-region-reflective
off

The 'cuttingplane' solver has options to control the number iterations and stopping tolerances. Moreover, this solver uses linprog as the master solver, and all linprog options are supported using optimoptions structures. All of these options are set using setSolver.

For example, you can use setSolver to increase the number of iterations for 'cuttingplane':

p = PortfolioCVaR;
p = setSolver(p, 'cuttingplane', 'MaxIter', 2000);
display(p.solverType);
display(p.solverOptions);
cuttingplane
                MaxIter: 2000
                 AbsTol: 1.0000e-06
                 RelTol: 1.0000e-05
    MasterSolverOptions: [1x1 optim.options.Linprog]

To change the master solver algorithm to'interior-point', with no display, use setSolver to modify 'MasterSolverOptions':

p = PortfolioCVaR;
options = optimoptions('linprog','Algorithm','interior-point','Display','off');
p = setSolver(p,'cuttingplane','MasterSolverOptions',options);
display(p.solverType)
display(p.solverOptions)
display(p.solverOptions.MasterSolverOptions.Algorithm)
display(p.solverOptions.MasterSolverOptions.Display)
cuttingplane
                MaxIter: 1000
                 AbsTol: 1.0000e-06
                 RelTol: 1.0000e-05
    MasterSolverOptions: [1x1 optim.options.Linprog]

interior-point
off

See Also

| | | | | | | |

Related Examples

More About

Was this topic helpful?