Main Content

estimateCustomObjectivePortfolio

Estimate optimal portfolio for user-defined objective function for Portfolio object

Since R2022b

Description

example

[pwgt,pbuy,psell,exitflag] = estimateCustomObjectivePortfolio(obj,fun) estimates optimal portfolio with a user-defined objective function for a Portfolio object. For details on using estimateCustomObjectivePortfolio, see Solver Guidelines for Custom Objective Problems Using Portfolio Objects.

example

[pwgt,pbuy,psell,exitflag] = estimateCustomObjectivePortfolio(___,Name=Value) adds optional name-value arguments.

Examples

collapse all

This example shows how to use estimateCustomObjectivePortfolio to solve a portfolio problem with a custom objective. You define the constraints for portfolio problems using functions for the Portfolio object and then you specify the objective function as an input to estimateCustomObjectivePortfolio. The objective function must be continuous (and preferably smooth).

Create Portfolio Object

Find the portfolio that solves the problem:

minx  xTxs.t.ixi=1         x0

Create a Portfolio object and set the default constraints using setDefaultConstraints. In this case, the portfolio problem does not involve the mean or covariance of the assets returns. Therefore, you do not need to define the assets moments to create the Portfolio object. You need only to define the number of assets in the problem.

% Create a Portfolio object
p = Portfolio(NumAssets=6);
% Define the constraints of the portfolio methods
p = setDefaultConstraints(p); % Long-only, fully invested weights

Define Objective Function

Define a function handle for the objective function xTx.

% Define the objective function
objFun = @(x) x'*x;

Solve Portfolio Problem

Use estimateCustomObjectivePortfolio to compute the solution to the problem.

% Solve portfolio problem
wMin = estimateCustomObjectivePortfolio(p,objFun)
wMin = 6×1

    0.1667
    0.1667
    0.1667
    0.1667
    0.1667
    0.1667

The estimateCustomObjectivePortfolio function automatically assumes that the objective sense is to minimize. You can change that default behavior by using the estimateCustomObjectivePortfolio name-value argument ObjectiveSense='maximize'.

This example shows how to use estimateCustomObjectivePortfolio to solve a portfolio problem with a custom objective and a return constraint. You define the constraints for portfolio problems, other than the return constraint, using functions for the Portfolio object and then you specify the objective function as an input to estimateCustomObjectivePortfolio. The objective function must be continuous (and preferably smooth). To specify a return constraint, you use the TargetReturn name-value argument.

Create Portfolio Object

Find the portfolio that solves the problem:

minx  xTxs.t.ixi=1         x0

Create a Portfolio object and set the default constraints using setDefaultConstraints.

% Create a Portfolio object
load('SixStocks.mat')
p = Portfolio(AssetMean=AssetMean,AssetCovar=AssetCovar);
% Define the constraints of the portfolio methods
p = setDefaultConstraints(p); % Long-only, fully invested weights

Define Objective Function

Define a function handle for the objective function xTx.

% Define the objective function
objFun = @(x) x'*x;

Add Return Constraints

When using the Portfolio object, you can use estimateFrontierByReturn to add a return constraint to the portfolio. However, when using the estimateCustomObjectivePortfolio function with a Portfolio object, you must add return constraints by using the TargetReturn name-value argument with a return target value.

The Portfolio object supports two types of return constraints: gross return and net return. The type of return constraint that is added to the portfolio problem is implicitly defined by whether you provide buy or sell costs to the Portfolio object using setCosts. If no buy or sell costs are present, the added return constraint is a gross return constraint. Otherwise, a net return constraint is added.

Gross Return Constraint

The gross portfolio return constraint for a portfolio is

r0+(μ-r0)Txμ0,

where r0 is the risk-free rate (with 0 as default), μ is the mean of assets returns, and μ0 is the target return.

Since buy or sell costs are not needed to add a gross return constraint, the Portfolio object does not need to be modified before using estimateCustomObjectivePortfolio.

% Set a return target
ret0 = 0.03;
% Solve portfolio problem with a gross return constraint
wGross = estimateCustomObjectivePortfolio(p,objFun, ...
    TargetReturn=ret0)
wGross = 6×1

    0.1377
    0.1106
    0.1691
    0.1829
    0.1179
    0.2818

The return constraint is not added to the Portfolio object. In other words, the Portfolio properties are not modified by adding a gross return constraint in estimateCustomObjectivePortfolio.

Net Return Constraint

The net portfolio return constraint for a portfolio is

r0+(μ-r0)Tx-cBTmax{0,x-x0}-cSTmax{0,x0-x}μ0,

where r0 is the risk-free rate (with 0 as default), μ is the mean of assets returns, cB is the proportional buy cost, cS is the proportional sell cost, x0 is the initial portfolio, and μ0 is the target return.

To add net return constraints to the portfolio problem, you must use setCosts with the Portfolio object. If the Portfolio object has either of these costs, estimateCustomObjectivePortfolio automatically assumes that any added return constraint is a net return constraint.

