This example shows how to liquidate a dollar value from a portfolio while minimizing market-impact costs using transaction cost analysis from the Kissell Research Group. This example always results in a portfolio that shrinks in size. The market-impact cost minimization is expressed as
where MI is the market-impact cost for the traded shares and x denotes the final weights for each stock.
This example requires an Optimization Toolbox™ license. For background information, see Optimization Theory Overview (Optimization Toolbox).
The optimization finds a local minimum for the market-impact cost of liquidating a dollar value from a portfolio. For ways to search for the global minimum, see Local vs. Global Optima (Optimization Toolbox).
To access the example code, enter edit KRGLiquidityOptimizationExample.m
at
the command line.
Retrieve the market-impact data from the Kissell Research Group
FTP site. Connect to the FTP site using the ftp
function
with a user name and password. Navigate to the MI_Parameters
folder
and retrieve the market-impact data in the MI_Encrypted_Parameters.csv
file. miData
contains
the encrypted market-impact date, code, and parameters.
f = ftp('ftp.kissellresearch.com','username','pwd'); mget(f,'MI_Encrypted_Parameters.csv'); close(f) miData = readtable('MI_Encrypted_Parameters.csv','delimiter', ... ',','ReadRowNames',false,'ReadVariableNames',true);
Create a Kissell Research Group transaction cost analysis object
k
. Specify initial settings for the date, market-impact
code, and number of trading days.
k = krg(miData,datetime('today'),1,250);
Load the example data TradeDataPortOpt
and
the covariance data CovarianceData
from the file KRGExampleData.mat
,
which is included with the Trading Toolbox™. Limit the data set
to the first 10 rows.
load KRGExampleData.mat TradeDataPortOpt CovarianceData n = 10; TradeDataPortOpt = TradeDataPortOpt(1:n,:); CovarianceData = CovarianceData(1:n,1:n); C = table2array(CovarianceData);
For a description of the example data, see Kissell Research Group Data Sets.
Set the portfolio liquidation value to $100,000,000. Set the portfolio risk boundaries between 90% and 110%. Set the maximum total market-impact cost to 50 basis points. Determine the number of stocks in the portfolio. Retrieve the upper bound constraint for the maximum market-impact cost for liquidating shares in each stock.
PortLiquidationValue = 100000000; PortRiskBounds = [0.9 1.10]; maxTotalMI = 0.005; numPortStocks = length(TradeDataPortOpt.Symbol); maxMI = TradeDataPortOpt.UB_MaxMI;
Determine the target portfolio value PortfolioTargetValue
by
subtracting the portfolio liquidation value from the total portfolio
value.
PortfolioValue = sum(TradeDataPortOpt.Value); absPortValue = abs(TradeDataPortOpt.Value); PortfolioAbsValue = sum(absPortValue); PortfolioTargetValue = PortfolioValue-PortLiquidationValue;
Determine the current portfolio weight w
based
on the value of each stock in the portfolio.
w = sign(TradeDataPortOpt.Shares).*absPortValue/PortfolioAbsValue;
Specify constraints Aeq
and beq
to
indicate that the weights must sum to one. Initialize the linear inequality
constraints A
and b
.
Aeq = ones(1,numPortStocks); beq = 1; A = []; b = [];
Retrieve the lower and upper bounds for the final portfolio
weight in TradeDataPortOpt
.
LB = TradeDataPortOpt.LB_Wt; UB = TradeDataPortOpt.UB_Wt;
Determine the lower and upper bounds for the number of shares in the final portfolio using other optional constraints in the example data set.
lbShares = max([TradeDataPortOpt.LB_MinShares, ... TradeDataPortOpt.LB_MinValue./TradeDataPortOpt.Price, ... TradeDataPortOpt.LB_MinPctADV.*TradeDataPortOpt.ADV],[],2); ubShares = min([TradeDataPortOpt.UB_MaxShares, ... TradeDataPortOpt.UB_MaxValue./TradeDataPortOpt.Price, ... TradeDataPortOpt.UB_MaxPctADV.*TradeDataPortOpt.ADV],[],2);
Specify the initial portfolio weights.
x0 = TradeDataPortOpt.Value./sum(TradeDataPortOpt.Value); x = x0;
Define optimization options. Set the optimization algorithm
to sequential quadratic programming. Set the termination tolerance
on the function value and on x
. Set the tolerance
on the constraint violation. Set the termination tolerance on the
PCG iteration. Set the maximum number of function evaluations 'MaxFunEvals'
and
iterations 'MaxIter'
. The options 'MaxFunEvals'
and 'MaxIter'
are
set to large values so that the optimization can iterate many times
to find a local minimum. Set the minimum change in variables for finite
differencing.
options = optimoptions('fmincon','Algorithm','sqp', ... 'TolFun',10E-8,'TolX',10E-16,'TolCon',10E-8,'TolPCG',10E-8, ... 'MaxFunEvals',50000,'MaxIter',50000,'DiffMinChange',10E-8);
Define the function handle objectivefun
for
the sample objective function krgLiquidityFunction
.
To access the code for this function, enter edit krgLiquidityFunction.m
.
Define the function handle constraintsfun
for the
sample function krgLiquidityConstraint
that sets
additional constraints. To access the code for this function, enter edit
krgLiquidityConstraint.m
.
objectivefun = @(x) krgLiquidityFunction(x,TradeDataPortOpt, ... PortfolioTargetValue,k); constraintsfun = @(x) krgLiquidityConstraint(x,w,C,TradeDataPortOpt, ... PortfolioTargetValue,PortRiskBounds,lbShares,ubShares,maxMI,maxTotalMI,k);
Minimize the market-impact costs for the portfolio liquidation. fmincon
finds
the optimal value for the portfolio weight for each stock based on
the lower and upper bound values. It does this by finding a local
minimum for the market-impact cost.
[x,~,exitflag] = fmincon(objectivefun,x0,A,b,Aeq,beq,LB,UB, ...
constraintsfun,options);
To check whether fmincon
found a
local minimum, display the reason why the function stopped.
exitflag
exitflag = 1.00
fmincon
returns 1
when
it finds a local minimum. For details, see exitflag
(Optimization Toolbox).
Determine the optimized weight value x1
of
each stock in the portfolio in decimal format.
x1 = x.*PortfolioTargetValue/PortfolioValue;
Determine the optimized portfolio target value TargetValue
and
number of shares SharesToTrade
for each stock in
the portfolio.
TargetShares = x*PortfolioTargetValue./TradeDataPortOpt.Price; SharesToTrade = TradeDataPortOpt.Shares-TargetShares; TargetValue = x*PortfolioTargetValue; TradeDataPortOpt.Shares = abs(SharesToTrade);
Determine the optimized percentage of volume strategy.
TradeDataPortOpt.TradeTime = TradeDataPortOpt.TradeTime ... .* TradeDataPortOpt.ADV; TradeDataPortOpt.POV = krg.tradetime2pov(TradeDataPortOpt.TradeTime, ... TradeDataPortOpt.Shares);
Estimate the market-impact costs MI
for
the number of shares to liquidate.
MI = marketImpact(k,TradeDataPortOpt)/10000;
To view the market-impact cost in decimal format, specify the display format. Display the market-impact cost for the first three stocks in the portfolio.
format MI(1:3)
ans = 1.0e-03 * 0.1477 0.1405 0.1405
To view the target number of shares with two decimal places, specify the display format. Display the target number of shares for the first three stocks in the portfolio.
format bank
TargetShares(1:3)
ans = -23640.11 -154656.73 -61193.04
The negative values denote selling shares from the portfolio.
Display the traded value for the first three stocks in the portfolio.
TargetValue(1:3)
ans = -968062.45 -1521760.41 -2448131.64
To simulate trading the target number of shares on a historical date range, you can now conduct a stress test on the optimized portfolio. For details about conducting a stress test, see Conduct Stress Test on Portfolio.
[1] Kissell, Robert. “Creating Dynamic Pre-Trade Models: Beyond the Black Box.” Journal of Trading. Vol. 6, Number 4, Fall 2011, pp. 8–15.
[2] Kissell, Robert. “TCA in the Investment Process: An Overview.” Journal of Index Investing. Vol. 2, Number 1, Summer 2011, pp. 60–64.
[3] Kissell, Robert. The Science of Algorithmic Trading and Portfolio Management. Cambridge, MA: Elsevier/Academic Press, 2013.
[4] Chung, Grace and Robert Kissell. “An Application of Transaction Costs in the Portfolio Optimization Process.” Journal of Trading. Vol. 11, Number 2, Spring 2016, pp. 11–20.
krg
| marketImpact
| fmincon
(Optimization Toolbox) | optimoptions
(Optimization Toolbox)