Tracking Error

tracking error
risk budget
benchmark
active risk
Published

May 26, 2025

Summary

Tracking error is a useful and widely used risk measure to quantify how different a portfolio is compared to its benchmark. The common text book definition of TE = the standard deviation of portfolio less benchmark returns is a straight forward calculation.

\[ TE = \sigma(P_r-B_r) \]

where \(P_r\) is the vector of portfolio returns over the same periods of the benchmark returns \(B_r\).

However, the multi-variate calculation of tracking error based on the underlying holdings is less commonly discussed in text books. Using the current holdings can give us a better ex-ante calculation and allow us to compare hypothetical trades to see how they affect the portfolio tracking error. Another good use of doing the calculation from underlying holdings is a risk budget to see what portion of tracking error comes from each position or groups (e.g., sectors) of positions.

This post will discuss how to calculate tracking error based on the underlying components and highlight a common pitfall of analytics software that perform this calculation.

Definitions

Calculating the portfolio level tracking error multiplies the active weights and the corresponding covariance matrix. See for a formal definition of covariance matrices.

Equation 1:

\[ TE_{port} = \sqrt{(W_{act}'\Sigma W_{act})} \]

For example if we had a portfolio with four assets weighted 40%, 20%, 30%, and 10%. and benchmark with 30% to the first and second asset, 40% to the third asset, and 0% in the last asset, the active weights would be:

\[ W_p\begin{vmatrix} 0.4 \\ 0.2 \\ 0.3 \\ 0.1 \end{vmatrix} - W_b\begin{vmatrix} 0.3 \\ 0.3 \\ 0.4 \\ 0 \end{vmatrix} = W_{act}\begin{vmatrix} 0.1 \\ -0.1 \\ -0.1 \\ 0.1 \end{vmatrix} \]

And \(W_{act}'\) is simply the transpose of the weight column into a row.

Finally, in this example, the covariance matrix \(\Sigma\) is a 4 x 4 matrix of the corresponding assets.

The Pitfall

For long-only equity managers Equation 1 is straight forward. You can take the union of their holdings and the benchmark constituents to form active weights and a corresponding covariance matrix. From an allocator’s perspective that may have several active equity managers the calculation becomes more challenging. XYZ Growth Manager isn’t going to be explicitly in the equity benchmark to assign an active weight. You could drill down to the underlying holdings of each manager but rolling the weights back up to get manager-level risk weights is not easy. For example one manager could be overweight NVIDIA but if none of the other managers hold it the portfolio could be underweight NVIDIA. How do you divvy up the tracking error from being underweight across the managers (one of whom is overweight)? And this challenge doesn’t even get into issues like delayed reporting, private equity benchmarks without underlying constituents, and multi-strategy hedge funds where drilling down to the security level would take an advanced risk system.

The way I’ve seen analytics providers deal with these issues is to calculate tracking error a different way. You can get the same portfolio tracking error by multiplying the portfolio weights by a covariance of active returns. The active returns of each asset are the asset’s returns less the benchmark’s returns.

\[ ActRet_i = Ret_i - BenchRet \] \[ BenchRet_t = \sum\limits_{t=1}^n {A_t*W_t} \]

where each \(A_t\) is the benchmark’s underling asset (or constituent) return at period t and \(W_t\) is the corresponding weight. When the time-series of the benchmark returns is calculated this way (weighted average of returns each period) there is an implicit assumption that it’s rebalanced each period. E.g., if the return frequency is monthly, then there is a monthly rebalance assumption. This allows Equation 2 below to equal Equation 1.

Equation 2:

\[TE = \sqrt{W_p'\Sigma_{act}W_p}\]

where \(W_p\) is the column portfolio weight vector (\(W'p\) is the row transpose) and \(\Sigma_{act}\) is the covariance matrix of active returns (asset less benchmark defined above).

Although the portfolio tracking error is the same, the risk weights (vector with a length to corresponding assets) are different using the approach of Equation 2.

\[ RiskWgts = \frac{W\times \Sigma W}{\sigma_p^2} \]

where \(W\) is a column vector of weights, \(\Sigma\) is the covariance matrix, and \(\sigma^2\) is the portfolio tracking error squared. Note \(\times\) is element by element multiplication.

Using the approach of Equation 1, \(W\) would be an active weight vector while Equation 2 would shift the active part of the equation to the covariance matrix \(\Sigma\). The code snipits below will show an example of a four asset portfolio and how each equation leads to a different interpretation of where active risk is coming from.

Example with Data

Asset Portfolio Weight Benchmark Weight
U.S Stocks (IWV) 40% 30%
Int’l Stocks (ACWX) 20% 30%
Core Bonds (AGG) 30% 40%
Trend Following (PQTIX) 10% 0%

Read the monthly return history from Google Drive.

file_url <- paste0(
  'https://docs.google.com/uc?id=',
  "1SPTfML49u-Nd8cFJQ1p9P7hRPW_JOmIB",
  '&export=download'
)
ret <- read.csv(file_url)
summary(ret)
    Index                IWV                 ACWX                AGG           
 Length:135         Min.   :-0.136749   Min.   :-0.149729   Min.   :-0.041437  
 Class :character   1st Qu.:-0.019008   1st Qu.:-0.023106   1st Qu.:-0.005651  
 Mode  :character   Median : 0.014153   Median : 0.009601   Median : 0.001481  
                    Mean   : 0.009879   Mean   : 0.004917   Mean   : 0.001929  
                    3rd Qu.: 0.035376   3rd Qu.: 0.030791   3rd Qu.: 0.008943  
                    Max.   : 0.130520   Max.   : 0.133752   Max.   : 0.045890  
     PQTIX          
 Min.   :-0.089716  
 1st Qu.:-0.013145  
 Median : 0.004255  
 Mean   : 0.003150  
 3rd Qu.: 0.018974  
 Max.   : 0.069339  

Assign the portfolio and benchmark weights and calculate the active weight as the difference.

port_wgt <- c(0.4, 0.2, 0.3, 0.1)
bench_wgt <- c(0.3, 0.3, 0.4, 0)
active_wgt <- port_wgt - bench_wgt

Set up two functions to calculate the portfolio risk and risk weights.

port_risk <- function(w, sigma) {
  w <- matrix(w, ncol = 1)
  sqrt(t(w) %*% sigma %*% w)
}
risk_budget <- function(w, sigma) {
  w <- matrix(w, ncol = 1)
   (w * (sigma %*% w))/(t(w) %*% sigma %*% w)[1]
}

Calculate Equation 1) and 2).

# equation 1
sigma <- cov(ret[, -1]) * 12
sigma
               IWV         ACWX          AGG        PQTIX
IWV    0.023226163  0.018749394  0.002781663 -0.003763381
ACWX   0.018749394  0.021311489  0.003065614 -0.004667779
AGG    0.002781663  0.003065614  0.002353171 -0.001202069
PQTIX -0.003763381 -0.004667779 -0.001202069  0.009341553
active_wgt
[1]  0.1 -0.1 -0.1  0.1
eq_1 <- port_risk(active_wgt, sigma)
# equation 2
bench_rebal_wgt <- matrix(bench_wgt, nrow = nrow(ret), ncol = 4, byrow = TRUE)
bench_ret <- rowSums(bench_rebal_wgt * ret[, -1])
# subtract the benchmark return from each asset
active_ret <- ret[, -1] - matrix(bench_ret, nrow = nrow(ret), ncol = 4)
active_sigma <- cov(active_ret) * 12
eq_2 <- port_risk(port_wgt, active_sigma)
# results
eq_1
           [,1]
[1,] 0.01533441
eq_2
           [,1]
[1,] 0.01533441
abs(eq_1 - eq_2) < 1e-10
     [,1]
[1,] TRUE

Results

Calculate and compare risk weights

library(scales)
a <- risk_budget(active_wgt, sigma)
b <- risk_budget(port_wgt, active_sigma)
data.frame(
  Asset = c("U.S. Stocks", "Int'l Stocks", "Core Bonds", "Trend Following"),
  Active.Wgt = percent(active_wgt),
  Equation.1 = percent(a, 0.1),
  Equation.2 = percent(b, 0.1)
)
            Asset Active.Wgt Equation.1 Equation.2
1     U.S. Stocks        10%      -8.8%      54.0%
2    Int'l Stocks       -10%      43.8%     -43.0%
3      Core Bonds       -10%      16.3%      17.9%
4 Trend Following        10%      48.7%      71.0%

The two equations lead to very different interpretations of which assets are driving tracking error. The risk weights are additive, so we can combine like assets, e.g., sum U.S. and Int’l Stocks. Equation 1 has over 1/3 (35%) of the risk coming overweight U.S. / under weight Int’l, while Equation 2 suggests only 11% of the risk comes from these tilts. The Core Bonds’ risk weights are similar so the Trend Following in Equation 2 picks up the lion’s share of the TE and Equation 1 remains more balanced.

A practitioner using Equation 2 would be less worried about the U.S. / Int’l tilts and more concerned about the Trend Following position. The investor using Equation 1 would see a more balanced portfolio.

Which interpretation is correct? Should the allocator be concerned that Trend Following is over 70% of the risk budget? Is the U.S. Int’l tilt less significant than the bond underweight or more substantial part of the risk?

This next example will make it clear which approach should be used. To fully illustrate the flaw with Equation 2 let’s create a portfolio that doesn’t invest in one of the benchmark assets. We’ll create a portfolio that is at benchmark weights for U.S. and Int’l Stocks (30% each), does not invest in core bonds, and allocates 40% to Trend Following.

port_wgt <- c(0.3, 0.3, 0, 0.4)
active_wgt <- port_wgt - bench_wgt
eq_1 <- port_risk(active_wgt, sigma)
eq_1
           [,1]
[1,] 0.04749545
eq_2 <- port_risk(port_wgt, active_sigma)
eq_2
           [,1]
[1,] 0.04749545
a <- risk_budget(active_wgt, sigma)
b <- risk_budget(port_wgt, active_sigma)
data.frame(
  Asset = c("U.S. Stocks", "Int'l Stocks", "Core Bonds", "Trend Following"),
  Active.Wgt = percent(active_wgt),
  Equation.1 = percent(a, 0.1),
  Equation.2 = percent(b, 0.1)
)
            Asset Active.Wgt Equation.1 Equation.2
1     U.S. Stocks         0%       0.0%      -4.5%
2    Int'l Stocks         0%       0.0%     -10.8%
3      Core Bonds       -40%      25.2%       0.0%
4 Trend Following        40%      74.8%     115.3%

Now it’s clear the Equation 1 has the more intuitive risk budget. U.S. and Int’l stocks are at benchmark weight and therefore are not driving any tracking error. The active weights are Core Bonds and Trend Following which split the risk budget. Trend Following has a higher volatility than Core Bonds and is less correlated to a 60 / 40 benchmark, so it makes sense that it has a higher share of the risk budget.

# volatility 
round(sqrt(diag(sigma)), 3)
  IWV  ACWX   AGG PQTIX 
0.152 0.146 0.049 0.097 
# correlation
round(cov2cor(sigma), 2)
        IWV  ACWX   AGG PQTIX
IWV    1.00  0.84  0.38 -0.26
ACWX   0.84  1.00  0.43 -0.33
AGG    0.38  0.43  1.00 -0.26
PQTIX -0.26 -0.33 -0.26  1.00

Equation 2 has negative values for U.S. and Int’l stocks which is not intuitive given they are at the same weight as the benchmark. The Core Bonds underweight has a zero risk weight because the absolute weight was zero, however what drives (or should drive) tracking error is the relative weight. This is the core of problem with Equation 2 - the weights are absolute weights and when you don’t invest in something that’s in the benchmark you’ll get a risk budget that doesn’t account for that decision.