Portfolio Optimization

๐Ÿ”ฅ Vibe Prompt

"Allocate $1M across 5 stocks. Maximize expected return with risk <25%. Each stock has min investment of $50K."

What is Portfolio Optimization?

Portfolio Optimization allocates capital across assets to maximize expected return while limiting risk. This is the core problem of modern portfolio theory (Markowitz, Nobel Prize 1990).

| Concept | Meaning | |---------|---------| | Expected Return | Weighted average of asset returns | | Risk Score | Portfolio weighted risk (std dev or rating) | | Diversification | Minimum 5% per selected asset | | Full Investment | Sum of weights = 100% | | Concentration Limit | Max 3 assets selected |

Implementation with PuLP

import pulp

# Assets data
assets = ["TSMC", "MTK", "Delta", "Chunghwa", "Fubon"]
expected_return = [0.15, 0.12, 0.10, 0.04, 0.08]  # Expected annual return
risk = [0.35, 0.30, 0.25, 0.10, 0.20]  # Risk score (standard deviation-like)

prob = pulp.LpProblem("Portfolio_Optimization", pulp.LpMaximize)

# Decision variables
weights = {a: pulp.LpVariable(f"weight_{a}", lowBound=0, upBound=1) for a in assets}
selected = {a: pulp.LpVariable(f"select_{a}", cat="Binary") for a in assets}

# Objective: maximize expected return
prob += pulp.lpSum(expected_return[i] * weights[assets[i]] for i in range(len(assets)))

# Constraint 1: Fully invested
prob += pulp.lpSum(weights.values()) == 1

# Constraint 2: Portfolio risk โ‰ค 25%
prob += pulp.lpSum(risk[i] * weights[assets[i]] for i in range(len(assets))) <= 0.25

# Constraint 3: If selected, min 5% (no token positions)
for a in assets:
    prob += weights[a] >= 0.05 * selected[a]
    prob += weights[a] <= selected[a]

# Constraint 4: At most 3 assets
prob += pulp.lpSum(selected.values()) <= 3

# Solve
prob.solve(pulp.PULP_CBC_CMD(msg=False))

# Results
print(f"\n{'='*60}")
print(f"Portfolio Optimization โ€” Status: {pulp.LpStatus[prob.status]}")
print(f"{'='*60}")
print(f"Expected return: {pulp.value(prob.objective)*100:.2f}%")

print(f"\n{'Asset':<12} {'Weight':<10} {'Return':<10} {'Risk':<10} {'Contrib':<10}")
print(f"{'-'*52}")

portfolio_risk = 0
for i, a in enumerate(assets):
    w = weights[a].value()
    if w and w > 0.001:
        contr = expected_return[i] * w
        portfolio_risk += risk[i] * w
        print(f"{a:<12} {w*100:<9.1f}% {expected_return[i]*100:<9.1f}% {risk[i]*100:<9.1f}% {contr*100:<9.2f}%")

print(f"\nPortfolio risk: {portfolio_risk*100:.2f}% (limit: 25%) โœ…" if portfolio_risk <= 0.25 else "โŒ OVER LIMIT")
print(f"Number of assets: {sum(1 for a in assets if weights[a].value() > 0.001)}")

Efficient Frontier

By varying the risk limit and plotting results, we get the Efficient Frontier โ€” the optimal return for each risk level:

import matplotlib.pyplot as plt

risk_limits = [0.10, 0.15, 0.20, 0.25, 0.30, 0.35, 0.40]
optimal_returns = []

for max_risk in risk_limits:
    prob = pulp.LpProblem("Portfolio", pulp.LpMaximize)
    weights = {a: pulp.LpVariable(f"w_{a}", 0, 1) for a in assets}
    selected = {a: pulp.LpVariable(f"s_{a}", cat="Binary") for a in assets}
    
    prob += pulp.lpSum(expected_return[i] * weights[assets[i]] for i in range(len(assets)))
    prob += pulp.lpSum(weights.values()) == 1
    prob += pulp.lpSum(risk[i] * weights[assets[i]] for i in range(len(assets))) <= max_risk
    for a in assets:
        prob += weights[a] >= 0.05 * selected[a]
        prob += weights[a] <= selected[a]
    
    prob.solve(pulp.PULP_CBC_CMD(msg=False))
    optimal_returns.append(pulp.value(prob.objective) if prob.status == 1 else 0)

plt.plot(risk_limits, optimal_returns, 'bo-', linewidth=2)
plt.xlabel('Risk Limit')
plt.ylabel('Expected Return')
plt.title('Efficient Frontier')
plt.grid(True, alpha=0.3)
plt.show()

Applications

