Problem Statement
A quantitative analyst needs to allocate capital across multiple assets to:
Maximize returns for a given risk level, or
Minimize risk for a given return target
This is the classic Markowitz mean-variance optimization problem.
Implementation
import numpy as np
from optyx import VectorVariable, Problem
# Asset data
assets = ["Tech" , "Energy" , "Finance" , "Healthcare" , "Consumer" , "Utilities" ]
n_assets = len (assets)
# Expected annual returns
expected_returns = np.array([0.12 , 0.08 , 0.10 , 0.09 , 0.07 , 0.05 ])
# Covariance matrix (simplified)
cov_matrix = np.array([
[0.040 , 0.010 , 0.015 , 0.008 , 0.005 , 0.002 ],
[0.010 , 0.035 , 0.012 , 0.006 , 0.004 , 0.003 ],
[0.015 , 0.012 , 0.030 , 0.010 , 0.006 , 0.004 ],
[0.008 , 0.006 , 0.010 , 0.025 , 0.008 , 0.005 ],
[0.005 , 0.004 , 0.006 , 0.008 , 0.020 , 0.006 ],
[0.002 , 0.003 , 0.004 , 0.005 , 0.006 , 0.015 ],
])
print ("Asset Universe:" )
for i, asset in enumerate (assets):
print (f" { asset} : E[r]= { expected_returns[i]:.1%} , σ= { np. sqrt(cov_matrix[i,i]):.1%} " )
Asset Universe:
Tech: E[r]=12.0%, σ=20.0%
Energy: E[r]=8.0%, σ=18.7%
Finance: E[r]=10.0%, σ=17.3%
Healthcare: E[r]=9.0%, σ=15.8%
Consumer: E[r]=7.0%, σ=14.1%
Utilities: E[r]=5.0%, σ=12.2%
Creating the Optimization Model
# Decision variables: portfolio weights
# Create a vector of variables w[0]...w[5]
weights = VectorVariable("w" , n_assets, lb= 0 , ub= 0.4 )
# Portfolio return: vector dot product (returns @ weights)
portfolio_return = expected_returns @ weights
# Portfolio variance: math-like w · (Σw) = w.T @ Σ @ w
portfolio_variance = weights.dot(cov_matrix @ weights)
# Constraints
total_weight = sum (weights)
print (f"Variables: { n_assets} " )
print (f"Objective: minimize portfolio variance" )
Variables: 6
Objective: minimize portfolio variance
Solving for Minimum Variance Portfolio
target_return = 0.08 # 8% target
solution = (
Problem("min_variance" )
.minimize(portfolio_variance)
.subject_to(total_weight >= 0.99 )
.subject_to(total_weight <= 1.01 )
.subject_to(portfolio_return >= target_return)
.solve()
)
print ("=" * 50 )
print ("MINIMUM VARIANCE PORTFOLIO" )
print (f"Target Return: { target_return:.1%} " )
print ("=" * 50 )
print (" \n Optimal Allocation:" )
# Extract values using the vector helper
w_opt = weights.to_numpy(solution.values)
for i, asset in enumerate (assets):
w = w_opt[i]
if w > 0.01 :
print (f" { asset:12s} : { w:6.1%} " )
actual_return = w_opt @ expected_returns
actual_risk = np.sqrt(solution.objective_value)
print (f" \n Portfolio Metrics:" )
print (f" Expected Return: { actual_return:.2%} " )
print (f" Risk (std dev): { actual_risk:.2%} " )
print (f" Sharpe Ratio: { actual_return/ actual_risk:.2f} " )
==================================================
MINIMUM VARIANCE PORTFOLIO
Target Return: 8.0%
==================================================
Optimal Allocation:
Tech : 16.2%
Energy : 10.1%
Finance : 10.8%
Healthcare : 16.9%
Consumer : 19.9%
Utilities : 25.1%
Portfolio Metrics:
Expected Return: 8.00%
Risk (std dev): 9.57%
Sharpe Ratio: 0.84
Efficient Frontier
The efficient frontier shows the best risk-return trade-offs:
def solve_for_return_target(target):
"""Solve minimum variance portfolio for given return target."""
w = VectorVariable("w" , n_assets, lb= 0 , ub= 0.4 )
port_ret = expected_returns @ w
port_var = w.dot(cov_matrix @ w) # Math-like: w · (Σw)
total = w.sum ()
sol = (
Problem()
.minimize(port_var)
.subject_to(total >= 0.99 )
.subject_to(total <= 1.01 )
.subject_to(port_ret >= target)
.solve()
)
if sol.status.name == "OPTIMAL" :
return np.sqrt(sol.objective_value), target
return None , None
# Compute frontier
targets = np.linspace(0.05 , 0.11 , 13 )
frontier_points = []
for t in targets:
risk, ret = solve_for_return_target(t)
if risk is not None :
frontier_points.append((risk, ret))
print (" \n Efficient Frontier:" )
print ("-" * 40 )
print (f" { 'Return' :>10} | { 'Risk' :>10} | { 'Sharpe' :>10} " )
print ("-" * 40 )
for risk, ret in frontier_points:
sharpe = ret / risk if risk > 0 else 0
print (f" { ret:>10.2%} | { risk:>10.2%} | { sharpe:>10.2f} " )
Efficient Frontier:
----------------------------------------
Return | Risk | Sharpe
----------------------------------------
5.00% | 9.22% | 0.54
5.50% | 9.22% | 0.60
6.00% | 9.22% | 0.65
6.50% | 9.22% | 0.70
7.00% | 9.22% | 0.76
7.50% | 9.29% | 0.81
8.00% | 9.57% | 0.84
8.50% | 10.06% | 0.84
9.00% | 10.69% | 0.84
9.50% | 11.46% | 0.83
10.00% | 12.36% | 0.81
10.50% | 13.51% | 0.78
11.00% | 14.19% | 0.78
Visualizing the Frontier (ASCII)
# Simple ASCII visualization
print (" \n Efficient Frontier (ASCII)" )
print ("Return ↑" )
print (" |" )
# Scale to 40 columns
risks = [p[0 ] for p in frontier_points]
returns = [p[1 ] for p in frontier_points]
min_risk, max_risk = min (risks), max (risks)
for i, (risk, ret) in enumerate (reversed (frontier_points)):
# Map risk to column position (0-40)
col = int ((risk - min_risk) / (max_risk - min_risk + 0.001 ) * 40 )
line = " " * 7 + "|" + " " * col + "*"
label = f" { ret:.1%} "
print (line + label)
print (" |" + "-" * 45 + " Risk →" )
print (f" 0 { min_risk:.1%} { max_risk:.1%} " )
Efficient Frontier (ASCII)
Return ↑
|
| * 11.0%
| * 10.5%
| * 10.0%
| * 9.5%
| * 9.0%
| * 8.5%
| * 8.0%
|* 7.5%
|* 7.0%
|* 6.5%
|* 6.0%
|* 5.5%
|* 5.0%
|--------------------------------------------- Risk →
0 9.2% 14.2%
Rebalancing on Price Shock
What if Tech drops 20%?
# Simulate price shock
shocked_returns = expected_returns.copy()
shocked_returns[0 ] = 0.05 # Tech return drops
# Re-optimize
w_shocked = VectorVariable("w_shocked" , n_assets, lb= 0 , ub= 0.4 )
port_ret = shocked_returns @ w_shocked
port_var = w_shocked.dot(cov_matrix @ w_shocked) # Math-like: w · (Σw)
solution_shocked = (
Problem()
.minimize(port_var)
.subject_to(w_shocked.sum () >= 0.99 )
.subject_to(w_shocked.sum () <= 1.01 )
.subject_to(port_ret >= 0.07 ) # Lower target after shock
.solve()
)
print (" \n Rebalanced Portfolio (after Tech shock):" )
w_new_vals = solution_shocked[w_shocked]
for i, asset in enumerate (assets):
w_old_val = w_opt[i]
w_new_val = w_new_vals[i]
if abs (w_old_val - w_new_val) > 0.01 :
print (f" { asset:12s} : { w_old_val:6.1%} → { w_new_val:6.1%} " )
Rebalanced Portfolio (after Tech shock):
Tech : 16.2% → 4.5%
Energy : 10.1% → 13.4%
Finance : 10.8% → 12.1%
Healthcare : 16.9% → 15.7%
Utilities : 25.1% → 32.5%
Key Takeaways
Natural formulation : The math translates directly to Optyx code
Automatic gradients : No need to derive Jacobians of the covariance term
Fast re-optimization : Rebalancing after shocks takes milliseconds
Extensible : Easy to add sector constraints, transaction costs, etc.
Extensions
Try adding: - Sector exposure limits - Transaction costs - Minimum holding sizes - Cardinality constraints (max N assets)
See the Mining Fleet Dispatch example for another real-world application.