Skip to content

Fundamentals of Portfolio Optimization ​

The previous chapter measured investment returns and risk. We now ask how to combine assets: which allocations offer attractive expected returns for the amount of variability an investor accepts?

We begin with a single investment horizon and explicitly assumed expected returns, volatilities, and correlations. These inputs determine a set of feasible portfolios. Optimization then selects among them under constraints such as full investment and long-only positions. Later examples introduce historical estimates, target returns, short selling, and a risk-free asset.

Run this setup before working through the examples in order. The toolkit provides reusable portfolio calculations; each example makes the investment inputs and results visible.

python
import json
import numpy as np
import pandas as pd
import PortfolioOptimizationKit as pok

Modern Portfolio Theory (MPT) ​

Introduction to MPT ​

Modern Portfolio Theory (MPT) compares portfolios using expected return and variance. Diversification works through the way asset returns move together, not simply through the number of assets held. Adding an asset can reduce portfolio risk, but an arbitrary mix need not be less volatile than every constituent.

For N assets, let Ri be asset i's simple total return over a common horizon, and let wi be its share of initial portfolio wealth. We start with fully invested, long-only allocations:

∑i=1Nwi=1,wi≥0.

The weights are chosen at the beginning of the period. Assume no external cash flows or transaction costs during that period. Define μi=E[Ri], σi2=Var(Ri), and Σij=Cov(Ri,Rj). Expected returns and covariances must describe the same horizon and use the same asset ordering as the weights.

The return and variance identities below require finite second moments, not Gaussian returns. Using only mean and variance as a selection criterion nevertheless leaves out other features discussed in Chapter 1.1, including tail shape and drawdown.

Portfolio Return ​

Suppose initial wealth is V0. The amount initially invested in asset i is wiV0, and its end-of-period value is wiV0(1+Ri). Summing across assets gives:

V1=∑i=1NwiV0(1+Ri)=V0(1+∑i=1NwiRi).

Therefore the portfolio's realized return is:

Rp=V1V0−1=∑i=1NwiRi=wTR.

Taking expectations, with weights fixed at the investment decision, gives:

μp=E[Rp]=∑i=1Nwiμi=wTμ.

This linearity does not require independence. For fixed weights and expected asset returns, changing correlation changes risk but not expected portfolio return.

Expected return is different from historical compounded growth. Weighting asset CAGRs generally gives neither the portfolio's CAGR nor a justified forecast. For a historical portfolio, first calculate each period's return using the weights held at that period's start, then compound the portfolio returns. Maintaining the same target weights across periods implies rebalancing; buy-and-hold weights generally drift.

Portfolio Volatility ​

Understanding Portfolio Volatility ​

Portfolio volatility is the standard deviation of Rp. Unlike expected return, it is generally not the weighted average of the individual volatilities, because it depends on covariances.

Calculating and Minimizing Volatility ​

For two assets, subtract the portfolio mean and expand the square:

σp2=E[(w1(R1−μ1)+w2(R2−μ2))2]=w12σ12+w22σ22+2w1w2Cov(R1,R2).

The first two terms measure each asset's own variance contribution. The cross term captures how the two return deviations move together. For positive asset volatilities, write covariance in terms of correlation ρ:

Cov(R1,R2)=ρσ1σ2,−1≤ρ≤1.

Taking the square root gives:

σp=w12σ12+w22σ22+2w1w2ρσ1σ2.

For fixed positive weights and asset volatilities, reducing correlation reduces this variance. A weight of zero removes the corresponding asset and the covariance term.

In matrix form, the two-asset covariance matrix is:

Σ=(σ12ρσ1σ2ρσ1σ2σ22).

For N assets, Σ is an N×N matrix with variances on the diagonal and covariances elsewhere. With w an N×1 vector:

σp2=wTΣw,σp=wTΣw.

A valid covariance matrix is symmetric and positive semidefinite: it cannot imply a negative variance for any portfolio. The matrix product expresses the same variance and covariance contributions as the two-asset expansion.

Efficient Frontiers ​

Understanding Efficient Frontiers ​

The feasible set contains the portfolios allowed by the investment constraints. A portfolio is mean–variance efficient if no other feasible portfolio offers at least as much expected return with no more volatility, with a strict improvement in at least one of the two measures.

The efficient frontier is the set of those non-dominated portfolios. It depends on the assumed expected returns, covariance matrix, and constraints. It describes model-based trade-offs rather than guaranteed future performance.

The global minimum-variance (GMV) portfolio has the lowest variance across the feasible set. In the usual non-degenerate case, the efficient frontier extends from GMV toward higher expected returns. Feasible portfolios on the lower-return branch can be dominated; plotting every allocation does not make every plotted point efficient.

Calculating Efficient Frontiers ​

One way to trace the minimum-variance boundary is to solve for the lowest variance at each target expected return μ⋆:

minwwTΣwsubject towTμ=μ⋆,1Tw=1,wi≥0.

For fully invested, long-only weights, a feasible target must lie between the smallest and largest expected asset returns. The target-return boundary can include dominated portfolios; the efficient part is its upper, non-dominated portion. We will implement this constrained optimization later. First, two assets let us see the trade-off directly by varying their weights.

Examining Efficient Frontiers with Two-Asset Portfolios ​

Setting Up the Scenario ​

Use the following hypothetical one-year inputs. These are assumed expected returns and volatilities, so no historical estimation or additional annualization is needed.

AssetExpected annual returnAnnual volatility
Asset 18%10%
Asset 212%20%
python
expected_returns = np.array([0.08, 0.12])
volatilities = np.array([0.10, 0.20])

Worked Example: A 60/40 Allocation ​

Allocate 60% to Asset 1 and 40% to Asset 2, with correlation ρ=0.25. Expected return is:

μp=0.6(0.08)+0.4(0.12)=0.096=9.60%.

Portfolio variance is:

σp2=0.62(0.10)2+0.42(0.20)2+2(0.6)(0.4)(0.25)(0.10)(0.20)=0.0124.

Hence volatility is 0.0124≈11.14%. The weighted average of the asset volatilities would be 14%, which is not the portfolio volatility. The portfolio is also more volatile than Asset 1 alone, illustrating why diversification does not mean that every mix beats the least volatile constituent.

python
weights = np.array([0.60, 0.40])
rho = 0.25
sigma_1, sigma_2 = volatilities
covariance = np.array([
    [sigma_1 ** 2, rho * sigma_1 * sigma_2],
    [rho * sigma_1 * sigma_2, sigma_2 ** 2],
])

portfolio_mean = pok.portfolio_return(weights, expected_returns)
portfolio_variance = weights @ covariance @ weights
portfolio_vol = pok.portfolio_volatility(weights, covariance)

print(f"Expected annual return: {portfolio_mean:.2%}")
print(f"Annual return variance: {portfolio_variance:.6f}")
print(f"Annual volatility: {portfolio_vol:.2%}")

Try it: change rho to 0.0, then 1.0. The same allocation has volatility of 10% and 14%, respectively, while expected return remains 9.60%.

Finding the Two-Asset Minimum-Variance Weight ​

Write w1=w and w2=1−w, and set c=ρσ1σ2. Portfolio variance becomes a quadratic in w:

σp2(w)=w2σ12+(1−w)2σ22+2w(1−w)c.

Setting its derivative to zero gives:

