Risk Aversion Parameter Estimation for Inverse Optimization
The risk aversion parameter \(\lambda\) is a critical input for Mean-Variance inverse optimization. It links portfolio weights to implied expected returns through the first-order condition:
This module provides multiple methods to estimate \(\lambda\) from market data, along with validation and sensitivity analysis tools.
Extract \(\lambda\) from realized portfolio returns and covariance.
When to use: When you have historical return data for the portfolio.
Derive \(\lambda\) from CAPM market equilibrium.
Typical value: 2.5 (range: 2.0-4.0)
Derive \(\lambda\) from an assumed Sharpe ratio.
Typical Sharpe: 0.4 for market portfolio
Bayesian blend of observed and prior estimates.
When to use: Low confidence in observed data.
Starting from the MV utility maximization problem:
The first-order condition gives:
Multiplying both sides by \(w'\):
from brisma.bantleon import (
extract_lambda_from_observed,
black_litterman_lambda,
confidence_weighted_lambda,
lambda_sensitivity_analysis,
validate_lambda,
estimate_lambda_ensemble,
)
import numpy as np
# Portfolio data
weights = np.array([0.30, 0.25, 0.20, 0.15, 0.10])
returns = np.array([0.08, 0.06, 0.05, 0.04, 0.02])
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],
])
# Method 1: From observed returns
result = extract_lambda_from_observed(weights, returns, cov)
print(f"Lambda: {result.value:.4f}")
print(f"Confidence: {result.confidence:.2f}")
# Method 2: Black-Litterman
bl = black_litterman_lambda(
market_return=0.08,
rf_rate=0.02,
market_volatility=0.15
)
print(f"Lambda BL: {bl.value:.4f}")
# Ensemble estimation
ensemble = estimate_lambda_ensemble(
weights, returns, cov,
market_return=0.08, rf_rate=0.02, market_volatility=0.15
)
print(f"Recommended: {ensemble['recommended'].value:.4f}")
| Source | Lambda Range | Notes |
|---|---|---|
| Black & Litterman (1992) | 2.5 (default) | Global equity market equilibrium |
| Merton (1980) | 1.0 - 3.0 | Based on historical US equity returns |
| Fama & French (2002) | 2.0 - 4.0 | Cross-section of expected returns |
| He & Litterman (1999) | 2.5 - 3.5 | Refined B-L implementation |
| Category | Lambda Range | Interpretation |
|---|---|---|
| Very Low | < 0.5 | Check inputs - unrealistic |
| Low | 0.5 - 1.5 | Aggressive investor |
| Normal | 1.5 - 4.0 | Typical institutional investor |
| High | 4.0 - 8.0 | Conservative investor |
| Extreme | > 8.0 | Check inputs - unrealistic |
Understanding how implied returns change with \(\lambda\) is critical for robust analysis:
result = lambda_sensitivity_analysis(weights, cov, lambda_range=(1, 5), n_points=5)
for lam, port_ret in zip(result["lambda_values"], result["portfolio_return"]):
print(f"Lambda {lam:.2f} -> Portfolio Return {port_ret:.2%}")
# Output:
# Lambda 1.00 -> Portfolio Return 1.14%
# Lambda 2.00 -> Portfolio Return 2.29%
# Lambda 3.00 -> Portfolio Return 3.43%
# Lambda 4.00 -> Portfolio Return 4.57%
# Lambda 5.00 -> Portfolio Return 5.72%
| Function | Description |
|---|---|
extract_lambda_from_observed() |
Extract lambda from portfolio weights and realized returns |
black_litterman_lambda() |
Calculate Black-Litterman equilibrium lambda |
confidence_weighted_lambda() |
Blend observed and prior lambda estimates |
lambda_from_sharpe() |
Derive lambda from assumed Sharpe ratio |
lambda_sensitivity_analysis() |
Analyze implied returns across lambda range |
validate_lambda() |
Validate lambda against literature benchmarks |
estimate_lambda_ensemble() |
Estimate lambda using multiple methods |
| Class | Description |
|---|---|
LambdaEstimate |
Container for lambda estimation results with value, method, confidence, and details |