| Domain | Use Case | |--------|---------| | Retirement Funds | Balance growth vs safety (target-date funds) | | Hedge Funds | Long/short portfolios with leverage constraints | | Insurance | Asset-liability matching for policy reserves | | Endowments | Yale model: diversify across asset classes | | Corporate Treasury | Short-term cash investment optimization |

Summary

| Aspect | Detail | |--------|--------| | Problem | Allocate capital: max return, min risk | | Variables | Continuous (weights) + Binary (selection) | | Constraints | Full investment, risk โ‰ค 25%, min/max per asset | | Objective | Maximize expected portfolio return | | Solver | CBC (MILP) | | Extension | Correlations, rebalancing, transaction costs, ESG constraints |

Constraint Programming Complete! ๐ŸŽ‰

  • โœ… PuLP Basics
  • โœ… Staff Scheduling
  • โœ… Resource Allocation
  • โœ… Supply Chain
  • โœ… Portfolio Optimization
  • โœ… Efficient Frontier

Summary

  • Modern Portfolio Theory optimizes risk-return tradeoff
  • Efficient Frontier shows optimal portfolios
  • Constraints can reflect real-world limitations
  • Rebalancing periodically maintains target allocation

Efficient Frontier with Pulp

The efficient frontier is the set of portfolios that gives the highest return for each risk level:

import pulp
import matplotlib.pyplot as plt

def efficient_frontier(expected_returns, cov_matrix, risk_levels):
    """
    Calculate the efficient frontier by solving for each risk level.
    
    Args:
        expected_returns: list of expected returns for each asset
        cov_matrix: covariance matrix
        risk_levels: list of max risk values to try
    
    Returns:
        list of (risk, return) tuples on the efficient frontier
    """
    n = len(expected_returns)
    frontier = []

    for max_risk in risk_levels:
        prob = pulp.LpProblem("Efficient_Frontier", pulp.LpMaximize)
        weights = [pulp.LpVariable(f"w_{i}", 0, 1) for i in range(n)]

        # Objective: maximize expected return
        prob += pulp.lpSum([expected_returns[i] * weights[i] for i in range(n)])

        # Constraints
        prob += pulp.lpSum(weights) == 1  # fully invested

        # Risk constraint: portfolio variance <= max_risk
        # For simplicity, use weighted average of variances
        risk_expr = pulp.lpSum([
            cov_matrix[i][i] * weights[i] * weights[i]
            for i in range(n)
        ])
        # Add covariance terms
        for i in range(n):
            for j in range(i+1, n):
                term = 2 * cov_matrix[i][j] * weights[i] * weights[j]
                # This creates a quadratic term โ€” for LP we approximate
                # In practice, use a QP solver like cvxopt

        prob.solve(pulp.PULP_CBC_CMD(msg=False))

        if pulp.LpStatus[prob.status] == 'Optimal':
            opt_weights = [pulp.value(w) for w in weights]
            opt_return = sum(expected_returns[i] * opt_weights[i] for i in range(n))
            frontier.append((max_risk, opt_return))

    return frontier

Note: True efficient frontier calculation requires quadratic programming (QP) because variance is a quadratic function of weights. For LP approximation, use piecewise linear risk measures or CVaR instead.

Practical Applications

| Domain | Portfolio Optimization Use | |--------|---------------------------| | Personal Finance | Retirement portfolio: stocks + bonds + cash | | Wealth Management | Client risk profiling and asset allocation | | Pension Funds | Long-term liability-driven investment | | Insurance | Asset-liability matching for policy reserves | | Endowment | Yale model: diversify across alternative assets | | Corporate Treasury | Short-term cash investment optimization |

Summary

Portfolio optimization with PuLP allocates capital across assets to maximize return while constraining risk. The classic model uses expected returns, variance as risk measure, and constraints for full investment and position limits. Real portfolios add transaction costs, rebalancing, and ESG constraints.

Key takeaways:

  • Decision variables: continuous weights for each asset (0 to 1)
  • Objective: maximize expected portfolio return
  • Constraints: fully invested, max risk, min/max per asset
  • Efficient frontier: optimal risk-return tradeoff curve
  • QP solvers handle quadratic risk (variance) naturally
  • LP can approximate with linear risk measures (CVaR)
  • Real portfolios include: transaction costs, rebalancing, ESG screening
  • Portfolio optimization is the foundation of modern investment management

What's Next: Algorithm โ€” Combinatorial Optimization

The next course covers combinatorial optimization โ€” bin packing, job scheduling, and cutting stock problems.

Unlock Full Tutorial

This chapter is paid content. Join the project to unlock over 5000 words of deep analysis, including 10+ god-tier Prompts and real Source Code examples!