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
Datafeed 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.1405To 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.04The 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.64To 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)