dσp2dw=2w(σ12+σ22−2c)−2(σ22−c)=0,

and, when the denominator is positive:

w1GMV=σ22−cσ12+σ22−2c,w2GMV=1−w1GMV.

For long-only investing, a solution outside [0,1] is replaced by the nearest endpoint. At ρ=0.25, the optimal weights are 87.5% in Asset 1 and 12.5% in Asset 2. They give expected return 8.50% and volatility approximately 9.68%. Here the combination is less volatile than either asset alone.

If both volatilities are equal and correlation is one, the denominator is zero: every allocation has the same variance. The experiment below displays the equally weighted allocation as one representative minimum-variance portfolio in that special case. If expected returns differ, the higher-return endpoint is the efficient choice.

Comparing Correlation Scenarios ​

Run the setup and asset-input cells first. We calculate 101 long-only allocations for each correlation, holding the expected returns and volatilities fixed. The black diamond marks the analytical GMV allocation. No random simulation is needed for this comparison.

python
import pandas as pd
import numpy as np
import PortfolioOptimizationKit as pok
import json

correlations = [1.0, 0.5, 0.25, 0.0, -0.5, -1.0]
weights_1 = np.linspace(0, 1, num=101)
weight_grid = np.column_stack([weights_1, 1 - weights_1])
sigma_1, sigma_2 = volatilities
plot_data = {}
gmv_rows = []

for k_rho, rho in enumerate(correlations):
    cov_12 = rho * sigma_1 * sigma_2
    covariance = np.array([[sigma_1 ** 2, cov_12], [cov_12, sigma_2 ** 2]])

    portfolios = []
    for weights in weight_grid:
        portfolio_ret = pok.portfolio_return(weights, expected_returns)
        portfolio_vol = pok.portfolio_volatility(weights, covariance)
        portfolios.append([
            float(portfolio_vol * 100),
            float(portfolio_ret * 100),
            float(weights[1]),  # Asset 2's weight controls the point's color.
        ])

    denominator = sigma_1 ** 2 + sigma_2 ** 2 - 2 * cov_12
    gmv_weight_1 = np.clip((sigma_2 ** 2 - cov_12) / denominator, 0, 1) if denominator > 0 else 0.5
    gmv_weights = np.array([gmv_weight_1, 1 - gmv_weight_1])
    gmv_ret = pok.portfolio_return(gmv_weights, expected_returns)
    gmv_vol = pok.portfolio_volatility(gmv_weights, covariance)
    gmv_rows.append({
        "Correlation": rho,
        "Asset 1 weight (%)": gmv_weights[0] * 100,
        "Asset 2 weight (%)": gmv_weights[1] * 100,
        "Expected return (%)": gmv_ret * 100,
        "Volatility (%)": gmv_vol * 100,
    })

    plot_data[f"ef_{k_rho}"] = {
        "type": "scatter",
        "yAxisName": "Expected annual return (%)",
        "title": f"Feasible Portfolios: ρ = {rho:.2f}",
        "series": {"Portfolios": portfolios, "GMV": [[float(gmv_vol * 100), float(gmv_ret * 100)]]},
        "xAxis": {"type": "value", "name": "Annual volatility (%)", "min": 0},
        "yAxis": {"type": "value", "name": "Expected annual return (%)", "scale": True},
        "visualMap": {
            "min": 0,
            "max": 1,
            "dimension": 2,
            "seriesIndex": 0,
            "inRange": {"color": ["#0000FF", "#00FF00"]},
        },
    }

gmv_comparison = pd.DataFrame(gmv_rows).set_index("Correlation")
print(gmv_comparison.to_string(float_format=lambda value: f"{value:.2f}"))
print("\n<ECHARTS_DATA>" + json.dumps(plot_data, allow_nan=False))

Analyzing Results and Implications ​

Each panel shows the feasible allocations, not only the efficient frontier. Blue denotes 100% Asset 1 and green denotes 100% Asset 2. Every panel has the same expected-return range, 8% to 12%, because correlation does not enter the expected-return formula.

For these inputs, Asset 2 has the higher expected return. Moving from the GMV point toward Asset 2 traces the efficient branch. Allocations on the lower-return branch are dominated: they accept more risk than GMV while offering less expected return. At ρ=0.25, even holding only Asset 1 is dominated by GMV, which offers 8.50% expected return with 9.68% volatility instead of 8% with 10% volatility.

The limiting correlations make the diversification mechanism particularly clear:

  • At ρ=1, long-only portfolio volatility is w1σ1+w2σ2. GMV holds only the less volatile Asset 1.
  • At ρ=−1, volatility is |w1σ1−w2σ2|. Weights w1=σ2/(σ1+σ2)=2/3 and w2=1/3 exactly offset the random return deviations in this model. Expected return is approximately 9.33%, with zero theoretical variance.

The zero-variance result depends on exact negative correlation and the specified volatilities. Estimated market relationships will not generally sustain that exact offset. It illustrates a mathematical limiting case rather than an empirically guaranteed hedge.

Try it: compare the 60/40 allocation across the six panels. Its expected return stays at 9.60%, while volatility falls as correlation decreases. Then compare the black diamonds to see how the minimum-risk weights themselves change with correlation.

Real-World Case Study: Portfolio Optimization with U.S. Stocks ​

We now replace assumed moments with estimates from the bundled price history of Amazon (AMZN), Coca-Cola (KO), and Microsoft (MSFT). The question is: given these three assets and a chosen estimation sample, which fully invested, long-only allocations offer the best estimated mean–variance trade-offs?

This is an in-sample construction exercise, not an out-of-sample strategy backtest. The same history supplies the estimates used to select and describe the portfolios. The three companies are selected for illustration; the results do not establish that this universe or these weights would have been an investable choice known in advance.

Stock Selection and Data Retrieval ​

pok.get_stock_dynamic() loads a stored CSV snapshot rather than fetching live prices. The supplied file contains 3,519 daily price observations, from 10 January 2011 to 3 January 2025, for the three stocks.

The collection script uses Yahoo Finance's Close field, but the snapshot does not record an explicit corporate-action adjustment policy. We therefore analyze the supplied price series as an educational dataset. A total-return performance study would first establish how splits and distributions were handled.

python
import PortfolioOptimizationKit as pok
import pandas as pd

tickers = ["AMZN", "KO", "MSFT"]
stocks = pok.get_stock_dynamic()[tickers]
n_assets = len(tickers)

print(f"Price observations: {len(stocks)}")
print(f"Coverage: {stocks.index[0]:%Y-%m-%d} to {stocks.index[-1]:%Y-%m-%d}")
print(stocks.head().to_string(float_format=lambda value: f"{value:.2f}"))

The loader preserves the CSV's price units and uses trading dates as its index. The collection script already rounded prices to two decimal places; the calculations below introduce no further rounding. Dividing all prices by a constant would leave returns unchanged, but rounding those rescaled prices could materially distort returns. Format displayed values instead of altering the inputs.

Calculating Returns, Volatility, and Portfolio Metrics ​

Compute daily simple returns ri,t=Pi,t/Pi,t−1−1. There is no return before the first price, leaving 3,518 observed daily returns. Use a common observation sample for all assets when estimating the covariance matrix.

With p=252 trading observations per year, we use annualized arithmetic moments:

