From everything-claude-trading
> Black-Litterman model for portfolio allocation — combining equilibrium returns with investor views.
npx claudepluginhub brainbytes-dev/everything-claude-tradingThis skill uses the workspace's default tool permissions.
> Black-Litterman model for portfolio allocation — combining equilibrium returns with investor views.
Provides Ktor server patterns for routing DSL, plugins (auth, CORS, serialization), Koin DI, WebSockets, services, and testApplication testing.
Conducts multi-source web research with firecrawl and exa MCPs: searches, scrapes pages, synthesizes cited reports. For deep dives, competitive analysis, tech evaluations, or due diligence.
Provides demand forecasting, safety stock optimization, replenishment planning, and promotional lift estimation for multi-location retailers managing 300-800 SKUs.
Black-Litterman model for portfolio allocation — combining equilibrium returns with investor views.
Mean-variance optimization requires expected return estimates, but:
Black and Litterman (1992) proposed starting from equilibrium returns (the market-implied view) and blending in investor views with explicit confidence levels.
The implied excess returns that make the current market portfolio optimal under MVO:
π = δ Σ w_mkt
Where:
These equilibrium returns serve as the neutral starting point — if you have no views, you hold the market portfolio.
Views are expressed as linear combinations of expected returns:
Absolute view: "Equity A will return 8% per year"
P = [1, 0, 0, ..., 0], Q = [0.08]
Relative view: "Equity A will outperform Equity B by 2%"
P = [1, -1, 0, ..., 0], Q = [0.02]
Sector view: "Tech will outperform Utilities by 3%"
P = [w_tech_1, w_tech_2, ..., -w_util_1, -w_util_2, ...], Q = [0.03]
Each view k has an associated confidence expressed through the uncertainty matrix Ω (K x K diagonal matrix).
Posterior expected returns:
μ_BL = [(τΣ)^(-1) + P'Ω^(-1)P]^(-1) [(τΣ)^(-1)π + P'Ω^(-1)Q]
Posterior covariance:
Σ_BL = Σ + [(τΣ)^(-1) + P'Ω^(-1)P]^(-1)
Where:
This is the critical practical step. Approaches:
import numpy as np
def implied_returns(delta, sigma, w_mkt):
"""Reverse-optimize to get equilibrium excess returns."""
return delta * sigma @ w_mkt
# Calibrate delta from market Sharpe ratio
market_return = 0.07 # expected excess return
market_vol = 0.15
delta = market_return / (market_vol ** 2) # approximately 3.1
# Market cap weights (example: 5 assets)
w_mkt = np.array([0.30, 0.25, 0.20, 0.15, 0.10])
pi = implied_returns(delta, sigma, w_mkt)
# View 1: Asset 0 will return 10% (absolute)
# View 2: Asset 1 will outperform Asset 3 by 3% (relative)
K = 2 # number of views
N = 5 # number of assets
P = np.zeros((K, N))
P[0, 0] = 1.0 # absolute view on asset 0
P[1, 1] = 1.0; P[1, 3] = -1.0 # relative view: asset 1 vs asset 3
Q = np.array([0.10, 0.03])
# View uncertainty (He-Litterman approach)
tau = 0.05
omega = np.diag([
tau * P[0] @ sigma @ P[0], # view 1 uncertainty
tau * P[1] @ sigma @ P[1], # view 2 uncertainty
])
def black_litterman(pi, sigma, P, Q, omega, tau=0.05):
"""Compute BL posterior expected returns and covariance."""
tau_sigma_inv = np.linalg.inv(tau * sigma)
omega_inv = np.linalg.inv(omega)
# Posterior precision and mean
posterior_precision = tau_sigma_inv + P.T @ omega_inv @ P
posterior_cov = np.linalg.inv(posterior_precision)
posterior_mean = posterior_cov @ (tau_sigma_inv @ pi + P.T @ omega_inv @ Q)
# Full posterior covariance for optimization
sigma_BL = sigma + posterior_cov
return posterior_mean, sigma_BL
mu_BL, sigma_BL = black_litterman(pi, sigma, P, Q, omega, tau)
import cvxpy as cp
w = cp.Variable(N)
ret = mu_BL @ w
risk = cp.quad_form(w, sigma_BL)
prob = cp.Problem(
cp.Maximize(ret - delta * risk),
[cp.sum(w) == 1, w >= 0]
)
prob.solve()
optimal_weights = w.value
The power of BL is that weights move intuitively from the benchmark:
Instead of asset-level views, express views on factors:
# Factor exposures: B is N x F matrix (N assets, F factors)
# View: "Value factor will return 3% next year" with moderate confidence
# Convert factor view to asset-space: P_factor = B[:, value_col].T
P_factor = factor_loadings[:, 0].reshape(1, -1) # value factor
Q_factor = np.array([0.03])
omega_factor = np.array([[tau * P_factor @ sigma @ P_factor.T]])
def idzorek_omega(P, sigma, tau, Q, pi, delta, w_mkt, confidence_pct):
"""
confidence_pct: array of confidences from 0 (no view) to 1 (certainty)
Returns diagonal omega matrix.
"""
omega_diag = np.zeros(len(Q))
for k in range(len(Q)):
# At 100% confidence: omega_kk -> 0 (certain view)
# At 0% confidence: omega_kk -> inf (no view, stay at equilibrium)
p_k = P[k:k+1, :]
alpha = 1.0 / confidence_pct[k] - 1.0
omega_diag[k] = alpha * (p_k @ (tau * sigma) @ p_k.T).item()
return np.diag(omega_diag)
confidence = np.array([0.75, 0.50]) # 75% confident in view 1, 50% in view 2
omega = idzorek_omega(P, sigma, tau, Q, pi, delta, w_mkt, confidence)
import pandas as pd
# Load market data
prices = pd.read_csv('prices.csv', index_col=0, parse_dates=True)
returns = prices.pct_change().dropna()
market_caps = pd.Series({'SPY': 4.5e12, 'EFA': 2.1e12, 'EEM': 0.8e12,
'AGG': 1.5e12, 'TLT': 0.3e12})
w_mkt = (market_caps / market_caps.sum()).values
from sklearn.covariance import LedoitWolf
sigma = LedoitWolf().fit(returns).covariance_ * 252 # annualize
pi = implied_returns(delta=2.5, sigma=sigma, w_mkt=w_mkt)
# Views: EM will outperform DM by 2%, Bonds underweight
P = np.array([
[0, -1, 1, 0, 0], # EEM - EFA
[0, 0, 0, -0.5, -0.5] # underweight bonds (won't sum to 0 — need asset on other side)
])
# Better: relative view needs both sides
P = np.array([
[0, -1, 1, 0, 0], # EEM outperforms EFA by 2%
[0.5, 0.5, 0, -0.5, -0.5] # equities outperform bonds by 4%
])
Q = np.array([0.02, 0.04])