Portfolio optimization is a balance between maximizing returns and minimizing risk.

While it might sound easy, it’s actually very difficult compare investment strategies.

First, we have to accurately forecast future returns and risk.

Then, we have to use tricky optimization models to build the portfolios subject to our constraints.

Not to mention come up with a strategy that works!

Most non-professionals take a naive approach to building portfolios by dollar-weighting. That might work but there are more profitable ways.

It’s the process of building portfolio weights that we’ll discuss in today’s newsletter.

And lucky for us, it only takes a few lines of Python code.

Let’s go!

## Easily compare investment strategies

Modern portfolio theory seeks to maximize returns while minimizing risk. But it has significant limitations. Most notably under performing simple allocation models.

Over time, methods have been introduced to address the issues of mean-variance optimization. These include covariance shrinkage, regularization, using different risk measures, among others.

skfolio addresses the sprawling list of techniques used to optimize portfolios. It’s a library for portfolio optimization built on top of scikit-learn. It lets us easily build, fine-tune, and cross-validate portfolio models.

By reading today’s newsletter, you’ll be able to use skfolio to build three different of portfolios and compare their performance.

### Imports and set up

We’ll use scikit-learn for creating data splits, skfolio for optimizing the portfolios, and OpenBB for data. We’ll do our analysis on a list of sector-based ETFs.

from plotly.io import show from sklearn.model_selection import train_test_split from skfolio import Population from skfolio.optimization import ( EqualWeighted, MaximumDiversification, Random ) from skfolio.preprocessing import prices_to_returns from openbb import obb sectors = [ "XLE", "XLF", "XLU", "XLI", "GDX", "XLK", "XLV", "XLY", "XLP", "XLB", "XOP", "IYR", "XHB", "ITB", "VNQ", "GDXJ", "IYE", "OIH", "XME", "XRT", "SMH", "IBB", "KBE", "KRE", "XTL", ]

Download the historic price data, manipulate the DataFrame to use with skfolio, and split the data into training and testing sets.

df = obb.equity.price.historical( sectors, start_date="2010-01-01", provider="yfinance" ).to_df() pivoted = df.pivot( columns="symbol", values="close" ).dropna() X = prices_to_returns(pivoted) X_train, X_test = train_test_split( X, test_size=0.33, shuffle=False )

First, we fetch historical price data for the ETFs from the start of 2010. Then we pivot the DataFrame so each column represents a different symbol with their closing prices. We use the skfolio helper function to convert these prices into returns and save 33% of the data for testing.

### Build the models

The next step is to fit different models to the data. We’ll use skfolio to create three separate portfolios: maximum diversification, equal weighted, and random weighted.

model = MaximumDiversification() model.fit(X_train) ptf_model_train = model.predict(X_train) bench = EqualWeighted() bench.fit(X_train) ptf_bench_train = bench.predict(X_train) random = Random() random.fit(X_train) ptf_random_train = random.predict(X_train) print(f"Maximum Diversification: {ptf_model_train.diversification:0.2f}") print(f"Equal Weighted model: {ptf_bench_train.diversification:0.2f}") print(f"Random Weighted model: {ptf_random_train.diversification:0.2f}")

For each of the models, we instantiate the skfolio class using the training data. Then we fit the data and create the predictions. Finally, we display the weighted average of volatility for each asset divided by the portfolio volatility to compare diversification of the portfolios. As we expect, the maximum diversification portfolio has the highest diversification.

### Predict the portfolio weights

We can use skfolio to predict the portfolio weights for each of the weighting methods.

ptf_model_test = model.predict(X_test) ptf_bench_test = bench.predict(X_test) ptf_random_test = random.predict(X_test) population = Population([ ptf_model_test, ptf_bench_test, ptf_random_test ]) population.plot_composition()

The result is a visualization of the weights of the sector ETFs for each of the portfolios.

Generate the cumulative returns of each strategy to visualize how they performed over the analysis period.

population.plot_cumulative_returns()

The result is a chart that resembles the following.

It’s interesting to note the portfolio with the maximum diversification underperforms both the equally weighted and randomly weighted portfolios. You might conclude that being heavily weighted in XLU (utilities) was a drag on the overall performance of the strategy.

Finally, we can generate a full summary of the strategies we created.

population.summary()

The result is a DataFrame with 47 risk metrics for each of the three portfolios.

### Next steps

While skfolio presents a the cumulative returns of each portfolio, we need to apply periodic rebalancing to better represent a real investment strategy. As a next step, plug skfolio into your favorite backtesting library and rebalance every month. How do the returns change?