μ^ann=pr¯daily,Σ^ann=pΣ^daily,σ^port,ann=pwTΣ^dailyw.

These are conventional annualized moments, not the realized compounded return and variance of a one-year holding period. The risk scaling assumes stable period variance and zero serial covariances. Expected-return estimation is particularly uncertain: historical arithmetic means are inputs to this exercise, not known future returns.

To visualize the opportunity set, generate 4,000 random allocations with non-negative weights summing to one. A Dirichlet distribution with all parameters equal to one samples uniformly over this simplex. This samples portfolio weights, not future market paths. A fixed seed makes the comparison reproducible.

python
import PortfolioOptimizationKit as pok
import pandas as pd
import numpy as np

# Run the price-data example first.
daily_returns = pok.compute_returns(stocks).dropna()
periods_per_year = 252
annual_returns = daily_returns.mean() * periods_per_year
cov_matrix = daily_returns.cov()  # Covariance of daily returns, using n-1.
risk_free_rate = 0.0

asset_estimates = pd.DataFrame({
    "Estimated annual mean (%)": annual_returns * 100,
    "Annualized volatility (%)": pok.annualize_vol(daily_returns, periods_per_year) * 100,
    "Historical CAGR (%)": pok.annualize_rets(daily_returns, periods_per_year) * 100,
})
print(asset_estimates.to_string(float_format=lambda value: f"{value:.2f}"))

num_portfolios = 4000
n_assets = stocks.shape[1]
rng = np.random.default_rng(42)
weight_samples = rng.dirichlet(np.ones(n_assets), size=num_portfolios)
portfolio_metrics = []
for weights in weight_samples:
    portfolio_return = pok.portfolio_return(weights, annual_returns)
    portfolio_volatility = pok.annualize_vol(
        pok.portfolio_volatility(weights, cov_matrix), periods_per_year
    )
    sharpe_ratio = (portfolio_return - risk_free_rate) / portfolio_volatility
    portfolio_metrics.append({
        "return": portfolio_return,
        "volatility": portfolio_volatility,
        "sharpe": sharpe_ratio,
    })

portfolios_df = pd.DataFrame(portfolio_metrics)
weights_df = pd.DataFrame(weight_samples, columns=stocks.columns)
print("\nFirst five allocations and metrics (weights and returns as decimals):")
print(pd.concat([weights_df.head(), portfolios_df.head()], axis=1).to_string(
    float_format=lambda value: f"{value:.4f}"
))

The estimated annual arithmetic means are approximately 28.20% for AMZN, 9.33% for KO, and 24.61% for MSFT. Their historical CAGRs are different and are shown separately. Portfolio construction uses the arithmetic estimates, not the CAGR column.

We use a zero risk-free benchmark in this first comparison, so each estimated Sharpe ratio is annualized mean return divided by annualized volatility. A random sample can reveal the opportunity set, but its best point is only the best allocation among those drawn. It does not establish the mathematical optimum.

Visualizing Portfolios and the Efficient Frontier ​

Use the same estimated moments and sampled allocations to draw the efficient frontier. The toolkit first finds GMV, then solves minimum-variance problems for target expected returns from GMV's return up to the largest expected asset return. This selects the upper, non-dominated boundary rather than connecting the best random points.

Run the price and portfolio-metric examples first. Both chart axes are displayed in percent; the underlying calculations remain in decimal units.

python
import PortfolioOptimizationKit as pok
import pandas as pd
import numpy as np
import json

df_frontier = pok.efficient_frontier(
    50, daily_returns, cov_matrix, periods_per_year, risk_free_rate=risk_free_rate
)
scatter_data = portfolios_df[["volatility", "return", "sharpe"]].to_numpy().copy()
scatter_data[:, :2] *= 100
frontier_data = (df_frontier[["volatility", "return"]] * 100).values.tolist()
plot_data = {
    "stockFrontier": {
        "type": "scatter",
        "title": "Estimated Long-Only Efficient Frontier",
        "yAxisName": "Estimated annual return (%)",
        "xAxis": {"type": "value", "name": "Annualized volatility (%)", "min": 0},
        "yAxis": {"type": "value", "name": "Estimated annual return (%)", "scale": True},
        "visualMap": {
            "dimension": 2,
            "seriesIndex": 0,
            "min": float(portfolios_df["sharpe"].min()),
            "max": float(portfolios_df["sharpe"].max()),
            "inRange": {"color": ["#0000FF", "#00FF00"]},
        },
        "series": {"Portfolios": scatter_data.tolist(), "Frontier": frontier_data},
    },
}
print("\n<ECHARTS_DATA>" + json.dumps(plot_data, allow_nan=False))

Each point represents an allocation evaluated under the same estimated moments. The color indicates its estimated Sharpe ratio. The frontier begins at GMV and traces increasing target expected returns. The lower-return portion of the minimum-variance boundary is excluded because it is dominated.

Try it: increase num_portfolios to 10000 in the preceding cell and rerun both examples. The cloud becomes denser, but the optimized frontier is determined by the estimated moments and constraints, not by the number of random allocations.

Identifying Key Portfolios: GMV and MSR ​

Now solve explicitly for the GMV portfolio and the maximum-Sharpe (MSR) portfolio under the same long-only constraints. GMV minimizes estimated variance; MSR maximizes estimated excess return per unit of volatility. The toolkit checks solver success and constraint satisfaction before returning weights.

Run the preceding examples first. This cell reuses their estimates, sampled portfolios, and chart data, so the comparison is like-for-like.

python
import PortfolioOptimizationKit as pok
import pandas as pd
import numpy as np
import json

key_weights = {
    "GMV": pok.minimize_volatility(annual_returns, cov_matrix),
    "MSR": pok.maximize_sharpe_ratio(
        annual_returns, cov_matrix, risk_free_rate, periods_per_year
    ),
}
key_metrics = {}
for name, weights in key_weights.items():
    ret = pok.portfolio_return(weights, annual_returns)
    vol = pok.annualize_vol(pok.portfolio_volatility(weights, cov_matrix), periods_per_year)
    sharpe = (ret - risk_free_rate) / vol
    key_metrics[name] = {"return": ret, "volatility": vol, "sharpe": sharpe}
    plot_data["stockFrontier"]["series"][name] = [[float(vol * 100), float(ret * 100)]]

key_portfolios = pd.DataFrame.from_dict(key_metrics, orient="index")
print("Optimized weights (%):")
print((pd.DataFrame(key_weights, index=stocks.columns).T * 100).to_string(
    float_format=lambda value: f"{value:.2f}"
))
metrics_display = key_portfolios.copy()
metrics_display[["return", "volatility"]] *= 100
metrics_display = metrics_display.rename(columns={
    "return": "Estimated return (%)", "volatility": "Volatility (%)", "sharpe": "Sharpe"
})
print("\n" + metrics_display.to_string(float_format=lambda value: f"{value:.3f}"))
print(f"\nLowest sampled volatility: {portfolios_df['volatility'].min():.2%}")
print(f"Highest sampled Sharpe: {portfolios_df['sharpe'].max():.3f}")
print("\n<ECHARTS_DATA>" + json.dumps(plot_data, allow_nan=False))

The optimized GMV volatility should be no greater than the lowest sampled volatility, and optimized MSR should have a Sharpe ratio at least as high as the best sampled allocation, within numerical tolerance. The objective values are optimal for this estimated problem; they do not establish future performance.

