Equilibrium returns are expected returns that make a reference portfolio the optimal solution to a mean–variance optimization problem. They address the instability of portfolios produced from competing expected-return estimates by deriving returns from observed portfolio weights instead. section 12.1
For market portfolio weights p, covariance matrix Σ, and aggregate risk aversion λ, the implied returns satisfy μ = λΣp. Because proportional values are sufficient, λ need not be known exactly. The result represents aggregate investor expectations and risk aversion. section 12.1.2
A practical equilibrium-return workflow is:
- Choose market-capitalization weights or another defensible reference portfolio. section 12.1.2
- Estimate the covariance matrix and combine it with the weights and risk-aversion parameter to calculate μ. section 12.4.1
- Use equilibrium returns as a prior, then combine them with investor views to obtain posterior expected returns and covariance. section 12.3.3
US sector-selection strategy
Using 11 US equity sectors, historical ETF returns, market-cap weights, and risk aversion of 1.4 produces equilibrium expected returns implying roughly 7% for the overall market. A manager then specifies that defensive sectors will outperform cyclical sectors by 1% and that real estate will return 2%, before updating the equilibrium estimates. section 12.4.1
Listing 12.2 Computing equilibrium expected returns for US sectors
import yfinance as yf
tickers = ['VOX', 'VCR', 'VDC', 'VDE', 'VFH', 'VHT', 'VIS', 'VGT',
'VAW', 'VNQ', 'VPU']
prices = yf.download(tickers, start='2018-01-01', end='2022-12-31')
rets = prices['Adj Close'].pct_change()
rets = rets.dropna(axis=0, how='any')[tickers]
sigma = rets.cov() * 252
\pagebreak
weights = [.077, .107, .067, .048, .117, .143, .085, .273, .028, .027, .028]
weights = pd.Series(weights, index=tickers)
mu = compute_equilibrium_ers(weights, sigma, 1.4)
Shows Python code for downloading sector ETF prices, estimating annualized covariance, defining market weights, and computing equilibrium expected returns. section 12.4.1
Listing 12.1 Posterior mean and covariance
import numpy as np
import pandas as pd
def compute_posterior_mean(mu, C, A, b, omega):
c_inv = np.linalg.inv(C)
a_t_oinv = A.T @ np.linalg.inv(omega) #1
er = np.linalg.inv(c_inv + a_t_oinv @ A) @ (c_inv @ mu + a_t_oinv @ b)
return pd.Series(er, index=mu.index)
def compute_posterior_cov(sigma, C, A, omega):
return sigma + np.linalg.inv((np.linalg.inv(C) + A.T @ omega @ A))
Provides general Python functions for calculating posterior mean and covariance after incorporating views, without requiring diagonal view uncertainty or C = τΣ. section 12.3.3
