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 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 functions 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
functions. 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 that you have a matrix of scenarios in the AssetScenarios
variable.
The scenarios are set through the PortfolioCVaR
function
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
function 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
function
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([], []);
startrow
to endrow
,
usescenarios = p.localScenarioHandle(startrow, endrow);
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
function
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 functions (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 function 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. So, "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 default behavior of simulateNormalScenariosByData
is
to work with asset returns. If, instead, you have asset prices as
in the variable Y, simulateNormalScenariosByData
accepts
a name-value pair argument name 'DataFormat'
with
a corresponding value set to 'prices'
to indicate
that the input to the function 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
function
has a name-value pair 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
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 get
different estimates for the moments in p
and q
. The simulateNormalScenariosByData
function
also accepts asset returns or prices stored in financial time series
(fints
) objects. The function
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
function
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 PortfolioCVaR
function:
r0 = 0.01/12;
p = PortfolioCVaR;
p = PortfolioCVaR('RiskFreeRate', r0);
disp(p.RiskFreeRate);
8.3333e-04
Note:
If your portfolio problem has a budget constraint such that
your portfolio weights must sum to |
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 that 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 PortfolioCVaR
function. 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 PortfolioCVaR
function
and setCosts
function 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 PortfolioCVaR
function 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