The weights also reveal concentration. Inspect which stocks dominate each solution and how that relates to their estimated means, volatilities, and covariances. Try it: restrict the price history with .loc["2020":] in the loading cell and rerun the case study. Changes in the optimized weights illustrate estimation sensitivity rather than a newly proven investment opportunity.

Portfolio Feature Calculation Function ​

The toolkit already provides get_portfolio_features() to report these three metrics. As a simple benchmark, evaluate a portfolio holding one third in each stock. Supply annualized arithmetic expected returns, the daily covariance matrix, and 252 periods per year; the helper annualizes volatility once.

python
equal_weights = np.repeat(1 / n_assets, n_assets)
equal_weight_metrics = pok.get_portfolio_features(
    equal_weights, annual_returns, cov_matrix, risk_free_rate, periods_per_year
)

The equally weighted portfolio requires no expected-return optimization. Comparing it with GMV and MSR helps distinguish the benefit of the estimated allocation rule from the benefit of simply holding a diversified mix. Next we examine the optimization problems and their constraints in more detail.

Strategies for Optimizing Portfolios ​

We now examine the investment objective behind each allocation. Use the same estimates and sampled portfolios from the stock case study: annual_returns contains annualized arithmetic means, and cov_matrix contains daily covariances. In the mathematical programs below, write μ for the annual mean vector and Σa=252Σd for annualized covariance.

Run the preceding case-study cells before these examples. Reusing one estimation sample makes the comparisons meaningful; changing the sample requires recalculating the inputs and portfolios together.

Seeking Minimum Volatility ​

The global minimum-variance portfolio answers: which feasible allocation has the least estimated return variability? It solves:

minw12wTΣawsubject to1Tw=1,wi≥0.

The factor 1/2 simplifies derivatives but does not change the minimizing weights. Minimizing volatility gives the same allocation because the square root is increasing. Multiplying the entire covariance matrix by a positive annualization factor also leaves the minimizing weights unchanged.

GMV depends on covariances, not expected returns. The toolkit accepts the return vector to identify the assets and support optional return constraints, but the unconstrained GMV objective does not use its values. With a positive semidefinite covariance matrix and these linear constraints, the variance minimization problem is convex.

python
import PortfolioOptimizationKit as pok
import pandas as pd
import numpy as np
import json

gmv_weights = pok.minimize_volatility(annual_returns, cov_matrix)
gmv_ret, gmv_vol, gmv_sharpe = pok.get_portfolio_features(
    gmv_weights, annual_returns, cov_matrix, risk_free_rate, periods_per_year
)
print("\nGMV weights (%):")
print(pd.Series(gmv_weights * 100, index=stocks.columns).to_string(
    float_format=lambda value: f"{value:.2f}"
))
sample_min_vol = portfolios_df["volatility"].min()
print(f"\nLowest sampled volatility: {sample_min_vol:.4%}")
print(f"Optimized GMV volatility: {gmv_vol:.4%}")

frontier_data = (df_frontier[["volatility", "return"]] * 100).values.tolist()
strategy_plot = {
    "portfolioStrategy": {
        "type": "line",
        "title": "Global Minimum-Variance Portfolio",
        "xAxis": {"type": "value", "name": "Annualized volatility (%)", "min": 0},
        "yAxis": {"type": "value", "name": "Estimated annual return (%)", "scale": True},
        "series": {"Frontier": frontier_data, "GMV": [[gmv_vol * 100, gmv_ret * 100]]},
    },
}
print("\n<ECHARTS_DATA>" + json.dumps(strategy_plot, allow_nan=False))

For the full snapshot, GMV has estimated annual return approximately 12.96% and volatility 16.25%. A dense random sample may get close, but its minimum is only a minimum over the allocations drawn. The optimizer solves the specified covariance problem; neither result establishes future realized risk.

Try it: evaluate pok.minimize_volatility(2 * annual_returns, cov_matrix). The GMV weights stay the same because the covariance estimate has not changed. Doubling expected returns changes the reported expected performance, not the variance-minimizing allocation.

Targeting Specific Returns with Minimized Volatility ​

An investor may require a particular expected return rather than the lowest possible variance. For an exact target μ⋆, add an equality constraint:

minw12wTΣawsubject towTμ=μ⋆,1Tw=1,wi≥0.

This is still a convex variance-minimization problem. The return constraint fixes an estimated mean, not a guaranteed realized outcome. Under the current long-only, fully invested constraints, feasibility requires:

miniμi≤μ⋆≤maxiμi.

For the supplied snapshot, that range is approximately 9.33% to 28.20% annually. Run the GMV example first, then set an annual target of 16%:

python
target_return = 0.16
print(f"Feasible expected-return range: {annual_returns.min():.2%} to {annual_returns.max():.2%}")

The solver finds the least volatile portfolio meeting this target. Verify the reported estimated return and inspect the required allocation:

python
import PortfolioOptimizationKit as pok
import pandas as pd
import numpy as np
import json

target_weights = pok.minimize_volatility(annual_returns, cov_matrix, target_return)
print(f"Target estimated annual return: {target_return:.2%}")
target_ret, target_vol, target_sharpe = pok.get_portfolio_features(
    target_weights, annual_returns, cov_matrix, risk_free_rate, periods_per_year
)
print("\nTarget-return weights (%):")
print(pd.Series(target_weights * 100, index=stocks.columns).to_string(
    float_format=lambda value: f"{value:.2f}"
))

strategy_plot["portfolioStrategy"]["title"] = f"Minimum Volatility at {target_return:.0%} Expected Return"
strategy_plot["portfolioStrategy"]["series"] = {
    "Frontier": frontier_data,
    "MinVolForTarget": [[target_vol * 100, target_ret * 100]],
}
print("\n<ECHARTS_DATA>" + json.dumps(strategy_plot, allow_nan=False))

At the 16% target, estimated annual volatility is approximately 16.87%, compared with GMV's 16.25%. The higher expected return requires moving away from the minimum-risk allocation.

An exact target below GMV's expected return can be feasible but dominated. If the objective were instead at least μ⋆, the constraint would be wTμ≥μ⋆; GMV would remain optimal whenever it already met that minimum. The toolkit's target_return parameter imposes equality.

Try it: set target_return = 0.10 and rerun the solution cell. It produces a feasible portfolio with approximately 16.83% volatility, but GMV offers both higher expected return and lower risk. Then try 0.40: it exceeds the current 28.20% maximum expected asset return, so the toolkit reports an infeasible target rather than returning misleading weights.

Maximizing the Sharpe Ratio ​

GMV minimizes risk, whereas maximum Sharpe seeks the greatest estimated excess return per unit of volatility. For an annual benchmark return rf on the same reporting basis as μ:

SR(w)=wTμ−rfwTΣaw.

The numerator is expected excess return, not compounded growth. We retain the zero benchmark for the default example. With a nonzero benchmark, its annualization convention must match the arithmetic mean inputs.

The optimization is:

maxwSR(w)subject to1Tw=1,wi≥0.

The numerical solver minimizes −SR(w), which has the same solution. When positive excess return is attainable, the maximum-Sharpe portfolio lies on the efficient frontier; it need not have GMV's risk level. The ratio is undefined at zero volatility.

