Inverse Optimization

Extract Implied Expected Returns from Portfolio Weights

The Core Question

Given that an investor holds portfolio weights \(w\), what expected returns \(\mu^*\) would make those weights optimal?

Key Insight: Implied returns depend on the assumed utility function. The same portfolio can imply different returns under Mean-Variance, MRAR, or Omega objectives.

1. Supported Utility Functions

Mean-Variance

Closed-form solution

$$\mu^* = \lambda \cdot Q \cdot w$$

Utility: \(U = w'\mu - \frac{\lambda}{2}w'Qw\)

When to use: Standard portfolio theory, Black-Litterman model

MRAR (Morningstar)

Numerical optimization

$$U(r) = \frac{(1+r)^{1-\gamma}}{1-\gamma}$$

Parameter: \(\gamma\) = risk aversion (Morningstar uses \(\gamma=2\))

When to use: CRRA preferences, downside risk focus

Omega Ratio

Numerical optimization

$$\Omega(L) = \frac{\int_L^\infty (1-F(r))dr}{\int_{-\infty}^L F(r)dr}$$

Parameter: \(L\) = threshold return

When to use: Full distribution analysis, tail risk

2. Mathematical Derivation (Mean-Variance)

From Utility Maximization to Implied Returns

Step 1: Mean-Variance utility maximization:

$$\max_w \left\{ w'\mu - \frac{\lambda}{2} w'Qw \right\}$$

Step 2: First-order condition:

$$\frac{\partial U}{\partial w} = \mu - \lambda Qw = 0$$

Step 3: Solve for implied returns:

$$\mu^* = \lambda Qw$$
Interpretation: Each asset's implied return is proportional to its marginal contribution to portfolio risk \((Qw)\) times risk aversion \(\lambda\).

3. Python Usage

from brisma import (
    inverse_optimize_mv,
    inverse_optimize_mrar,
    inverse_optimize_omega,
    inverse_optimize,
    compare_utilities,
)
import numpy as np

# Portfolio data
weights = np.array([0.30, 0.25, 0.20, 0.15, 0.10])
cov = np.array([
    [0.040, 0.015, 0.010, 0.005, 0.002],
    [0.015, 0.025, 0.008, 0.004, 0.001],
    [0.010, 0.008, 0.016, 0.003, 0.001],
    [0.005, 0.004, 0.003, 0.009, 0.001],
    [0.002, 0.001, 0.001, 0.001, 0.001],
])

# Mean-Variance inverse optimization
result = inverse_optimize_mv(weights, cov, lambda_param=2.5, rf_rate=0.02)
print(f"Implied Returns: {result.implied_returns}")
print(f"Portfolio Return: {result.details['portfolio_return']:.2%}")
print(f"Portfolio Sharpe: {result.details['portfolio_sharpe']:.2f}")

# Unified interface
result_mrar = inverse_optimize(weights, cov, utility_type="mrar", lambda_param=2.0)
result_omega = inverse_optimize(weights, cov, utility_type="omega", lambda_param=0.0)

# Compare all utilities
comparison = compare_utilities(weights, cov, rf_rate=0.02)
for name, res in comparison.items():
    print(f"{name}: {res.details['portfolio_return']:.2%}")

4. Utility Comparison

Same Portfolio, Different Implied Returns

Utility Parameter Mean Implied Return Portfolio Return
Mean-Variance \(\lambda = 2.5\) 4.32% 4.86%
MRAR \(\gamma = 2.0\) 2.04% 2.05%
Omega \(L = 0\%\) 2.04% 2.05%
Note: MRAR and Omega produce similar results because both incorporate higher moments and downside risk. Mean-Variance produces higher implied returns because it only considers variance.

5. Lambda Sensitivity

For Mean-Variance, implied returns scale linearly with \(\lambda\):

Lambda Portfolio Return Interpretation
1.0 1.14% Low risk aversion
2.0 2.29% Moderate
2.5 2.86% Black-Litterman default
3.0 3.43% Conservative
5.0 5.72% Very conservative

6. API Reference

Functions

Function Description
inverse_optimize_mv() Mean-Variance inverse optimization (closed-form)
inverse_optimize_mrar() MRAR/CRRA inverse optimization (numerical)
inverse_optimize_omega() Omega ratio inverse optimization (numerical)
inverse_optimize() Unified interface for all utility types
compare_utilities() Compare implied returns across all utilities

Data Classes

Class Description
ImpliedReturnsResult Container for inverse optimization results with implied_returns, utility_type, lambda_param, and details dict