Main Content

Create Basket Summary and Efficient Trading Frontier

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.

Estimate Trading Costs in Basket

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

Determine Risk Components in Basket

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 Basket Report Summary

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')

Plot figure displays a pie chart that shows market capitalization by volatility for large, mid, and small capitalization.

Create Principal Bid Summary

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.

References

[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.

See Also

| |

Related Topics