Compare the optimized allocation with the actual best sampled portfolio from our case study. Recalculate the sampled Sharpe ratios using the chosen benchmark so that changing it keeps the comparison consistent.

python
import PortfolioOptimizationKit as pok
import pandas as pd
import numpy as np
import json

risk_free_rate = 0.0
msr_weights = pok.maximize_sharpe_ratio(
    annual_returns, cov_matrix, risk_free_rate, periods_per_year
)
ret_msr = pok.portfolio_return(msr_weights, annual_returns)
vol_msr = pok.annualize_vol(pok.portfolio_volatility(msr_weights, cov_matrix), periods_per_year)
shp_msr = (ret_msr - risk_free_rate) / vol_msr

sample_sharpes = (portfolios_df["return"] - risk_free_rate) / portfolios_df["volatility"]
best_sample_index = sample_sharpes.idxmax()
best_sample = portfolios_df.loc[best_sample_index]
msr_comparison = pd.DataFrame({
    "Estimated return (%)": [best_sample["return"] * 100, ret_msr * 100],
    "Volatility (%)": [best_sample["volatility"] * 100, vol_msr * 100],
    "Sharpe": [sample_sharpes.loc[best_sample_index], shp_msr],
}, index=["Best sampled", "Optimized MSR"])
print(msr_comparison.to_string(float_format=lambda value: f"{value:.4f}"))
print("\nMSR weights (%):")
print(pd.Series(msr_weights * 100, index=stocks.columns).to_string(
    float_format=lambda value: f"{value:.2f}"
))

strategy_plot["portfolioStrategy"]["title"] = f"Maximum Sharpe: Benchmark {risk_free_rate:.2%}"
strategy_plot["portfolioStrategy"]["series"] = {
    "Frontier": frontier_data,
    "MSR": [[vol_msr * 100, ret_msr * 100]],
}
print("\n<ECHARTS_DATA>" + json.dumps(strategy_plot, allow_nan=False))

With the full sample and zero benchmark, MSR has estimated annual return approximately 22.14%, volatility 21.23%, and Sharpe ratio 1.0425. Its estimated risk-adjusted return exceeds GMV's, but its volatility is also higher. A higher Sharpe ratio does not by itself establish that the investor should accept that risk level.

Try it: change risk_free_rate in this cell to 0.02. Both the optimization and sampled comparison use the new benchmark. Unlike GMV, the maximum-Sharpe weights generally depend on expected returns and the benchmark as well as covariance.

Achieving Maximum Sharpe Ratio with Set Volatility ​

Suppose the investor specifies an exact positive annual volatility σ0. The additional constraint must equate variance with squared volatility:

wTΣaw=σ0⟺wTΣaw=σ02.

Variance has squared-return units: 20% annual volatility corresponds to annual variance 0.202=0.04. At a fixed positive volatility, the Sharpe denominator is constant, so maximizing Sharpe is equivalent to maximizing expected return:

maxwwTμsubject towTΣaw=σ02,1Tw=1,wi≥0.

Unlike a variance cap, an exact variance equality is generally nonconvex: different allocations can meet the same risk target. The toolkit compares solutions from multiple starting allocations and verifies their constraints; this improves the numerical search but is not a general proof of global optimality.

Under these long-only constraints, attainable volatility ranges from GMV's volatility to the largest individual asset volatility. The upper bound follows from σ(w)≤∑iwiσi≤maxiσi, and is attained by holding the most volatile asset alone.

For the current estimates, the feasible range is approximately 16.25% to 32.73% annually. Set the exact target to 20%:

python
target_volatility = 0.2
max_feasible_vol = pok.annualize_vol(daily_returns, periods_per_year).max()
print(f"Feasible annual volatility range: {gmv_vol:.2%} to {max_feasible_vol:.2%}")
python
import PortfolioOptimizationKit as pok
import pandas as pd
import numpy as np
import json

weights_at_vol = pok.maximize_sharpe_ratio(
    annual_returns, cov_matrix, risk_free_rate, periods_per_year,
    target_volatility=target_volatility,
)
ret_at_vol, vol_at_vol, sharpe_at_vol = pok.get_portfolio_features(
    weights_at_vol, annual_returns, cov_matrix, risk_free_rate, periods_per_year
)
print("\nExact-volatility weights (%):")
print(pd.Series(weights_at_vol * 100, index=stocks.columns).to_string(
    float_format=lambda value: f"{value:.2f}"
))
print(f"\nTarget-constrained Sharpe: {sharpe_at_vol:.4f}")
print(f"Unconstrained MSR Sharpe: {shp_msr:.4f}")

strategy_plot["portfolioStrategy"]["title"] = f"Best Expected Return at {target_volatility:.0%} Volatility"
strategy_plot["portfolioStrategy"]["series"] = {
    "Frontier": frontier_data,
    "MaxSharpePort": [[vol_at_vol * 100, ret_at_vol * 100]],
}
print("\n<ECHARTS_DATA>" + json.dumps(strategy_plot, allow_nan=False))

With the zero benchmark, the 20% volatility portfolio has estimated annual return approximately 20.79% and Sharpe ratio 1.0394, slightly below the unrestricted MSR ratio of 1.0425. The extra constraint reduces the available choices; it cannot improve the maximum attainable Sharpe ratio under the same inputs.

An exact target is different from a risk limit. A volatility cap would use wTΣaw≤σ02. A 25% cap would permit the 21.23%-volatility MSR portfolio, whereas an exact 25% target would force additional risk. More generally, an exact-volatility solution can be dominated if a less risky portfolio already offers at least as much expected return. It does not automatically belong to the efficient frontier.

Try it: set target_volatility = 0.15. This is below the current GMV volatility, so the toolkit rejects the target. Then try 0.25 and compare the allocation and Sharpe ratio with the unrestricted MSR solution.

Reflections on Portfolio Constraints ​

The preceding portfolios obeyed two separate restrictions: long-only holdings, wi≥0, and a fully invested risky-asset budget, 1Tw=1. Removing either changes the investment problem.

Weights are positions relative to initial equity. A negative weight represents a short position: borrowing an asset and selling it, with an obligation to return it later. Net risky exposure is ∑iwi; gross risky exposure is ∑i|wi|. Weights can sum to one while gross exposure exceeds 100% because short-sale proceeds finance additional long positions.

We retain annual expected returns μ and annual covariance Σa. For the inverse-based derivations, assume Σa is positive definite, so variance is strictly convex and the inverse exists. These idealized problems omit transaction costs, stock-borrow fees, and position or margin limits.

Short Selling & Flexible Weights: Finding Low Volatility Portfolio for a Set Return ​

First allow signed risky weights without imposing their sum. To account for the remaining capital, define a cash weight:

wc=1−1Tw.

A positive cash weight means lending or holding cash; a negative one means borrowing. Assume both earn or cost zero interest here. The total portfolio then has expected return wTμ and variance wTΣaw. Removing the risky-asset budget therefore allows both uninvested cash and leverage. At a nonzero common financing rate, expected return would instead be rf+wT(μ−rf1).

For an exact expected-return target μ⋆, solve:

minw12wTΣawsubject towTμ=μ⋆.

The Lagrangian attaches a multiplier to the return constraint:

L(w,λ)=12wTΣaw−λ(wTμ−μ⋆).

Because covariance is symmetric, the gradient of its half-quadratic form is Σaw. Setting the derivatives to zero gives:

