This example shows how to evaluate trading cost and risk components for a basket using transaction cost analysis from the Kissell Research Group. To create a basket summary, estimate trading costs for the entire basket using basket optimization techniques, and then calculate risk statistics for the basket. Using the basket summary, you can provide brokers and third parties with enough information to assess the overall execution costs and trading difficulty of the basket. The basket summary enables providing transaction information without revealing the actual orders. Another way brokers use a basket summary is to assess a fair value principal bid estimate. A principal bid is a transaction where the broker charges a bid premium that is higher than the associated commission. Brokers present this transaction with guaranteed completion for a given price.
In this example, you can see a basket summary analysis table and a principal bid summary. The basket summary provides trading cost estimates for the basket across different categories, such as side, market capitalization, and market sector. The principal bid summary contains the efficient trading frontier that provides the different estimated trading costs for different time periods. The efficient trading frontier shows how cost and risk change by trading more aggressively or passively. With passive trading, market impact decreases as timing risk increases. With aggressive trading, market impact increases as timing risk decreases.
The code in this example depends on the output data from the example Optimize Trade Schedule Trading Strategy for Basket. Run the code in that example first and then run the code in this example.
To access the example code, enter edit KRGBasketAnalysisExample.m
at
the command line.
After executing the code in this example, you can submit an order for execution using Bloomberg®, for example.
Determine the covariance matrix. Covariance indicates how the prices of stocks in the basket relate to each other.
% Covariance matrix is annualized covariance matrix in decimals. % Convert to ($/Shares)^2 units for the trade period, this matrix is for a % two-sided portfolio, buys and sells or long and short. diagPrice = diag(TradeDataTradeOpt.Price); C1 = TradeDataTradeOpt.SideIndicator * TradeDataTradeOpt.SideIndicator' .* ... diagPrice * CovarianceTradeOpt * diagPrice; % Covariance Matrix in $/Share^2 by Day CD = diagPrice * CovarianceTradeOpt * diagPrice; % compute Covariance Matrix in ($/share)^2 CD = CD / k.TradeDaysInYear; % scale to 1-day CD = TradeDataTradeOpt.SideIndicator * TradeDataTradeOpt.SideIndicator' ... .* CD;
Add the estimated trading costs from the trade schedule optimization to the basket data.
% Market impact in basis points TradeDataTradeOpt.MI = MI ./ (TradeDataTradeOpt.Shares .* ... TradeDataTradeOpt.Price) .* 10000; % Timing risk in basis points TradeDataTradeOpt.TR = TR ./ (TradeDataTradeOpt.Shares .* ... TradeDataTradeOpt.Price) .* 10000; % Percentage of volume, price appreciation and liquidity factor TradeDataTradeOpt.POV = POV; TradeDataTradeOpt.PA = PA; TradeDataTradeOpt.LF = liquidityFactor(k,TradeDataTradeOpt);
Calculate trading costs in basis points, cents per share, and dollars.
% Build optimal cost table OptimalCostTable = table(cell(3,1),zeros(3,1),zeros(3,1),zeros(3,1), ... zeros(3,1),'VariableNames',{'CostUnits','MI','PA','TotalCost','TR'}); OptimalCostTable.CostUnits(1) = {'Basis Points'}; OptimalCostTable.CostUnits(2) = {'Cents per Share'}; OptimalCostTable.CostUnits(3) = {'Dollars'}; % Market impact, OptimalCostTable.MI(1) = TotMI; OptimalCostTable.MI(2) = TotMI / 100 * mean(TradeDataTradeOpt.Price); OptimalCostTable.MI(3) = TotMI / 100 * (TradeDataTradeOpt.Shares' * ... TradeDataTradeOpt.Price); % Price appreciation OptimalCostTable.PA(1) = TotPA; OptimalCostTable.PA(2) = TotPA / 100 * mean(TradeDataTradeOpt.Price); OptimalCostTable.PA(3) = TotPA / 100 * (TradeDataTradeOpt.Shares' * ... TradeDataTradeOpt.Price); % Total cost OptimalCostTable.TotalCost(1) = TotMI + TotPA; OptimalCostTable.TotalCost(2) = (TotMI + TotPA) / 100 * mean(TradeDataTradeOpt.Price); OptimalCostTable.TotalCost(3) = (TotMI + TotPA) / 100 * ... (TradeDataTradeOpt.Shares' * TradeDataTradeOpt.Price); % Timing risk OptimalCostTable.TR(1) = TotTR; OptimalCostTable.TR(2) = TotTR / 100 * mean(TradeDataTradeOpt.Price); OptimalCostTable.TR(3) = TotTR / 100 * ... (TradeDataTradeOpt.Shares' * TradeDataTradeOpt.Price);
Display the optimal costs for the basket. Format the display output to show cents and dollars. Optimal costs are market impact, price appreciation, total cost, and timing risk.
format bank
OptimalCostTable
OptimalCostTable = 3×5 table array CostUnits MI PA TotalCost TR _________________ ____________ ____ ____________ ____________ 'Basis Points' 38.30 0.00 38.30 26.57 'Cents per Share' 14.88 0.00 14.88 10.32 'Dollars' 171134479.73 0.00 171134479.73 118710304.48
Calculate risk statistics. The marginal contribution to risk captures the risk of changing one of the components in the basket, such as the number of shares. The risk contribution is the risk for each trade in the basket.
% Portfolio Risk in Dollars PortfolioRisk = sqrt(TradeDataTradeOpt.Shares' * CD * ... TradeDataTradeOpt.Shares); % MCR and RC calculations PortfolioRiskMCR = zeros(numberStocks,1); PortfolioRiskRC =zeros(numberStocks,1); SharesMCR = TradeDataTradeOpt.Shares; SharesRC = TradeDataTradeOpt.Shares; for i = 1:numberStocks SharesMCR(i) = TradeDataTradeOpt.Shares(i) * 0.90; SharesRC(i) = 0; PortfolioRiskMCR(i) = sqrt(SharesMCR' * CD * SharesMCR); PortfolioRiskRC(i) = sqrt(SharesRC' * CD * SharesRC); end TradeDataTradeOpt.MCR = PortfolioRisk ./ PortfolioRiskMCR - 1; TradeDataTradeOpt.RC = PortfolioRisk ./ PortfolioRiskRC - 1;
Display the side, symbol, and number of shares for the safest trade in the basket using the risk contribution.
minrisk = min(TradeDataTradeOpt.RC); for i = 1:25 if TradeDataTradeOpt.RC(i) == minrisk idx = i; end end [TradeDataTradeOpt.Side(idx) TradeDataTradeOpt.Symbol(idx) ... TradeDataTradeOpt.Shares(idx)]
ans = 1×3 cell array 'B' 'ABC' [100000]
The buy order of 100,000 shares of stock ABC
contributes
the most overall portfolio risk.
Create a table for the basket report summary.
% Get sector identifiers uniqueSectors = unique(TradeDataTradeOpt.Sector); numSectors = size(uniqueSectors,1); numGroups = 14 + size(uniqueSectors,1); % Using 14 categories plus number of sectors % Preallocate BasketReport table BasketReport = table; BasketReport.BasketCategory = cell(numGroups,1); BasketReport.Number = zeros(numGroups,1); BasketReport.Weight = zeros(numGroups,1); BasketReport.MI = zeros(numGroups,1); BasketReport.TR = zeros(numGroups,1); BasketReport.POV = zeros(numGroups,1); BasketReport.TradeTime = zeros(numGroups,1); BasketReport.PctADV = zeros(numGroups,1); BasketReport.Price = zeros(numGroups,1); BasketReport.Volatility = zeros(numGroups,1); BasketReport.Risk = zeros(numGroups,1); BasketReport.RC = zeros(numGroups,1); BasketReport.MCR = zeros(numGroups,1); BasketReport.Beta = zeros(numGroups,1); BasketReport.LF = zeros(numGroups,1); BasketReport.TotalValue = zeros(numGroups,1); BasketReport.BuyValue = zeros(numGroups,1); BasketReport.SellValue = zeros(numGroups,1); BasketReport.NetValue = zeros(numGroups,1); BasketReport.Shares = zeros(numGroups,1); BasketReport.BuyShares = zeros(numGroups,1); BasketReport.SellShares = zeros(numGroups,1);
Calculate the basket report summary.
Divide the trades in the basket into these categories:
Total
— All trades in basket
Buy
— Buy trades
Cover
— Buy trades that
cover a short position
Sell
— Sell trades
Short
— Short trades
<=1%
— Trades that have
percentage of average daily volume less than or equal to 1%
1%-3%
— Trades that have
percentage of average daily volume between 1% and 3%
3%-5%
— Trades that have
percentage of average daily volume between 3% and 5%
5%-10%
— Trades that have
percentage of average daily volume between 5% and 10%
10%-20%
— Trades that have
percentage of average daily volume between 10% and 20%
>20%
— Trades that have
percentage of average daily volume greater than 20%
LC
— Large-capitalization
stock trades
MC
— Mid-capitalization
stock trades
SC
— Small-capitalization
stock trades
Consumer Discretionary
—
Trades in the consumer discretionary industry
Consumer Staples
— Trades
in the consumer staples industry
Energy
— Trades in the energy
industry
Financials
— Trades in the
financial industry
Health Care
— Trades in
the health care industry
Industrials
— Trades in
the industrial industry
Information Technology
—
Trades in the information technology industry
Materials
— Trades in the
materials industry
Telecommunication Services
—
Trades in the telecommunication services industry
Utilities
— Trades in the
utilities industry
For stocks in each category, calculate these values:
Weight
— Total trade value
weight
MI
— Weighted average market-impact
cost
TR
— Timing risk
POV
— Weighted average percentage
of volume rate
TradeTime
— Weighted average
trade time to complete the order
PctADV
— Weighted average
order size (measured as percentage of average daily volume)
Price
— Weighted average
share price
Volatility
— Weighted average
volatility
Risk
— Portfolio risk
RC
— Risk contribution to
the overall portfolio risk (shows the amount of risk that an order
contributes to the basket)
MCR
— Marginal contribution
to risk (shows the amount of risk that 10% of shares in the order
contribute to the basket)
Beta
— Weighted average
beta
LF
— Weighted average liquidity
factor
TotalValue
— Total trade
value
BuyValue
— Total trade value
of the buy transactions
SellValue
— Total trade
value of the sell transactions
NetValue
— Difference between
total trade value of the buy and sell transactions
Shares
— Number of shares
BuyShares
— Number of shares
to buy
SellShares
— Number of shares
to sell
% Fill table, indRecord is index of matching TradeData rows j = 0; for i = 1:24 switch i % Total case 1 indRecord = true(numberStocks,1); BasketReport.BasketCategory(i) = {'Total'}; % Side case 2 indRecord = strcmp(TradeDataTradeOpt.Side,'B') | ... strcmp(TradeDataTradeOpt.Side,'Buy'); BasketReport.BasketCategory(i) = {'Buy'}; case 3 indRecord = strcmp(TradeDataTradeOpt.Side,'C') | ... strcmp(TradeDataTradeOpt.Side,'Cover'); BasketReport.BasketCategory(i) = {'Cover'}; case 4 indRecord = strcmp(TradeDataTradeOpt.Side,'S') | ... strcmp(TradeDataTradeOpt.Side,'Sell'); BasketReport.BasketCategory(i) = {'Sell'}; case 5 indRecord = strcmp(TradeDataTradeOpt.Side,'SS') | ... strcmp(TradeDataTradeOpt.Side,'Short') | ... strcmp(TradeDataTradeOpt.Side,'Sell Short'); BasketReport.BasketCategory(i) = {'Short'}; % Liquidity Category case 6 % Percentage of average daily volume is less than 1 % indRecord = (TradeDataTradeOpt.PctADV <= 0.01); BasketReport.BasketCategory(i) = {'<=1%'}; case 7 % Percentage of average daily volume is between 1 and 3 % indRecord = (TradeDataTradeOpt.PctADV > 0.01 & ... TradeDataTradeOpt.PctADV <= 0.03); BasketReport.BasketCategory(i) = {'1%-3%'}; case 8 % Percentage of average daily volume is between 3 and 5 % indRecord = (TradeDataTradeOpt.PctADV > 0.03 & ... TradeDataTradeOpt.PctADV <= 0.05); BasketReport.BasketCategory(i) = {'3%-5%'}; case 9 % Percentage of average daily volume is between 5 and 10 % indRecord = (TradeDataTradeOpt.PctADV > 0.05 & ... TradeDataTradeOpt.PctADV <= 0.10); BasketReport.BasketCategory(i) = {'5%-10%'}; case 10 % Percentage of average daily volume is between 10 and 20 % indRecord = (TradeDataTradeOpt.PctADV > 0.10 & ... TradeDataTradeOpt.PctADV <= 0.20); BasketReport.BasketCategory(i) = {'10%-20%'}; case 11 % Percentage of average daily volume is greater than 20 % indRecord = (TradeDataTradeOpt.PctADV > 0.20); BasketReport.BasketCategory(i) = {'>20%'}; % Market cap case 12 % Large cap indRecord = (TradeDataTradeOpt.MktCap > 10000000000); BasketReport.BasketCategory(i) = {'LC'}; case 13 % Mid cap indRecord = (TradeDataTradeOpt.MktCap > 1000000000 & ... TradeDataTradeOpt.MktCap <= 10000000000); BasketReport.BasketCategory(i) = {'MC'}; case 14 % Small cap indRecord = (TradeDataTradeOpt.MktCap <= 1000000000); BasketReport.BasketCategory(i)={'SC'}; % Sectors % Description of basket category case {15, 16, 17, 18, 19, 20, 21, 22, 23, 24} j = j + 1; if j <= numSectors indRecord = strcmp(TradeDataTradeOpt.Sector,uniqueSectors(j)); BasketReport.BasketCategory(i) = uniqueSectors(j); end end % Get subset of TradeData TD = TradeDataTradeOpt(indRecord,:); if ~isempty(TD) % Covariance Matrix in $/Shares^2 CC2 = CC(indRecord,indRecord); %Trading Period Covariance Matrix in $/Shares^2 C2 = C1(indRecord,indRecord); %Annualized Covariance Matrix in $/Shares^2 RR = R(indRecord,:); %Residuals for Stocks in group % Basket Summary Calculations Weight2 = TD.Value / sum(TD.Value); % Side I_Buy = (TD.SideIndicator == 1); I_Sell = (TD.SideIndicator == -1); % Fill basket report table BasketReport.Number(i) = size(TD,1); % Number of records that match criteria BasketReport.Weight(i) = sum(TD.Value)/PortfolioValue; % Weight of assets in criteria BasketReport.MI(i) = Weight2' * TD.MI; % Market impact of assets BasketReport.TR(i) = sqrt(trace(RR'*CC2*RR)) / sum(TD.Value) * 10000; % Timing risk of assets BasketReport.POV(i) = Weight2' * TD.POV; % POV of assets BasketReport.TradeTime(i) = Weight2' * TD.TradeTime; % Tradetime of assets BasketReport.PctADV(i) = Weight2' * TD.PctADV; % Percentage of ADV BasketReport.Price(i) = Weight2' * TD.Price; % Total price of assets BasketReport.Volatility(i) = Weight2' * TD.Volatility; % Volatility BasketReport.Risk(i) = sqrt(TD.Shares' * C2 * TD.Shares) / ... sum(TD.Value); % Risk value % RC and MCR Shares2 = TradeDataTradeOpt.Shares; Shares3 = TradeDataTradeOpt.Shares; Shares2(indRecord) = 0; Shares3(indRecord) = Shares3(indRecord) * 0.90; if sum(Shares2) > 0 BasketReport.RC(i) = PortfolioRisk / sqrt(Shares2' * CD * Shares2) - 1; else BasketReport.RC(i) = 0; end BasketReport.MCR(i) = PortfolioRisk / sqrt(Shares3' * CD * Shares3) - 1; % Beta value, liquidity factor and total value BasketReport.Beta(i) = sum(Weight2 .* TD.SideIndicator .* TD.Beta); BasketReport.LF(i) = Weight2' * TD.LF; BasketReport.TotalValue(i) = sum(TD.Value); % Calculate buy share values if sum(I_Buy) > 0 BasketReport.BuyValue(i) = sum(TD.Value(I_Buy)); BasketReport.BuyShares(i) = sum(TD.Shares(I_Buy)); else BasketReport.BuyValue(i) = 0; BasketReport.BuyShares(i) = 0; end % Calculate sell share values if sum(I_Sell) > 0 BasketReport.SellValue(i) = sum(TD.Value(I_Sell)); BasketReport.SellShares(i) = sum(TD.Shares(I_Sell)); else BasketReport.SellValue(i) = 0; BasketReport.SellShares(i) = 0; end % Calculate net value of criteria and number of shares BasketReport.NetValue(i) = BasketReport.BuyValue(i) - ... BasketReport.SellValue(i); BasketReport.Shares(i) = sum(TD.Shares); end end % Remove rows with no stocks indRecord = (BasketReport.Number > 0); BasketReport = BasketReport(indRecord,:);
Display market capitalization by volatility as a pie chart.
pie(BasketReport.Volatility(8:10),BasketReport.BasketCategory(8:10))
title('Market Capitalization by Volatility')
Determine the efficient trading frontier by time. Use different trade time scenarios. Estimate trading costs for price appreciation, market impact, and timing risk for each scenario.
ScenarioTime = [0.10;0.25;0.50;0.75;1.0;1.50;2.0;2.5;3.0;3.5;4.0;4.5;5.0]; numScenarios = size(ScenarioTime,1); ETFCosts = zeros(numScenarios,5); TableVariableNames = TradeDataTradeOpt.Properties.VariableNames; if sum(strcmp(TableVariableNames,'DeltaP')) > 0 DeltaP = TradeDataTradeOpt.DeltaP; elseif sum(strcmp(TableVariableNames,'Alpha_bp')) > 0 DeltaP = TradeDataTradeOpt.Alpha_bp; else DeltaP = zeros(NumberStocks,1); end % Convert DeltaP from basis points per day to cents/share per period DeltaP = DeltaP / 1000 .* TradeDataTradeOpt.Price / totalNumberPeriods; for i = 1:numScenarios TradeTime = ScenarioTime(i); TradeDataTradeOpt.POV = TradeDataTradeOpt.Shares ./ ... (TradeDataTradeOpt.Shares + TradeTime .* TradeDataTradeOpt.ADV); % Price Appreciations in Dollars PA = 1/2 * TradeDataTradeOpt.Shares .* DeltaP .* TradeTime; TotPA = sum(PA) / (TradeDataTradeOpt.Shares' * ... TradeDataTradeOpt.Price) .* 10000; % bp PA = PA ./ (TradeDataTradeOpt.Shares .* ... TradeDataTradeOpt.Price) * 10000; % bp % Market Impact in Dollars MI = marketImpact(k,TradeDataTradeOpt) .* TradeDataTradeOpt.Shares .* ... TradeDataTradeOpt.Price ./ 10000; %dollars; TotMI = sum(MI) / (TradeDataTradeOpt.Shares' * ... TradeDataTradeOpt.Price) .* 10000; % bp MI = MI ./ (TradeDataTradeOpt.Shares .* ... TradeDataTradeOpt.Price) * 10000; % bp % Timing Risk in Dollars TotTR = sqrt(1/3 * TradeDataTradeOpt.Shares' * ... (CD * TradeTime) * TradeDataTradeOpt.Shares) / ... (TradeDataTradeOpt.Shares' * TradeDataTradeOpt.Price) * 10000; % Total Cost Dollars TotTC = (TotMI + TotPA); % ETF Cost Table ETFCosts(i,1) = TradeTime; ETFCosts(i,2) = TotMI; ETFCosts(i,3) = TotPA; ETFCosts(i,4) = TotTC; ETFCosts(i,5) = TotTR; end % Save as Table ETFCosts = table(ETFCosts(:,1),ETFCosts(:,2),ETFCosts(:,3),ETFCosts(:,4), ... ETFCosts(:,5),'VariableNames',{'Days','MI_bp','PA_bp','TotalCost_bp', ... 'TR_bp'});
Determine the trade time with the lowest total cost.
mintotcost = min(ETFCosts.TotalCost_bp); for i = 1:numScenarios if(ETFCosts.TotalCost_bp(i) == mintotcost) scenario = ETFCosts.Days(i); end end scenario
scenario = 5
For details about the preceding calculations, contact the Kissell Research Group.
[1] Kissell, Robert. The Science of Algorithmic Trading and Portfolio Management. Cambridge, MA: Elsevier/Academic Press, 2013.
[2] Malamut, Roberto. “Multi-Period Optimization Techniques for Trade Scheduling.” Presentation at the QWAFAFEW New York Conference, April 2002.
[3] Kissell, Robert, and Morton Glantz. Optimal Trading Strategies. New York, NY: AMACOM, Inc., 2003.
krg
| liquidityFactor
| marketImpact