liveBook · Manning

Equilibrium return

As explained in Build a Robo-Advisor with Python (From Scratch)

An equilibrium return is an expected investment return implied by market portfolio weights, investor expectations, and risk aversion.

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

How Build a Robo-Advisor with Python (From Scratch) covers it

How deep it goes
Derives μ = λΣp and implements posterior calculations, while deferring the mathematics behind the Bayesian update.
Examples
Uses US sector ETFs, market-cap weights, defensive-versus-cyclical views, real-estate views, and optimized sector allocations.
What you can do after
Apply it via Python to calculate equilibrium returns, incorporate views, and construct optimized allocations.
How it connects
Connects mean–variance optimization, covariance matrices, risk aversion, reference portfolios, investor views, and Bayesian updating.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Build a Robo-Advisor with Python (From Scratch). The section links point to the parts of the book it draws on.