BusinessMath Quarterly Series
12 min read
Part 23 of 12-Week BusinessMath Series
Investment portfolio construction requires balancing multiple competing objectives:
Manual portfolio construction (guessing weights in a spreadsheet) doesn’t find mathematically optimal solutions.
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.
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.
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.
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 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.
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.
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%
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))")
→ Full API Reference: BusinessMath Docs – 5.2 Portfolio Optimization
Modifications to try:
Wealth manager use case: “I manage 50 client accounts, each with different risk tolerances and constraints. I need to generate optimal portfolios programmatically, not manually tune weights in Excel.”
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)
Real example: 100% stocks = 20% vol. Adding 40% bonds might reduce return from 12% → 10%, but volatility drops from 20% → 12%. Sharpe improves: (10%-2%)/12% > (12%-2%)/20%.
Rule of thumb: Low-correlation assets punch above their weight in optimal portfolios.
─────────────────────────────────────────────────
The hardest part of portfolio optimization was handling numerical instability in covariance matrices. Real-world correlation matrices are often:
We implemented multiple safeguards:
Without these, 30% of real-world portfolios would fail to optimize.
Related Methodology: Numerical Stability (Week 2) - Covered condition numbers and ill-posed problems.
Coming up Wednesday: Core Optimization APIs - Understanding the unified optimizer interface, custom objective functions, and algorithm selection.
Series Progress:
Tagged with: businessmath, swift, portfolio, optimization, modern-portfolio-theory, sharpe-ratio, efficient-frontier