% Add buy and sell costs to the Portfolio object
buyCost = 0.002;
sellCost = 0.001;
initPort = zeros(p.NumAssets,1);
p = setCosts(p,buyCost,sellCost,initPort);
% Solve portfolio problem with a net return constraint
% wNet = estimateCustomObjectivePortfolio(p,objFun, ...
%    TargetReturn=ret0)

As with the gross return constraint, the net return constraint is not added to the Portfolio object properties, however the Portfolio object is modified by the addition of buy or sell costs. Adding buy or sell costs to the Portfolio object does not affect any constraints besides the return constraint, but these costs do affect the solution of maximum return problems because the solution is the maximum net return instead of the maximum gross return.

This example shows how to use estimateCustomObjectivePortfolio to solve a portfolio problem with a custom objective and cardinality constraints. You define the constraints for portfolio problems using functions for the Portfolio object and then you specify the objective function as an input to estimateCustomObjectivePortfolio. For problems with cardinality constraints or continuous bounds, the objective function must be continuous and convex.

Create Portfolio Object

Find a long-only, fully weighted portfolio with half the assets that minimizes the tracking error to the equally weighted portfolio. Furthermore, if an asset is present in the portfolio, at least 10% should be invested in that asset. The porfolio problem is as follows:

minx  (x-x0)TΣ(x-x0)s.t.ixi=1,         i#(xi0)=3,         xi0.1orxi=0

Create a Portfolio object and set the assets moments.

load('SixStocks.mat')
p = Portfolio(AssetMean=AssetMean,AssetCovar=AssetCovar);
nAssets = size(AssetMean,1);

Define the Portfolio constraints.

% Fully invested portfolio
p = setBudget(p,1,1);
% Cardinality constraint
p = setMinMaxNumAssets(p,3,3);
% Conditional bounds
p = setBounds(p,0.1,[],BoundType="conditional");

Define Objective Function

Define a function handle for the objective function.

% Define the objective function
EWP = 1/nAssets*ones(nAssets,1);
trackingError = @(x) (x-EWP)'*p.AssetCovar*(x-EWP);

Solve Portfolio Problem

Use estimateCustomObjectivePortfolio to compute the solution to the problem.

% Solve portfolio problem
wMinTE = estimateCustomObjectivePortfolio(p,trackingError)
wMinTE = 6×1

    0.1795
    0.3507
         0
    0.4698
         0
         0

This example shows how to use the name-value arguments ObjectiveBound and InitialPoint with estimateCustomObjectivePortfolio.

Load the returns data in CAPMuniverse.mat. Then, create a standard mean-variance Portfolio object with default constraints, which is a long-only portfolio whose weights sum to 1. For this example, you can define the feasible region of weights X as

X={x|i=1nxi=1,xi0}.

% Load data
load CAPMuniverse

% Create a mean-variance Portfolio object with default constraints
p = Portfolio(AssetList=Assets(1:12));
p = estimateAssetMoments(p,Data(:,1:12));
p = setDefaultConstraints(p);

% Add conditional bounds
pInt = setBounds(p,0.1,[],BoundType='cond');

Using ObjectiveBound

Use the ObjectiveBound name-value argument to provide an initial bound for the objective function. Providing an initial bound saves computation time because the solver does not have to solve an initial NLP relaxation to obtain the initial bound. Set up the MINLP solver, then test its performance when you do not provide an initial bound.

% Minimize tracking error without a lower bound
wTE = 1/p.NumAssets*ones(p.NumAssets,1);
trackingError = @(w) (w-wTE)'*p.AssetCovar*(w-wTE);
pInt2 = setSolverMINLP(pInt,'OuterApproximation',...
    ExtendedFormulation=true);
s = tic;
wTE_NoBound = estimateCustomObjectivePortfolio(pInt2,trackingError);
timeTE_NoBound = toc(s)
timeTE_NoBound = 6.8024

Since the lowest value that can be achieved by the tracking error is 0, you can use that as the lower bound with the ObjectiveBound name-value argument. Test the solver's performance when you do provide this lower bound. The solver is much faster when you provide one.

% Minimize tracking error with a lower bound
s = tic;
wTE_WithBound = estimateCustomObjectivePortfolio(pInt2,trackingError,ObjectiveBound=0);
timeTE_WithBound = toc(s)
timeTE_WithBound = 1.1646

Because you are minimizing the objective function, the bound that you provide to estimateCustomObjectivePortfolio must be a lower bound.

If you want to maximize the objective, then when you set ObjectiveSense to 'maximize', the ObjectiveBound is the upper bound of the objective function. Maximize the Sharpe ratio using ObjectiveBound.

% Maximize Sharpe ratio with upper bound
sharpeRatio = @(x) (p.AssetMean'*x)/sqrt(x'*p.AssetCovar*x);
wSR = estimateCustomObjectivePortfolio(pInt,sharpeRatio,...
    ObjectiveSense='maximize',ObjectiveBound=1);

Note that the ObjectiveBound name-value argument is ignored in continuous problems.

Using InitialPoint

Use the InitialPoint name-value argument to provide an initial point to the Portfolio solvers at the beginning of the iterations. You can use this name-value argument in continuous problems with nonconvex objectives to search the feasible region to find different local minima.

