Robust Portfolio
Worst-case optimization under parameter uncertainty
Standard mean-variance optimization treats your estimated mean and covariance as exact. They never are. Robust portfolio optimization assumes the true parameters lie within an uncertainty set around your estimates and optimizes for the worst case. The result is a portfolio that degrades gracefully when your estimates are wrong.
API
Robust optimization
python
import numpy as np
mu = np.array([0.05, 0.03, 0.04]) # estimated expected returns
cov = np.array([ # estimated covariance matrix
[0.04, 0.01, 0.005],
[0.01, 0.02, 0.008],
[0.005, 0.008, 0.03],
])
result = hz.robust_optimize(
mu=mu,
cov=cov,
uncertainty_radius=0.02, # radius of ellipsoidal uncertainty set
)
result.weights # optimal portfolio weights
result.worst_case_mu # worst-case expected return within the set
result.worst_case_return # portfolio return under worst-case parameters
result.objective # objective value (worst-case utility)
Worst-case return for a given portfolio
python
w = np.array([0.4, 0.3, 0.3])
wcr = hz.worst_case_return(w, mu, cov, uncertainty_radius=0.02)
wcr.return_value # worst-case expected return
wcr.worst_mu # the adversarial mean vector
Robust efficient frontier
python
frontier = hz.robust_efficient_frontier(
mu=mu,
cov=cov,
uncertainty_radius=0.02,
n_points=20,
)
frontier.weights # (20, N) array of portfolio weights
frontier.returns # (20,) worst-case returns
frontier.risks # (20,) portfolio standard deviations
Ellipsoidal uncertainty
The model assumes the true mean mu_true lies within an ellipsoid:
(mu_true - mu)^T @ Sigma^{-1} @ (mu_true - mu) <= epsilon^2
where epsilon is the uncertainty_radius. The optimizer finds the mu_true within this set that minimizes your portfolio return, then maximizes over weights:
max_w min_{mu_true in set} w^T @ mu_true - (gamma/2) * w^T @ Sigma @ w
This has a closed-form reduction to a second-order cone program.
Example: robust allocation across prediction markets
python
# Three prediction markets with uncertain edge estimates
mu = np.array([0.03, 0.015, 0.025]) # estimated edges
cov = np.array([
[0.01, 0.003, 0.002],
[0.003, 0.008, 0.001],
[0.002, 0.001, 0.012],
])
# Standard optimization (fragile)
standard = hz.robust_optimize(mu, cov, uncertainty_radius=0.0)
# Robust optimization (accounts for estimation error)
robust = hz.robust_optimize(mu, cov, uncertainty_radius=0.015)
print(f"Standard weights: {standard.weights}") # concentrated
print(f"Robust weights: {robust.weights}") # more diversified
When to use
- Small sample sizes: when you have limited historical data, your mean/covariance estimates have wide confidence intervals. Robust optimization respects this.
- Regime uncertainty: if you suspect the market regime may shift, a larger uncertainty radius protects against the new regime’s parameters.
- Prediction markets: edge estimates are noisy (based on model probabilities vs. market prices). Robust optimization prevents over-concentration in the market where your edge estimate happens to be highest.
- Complements entropy pooling: use entropy pooling to form your best estimate, then robust optimization to protect against that estimate being wrong.