Modern Portfolio Theory and Mean-Variance Optimization › Practical Implementation with Simulated Return Data

Michael BrenndoerferDecember 13, 20255 min read
Quantitative FinanceAll chapters
Part 9 of 15Portfolio Theory and Investment Analysis
Chapter 46Modern Portfolio Theory and Mean-Variance Optimization › Practical Implementation with Simulated Return Data
Reading section 8 of 12 · Practical Implementation with Simulated Return Data · 5 minSection 8 of 12 · 5 min

Book contents

Section 8 of 12 · 5 min read

Modern Portfolio Theory and Mean-Variance Optimization › Practical Implementation with Simulated Return DataOne section · Sections in this chapter8 / 12 ↓
Reading tools Definitions & inline links

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

Let's apply mean-variance optimization to a seeded five-asset Gaussian return sample with a specified covariance structure. The 1,260 observations represent five simulated trading years, not historical market observations.

In[26]:
Code
import numpy as np

# Simulated daily returns for 5 assets (252 trading days x 5 simulated years)
np.random.seed(42)
n_days = 252 * 5
n_assets = 5
asset_names = ["Large Cap", "Small Cap", "Intl Stocks", "Bonds", "REITs"]

# True parameters for simulation
true_annual_returns = np.array([0.10, 0.12, 0.08, 0.04, 0.09])
true_annual_vols = np.array([0.16, 0.22, 0.18, 0.04, 0.20])

# Correlation structure
true_corr = np.array(
    [
        [1.00, 0.75, 0.65, 0.10, 0.55],
        [0.75, 1.00, 0.60, 0.05, 0.50],
        [0.65, 0.60, 1.00, 0.15, 0.45],
        [0.10, 0.05, 0.15, 1.00, 0.20],
        [0.55, 0.50, 0.45, 0.20, 1.00],
    ]
)

# Convert to daily parameters
daily_returns_mean = true_annual_returns / 252
daily_vols = true_annual_vols / np.sqrt(252)
true_cov_daily = np.diag(daily_vols) @ true_corr @ np.diag(daily_vols)

# Generate returns using Cholesky decomposition
L = np.linalg.cholesky(true_cov_daily)
z = np.random.randn(n_days, n_assets)
returns = daily_returns_mean + z @ L.T
In[27]:
Code
# Estimate annualized parameters from simulated daily returns (IID arithmetic scaling)
estimated_mu = returns.mean(axis=0) * 252  # Annualize
estimated_cov = np.cov(returns.T) * 252  # Annualize

# Compute the long-only minimum-variance curve, including its lower branch
frontier_ret, frontier_risk, frontier_w = efficient_frontier(
    estimated_mu, estimated_cov, n_points=100, allow_short=False
)

# Select the best sampled Sharpe-ratio portfolio (approximate tangent), rf = 2%
rf = 0.02
sharpe_ratios = (frontier_ret - rf) / frontier_risk
max_sharpe_idx = np.argmax(sharpe_ratios)
tangent_weights = frontier_w[max_sharpe_idx]
tangent_return = frontier_ret[max_sharpe_idx]
tangent_risk = frontier_risk[max_sharpe_idx]
max_sharpe_ratio = sharpe_ratios[max_sharpe_idx]

# Calculate Capital Allocation Line (CAL)
cml_risks = np.linspace(0, tangent_risk * 1.5, 50)
cml_returns = rf + max_sharpe_ratio * cml_risks
Out[28]:
Visualization
Simulated-return risk-return chart with five labeled assets, a long-only minimum-variance curve, an approximate tangent star near 16.8% risk and 13.5% return, and a dashed capital-allocation line from the 2% risk-free point.
Minimum-variance curve estimated from seeded simulated returns. The star marks the highest-Sharpe portfolio among 100 target-return grid points, an approximation to continuous tangency. The dashed capital-allocation line combines this fund with a 2% risk-free asset; its extension assumes frictionless borrowing and lending at that same rate.

This chart uses simulated returns. The dashed capital-allocation line combines the 2% risk-free asset with the best Sharpe portfolio found on the finite grid. It approximates the tangency opportunity set in the stated mean-variance model; extending beyond the risky fund assumes borrowing at the same risk-free rate, without trading frictions or additional leverage constraints.

Out[29]:
Console
Approximate Tangent Portfolio (Best Sampled Sharpe Ratio):
--------------------------------------------------
  Large Cap      :  14.49%
  Small Cap      :   0.00%
  Intl Stocks    :   0.00%
  Bonds          :   7.02%
  REITs          :  78.50%
--------------------------------------------------
  Expected Return: 13.45%
  Standard Deviation: 16.79%
  Sharpe Ratio: 0.682
Out[30]:
Visualization
Donut chart showing the tangent portfolio allocated 78.5% to REITs, 14.5% to Large Cap, and 7.0% to Bonds.
Allocation of the approximate maximum-Sharpe portfolio in this simulated example. The selected long-only grid solution allocates about 78.5% to REITs, 14.5% to Large Cap, and 7.0% to Bonds; Small Cap and International Stocks receive zero weight.

The selected portfolio has the highest Sharpe ratio among the sampled targets. In the frictionless mean-variance model with a suitable tangency portfolio and unrestricted borrowing and lending at the same risk-free rate, efficient allocations combine that risky fund with the risk-free asset. Actual funding limits, objectives, and exposures can change this conclusion. The next chapter connects portfolio choice to CAPM equilibrium.