Maximize your Sharpe with a powerful optimization

August 31, 2024
Facebook logo.
Twitter logo.
LinkedIn logo.
Get this code in Google Colab

Maximize your Sharpe with a powerful optimization

When building a portfolio of stocks, most people allocate their money equally to each asset. That’s a great way to start, but professionals do it differently. They typically use sophisticated optimization routines to find the weights that maximize some return metric and minimize some risk metric.

https://x.com/pyquantnews/status/1829129536153235642

Those techniques are usually out of reach for non-professionals.

Riskfolio makes it easy to build robust portfolios using sophisticated optimization. This can give non-professionals an edge since the assets are weighted to minimize risk. These techniques are not perfect, but they are a great way to set up winning portfolios.

By reading today’s newsletter, you’ll build a portfolio which optimizes the Sharpe ratio based on a set of market factors.

Let’s go!

Maximize your Sharpe with a powerful optimization

Portfolio optimization aims to maximize returns while managing risk. Integrating risk factors and principal components regression (PCR) offers a sophisticated approach. Risk factors are key in understanding asset returns and constructing robust portfolios. These can include market risk, size risk, value risk, and momentum risk.

In fact, these factors explain most returns in most portfolios.

In practice, PCR combines principal component analysis (PCA) with linear regression. PCA reduces correlated variables into uncorrelated principal components, capturing the maximum variance. These components are then used as predictors in regression models.

Professionals use this method to optimize portfolios. They gather historical return data, apply PCA, and perform regression analysis. This process helps to predict asset returns and construct portfolios with optimized weights.

Let's see how it works with Python.

Imports and set up

We will start by loading some financial data for the “Mag 7” stocks as well as ETFs that track factors.

1import yfinance as yf
2import riskfolio as rf
3import pandas as pd
4import warnings
5pd.options.display.float_format = "{:.4%}".format
6warnings.filterwarnings("ignore")
7
8mag_7 = [
9    "AMZN",
10    "AAPL",
11    "NVDA",
12    "META",
13    "TSLA",
14    "MSFT",
15    "GOOG",
16]
17
18factors = ["MTUM", "QUAL", "VLUE", "SIZE", "USMV"]

Next we’ll download the data using yFinance.

1start = "2020-01-01"
2end = "2024-07-31"
3
4port_returns = (
5    yf
6    .download(
7        mag_7, 
8        start=start, 
9        end=end
10    )["Adj Close"]
11    .pct_change()
12    .dropna()
13)
14
15factor_returns = (
16    yf
17    .download(
18        factors, 
19        start=start, 
20        end=end
21    )["Adj Close"]
22    .pct_change()
23    .dropna()
24)

We use the yfinance library to download historical price data for our assets. We specify the time period for the data. After downloading, we calculate daily returns from the adjusted closing prices and remove any missing values.

Set up the Riskfolio portfolio optimization model

We’ll use Riskfolio’s portfolio optimization routines to find the portfolio that maximizes the Sharpe ratio.

1port = rf.Portfolio(returns=port_returns)
2
3port.assets_stats(method_mu="hist", method_cov="ledoit")
4
5port.lowerret = 0.00056488 * 1.5
6
7loadings = rf.loadings_matrix(
8    X=factor_returns,
9    Y=port_returns, 
10    feature_selection="PCR",
11    n_components=0.95
12)

We start by creating a Portfolio object with our portfolio’s historic returns. We set the methods to compute expected returns and the covariance matrix using historical data and the Ledoit-Wolf covariance shrinkage estimator. Setting a lower return constraint will slightly overweight assets with higher volatility.

We can print out the factor loadings on each of our portfolio assets.

1loadings.style.format("{:.4f}").background_gradient(cmap='RdYlGn')

The result is the following heatmap.

Compute the optimal weights for the maximum Sharpe portfolio

To use PCA regression, we need to set the factor stats.

1port.factors = factor_returns
2port.factors_stats(
3    method_mu="hist",
4    method_cov="ledoit",
5    feature_selection="PCR",
6    dict_risk=dict(
7        n_components=0.95
8    )
9)

This code uses the factor returns to set the factor stats. We use the same methods for expected returns and computing the covariance matrix. In this example, we select the factors that explain 95% of the variance.

Finally, run the optimizer.

1w = port.optimization(
2    model="FM",
3    rm="MV",
4    obj="Sharpe",
5    hist=False,
6)

Optimize the portfolio using a factor model, variance as the risk measure, and Sharpe ratio as the objective function. The output is a pandas DataFrame of weights. You can plot them for visualization.

1ax = rf.plot_pie(
2    w=w,
3    title='Sharpe FM Mean Variance',
4    others=0.05,
5    nrow=25,
6    cmap="tab20"
7)

The result is a pie chart which depicts each of the optimal weights.

It looks like we need to be heavily weighed in NVDA based on the factor loadings to maximize the Sharpe ratio. Not surprising given it’s run up of the last few years.

Your next steps

Now that you have optimized your portfolio using principal components regression, try experimenting with different assets or time periods to see how the results change. You could also explore using more or fewer principal components to understand their impact on your portfolio optimization.

Man with glasses and a wristwatch, wearing a white shirt, looking thoughtfully at a laptop with a data screen in the background.