Download the notebook here!
Interactive online version:
Minimax regret portfolio allocation
This tutorial walks through a complete minimax regret allocation. We define a set of investment initiatives with scenario returns and confidence scores, preprocess them (filtering and confidence penalization), and solve for the portfolio that minimizes the maximum regret across all scenarios.
Workflow overview
Define investment opportunities
Run the solver
Inspect results
Initial Setup
[1]:
from impact_engine_allocate.allocation import MinimaxRegretAllocation, preprocess, empty_rule_result
import pandas as pd
Step 1 — Define investment opportunities
Each initiative has a cost, return estimates under three scenarios (R_best, R_med, R_worst), and a confidence score between 0 and 1. Low-confidence initiatives have their effective returns pulled toward the worst case.
[2]:
investment_opportunities = [
{"id": "A", "cost": 4, "R_best": 15, "R_med": 10, "R_worst": 2, "confidence": 0.9},
{"id": "B", "cost": 3, "R_best": 12, "R_med": 8, "R_worst": 1, "confidence": 0.6},
{"id": "C", "cost": 3, "R_best": 9, "R_med": 6, "R_worst": 2, "confidence": 0.80},
{"id": "D", "cost": 2, "R_best": 7, "R_med": 5, "R_worst": 3, "confidence": 0.40},
{"id": "E", "cost": 5, "R_best": 18, "R_med": 9, "R_worst": 0, "confidence": 0.50},
]
Inspect the initiatives as a DataFrame. Initiative D has confidence below the threshold we set next and will be filtered out.
[3]:
investments = pd.DataFrame(investment_opportunities)
investments.set_index("id", inplace=True)
investments
[3]:
| cost | R_best | R_med | R_worst | confidence | |
|---|---|---|---|---|---|
| id | |||||
| A | 4 | 15 | 10 | 2 | 0.9 |
| B | 3 | 12 | 8 | 1 | 0.6 |
| C | 3 | 9 | 6 | 2 | 0.8 |
| D | 2 | 7 | 5 | 3 | 0.4 |
| E | 5 | 18 | 9 | 0 | 0.5 |
Step 2 — Run the solver
Set the budget, minimum confidence threshold, and minimum aggregate worst-case return. preprocess() filters out low-confidence initiatives and computes confidence-penalized effective returns. The solver then selects the portfolio that minimizes the maximum regret across the three scenarios.
[4]:
BUDGET = 10
MIN_CONFIDENCE = 0.5
MIN_RETURN = 4
processed = preprocess(investment_opportunities, MIN_CONFIDENCE)
if not processed:
results = empty_rule_result("No Eligible Initiatives", "minimax_regret")
else:
solver = MinimaxRegretAllocation()
results = solver(processed, BUDGET, MIN_RETURN)
print(f"Solver Status: {results['status']}")
if results["status"] == "Optimal":
print(f"Minimized Maximum Regret: {results['objective_value']:.2f}")
print(f"Selected Initiatives: {results['selected_initiatives']}")
print(f"Total Cost of Selected Initiatives: {results['total_cost']:.2f}")
Solver Status: Optimal
Minimized Maximum Regret: 0.00
Selected Initiatives: ['A', 'B', 'C']
Total Cost of Selected Initiatives: 10.00
Step 3 — Inspect results
The result dict contains scenario-level detail: effective returns for the selected portfolio, optimal per-scenario returns (\(V_j^*\)), and the calculated regrets. The maximum regret across scenarios matches the objective value.
[5]:
if results["status"] == "Optimal":
print("Effective Returns of Selected Portfolio:")
for s, r_val in results["total_actual_returns"].items():
print(f" Scenario {s}: {r_val:.2f}")
detail = results.get("detail", {})
v_j_star = detail.get("v_j_star", {})
regrets = detail.get("regrets", {})
print("\nOptimal Returns for Each Scenario (V_j_star):")
for s, v_val in v_j_star.items():
print(f" Scenario {s}: {v_val:.2f}")
print("\nCalculated Regrets for the Selected Portfolio:")
for s, r_val in regrets.items():
print(f" Scenario {s}: {r_val:.2f}")
if regrets:
actual_max_regret = max(regrets.values())
print(f"\nActual Max Regret: {actual_max_regret:.2f}")
matches = abs(actual_max_regret - results["objective_value"]) < 1e-6
print(f"Matches minimized max regret? {matches}")
else:
print("Optimization did not find an optimal solution.")
Effective Returns of Selected Portfolio:
Scenario best: 28.90
Scenario med: 19.60
Scenario worst: 5.00
Optimal Returns for Each Scenario (V_j_star):
Scenario best: 28.90
Scenario med: 19.60
Scenario worst: 5.00
Calculated Regrets for the Selected Portfolio:
Scenario best: 0.00
Scenario med: 0.00
Scenario worst: 0.00
Actual Max Regret: 0.00
Matches minimized max regret? True