Note
Go to the end to download the full example code. or to run this example in your browser via JupyterLite or Binder
Maximum Sharpe Ratio#
This tutorial uses the MeanRisk
optimization to find the
maximum Sharpe Ratio portfolio.
Data#
We load the S&P 500 dataset composed of the daily prices of 20 assets from the S&P 500 Index composition starting from 1990-01-02 up to 2022-12-28. Prices are transformed into linear returns (see data preparation) and split into a training set and a test set without shuffling to avoid data leakage.
import numpy as np
from plotly.io import show
from sklearn.model_selection import train_test_split
from skfolio import Population, RiskMeasure
from skfolio.datasets import load_sp500_dataset
from skfolio.optimization import InverseVolatility, MeanRisk, ObjectiveFunction
from skfolio.preprocessing import prices_to_returns
prices = load_sp500_dataset()
X = prices_to_returns(prices)
X_train, X_test = train_test_split(X, test_size=0.33, shuffle=False)
print(X_train.head())
AAPL AMD BAC ... UNH WMT XOM
Date ...
1990-01-03 0.007576 -0.030303 0.008045 ... -0.019355 0.000000 -0.010079
1990-01-04 0.003759 -0.015500 -0.021355 ... -0.009868 -0.005201 -0.009933
1990-01-05 0.003745 -0.031996 -0.021821 ... -0.043189 -0.010732 -0.005267
1990-01-08 0.003731 0.000000 0.005633 ... -0.020833 0.013630 0.015381
1990-01-09 -0.007435 0.016527 0.000000 ... -0.024823 -0.026619 -0.020114
[5 rows x 20 columns]
Model#
We create a Maximum Sharpe Ratio model and then fit it on the training set.
portfolio_params
are parameters passed to the Portfolio
returned by the predict
method. It can be
omitted, here we use it to give a name to our maximum Sharpe Ration portfolio:
model = MeanRisk(
risk_measure=RiskMeasure.STANDARD_DEVIATION,
objective_function=ObjectiveFunction.MAXIMIZE_RATIO,
portfolio_params=dict(name="Max Sharpe"),
)
model.fit(X_train)
model.weights_
array([9.43840487e-02, 2.34371950e-08, 7.98221516e-09, 1.20893768e-01,
3.18425289e-02, 1.42034916e-08, 1.69401899e-04, 1.24118110e-01,
1.58750775e-08, 2.77981646e-02, 2.44878272e-08, 2.78735867e-08,
1.16363413e-01, 5.73885972e-02, 1.96119090e-07, 1.09506078e-01,
8.64772142e-02, 1.84019362e-01, 1.34686982e-02, 3.35703047e-02])
To compare this model, we use an inverse volatility benchmark using
the InverseVolatility
estimator:
benchmark = InverseVolatility(portfolio_params=dict(name="Inverse Vol"))
benchmark.fit(X_train)
benchmark.weights_
array([0.03306735, 0.02548697, 0.03551377, 0.0296872 , 0.06358463,
0.05434705, 0.04742354, 0.07049715, 0.03882539, 0.06697905,
0.05570808, 0.05576851, 0.04723274, 0.06351213, 0.05581397,
0.0676481 , 0.02564642, 0.03970752, 0.05744543, 0.06610498])
Prediction#
We predict the model and the benchmark on the test set:
pred_model = model.predict(X_test)
pred_bench = benchmark.predict(X_test)
The predict
method returns a Portfolio
object.
Portfolio
is an array-container making it compatible
with scikit-learn
tools: calling np.asarray(pred_model)
gives the portfolio
returns (same as pred_model.returns
):
np.asarray(pred_model)
array([ 0.00805141, 0.01084095, 0.00199143, ..., 0.00932287,
0.00152752, -0.01787273])
The Portfolio
class contains a vast number of properties
and methods used for analysis.
pred_model.plot_cumulative_returns()
pred_model.plot_composition()
pred_model.summary()
print(pred_model.annualized_sharpe_ratio)
print(pred_bench.annualized_sharpe_ratio)
1.0399693503172203
1.0036976120249752
Analysis#
For improved analysis, we load both predicted portfolios into a
Population
:
population = Population([pred_model, pred_bench])
The Population
class also contains a
vast number of properties and methods used for analysis.
Let’s plot each portfolio composition:
population.plot_composition()
Note
Every plot
methods in skfolio
returns a plotly
figure.
To display a plotly figure, you may need to call show()
and change the
default renderer: https://plotly.com/python/renderers/
Let’s plot each portfolio cumulative returns:
fig = population.plot_cumulative_returns()
# show(fig) is only used for the documentation sticker.
show(fig)
Finally, let’s display the full summary of both strategies evaluated on the test set:
population.summary()
Conclusion#
From the analysis on the test set, we see that the Maximum Sharpe Ratio portfolio outperform the inverse-volatility benchmark for the mean and the ratio measures including the Sharpe Ratio, and underperforms for the deviation and shortfall measures.
See also
This was a toy example, for more advanced concepts check the user guide or the other examples.
Total running time of the script: (0 minutes 2.182 seconds)