Documentation |
The CVaR of a portfolio is a conditional expectation. (For the definition of the CVaR function, see Risk Proxy.) Therefore, the CVaR portfolio optimization problem is a stochastic optimization problem. Given a sample of scenarios, the conditional expectation that defines the sample CVaR of the portfolio can be expressed as a finite sum, a weighted average of losses. The weights of the losses depend on their relative magnitude; for a confidence level α, only the worst (1 − α) x 100% losses get a positive weight. As a function of the portfolio weights, the CVaR of the portfolio is a convex function (see [48], [49] Rockafellar & Uryasev at Portfolio Optimization). It is also a nonsmooth function, but its edges are less sharp as the sample size increases.
There are reformulations of the CVaR portfolio optimization problem (see [48], [49] at Rockafellar & Uryasev) that result in a linear programming problem, which can be solved either with standard linear programming techniques or with stochastic programming solvers. The PortfolioCVaR object, however, does not reformulate the problem in such a manner. The PortfolioCVaR object computes the CVaR as a nonlinear function. The convexity of the CVaR, as a function of the portfolio weights and the somewhat dull edges when the number of scenarios is large, make the CVaR portfolio optimization problem tractable, in practice, for certain nonlinear programming solvers, such as fmincon from Optimization Toolbox™. The problem can also be solved using a cutting-plane method (see Kelley [45] at Portfolio Optimization). For more information, see Algorithms section of setSolver. To learn more about the workflow when using PortfolioCVaR objects, see PortfolioCVaR Object Workflow.
Since conditional value-at-risk portfolio optimization works with scenarios of asset returns to perform the optimization, several ways exist to specify and simulate scenarios. In many applications with CVaR portfolio optimization, asset returns may have distinctly nonnormal probability distributions with either multiple modes, binning of returns, truncation of distributions, and so forth. In other applications, asset returns are modeled as the result of various simulation methods that might include Monte-Carlo simulation, quasi-random simulation, and so forth. In many cases, the underlying probability distribution for risk factors may be multivariate normal but the resultant transformations are sufficiently nonlinear to result in distinctively nonnormal asset returns.
For example, this occurs with bonds and derivatives. In the case of bonds with a nonzero probability of default, such scenarios would likely include asset returns that are −100% to indicate default and some values slightly greater than −100% to indicate recovery rates.
Although the PortfolioCVaR object has methods to simulate multivariate normal scenarios from either data or moments (simulateNormalScenariosByData and simulateNormalScenariosByMoments), the usual approach is to specify scenarios directly from your own simulation methods. These scenarios are entered directly as a matrix with a sample for all assets across each row of the matrix and with samples for an asset down each column of the matrix. The architecture of the CVaR portfolio optimization tools references the scenarios through a function handle so scenarios that have been set cannot be accessed directly as a property of the PortfolioCVaR object.
Suppose you have a matrix of scenarios in the AssetScenarios variable. The scenarios are set through the PortfolioCVaR constructor with:
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('Scenarios', AssetScenarios);
disp(p.NumAssets);
disp(p.NumScenarios);
4 20000
Notice that the PortfolioCVaR object determines and fixes the number of assets in NumAssets and the number of scenarios in NumScenarios based on the scenario's matrix. You can change the number of scenarios by calling the PortfolioCVaR constructor with a different scenario matrix. However, once the NumAssets property has been set in the object, you cannot enter a scenario matrix with a different number of assets. The getScenarios method lets you recover scenarios from a PortfolioCVaR object. You can also obtain the mean and covariance of your scenarios using estimateScenarioMoments.
Although not recommended for the casual user, an alternative way exists to recover scenarios by working with the function handle that points to scenarios in the PortfolioCVaR object. To access some or all of the scenarios from a PortfolioCVaR object, the hidden property localScenarioHandle is a function handle that points to a function to obtain scenarios that have already been set. To get scenarios directly from a PortfolioCVaR object p, use
scenarios = p.localScenarioHandle([], []);
and to obtain a subset of scenarios from rows startrow to endrow, use
scenarios = p.localScenarioHandle(startrow, endrow);
where 1 ≤ startrow ≤ endrow ≤ numScenarios.
You can also set scenarios using setScenarios. For example, given the mean and covariance of asset returns in the variables m and C, the asset moment properties can be set:
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); disp(p.NumAssets); disp(p.NumScenarios);
4 20000
The estimateScenarioMoments method obtains estimates for the mean and covariance of scenarios in a PortfolioCVaR object.
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); [mean, covar] = estimateScenarioMoments(p)
mean = 0.0043 0.0085 0.0098 0.0153 covar = 0.0005 0.0003 0.0002 0.0000 0.0003 0.0024 0.0017 0.0010 0.0002 0.0017 0.0049 0.0029 0.0000 0.0010 0.0029 0.0102
As a convenience, the two methods (simulateNormalScenariosByData and simulateNormalScenariosByMoments) exist to simulate scenarios from data or moments under an assumption that they are distributed as multivariate normal random asset returns.
Given either return or price data, use the method simulateNormalScenariosByData to simulate multivariate normal scenarios. Either returns or prices are stored as matrices with samples going down the rows and assets going across the columns. In addition, returns or prices can be stored in a financial time series fints object (see Simulating Normal Scenarios from Time Series Data). To illustrate using simulateNormalScenariosByData, generate random samples of 120 observations of asset returns for four assets from the mean and covariance of asset returns in the variables m and C with portsim. The default behavior of portsim creates simulated data with estimated mean and covariance identical to the input moments m and C. In addition to a return series created by portsim in the variable X, a price series is created in the variable Y:
m = [ 0.0042; 0.0083; 0.01; 0.15 ]; C = [ 0.005333 0.00034 0.00016 0; 0.00034 0.002408 0.0017 0.000992; 0.00016 0.0017 0.0048 0.0028; 0 0.000992 0.0028 0.010208 ]; X = portsim(m', C, 120); Y = ret2tick(X);
Note: Portfolio optimization requires that you use total returns and not just price returns. Consequently, "returns" should be total returns and "prices" should be total return prices. |
Given asset returns and prices in variables X and Y from above, this sequence of examples demonstrates equivalent ways to simulate multivariate normal scenarios for the PortfolioCVaR object. Assume a PortfolioCVaR object created in p that uses the asset returns in X uses simulateNormalScenariosByData:
p = PortfolioCVaR; p = simulateNormalScenariosByData(p, X, 20000); [passetmean, passetcovar] = estimateScenarioMoments(p)
passetmean = 0.0043 0.0083 0.0102 0.1507 passetcovar = 0.0053 0.0003 0.0002 0.0000 0.0003 0.0024 0.0017 0.0010 0.0002 0.0017 0.0049 0.0028 0.0000 0.0010 0.0028 0.0101
The moments that you obtain from this simulation will likely differ from the moments listed here because the scenarios are random samples from the estimated multivariate normal probability distribution of the input returns X.
The default behavior of simulateNormalScenariosByData is to work with asset returns. If, instead, you have asset prices as in the variable Y, simulateNormalScenariosByData accepts an argument name 'DataFormat' with a corresponding value set to 'prices' to indicate that the input to the method is in the form of asset prices and not returns (the default value for the 'DataFormat' argument is 'returns'). This example simulates scenarios with the asset price data in Y for the PortfolioCVaR object q:
p = PortfolioCVaR; p = simulateNormalScenariosByData(p, Y, 20000, 'dataformat', 'prices'); [passetmean, passetcovar] = estimateScenarioMoments(p)
passetmean = 0.0043 0.0084 0.0094 0.1490 passetcovar = 0.0054 0.0004 0.0001 -0.0000 0.0004 0.0024 0.0016 0.0009 0.0001 0.0016 0.0048 0.0028 -0.0000 0.0009 0.0028 0.0100
Often when working with multiple assets, you have missing data indicated by NaN values in your return or price data. Although Multivariate Normal Regression goes into detail about regression with missing data, the simulateNormalScenariosByData method has an argument name 'MissingData' that indicates with a Boolean value whether to use the missing data capabilities of Financial Toolbox™. The default value for 'MissingData' is false which removes all samples with NaN values. If, however, 'MissingData' is set to true, simulateNormalScenariosByData uses the ECM algorithm to estimate asset moments. This example shows how this works on price data with missing values:
m = [ 0.0042; 0.0083; 0.01; 0.15 ]; C = [ 0.005333 0.00034 0.00016 0; 0.00034 0.002408 0.0017 0.000992; 0.00016 0.0017 0.0048 0.0028; 0 0.000992 0.0028 0.010208 ]; X = portsim(m', C, 120); Y = ret2tick(X); Y(1:20,1) = NaN; Y(1:12,4) = NaN;
Notice that the prices above in Y have missing values in the first and fourth series.
p = PortfolioCVaR; p = simulateNormalScenariosByData(p, Y, 20000, 'dataformat', 'prices'); q = PortfolioCVaR; q = simulateNormalScenariosByData(q, Y, 20000, 'dataformat', 'prices', 'missingdata', true); [passetmean, passetcovar] = estimateScenarioMoments(p) [qassetmean, qassetcovar] = estimateScenarioMoments(q)
passetmean = 0.0020 0.0074 0.0078 0.1476 passetcovar = 0.0055 0.0003 -0.0001 -0.0003 0.0003 0.0024 0.0019 0.0012 -0.0001 0.0019 0.0050 0.0028 -0.0003 0.0012 0.0028 0.0101 qassetmean = 0.0024 0.0085 0.0106 0.1482 qassetcovar = 0.0071 0.0004 -0.0001 -0.0004 0.0004 0.0032 0.0022 0.0012 -0.0001 0.0022 0.0063 0.0034 -0.0004 0.0012 0.0034 0.0127
The first PortfolioCVaR object, p, contains scenarios obtained from price data in Y where NaN values are discarded and the second PortfolioCVaR object, q, contains scenarios obtained from price data in Y that accommodate missing values. Each time you run this example, you will get different estimates for the moments in p and q.
The simulateNormalScenariosByData method also accepts asset returns or prices stored in financial time series (fints) objects. The method implicitly works with matrices of data or data in a fints object using the same rules for whether the data are returns or prices. To illustrate, use fints to create the fints object Xfts that contains asset returns generated with fints (see Estimating Asset Moments from Prices or Returns) and add series labels:
m = [ 0.0042; 0.0083; 0.01; 0.15 ]; C = [ 0.005333 0.00034 0.00016 0; 0.00034 0.002408 0.0017 0.000992; 0.00016 0.0017 0.0048 0.0028; 0 0.000992 0.0028 0.010208 ]; X = portsim(m', C, 120); d = (datenum('31-jan-2001'):datenum('31-dec-2010'))'; Xfts = fints(d, zeros(numel(d),4), {'Bonds', 'LargeCap', 'SmallCap', 'Emerging'}); Xfts = tomonthly(Xfts); Xfts.Bonds = X(:,1); Xfts.LargeCap = X(:,2); Xfts.SmallCap = X(:,3); Xfts.Emerging = X(:,4); p = PortfolioCVaR; p = simulateNormalScenariosByData(p, Xfts, 20000); [passetmean, passetcovar] = estimateScenarioMoments(p)
passetmean = 0.0044 0.0082 0.0102 0.1504 passetcovar = 0.0054 0.0004 0.0002 -0.0000 0.0004 0.0024 0.0017 0.0010 0.0002 0.0017 0.0047 0.0027 -0.0000 0.0010 0.0027 0.0102
The name-value inputs 'DataFormat' to handle return or price data and'MissingData' to ignore or use samples with missing values also work for fints data. In addition, simulateNormalScenariosByData extracts asset names or identifiers from a fints object if the argument name 'GetAssetList' is set to true (the default value is false). If the 'GetAssetList' value is true, the identifiers are used to set the AssetList property of the PortfolioCVaR object. Thus, repeating the formation of the PortfolioCVaR object q from the previous example with the 'GetAssetList' flag set to true extracts the series labels from the fints object:
p = simulateNormalScenariosByData(p, Xfts, 20000, 'getassetlist', true);
disp(p.AssetList)
'Bonds' 'LargeCap' 'SmallCap' 'Emerging'
If you set the'GetAssetList' flag set to true and your input data is in a matrix, simulateNormalScenariosByData uses the default labeling scheme from setAssetList as described in Setting Up a List of Asset Identifiers.
Given the mean and covariance of asset returns, use the simulateNormalScenariosByMoments method to simulate multivariate normal scenarios. The mean can be either a row or column vector and the covariance matrix must be a symmetric positive-semidefinite matrix. Various rules for scalar expansion apply. To illustrate using simulateNormalScenariosByMoments, start with moments in m and C and generate 20,000 scenarios:
m = [ 0.0042; 0.0083; 0.01; 0.15 ]; C = [ 0.005333 0.00034 0.00016 0; 0.00034 0.002408 0.0017 0.000992; 0.00016 0.0017 0.0048 0.0028; 0 0.000992 0.0028 0.010208 ]; p = PortfolioCVaR; p = simulateNormalScenariosByMoments(p, m, C, 20000); [passetmean, passetcovar] = estimateScenarioMoments(p)
passetmean = 0.0049 0.0083 0.0101 0.1503 passetcovar = 0.0053 0.0003 0.0002 -0.0000 0.0003 0.0024 0.0017 0.0010 0.0002 0.0017 0.0047 0.0028 -0.0000 0.0010 0.0028 0.0101
simulateNormalScenariosByMoments performs scalar expansion on arguments for the moments of asset returns. If NumAssets has not already been set, a scalar argument is interpreted as a scalar with NumAssets set to 1. simulateNormalScenariosByMoments provides an additional optional argument to specify the number of assets so that scalar expansion works with the correct number of assets. In addition, if either a scalar or vector is input for the covariance of asset returns, a diagonal matrix is formed such that a scalar expands along the diagonal and a vector becomes the diagonal.
The PortfolioCVaR object has a separate RiskFreeRate property that stores the rate of return of a riskless asset. Thus, you can separate your universe into a riskless asset and a collection of risky assets. For example, assume that your riskless asset has a return in the scalar variable r0, then the property for the RiskFreeRate is set using the constructor:
r0 = 0.01/12;
p = PortfolioCVaR;
p = PortfolioCVaR('RiskFreeRate', r0);
disp(p.RiskFreeRate);
8.3333e-04
The difference between net and gross portfolio returns is transaction costs. The net portfolio return proxy has distinct proportional costs to purchase and to sell assets which are maintained in the PortfolioCVaR object properties BuyCost and SellCost. Transaction costs are in units of total return and, as such, are proportional to the price of an asset so that they enter the model for net portfolio returns in return form. For example, suppose you have a stock currently priced $40 and your usual transaction costs are 5 cents per share. Then the transaction cost for the stock is 0.05/40 = 0.00125 (as defined in Net Portfolio Returns). Costs are entered as positive values and credits are entered as negative values.
To set up transaction costs, you must specify an initial or current portfolio in the InitPort property. If the initial portfolio is not set when you set up the transaction cost properties, InitPort is 0. The properties for transaction costs can be set using the constructor PortfolioCVaR. For example, assume that purchase and sale transaction costs are in the variables bc and sc and an initial portfolio is in the variable x0, then transaction costs are set:
bc = [ 0.00125; 0.00125; 0.00125; 0.00125; 0.00125 ]; sc = [ 0.00125; 0.007; 0.00125; 0.00125; 0.0024 ]; x0 = [ 0.4; 0.2; 0.2; 0.1; 0.1 ]; p = PortfolioCVaR('BuyCost', bc, 'SellCost', sc, 'InitPort', x0); disp(p.NumAssets); disp(p.BuyCost); disp(p.SellCost); disp(p.InitPort);
5 0.0013 0.0013 0.0013 0.0013 0.0013 0.0013 0.0070 0.0013 0.0013 0.0024 0.4000 0.2000 0.2000 0.1000 0.1000
You can also set the properties for transaction costs using setCosts. Assume that you have the same costs and initial portfolio as in the previous example. Given a PortfolioCVaR object p with an initial portfolio already set, use setCosts to set up transaction costs:
bc = [ 0.00125; 0.00125; 0.00125; 0.00125; 0.00125 ];
sc = [ 0.00125; 0.007; 0.00125; 0.00125; 0.0024 ];
x0 = [ 0.4; 0.2; 0.2; 0.1; 0.1 ];
p = PortfolioCVaR('InitPort', x0);
p = setCosts(p, bc, sc);
disp(p.NumAssets);
disp(p.BuyCost);
disp(p.SellCost);
disp(p.InitPort);
5 0.0013 0.0013 0.0013 0.0013 0.0013 0.0013 0.0070 0.0013 0.0013 0.0024 0.4000 0.2000 0.2000 0.1000 0.1000
You can also set up the initial portfolio's InitPort value as an optional argument to setCosts so that the following is an equivalent way to set up transaction costs:
bc = [ 0.00125; 0.00125; 0.00125; 0.00125; 0.00125 ]; sc = [ 0.00125; 0.007; 0.00125; 0.00125; 0.0024 ]; x0 = [ 0.4; 0.2; 0.2; 0.1; 0.1 ]; p = PortfolioCVaR; p = setCosts(p, bc, sc, x0); disp(p.NumAssets); disp(p.BuyCost); disp(p.SellCost); disp(p.InitPort);
5 0.0013 0.0013 0.0013 0.0013 0.0013 0.0013 0.0070 0.0013 0.0013 0.0024 0.4000 0.2000 0.2000 0.1000 0.1000
Both the constructor PortfolioCVaR and setCosts method implement scalar expansion on the arguments for transaction costs and the initial portfolio. If the NumAssets property is already set in the PortfolioCVaR object, scalar arguments for these properties are expanded to have the same value across all dimensions. In addition, setCosts lets you specify NumAssets as an optional final argument. For example, assume that you have an initial portfolio x0 and you want to set common transaction costs on all assets in your universe. You can set these costs in any of these equivalent ways:
x0 = [ 0.4; 0.2; 0.2; 0.1; 0.1 ]; p = PortfolioCVaR('InitPort', x0, 'BuyCost', 0.002, 'SellCost', 0.002);
or
x0 = [ 0.4; 0.2; 0.2; 0.1; 0.1 ];
p = PortfolioCVaR('InitPort', x0);
p = setCosts(p, 0.002, 0.002);
or
x0 = [ 0.4; 0.2; 0.2; 0.1; 0.1 ]; p = PortfolioCVaR; p = setCosts(p, 0.002, 0.002, x0);
To clear costs from your PortfolioCVaR object, use either the constructor or setCosts with empty inputs for the properties to be cleared. For example, you can clear sales costs from the PortfolioCVaR object p in the previous example:
p = PortfolioCVaR(p, 'SellCost', []);
estimatePortVaR | PortfolioCVaR | setCosts | setProbabilityLevel | setScenarios | simulateNormalScenariosByData | simulateNormalScenariosByMoments