∇wL=Σaw−λμ=0,∂L∂λ=μ⋆−wTμ=0.

Hence w=λΣa−1μ. Substitution into the return constraint yields:

λ=μ⋆μTΣa−1μ,w∗=μ⋆Σa−1μμTΣa−1μ.

For a nonzero mean vector, the denominator A=μTΣa−1μ is positive. Strict convexity makes this stationary point the unique minimum, with variance μ⋆2/A. Changing the target scales the same risky allocation direction; it does not normalize its weights to one.

Worked example. Return to the chapter's two assets, with annual means 8% and 12%, volatilities 10% and 20%, and correlation 0.25:

μ=(0.080.12),Σa=(0.010.0050.0050.04).

At a 10% target, Σa−1μ≈(6.9333,2.1333)T and A≈0.810667. The weights are approximately 85.53% and 26.32%, leaving −11.84% cash: this solution borrows to finance 111.84% net risky exposure.

The calculation below solves the linear system rather than explicitly constructing a matrix inverse. Run the chapter's imports first; this example supplies its own annual moments, distinct from the stock snapshot.

python
constraint_means = np.array([0.08, 0.12])
constraint_covariance = np.array([[0.01, 0.005], [0.005, 0.04]])
constraint_target = 0.10

return_direction = np.linalg.solve(constraint_covariance, constraint_means)
A = constraint_means @ return_direction
flex_weights = constraint_target * return_direction / A
cash_weight = 1 - flex_weights.sum()
flex_return = pok.portfolio_return(flex_weights, constraint_means)
flex_vol = pok.portfolio_volatility(flex_weights, constraint_covariance)

print(pd.Series(
    np.append(flex_weights, cash_weight) * 100,
    index=["Asset 1 (%)", "Asset 2 (%)", "Cash / borrowing (%)"],
).to_string(float_format=lambda value: f"{value:.2f}"))
print(f"Estimated annual return: {flex_return:.2%}")
print(f"Annual volatility: {flex_vol:.2%}")
print(f"Net risky exposure: {flex_weights.sum():.2%}")

Annual volatility is approximately 11.11%. Neither risky position is short: allowing shorts does not require using them. The leverage here comes from the negative cash position. Try it: change constraint_target to 0.08. Risky weights scale down, and cash becomes positive at approximately 10.53%.

Short Selling & Normalized Weights: Minimum Volatility Portfolio Given a Fixed Return ​

Now restore 1Tw=1 while continuing to allow signed positions. Cash is zero, but short positions can still finance gross risky exposure above 100%. The problem is:

minw12wTΣawsubject towTμ=μ⋆,1Tw=1.

Introduce separate multipliers for expected return and budget:

L(w,λ,δ)=12wTΣaw−λ(wTμ−μ⋆)−δ(1Tw−1).

The first-order conditions are:

∇wL=Σaw−λμ−δ1=0,∂L∂λ=μ⋆−wTμ=0,∂L∂δ=1−1Tw=0.

The first condition gives w=Σa−1(λμ+δ1). Define:

A=μTΣa−1μ,B=1TΣa−1μ,C=1TΣa−11.

Substituting into the two constraints produces a two-equation system:

(ABBC)(λδ)=(μ⋆1).

When asset means are not all identical, Δ=AC−B2>0, and:

λ=Cμ⋆−BΔ,δ=A−Bμ⋆Δ.

Collecting the target-independent and target-dependent terms gives:

w∗=f+μ⋆g,f=AΣa−11−BΣa−1μΔ,g=CΣa−1μ−BΣa−11Δ.

Here 1Tf=1, μTf=0, 1Tg=0, and μTg=1. Thus changing the target adds a zero-net-investment adjustment that changes expected return. If all means equal μ0, only target μ0 is feasible; the minimum is then the fully invested GMV allocation Σa−11/C, without using the singular two-constraint formula.

Hand check. At the same 10% target, the two-asset constraints are w1+w2=1 and 0.08w1+0.12w2=0.10. They force a 50/50 allocation. Its variance is 0.52(0.01)+0.52(0.04)+2(0.5)(0.5)(0.005)=0.015, giving 12.25% annual volatility.

Restore constraint_target = 0.10 in the preceding cell and run it before this one. Both solutions then use the same target and moments; only the risky-asset budget restriction changes.

python
ones = np.ones(len(constraint_means))
budget_direction = np.linalg.solve(constraint_covariance, ones)
B = ones @ return_direction
C = ones @ budget_direction
determinant = A * C - B ** 2
f = (A * budget_direction - B * return_direction) / determinant
g = (C * return_direction - B * budget_direction) / determinant
budget_weights = f + constraint_target * g
budget_return = pok.portfolio_return(budget_weights, constraint_means)
budget_vol = pok.portfolio_volatility(budget_weights, constraint_covariance)

print(pd.Series(budget_weights * 100, index=["Asset 1 (%)", "Asset 2 (%)"])
      .to_string(float_format=lambda value: f"{value:.2f}"))
print(f"Estimated annual return: {budget_return:.2%}")
print(f"Annual volatility: {budget_vol:.2%}")
print(f"Net risky exposure: {budget_weights.sum():.2%}")
print(f"Gross risky exposure: {np.abs(budget_weights).sum():.2%}")

The added budget equality raises minimum volatility from 11.11% to 12.25% at this target. More generally, adding a restriction cannot lower the minimum variance when the remaining inputs and constraints are identical. Conversely, allowing shorts cannot increase the minimum relative to a feasible long-only solution with the same budget and target.

Try it: change constraint_target to 0.14 in the first cell and rerun both. The fully invested solution becomes −50% in Asset 1 and 150% in Asset 2, with 200% gross risky exposure and approximately 29.15% volatility. A 14% mean is unattainable by long-only combinations of assets with means 8% and 12%; short selling expands feasibility, accompanied here by substantially higher risk.

Finally, minimum variance at an exact return is not automatically mean–variance efficient. This signed-weight boundary has variance (Cμ⋆2−2Bμ⋆+A)/Δ and reaches GMV at return B/C. Targets below that GMV return lie on the dominated branch. In the two-asset example, GMV's mean is 8.5%, so the 10% and 14% examples are on the upper branch.

Optimizing the Sharpe Ratio Portfolio with a Non-Zero Risk-Free Rate ​

A risk-free asset has a known return over the modeled holding period. Its variance and covariance with risky returns are zero; its correlation with them is undefined because its standard deviation is zero. A Treasury bill held to maturity can approximate a nominal risk-free payoff over a matching horizon, whereas a bond fund's market value fluctuates.

Continue with the stock case study's annualized arithmetic means and daily covariance. To use an effective annual cash rate qf consistently with these daily observations, convert it first:

rf,d=(1+qf)1/252−1,rf=252rf,d.

For a quoted 6% effective annual rate, the annualized arithmetic cash mean is approximately 5.8276%. Below, rf denotes this model input. As in the case study, the coordinates are annualized one-period means and volatilities, not compounded annual growth forecasts. Assume cash can be lent or borrowed at the same fixed rate when leverage is permitted.

Capital Market Line (CML) Essentials ​

Hold a fraction a of initial equity in a fully invested risky portfolio T, with mean μT and volatility σT>0, and the remainder in cash. At the daily observation level, its return is Rc,d=(1−a)rf,d+aRT,d. Cash has zero variance; annualizing the mean and variance as before gives:

μc=rf+a(μT−rf),σc2=a2σT2,σc=|a|σT.

For a≥0, eliminate a=σc/σT to obtain a capital allocation line (CAL):

μc=rf+μT−rfσTσc.

The intercept is the cash return and the slope is the risky fund's Sharpe ratio. When positive excess return is attainable, choosing the feasible maximum-Sharpe fund gives the steepest line. This is the tangency construction; under long-only constraints, the optimal fund can lie on a boundary with some asset weights equal to zero.

Strictly, the Capital Market Line (CML) is the CAL through the market portfolio in CAPM equilibrium. Our three-stock estimated tangency portfolio is not an established market portfolio, so the examples label the plotted lines CAL.

  • a=0: all cash; volatility is zero and the Sharpe ratio is undefined.
  • 0<a<1: lend part of the capital and invest the remainder in the risky fund.
  • a=1: hold only the risky fund.
  • a>1: borrow cash to increase risky exposure. This part of the line requires borrowing at the assumed rate.

For a<0, the risky fund is shorted: volatility remains nonnegative, but a positive fund excess return becomes negative portfolio excess return. That allocation lies below the cash return, not on the upper CAL.

Hand check. If cash earns 2%, a risky fund has mean 10% and volatility 20%, and a=0.5, the combined mean is 2%+0.5(10%−2%)=6% and volatility is 10%. Changing a changes total risk, while the Sharpe ratio stays (10%−2%)/20%=0.4 for positive a.

Run the stock case study through the equally weighted benchmark first. This cell reuses its estimates and frontier, finds the long-only risky tangency fund, and invests half the capital in that fund:

python
import PortfolioOptimizationKit as pok
import pandas as pd
import numpy as np
import json

quoted_cash_rate = 0.06
risky_fraction = 0.5
cash_period_rate = np.expm1(np.log1p(quoted_cash_rate) / periods_per_year)
cml_rate = periods_per_year * cash_period_rate

tangent_weights = pok.maximize_sharpe_ratio(
    annual_returns, cov_matrix, cml_rate, periods_per_year
)
tangent_return = pok.portfolio_return(tangent_weights, annual_returns)
tangent_vol = pok.annualize_vol(pok.portfolio_volatility(tangent_weights, cov_matrix), periods_per_year)
tangent_sharpe = (tangent_return - cml_rate) / tangent_vol
combined_weights = risky_fraction * tangent_weights
combined_cash = 1 - risky_fraction
combined_return = cml_rate + risky_fraction * (tangent_return - cml_rate)
combined_vol = abs(risky_fraction) * tangent_vol

print(f"Effective annual cash rate: {quoted_cash_rate:.2%}")
print(f"Annualized arithmetic cash mean: {cml_rate:.4%}")
print(pd.DataFrame({
    "Tangency (%)": np.append(tangent_weights, 0.0) * 100,
    "Combined (%)": np.append(combined_weights, combined_cash) * 100,
}, index=list(stocks.columns) + ["Cash"]).to_string(float_format=lambda value: f"{value:.2f}"))
print(f"Tangency mean: {tangent_return:.2%}; volatility: {tangent_vol:.2%}; Sharpe: {tangent_sharpe:.4f}")
print(f"Combined mean: {combined_return:.2%}; volatility: {combined_vol:.2%}")

line_end = max(1.0, risky_fraction)
capital_line = [[0.0, cml_rate * 100],
                [line_end * tangent_vol * 100,
                 (cml_rate + line_end * (tangent_return - cml_rate)) * 100]]
allocation_plot = {
    "cml_plot": {
        "type": "line",
        "title": "Estimated Tangency Line: Long-Only Risky Assets",
        "xAxis": {"type": "value", "name": "Annualized volatility (%)", "min": 0},
        "yAxis": {"type": "value", "name": "Estimated annual return (%)", "scale": True},
        "series": {
            "Frontier": (df_frontier[["volatility", "return"]] * 100).values.tolist(),
            "CAL": capital_line,
            "MSR": [[tangent_vol * 100, tangent_return * 100]],
            "Portfolios": [[combined_vol * 100, combined_return * 100]],
        },
    },
}
print("\n<ECHARTS_DATA>" + json.dumps(allocation_plot, allow_nan=False))

For the full snapshot, the long-only tangency fund holds approximately 38.41% AMZN, 0% KO, and 61.59% MSFT. Its estimated mean is 25.99%, volatility 25.32%, and Sharpe ratio 0.7964. A half-cash allocation has mean 15.91% and volatility 12.66%. The Portfolios point marks that selected allocation.

Try it: set risky_fraction to 0, 1, and 1.25. The last choice means borrowing 25% of equity; the chart extends the CAL accordingly. With borrowing prohibited, only the segment from cash to the tangency fund is available through this mix. Higher-risk efficient choices may instead remain on the risky-asset frontier.

Next compare MSR, GMV, and equal weighting at the same cash rate. Reuse the preceding cell and the stock case study's GMV and equal-weight metrics. GMV and equal weighting are useful benchmarks; equal weighting generally lies inside the feasible region rather than on its efficient boundary.

python
import PortfolioOptimizationKit as pok
import pandas as pd
import numpy as np
import json

cml_comparison = pd.DataFrame([
    [tangent_return, tangent_vol],
    key_portfolios.loc["GMV", ["return", "volatility"]].tolist(),
    list(equal_weight_metrics[:2]),
], index=["MSR", "GMV", "EWP"], columns=["return", "volatility"])
cml_comparison["sharpe"] = (cml_comparison["return"] - cml_rate) / cml_comparison["volatility"]
cml_display = cml_comparison.rename(columns={
    "return": "Estimated return (%)", "volatility": "Volatility (%)", "sharpe": "Sharpe"
}).copy()
cml_display[["Estimated return (%)", "Volatility (%)"]] *= 100
print(cml_display.to_string(float_format=lambda value: f"{value:.4f}"))

gmv_risk_fraction = cml_comparison.loc["GMV", "volatility"] / tangent_vol
cal_return_at_gmv_risk = cml_rate + gmv_risk_fraction * (tangent_return - cml_rate)
print(f"\nCAL mean at GMV's volatility: {cal_return_at_gmv_risk:.2%}")
print(f"Required fraction in tangency fund: {gmv_risk_fraction:.2%}")

allocation_plot["cml_plot"]["title"] = "One Cash Rate: MSR, GMV, and Equal Weighting"
allocation_plot["cml_plot"]["series"] = {
    "Frontier": (df_frontier[["volatility", "return"]] * 100).values.tolist(),
    "CAL": capital_line,
    **{name: [[row["volatility"] * 100, row["return"] * 100]]
       for name, row in cml_comparison.iterrows()},
}
print("\n<ECHARTS_DATA>" + json.dumps(allocation_plot, allow_nan=False))

The cash-plus-tangency mix offers a higher estimated mean than GMV at GMV's volatility for these inputs. This is a comparison at equal modeled risk, not simply a comparison of two funds with different risk levels. At the tangency point, the line and risky frontier coincide. The improvement elsewhere depends on the cash and borrowing opportunities actually allowed.