Set up a risk parity portfolio with constraints, group the constraints, then estimate the portfolio without and with specifying an initial point. Compare the risk parities of these portfolios.

% Risk parity portfolio with constraints
sigma = [5;5;7;10;15;15;15;18]/100;
rho = [ 1    0.8  0.6 -0.2 -0.1 -0.2 -0.2 -0.2;
        0.8  1    0.4 -0.2 -0.2 -0.1 -0.2 -0.2;
        0.6  0.4  1    0.5  0.3  0.2  0.2  0.3;
       -0.2 -0.2  0.5  1    0.6  0.6  0.5  0.6;
       -0.1 -0.2  0.3  0.6  1    0.9  0.7  0.7;
       -0.2 -0.1  0.2  0.6  0.9  1    0.6  0.7;
       -0.2 -0.2  0.2  0.5  0.7  0.6  1    0.7;
       -0.2 -0.2  0.3  0.6  0.7  0.7  0.7  1];
covariance = corr2cov(sigma,rho);
riskParity = @(x) sum((x.*(covariance*x)/(x'*covariance*x)-1).^2);

% Group the contraints
p = Portfolio(NumAssets=8);
p = setDefaultConstraints(p);
G = [0 0 0 0 1 1 1 1];
p = setGroups(p,G,0.3,[]);

% Solve the problem
w = estimateCustomObjectivePortfolio(p,riskParity);
riskParity(w)
ans = 6.1365
% Use the InitialPoint name-value argument
x0 = [0.7; zeros(3,1); 0.3; zeros(3,1)];
w2 = estimateCustomObjectivePortfolio(p,riskParity,InitialPoint=x0);
riskParity(w2)
ans = 6.1667

The objective function is different for different initial portfolios, because the objective function used to compute the risk parity portfolios is nonconvex.

Using different initial points in mixed-integer problems should not return different values of the objective function because the objective function should always be convex. That is, there is only one value for the minimum of the objective function.

Input Arguments

collapse all

Object for portfolio, specified using a Portfolio object. When using a Portfolio object in the custom objective workflow, you do not need to use the mean-variance framework to estimate the mean and covariance. However, the custom objective workflow does require that you specify portfolio constraints. The Portfolio object that you use with the estimateCustomObjectivePortfolio function supports only the following constraints:

Note

If no initial portfolio is specified in obj.InitPort, the initial portfolio is assumed to be 0 so that pbuy = max(0, pwgt) and psell = max(0, -pwgt). If no tracking portfolio is specified in obj.TrackingPort, the tracking portfolio is assumed to be 0.

Data Types: object

Function handle that defines the objective function, specified using a function handle in terms of the portfolio weights.

Note

The objective function must be continuous and defined using only the portfolio weights as variables. If the portfolio problem has cardinality constraints and/or conditional bounds using setMinMaxNumAssets or setBounds, the objective function must also be convex. For more information, see Role of Convexity in Portfolio Problems.

Data Types: function_handle

Name-Value Arguments

Specify optional pairs of arguments as Name1=Value1,...,NameN=ValueN, where Name is the argument name and Value is the corresponding value. Name-value arguments must appear after other arguments, but the order of the pairs does not matter.

Example: pwgt = estimateCustomObjectivePortfolio(p,fun,ObjectiveSense="maximize",TargetReturn=0.05)

Sense of the optimization, specified as ObjectiveSense and a string or character vector with one of the following values:

  • "minimize" — The solution minimizes the objective function.

  • "maximize" — The solution maximizes the objective function.

Data Types: string | char

Since R2023b

User-supplied objective function bound, specified as ObjectiveBound and a numeric value. ObjectiveBound is useful to speed up solvers.

Note

If ObjectiveSense is 'minimize', then ObjectiveBound should be a lower bound of the objective function. If ObjectiveSense is 'maximize', then ObjectiveBound should be an upper bound of the objective function. If ObjectiveBound is not specified, a numerical lower bound is computed.

Data Types: double

Since R2023b

Weights allocation to initialize solver, specified as InitialPoint and a NumAssets-by-1 vector. InitialPoint is useful for continuous portfolio problems that may have many local minima. By specifying a starting point, you can make the algorithm search different local minima.

Note

If InitialPoint is not specified, it is set to the long-only, fully-invested, equally-weighted portfolio.

Data Types: double

Output Arguments

collapse all

Optimal weight allocation of the portfolio problem, returned as a NumAssets-by-1 vector.

Purchases relative to initial portfolio to achieve the optimal weight allocation of the portfolio problem, returned as a NumAssets-by-1 vector.

Sales relative to initial portfolio to achieve the optimal weight allocation of the portfolio problem, returned as a NumAssets vector.

Reason the solver stopped, returned as an enumeration variable or integer. There are two types of exitflag output. If the problem is continuous, the exitflag output is an enumeration variable. If the problem is mixed-integer, then the exitflag output is an integer.

References

[1] Cornuejols, G. and Reha Tütüncü. Optimization Methods in Finance. Cambridge University Press, 2007.

Version History

Introduced in R2022b

expand all