Mean-Variance-CDaR Surface#

This tutorial uses the MeanRisk optimization to find an ensemble of portfolios belonging to the Mean-Variance-CDaR efficient frontier.

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 2015-01-05 up to 2022-12-28:

import numpy as np
from plotly.io import show
from sklearn.model_selection import train_test_split

from skfolio import PerfMeasure, RatioMeasure, RiskMeasure
from skfolio.datasets import load_sp500_dataset
from skfolio.optimization import MeanRisk, ObjectiveFunction
from skfolio.preprocessing import prices_to_returns

prices = load_sp500_dataset()
prices = prices["2015":]

X = prices_to_returns(prices)
X_train, X_test = train_test_split(X, test_size=0.33, shuffle=False)

Model#

First, we create a Maximum Sharpe Ratio model that we fit on the training set:

model = MeanRisk(
    risk_measure=RiskMeasure.VARIANCE,
    objective_function=ObjectiveFunction.MAXIMIZE_RATIO,
)
portfolio = model.fit_predict(X_train)
print(portfolio.cdar)
0.1700238781664583

Let’s assume that we are not satisfied with the CDaR (Conditional Drawdown at Risk) of 17% corresponding to the maximum Sharpe portfolio. We want to analyze alternative portfolios that maximize the Sharpe under CDaR constraints. To have an idea of the feasible CDaR constraints, we analyze the Minimum CDaR portfolio:

model = MeanRisk(risk_measure=RiskMeasure.CDAR)
portfolio = model.fit_predict(X_train)
print(portfolio.cdar)
0.09718550832245015

The minimum CDaR is 9.72%. Now we find the pareto optimal portfolios that maximizes the Sharpe under CDaR constraint ranging from 9.72% to 17%:

model = MeanRisk(
    risk_measure=RiskMeasure.VARIANCE,
    objective_function=ObjectiveFunction.MAXIMIZE_RATIO,
    max_cdar=np.linspace(start=0.0972, stop=0.17, num=10),
)
model.fit(X_train)
print(model.weights_.shape)
(10, 20)

Analysis#

We predict this model on both the training set and the test set to analyze the deformation of the efficient frontier:

population_train = model.predict(X_train)
population_test = model.predict(X_test)

population_train.set_portfolio_params(tag="Train")
population_test.set_portfolio_params(tag="Test")

population = population_train + population_test

population.plot_measures(
    x=RiskMeasure.CDAR,
    y=RatioMeasure.ANNUALIZED_SHARPE_RATIO,
    color_scale=RatioMeasure.ANNUALIZED_SHARPE_RATIO,
    hover_measures=[RiskMeasure.MAX_DRAWDOWN, RatioMeasure.ANNUALIZED_SORTINO_RATIO],
)


Pareto Optimal Surface#

Instead of analyzing the Sharpe-CDaR efficient frontier, we can analyze the mean-Variance-CDaR pareto optimal surface:

variance_upper = population_train.max_measure(PerfMeasure.MEAN).variance
x = np.linspace(start=0.00012, stop=variance_upper, num=10)
y = np.linspace(start=0.10, stop=0.17, num=10)
x, y = map(np.ravel, np.meshgrid(x, y))

model = MeanRisk(
    objective_function=ObjectiveFunction.MAXIMIZE_RETURN,
    max_variance=x,
    max_cdar=y,
    raise_on_failure=False,
)
model.fit(X_train)

population_train = model.predict(X_train)

fig = population_train.plot_measures(
    x=RiskMeasure.ANNUALIZED_VARIANCE,
    y=RiskMeasure.CDAR,
    z=PerfMeasure.ANNUALIZED_MEAN,
    to_surface=True,
)
fig.update_layout(scene_camera=dict(eye=dict(x=-2, y=-0.5, z=1)))
show(fig)

Let’s plot the composition of the portfolios:

population_train.plot_composition()


Let’s compare the average and standard-deviation of the Sharpe Ratio and CDaR Ratio of the portfolios on the training set versus the test set:

Train:

print(population_train.measures_mean(measure=RatioMeasure.ANNUALIZED_SHARPE_RATIO))
print(population_train.measures_std(measure=RatioMeasure.ANNUALIZED_SHARPE_RATIO))
1.345087054431556
0.10292071071001885

Test:

print(population_test.measures_mean(measure=RatioMeasure.ANNUALIZED_SHARPE_RATIO))
print(population_test.measures_std(measure=RatioMeasure.ANNUALIZED_SHARPE_RATIO))
0.9038840095066056
0.06751700747046248

Total running time of the script: (0 minutes 21.463 seconds)

Gallery generated by Sphinx-Gallery