Try it: change quoted_cash_rate to 0.02 in the first cell and rerun both. The risky frontier's return-volatility coordinates stay fixed because asset means and covariance have not changed. The cash intercept, Sharpe ratios, and tangency weights change.

Maximizing the Sharpe Ratio Portfolio with a Non-Zero Risk-Free Asset ​

Now derive the solution when short sales of risky assets are allowed, with unrestricted cash lending or borrowing at the same rate. This differs from the long-only risky fund above. Assume annual covariance Σa is positive definite, and define the expected excess-return vector:

e=μ−rf1.

With cash weight wc=1−1Tw, total expected return is rf+wTe. An exact target μ⋆ therefore gives:

minw12wTΣawsubject towTe=μ⋆−rf.

This is the earlier flexible-risky-exposure problem expressed in excess returns. Cash completes the total budget; the risky weights alone need not sum to one. Its Lagrangian and first-order conditions are:

L(w,λ)=12wTΣaw−λ(wTe−(μ⋆−rf)),∇wL=Σaw−λe=0,∂L∂λ=μ⋆−rf−wTe=0.

The first equation gives w=λΣa−1e. For e≠0, define the positive scalar Q=eTΣa−1e. Substituting into the constraint yields:

λ=μ⋆−rfQ,w∗=μ⋆−rfQΣa−1e,wc∗=1−1Tw∗.

Strict convexity makes this the unique minimum-variance solution for the specified target. Q is a covariance-adjusted measure of expected excess returns, not the variance of the excess-return vector. If every expected excess return is zero, only the cash mean is attainable, and all cash minimizes variance.

Portfolio Return & Volatility ​

The constraint guarantees the estimated mean, not the realized return. Substitution verifies both moments:

μp=rf+(w∗)Te=rf+μ⋆−rfQQ=μ⋆,σp2=(w∗)TΣaw∗=(μ⋆−rf)2Q,σp=|μ⋆−rf|Q.

The absolute value is essential: volatility cannot be negative. Above the cash mean, these portfolios have Sharpe ratio Q and form the upper line μp=rf+Qσp. At the cash mean, the solution is all cash with zero volatility. Below it, the exact-target solution has negative excess return and is dominated by cash, even though it minimizes variance at that target.

Portfolio Weights for Full Allocation to Risky Assets (Maximum Sharpe Ratio) ​

Let D=1TΣa−1e. If D>0, the positive-excess risky direction can be normalized into a fully invested tangency fund:

wT=Σa−1eD,μT=rf+QD,σT=Q|D|.

Its Sharpe ratio is Q when D>0. The general covariance inequality (wTe)2≤(wTΣaw)Q bounds every squared Sharpe ratio by Q, and this direction attains the positive bound. The fund maximizes Sharpe; it is not generally GMV. If D=0, normalization is impossible; if D<0, it produces a negative-excess fund rather than an upper-line tangency fund. The original cash-plus-risky target formula still applies when Q>0.

For D>0, the total risky fraction for an above-cash target is a=(μ⋆−rf)/(μT−rf), and w∗=awT. This separates the risky fund's composition from the investor's chosen total risk exposure.

The analytical fund can contain negative weights. It matches the earlier long-only maximum-Sharpe solution only when its weights are also feasible under that restriction. Under the default cash rate, it shorts KO, so the two solutions should differ.

Run the first CAL cell before this example. We retain its converted cash rate and stock estimates, use annual covariance throughout, and target a 13% annualized arithmetic mean with signed risky positions:

python
import PortfolioOptimizationKit as pok
import pandas as pd
import numpy as np
import json

signed_target = 0.13
annual_covariance = cov_matrix.to_numpy() * periods_per_year
excess_means = annual_returns.to_numpy() - cml_rate
signed_direction = np.linalg.solve(annual_covariance, excess_means)
excess_quadratic = excess_means @ signed_direction
signed_tangent_weights = signed_direction / signed_direction.sum()
signed_tangent_return = pok.portfolio_return(signed_tangent_weights, annual_returns)
signed_tangent_vol = pok.portfolio_volatility(signed_tangent_weights, annual_covariance)
signed_tangent_sharpe = (signed_tangent_return - cml_rate) / signed_tangent_vol

signed_weights = (signed_target - cml_rate) * signed_direction / excess_quadratic
signed_cash = 1 - signed_weights.sum()
signed_return = cml_rate + signed_weights @ excess_means
signed_vol = pok.portfolio_volatility(signed_weights, annual_covariance)

print(pd.DataFrame({
    "Long-only tangency (%)": tangent_weights * 100,
    "Signed tangency (%)": signed_tangent_weights * 100,
}, index=stocks.columns).to_string(float_format=lambda value: f"{value:.2f}"))
print(f"Tangency Sharpe: long-only {tangent_sharpe:.4f}; signed {signed_tangent_sharpe:.4f}")
print("\nTarget allocation (%):")
print(pd.Series(np.append(signed_weights, signed_cash) * 100,
                index=list(stocks.columns) + ["Cash"])
      .to_string(float_format=lambda value: f"{value:.2f}"))
print(f"Target mean: {signed_return:.2%}; volatility: {signed_vol:.2%}")

line_max_vol = max(signed_tangent_vol, signed_vol)
signed_plot = {
    "cml_chart": {
        "type": "line",
        "title": "Tangency Lines: Different Risky-Asset Constraints",
        "xAxis": {"type": "value", "name": "Annualized volatility (%)", "min": 0},
        "yAxis": {"type": "value", "name": "Estimated annual return (%)", "scale": True},
        "series": {
            "Long-only CAL": [[0.0, cml_rate * 100],
                              [line_max_vol * 100, (cml_rate + tangent_sharpe * line_max_vol) * 100]],
            "Signed CAL": [[0.0, cml_rate * 100],
                           [line_max_vol * 100, (cml_rate + np.sqrt(excess_quadratic) * line_max_vol) * 100]],
            "MSR": [[signed_tangent_vol * 100, signed_tangent_return * 100]],
            "MinVolForTarget": [[signed_vol * 100, signed_return * 100]],
        },
    },
}
print("\n<ECHARTS_DATA>" + json.dumps(signed_plot, allow_nan=False))

With the default inputs, the signed tangency fund holds approximately 44.82% AMZN, −22.37% KO, and 77.55% MSFT. Its Sharpe ratio is 0.8020, compared with 0.7964 under the long-only restriction. The 13% target allocation holds approximately 13.50% AMZN, −6.74% KO, 23.36% MSFT, and 69.88% cash, with 8.94% volatility. Here MSR marks the signed tangency fund and MinVolForTarget marks the cash-plus-risky target allocation. Both plotted rays assume borrowing is available where needed.

Try it: set signed_target = cml_rate; the target allocation becomes all cash. Then try 0.04: the target is below the cash mean, so the minimum-variance point lies below the upper line while its volatility stays positive. Finally, change quoted_cash_rate to 0.02 in the first CAL cell and rerun the examples. For this sample, the analytical tangency weights then become nonnegative and agree with the long-only solution within numerical tolerance.

The chapter's central distinction is between choosing the risky fund and choosing its scale. Expected means, covariance, the cash rate, and risky-asset constraints determine the fund; lending or borrowing changes total exposure. All of these conclusions describe the estimated model, whose inputs and financing assumptions must match the investment problem.

After earning certification from EDHEC Business School, I translated complex financial theories into practical Python modules, openly shared under the MIT License.