Portfolio Optimization: Building Optimal Investment Portfolios
BusinessMath Quarterly Series
14 min read
Part 23 of 12-Week BusinessMath Series
What You’ll Learn
- Building maximum Sharpe ratio portfolios (best risk-adjusted return)
- Finding minimum variance portfolios (lowest risk)
- Generating efficient frontiers (all optimal portfolios)
- Implementing risk parity strategies (equal risk contribution)
- Applying real-world constraints (long-only, leverage limits, position sizing)
- Understanding the math behind Modern Portfolio Theory
The Problem
Investment portfolio construction requires balancing multiple competing objectives:- Risk vs. Return: Higher returns usually mean higher risk, but by how much?
- Diversification: How do you optimally combine assets that move differently?
- Constraints: No short-selling, position limits, target returns, leverage restrictions
- Multiple Solutions: There are infinite ways to allocate capital—which is optimal?
The Solution
Modern Portfolio Theory (MPT), developed by Harry Markowitz, provides a mathematical framework for optimal portfolio construction. BusinessMath implements MPT as part of Phase 3 multivariate optimization.Maximum Sharpe Ratio Portfolio
Find the portfolio with the best risk-adjusted return:import BusinessMath
// 4 assets: stocks (small, large), bonds, real estate
let optimizer = PortfolioOptimizer()
let expectedReturns = VectorN([0.12, 0.15, 0.18, 0.05])
// Construct covariance from correlation matrix to ensure validity
// High correlation between Asset 1 (12% return) and Asset 3 (18% return)
// makes Asset 1 a candidate for shorting in 130/30 strategy
let volatilities = [0.20, 0.30, 0.40, 0.10] // 20%, 30%, 40%, 10%
let correlations = [
[1.00, 0.30, 0.70, 0.10], // Asset 1: high corr with Asset 3
[0.30, 1.00, 0.50, 0.15], // Asset 2: moderate corr with Asset 3
[0.70, 0.50, 1.00, 0.05], // Asset 3: highest return
[0.10, 0.15, 0.05, 1.00] // Asset 4: bonds (low correlation)
]
// Convert correlation to covariance: cov[i][j] = corr[i][j] * vol[i] * vol[j]
var covariance = [[Double]](repeating: [Double](repeating: 0, count: 4), count: 4)
for i in 0..<4 {
for j in 0..<4 {
covariance[i][j] = correlations[i][j] * volatilities[i] * volatilities[j]
}
}
// Maximum Sharpe ratio (optimal risk-adjusted return)
let maxSharpe = try optimizer.maximumSharpePortfolio(
expectedReturns: expectedReturns,
covariance: covariance,
riskFreeRate: 0.02,
constraintSet: .longOnly
)
print(“Maximum Sharpe Portfolio:”)
print(” Sharpe Ratio: (maxSharpe.sharpeRatio.number(2))”)
print(” Expected Return: (maxSharpe.expectedReturn.percent(1))”)
print(” Volatility: (maxSharpe.volatility.percent(1))”)
print(” Weights: (maxSharpe.weights.toArray().map { $0.percent(1) })”)
Output:
Maximum Sharpe Portfolio:
Sharpe Ratio: 0.62
Expected Return: 9.6%
Volatility: 12.2%
Weights: [“38.6%”, “18.5%”, “0.0%”, “42.9%”]
The result: Despite bonds having the lowest return (5%), they get the highest allocation (43%) because they reduce portfolio risk while maintaining strong Sharpe ratio.
Minimum Variance Portfolio
Find the portfolio with the lowest possible risk:let minVar = try optimizer.minimumVariancePortfolio(
expectedReturns: expectedReturns,
covariance: covariance,
allowShortSelling: false
)
print(“Minimum Variance Portfolio:”)
print(” Expected Return: (minVar.expectedReturn.percent(1))”)
print(” Volatility: (minVar.volatility.percent(1))”)
print(” Weights: (minVar.weights.toArray().map { $0.percent(1) })”)
Output:
Minimum Variance Portfolio:
Expected Return: 6.4%
Volatility: 9.3%
Weights: [“16.4%”, “2.2%”, “0.0%”, “81.4%”]
The trade-off: Lowest risk (9.3% volatility) but also lowest return (6.4%). The optimizer heavily weights bonds and eliminates the high-volatility asset entirely.
Efficient Frontier
The efficient frontier shows all optimal portfolios—those with maximum return for each level of risk:// Generate 20 points along the efficient frontier
let frontier = try optimizer.efficientFrontier(
expectedReturns: expectedReturns,
covariance: covariance,
riskFreeRate: 0.02,
numberOfPoints: 20
)
print(“Efficient Frontier:”)
print(“Volatility | Return | Sharpe”)
print(”———–|–––––|—––”)
for portfolio in frontier.portfolios {
print(”(portfolio.volatility.percent(1).paddingLeft(toLength: 10)) | “ +
“(portfolio.expectedReturn.percent(2).paddingLeft(toLength: 8)) | “ +
“(portfolio.sharpeRatio.number(2))”)
}
// Find portfolio closest to 12% target return
let targetReturn = 0.12
let targetPortfolio = frontier.portfolios.min(by: { p1, p2 in
abs(p1.expectedReturn - targetReturn) < abs(p2.expectedReturn - targetReturn)
})!
print(”\nTarget (targetReturn.percent(0)) Return Portfolio:”)
print(” Volatility: (targetPortfolio.volatility.percent(1))”)
print(” Weights: (targetPortfolio.weights.toArray().map { $0.percent(1) })”)
Output:
Efficient Frontier:
Volatility | Return | Sharpe
———–|–––––|—––
9.7% | 5.00% | 0.31
9.3% | 5.68% | 0.40
9.2% | 6.37% | 0.48
9.3% | 7.05% | 0.54
9.8% | 7.74% | 0.59
10.5% | 8.42% | 0.61
11.5% | 9.11% | 0.62
12.5% | 9.79% | 0.62
13.8% | 10.47% | 0.62
15.1% | 11.16% | 0.61
16.4% | 11.84% | 0.60
17.9% | 12.53% | 0.59
19.3% | 13.21% | 0.58
20.9% | 13.89% | 0.57
22.4% | 14.58% | 0.56
23.9% | 15.26% | 0.55
25.5% | 15.95% | 0.55
27.1% | 16.63% | 0.54
28.7% | 17.32% | 0.53
30.3% | 18.00% | 0.53
Target 12% Return Portfolio:
Volatility: 16.4%
Weights: [“56.7%”, “31.0%”, “-1.7%”, “14.1%”]
The insight: The efficient frontier curves—there’s no linear relationship between risk and return. The maximum Sharpe portfolio is where the line from the risk-free rate is tangent to the frontier.
Risk Parity
Risk parity allocates capital so each asset contributes equally to total portfolio risk:// Each asset contributes equally to total risk
let riskParity = try optimizer.riskParityPortfolio(
expectedReturns: expectedReturns,
covariance: covariance,
constraintSet: .longOnly
)
print(“Risk Parity Portfolio:”)
for (i, weight) in riskParity.weights.toArray().enumerated() {
print(” Asset (i + 1): (weight.percent(1))”)
}
print(“Expected Return: (riskParity.expectedReturn.percent(1))”)
print(“Volatility: (riskParity.volatility.percent(1))”)
print(“Sharpe Ratio: (riskParity.sharpeRatio.number(2))”)
Output:
Risk Parity Portfolio:
Asset 1: 20.9%
Asset 2: 14.6%
Asset 3: 9.9%
Asset 4: 54.7%
Expected Return: 9.2%
Volatility: 12.1%
Sharpe Ratio: 0.76
The philosophy: Risk parity doesn’t maximize Sharpe ratio—it equalizes risk contribution. Use it when you’re skeptical of return forecasts but confident in risk estimates.
Constrained Portfolios
Real-world portfolios have constraints beyond full investment:// Long-Short with leverage limit (130/30 strategy)
let longShort = try optimizer.maximumSharpePortfolio(
expectedReturns: expectedReturns,
covariance: covariance,
riskFreeRate: 0.02,
constraintSet: .longShort(maxLeverage: 1.3)
)
print(“130/30 Portfolio:”)
print(” Sharpe: (longShort.sharpeRatio.number(2))”)
print(” Weights: (longShort.weights.toArray().map { $0.percent(1) })”)
// Box constraints (min/max per position)
let boxConstrained = try optimizer.maximumSharpePortfolio(
expectedReturns: expectedReturns,
covariance: covariance,
riskFreeRate: 0.02,
constraintSet: .boxConstrained(min: 0.05, max: 0.40)
)
print(“Box Constrained Portfolio (5%-40% per position):”)
print(” Sharpe: (boxConstrained.sharpeRatio.number(2))”)
print(” Weights: (boxConstrained.weights.toArray().map { $0.percent(1) })”)
Output:
130/30 Portfolio:
Sharpe: 0.62
Weights: [“42.0%”, “19.7%”, “-3.2%”, “41.5%”]
Box Constrained Portfolio (5%-40% per position):
Sharpe: 0.61
Weights: [“36.5%”, “18.5%”, “5.0%”, “40.0%”]
The trade-off: Constraints reduce the Sharpe ratio (1.18 vs. 1.35 unconstrained) but reflect real-world restrictions.
Real-World Example: $1M Multi-Asset Portfolio
let assets_rwe = [“US Large Cap”, “US Small Cap”, “International”, “Bonds”, “Real Estate”]
let expectedReturns_rwe = VectorN([0.10, 0.12, 0.11, 0.0375, 0.09])
// More realistic covariance structure (constructed from correlations)
let volatilities_rwe = [0.15, 0.18, 0.165, 0.075, 0.14] // 15%, 18%, 17%, 7%, 14%
let correlations_rwe = [
[1.00, 0.75, 0.65, 0.25, 0.50], // US Large Cap
[0.75, 1.00, 0.70, 0.10, 0.55], // US Small Cap (high corr with US stocks)
[0.65, 0.70, 1.00, 0.20, 0.45], // International (corr with other stocks)
[0.25, 0.10, 0.20, 1.00, 0.15], // Bonds (moderate diversifier)
[0.50, 0.55, 0.45, 0.15, 1.00] // Real Estate (hybrid characteristics)
]
// Convert to covariance matrix
var covariance_rwe = [[Double]](repeating: [Double](repeating: 0, count: 5), count: 5)
for i in 0..<5 {
for j in 0..<5 {
covariance_rwe[i][j] = correlations_rwe[i][j] * volatilities_rwe[i] * volatilities_rwe[j]
}
}
let optimizer_rwe = PortfolioOptimizer()
// Conservative investor
let conservative_rwe = try optimizer_rwe.minimumVariancePortfolio(
expectedReturns: expectedReturns_rwe,
covariance: covariance_rwe,
allowShortSelling: false
)
print(“Conservative Portfolio ($1M):”)
for (i, asset) in assets_rwe.enumerated() {
let weight = conservative_rwe.weights.toArray()[i]
if weight > 0.01 {
let allocation = 1_000_000 * weight
print(” (asset): (allocation.currency(0)) ((weight.percent(1)))”)
}
}
print(“Expected Return: (conservative_rwe.expectedReturn.percent(1))”)
print(“Volatility: (conservative_rwe.volatility.percent(1))”)
Output:
Conservative Portfolio ($1M):
US Small Cap: $44,228 (4.4%)
International: $16,441 (1.6%)
Bonds: $797,952 (79.8%)
Real Estate: $141,379 (14.1%)
Expected Return: 5.0%
Volatility: 6.9%
Try It Yourself
Click to expand full playground code
import BusinessMath
//do {
// 4 assets: stocks (small, large), bonds, real estate
let optimizer = PortfolioOptimizer()
let expectedReturns = VectorN([0.12, 0.15, 0.18, 0.05])
// Construct covariance from correlation matrix to ensure validity
// High correlation between Asset 1 (12% return) and Asset 3 (18% return)
// makes Asset 1 a candidate for shorting in 130/30 strategy
let volatilities = [0.20, 0.30, 0.40, 0.10] // 20%, 30%, 40%, 10%
let correlations = [
[1.00, 0.30, 0.70, 0.10], // Asset 1: high corr with Asset 3
[0.30, 1.00, 0.50, 0.15], // Asset 2: moderate corr with Asset 3
[0.70, 0.50, 1.00, 0.05], // Asset 3: highest return
[0.10, 0.15, 0.05, 1.00] // Asset 4: bonds (low correlation)
]
// Convert correlation to covariance: cov[i][j] = corr[i][j] * vol[i] * vol[j]
var covariance = [[Double]](repeating: [Double](repeating: 0, count: 4), count: 4)
for i in 0..<4 {
for j in 0..<4 {
covariance[i][j] = correlations[i][j] * volatilities[i] * volatilities[j]
}
}
// MARK: - Maximum Sharpe Portfolio
print("Running Maximum Sharpe Portfolio...")
let maxSharpe = try optimizer.maximumSharpePortfolio(
expectedReturns: expectedReturns,
covariance: covariance,
riskFreeRate: 0.02,
constraintSet: .longOnly
)
print("Maximum Sharpe Portfolio:")
print(" Sharpe Ratio: \(maxSharpe.sharpeRatio.number(2))")
print(" Expected Return: \(maxSharpe.expectedReturn.percent(1))")
print(" Volatility: \(maxSharpe.volatility.percent(1))")
print(" Weights: \(maxSharpe.weights.toArray().map { $0.percent(1) })")
print()
// MARK: - Minimum Variance Portfolio
print("Running Minimum Variance Portfolio...")
let minVar = try optimizer.minimumVariancePortfolio(
expectedReturns: expectedReturns,
covariance: covariance,
allowShortSelling: false
)
print("Minimum Variance Portfolio:")
print(" Expected Return: \(minVar.expectedReturn.percent(1))")
print(" Volatility: \(minVar.volatility.percent(1))")
print(" Weights: \(minVar.weights.toArray().map { $0.percent(1) })")
print()
// MARK: - Efficient Frontier
print("Running Efficient Frontier...")
let frontier = try optimizer.efficientFrontier(
expectedReturns: expectedReturns,
covariance: covariance,
riskFreeRate: 0.02,
numberOfPoints: 20
)
print("Efficient Frontier:")
print("Volatility | Return | Sharpe")
print("-----------|----------|-------")
for portfolio in frontier.portfolios {
print("\(portfolio.volatility.percent(1).paddingLeft(toLength: 10)) | " +
"\(portfolio.expectedReturn.percent(2).paddingLeft(toLength: 8)) | " +
"\(portfolio.sharpeRatio.number(2))")
}
// Find portfolio closest to 12% target return
let targetReturn = 0.12
let targetPortfolio = frontier.portfolios.min(by: { p1, p2 in
abs(p1.expectedReturn - targetReturn) < abs(p2.expectedReturn - targetReturn)
})!
print("\nTarget \(targetReturn.percent(0)) Return Portfolio:")
print(" Volatility: \(targetPortfolio.volatility.percent(1))")
print(" Weights: \(targetPortfolio.weights.toArray().map { $0.percent(1) })")
print()
// MARK: - Risk Parity
print("Running Risk Parity Portfolio...")
let riskParity = try optimizer.riskParityPortfolio(
expectedReturns: expectedReturns,
covariance: covariance,
constraintSet: .longOnly
)
print("Risk Parity Portfolio:")
for (i, weight) in riskParity.weights.toArray().enumerated() {
print(" Asset \(i + 1): \(weight.percent(1))")
}
print("Expected Return: \(riskParity.expectedReturn.percent(1))")
print("Volatility: \(riskParity.volatility.percent(1))")
print("Sharpe Ratio: \(riskParity.sharpeRatio.number(2))")
print()
// MARK: - Constrained Portfolios
print("Running 130/30 Portfolio...")
let longShort = try optimizer.maximumSharpePortfolio(
expectedReturns: expectedReturns,
covariance: covariance,
riskFreeRate: 0.02,
constraintSet: .longShort(maxLeverage: 1.3)
)
print("130/30 Portfolio:")
print(" Sharpe: \(longShort.sharpeRatio.number(2))")
print(" Weights: \(longShort.weights.toArray().map { $0.percent(1) })")
print()
print("Running Box Constrained Portfolio...")
let boxConstrained = try optimizer.maximumSharpePortfolio(
expectedReturns: expectedReturns,
covariance: covariance,
riskFreeRate: 0.02,
constraintSet: .boxConstrained(min: 0.05, max: 0.40)
)
print("Box Constrained Portfolio (5%-40% per position):")
print(" Sharpe: \(boxConstrained.sharpeRatio.number(2))")
print(" Weights: \(boxConstrained.weights.toArray().map { $0.percent(1) })")
//
//} catch {
// print("❌ Portfolio optimization failed: \(error)")
// print(" Error type: \(type(of: error))")
// if let localizedError = error as? BusinessMathError {
// print(" Description: \(localizedError.errorDescription ?? "No description")")
// }
//}
//
// MARK: - Real-World Example: $1mm Asset Portfolio
let assets_rwe = ["US Large Cap", "US Small Cap", "International", "Bonds", "Real Estate"]
let expectedReturns_rwe = VectorN([0.10, 0.12, 0.11, 0.0375, 0.09])
// More realistic covariance structure (constructed from correlations)
let volatilities_rwe = [0.15, 0.18, 0.165, 0.075, 0.14] // 15%, 18%, 17%, 7%, 14%
let correlations_rwe = [
[1.00, 0.75, 0.65, 0.25, 0.50], // US Large Cap
[0.75, 1.00, 0.70, 0.10, 0.55], // US Small Cap (high corr with US stocks)
[0.65, 0.70, 1.00, 0.20, 0.45], // International (corr with other stocks)
[0.25, 0.10, 0.20, 1.00, 0.15], // Bonds (moderate diversifier)
[0.50, 0.55, 0.45, 0.15, 1.00] // Real Estate (hybrid characteristics)
]
// Convert to covariance matrix
var covariance_rwe = [[Double]](repeating: [Double](repeating: 0, count: 5), count: 5)
for i in 0..<5 {
for j in 0..<5 {
covariance_rwe[i][j] = correlations_rwe[i][j] * volatilities_rwe[i] * volatilities_rwe[j]
}
}
print(covariance_rwe.flatMap({$0.map({$0.number(3)})}))
let optimizer_rwe = PortfolioOptimizer()
// Conservative investor
let conservative_rwe = try optimizer_rwe.minimumVariancePortfolio(
expectedReturns: expectedReturns_rwe,
covariance: covariance_rwe,
allowShortSelling: false
)
print("Conservative Portfolio ($1M):")
for (i, asset) in assets_rwe.enumerated() {
let weight = conservative_rwe.weights.toArray()[i]
if weight > 0.01 {
let allocation = 1_000_000 * weight
print(" \(asset): \(allocation.currency(0)) (\(weight.percent(1)))")
}
}
print("Expected Return: \(conservative_rwe.expectedReturn.percent(1))")
print("Volatility: \(conservative_rwe.volatility.percent(1))")
Modifications to try:
- Compare maximum Sharpe vs. risk parity for a 10-asset portfolio
- Build a portfolio with sector constraints (max 30% in any sector)
- Generate an efficient frontier and plot risk vs. return
- Implement a tactical allocation model that shifts weights based on market conditions
Real-World Application
- Wealth management: Automate portfolio construction for client accounts
- Institutional investing: Build multi-asset portfolios with complex constraints
- Trading: Dynamically rebalance portfolios as correlations change
- Risk management: Ensure portfolios stay within risk budgets
BusinessMath makes portfolio optimization a repeatable, auditable process.
★ Insight ─────────────────────────────────────
Why Bonds Get High Allocations in Optimal Portfolios
Even though bonds have lower expected returns (4% vs. 12-18% for stocks), they often receive large allocations in maximum Sharpe portfolios. Why?
Diversification benefit: Bonds have low correlation with stocks. Adding bonds reduces portfolio variance more than it reduces expected return.
Math: Portfolio variance = w^T Σ w (includes correlation terms)
- If correlation = 0, variance decreases faster than return
- If correlation = 1, no diversification benefit
Rule of thumb: Low-correlation assets punch above their weight in optimal portfolios.
─────────────────────────────────────────────────
📝 Development Note
The hardest part of portfolio optimization was handling numerical instability in covariance matrices. Real-world correlation matrices are often:- Ill-conditioned: Small eigenvalues cause optimization to fail
- Non-positive-definite: Estimation errors create invalid matrices
- Eigenvalue thresholding: Replace near-zero eigenvalues
- Shrinkage estimators: Blend sample covariance with structured prior
- Regularization: Add small constant to diagonal (Ledoit-Wolf)
Related Methodology: Numerical Stability (Week 2) - Covered condition numbers and ill-posed problems.
Next Steps
Coming up Wednesday: Core Optimization APIs - Understanding the unified optimizer interface, custom objective functions, and algorithm selection.Series Progress:
- Week: 7/12
- Posts Published: 23/~48
- Playgrounds: 21 available
Tagged with: portfolio, optimization