<a id="sphx-glr-auto-examples-distributionally-robust-cvar-plot-1-distributionally-robust-cvar-py"></a>

<a id="distributionally-robust-cvar"></a>

# Distributionally Robust CVaR

This tutorial introduces the [`DistributionallyRobustCVaR`](https://skfolio.org/generated/skfolio.optimization.DistributionallyRobustCVaR.html.md#skfolio.optimization.DistributionallyRobustCVaR)
model.

The Distributionally Robust CVaR model constructs a Wasserstein ball in the space of
multivariate and non-discrete probability distributions centered at the uniform
distribution on the training samples, and finds the allocation that minimizes the CVaR of
the worst-case distribution within this Wasserstein ball.

Mohajerin Esfahani and Kuhn (2018) proved that for piecewise linear objective functions,
which is the case of CVaR (Rockafellar and Uryasev), the distributionally robust
optimization problem over a Wasserstein ball can be reformulated as finite convex
programs.

It’s advised to use a solver that handles a high number of constraints like `Mosek`.
For accessibility, this example uses the default open source solver `CLARABEL` and to
increase convergence speed, we only use 3 years of data.

The radius of the Wasserstein ball is controlled with the `wasserstein_ball_radius`
parameter. Increasing the radius will increase the uncertainty about the
distribution, bringing the weights closer to the equal weighted portfolio.

<a id="data"></a>

## Data

We load the S&P 500 [dataset](https://skfolio.org/user_guide/datasets.html.md#datasets) composed of the daily prices of 20
assets from the S&P 500 Index composition starting from 2020-01-02 up to 2022-12-28:

```Python
from plotly.io import show
from sklearn.model_selection import train_test_split

from skfolio import Population
from skfolio.datasets import load_sp500_dataset
from skfolio.optimization import DistributionallyRobustCVaR, EqualWeighted
from skfolio.preprocessing import prices_to_returns

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

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

<a id="model"></a>

## Model

We create four distributionally robust CVaR models with different radius then fit them
on the training set:

```Python
model1 = DistributionallyRobustCVaR(
    wasserstein_ball_radius=0.1,
    portfolio_params=dict(name="Distributionally Robust CVaR - 0.1"),
)
model1.fit(X_train)

model2 = DistributionallyRobustCVaR(
    wasserstein_ball_radius=0.01,
    portfolio_params=dict(name="Distributionally Robust CVaR - 0.01"),
)
model2.fit(X_train)

model3 = DistributionallyRobustCVaR(
    wasserstein_ball_radius=0.001,
    portfolio_params=dict(name="Distributionally Robust CVaR - 0.001"),
)
model3.fit(X_train)

model4 = DistributionallyRobustCVaR(
    wasserstein_ball_radius=0.0001,
    portfolio_params=dict(name="Distributionally Robust CVaR - 0.0001"),
)
model4.fit(X_train)
model4.weights_
```

```none
array([4.84531907e-11, 1.01910610e-10, 1.81629430e-11, 7.56967308e-11,
       6.67249461e-11, 5.07087663e-10, 3.97503410e-02, 2.42882219e-09,
       3.84348572e-11, 8.53379955e-02, 5.85983398e-02, 3.38460704e-01,
       9.62545194e-11, 7.42768380e-11, 6.90123331e-10, 6.06982611e-10,
       4.16112642e-02, 1.18803064e-09, 4.36241349e-01, 1.34914228e-10])
```

To compare the models, we use an equal weighted benchmark using
the [`EqualWeighted`](https://skfolio.org/generated/skfolio.optimization.EqualWeighted.html.md#skfolio.optimization.EqualWeighted) estimator:

```Python
bench = EqualWeighted()
bench.fit(X_train)
bench.weights_
```

```none
array([0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05,
       0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05])
```

<a id="prediction"></a>

## Prediction

We predict the models and the benchmark on the test set:

```Python
ptf_model1_test = model1.predict(X_test)
ptf_model2_test = model2.predict(X_test)
ptf_model3_test = model3.predict(X_test)
ptf_model4_test = model4.predict(X_test)
ptf_bench_test = bench.predict(X_test)
```

<a id="analysis"></a>

## Analysis

We load all predicted portfolios into a [`Population`](https://skfolio.org/generated/skfolio.population.Population.html.md#skfolio.population.Population) and
plot their compositions:

```Python
population = Population(
    [ptf_model1_test, ptf_model2_test, ptf_model3_test, ptf_model4_test, ptf_bench_test]
)
population.plot_composition()
```

<style>html[data-theme="dark"] div.output_subarea:has(.plotly-graph-div){background:#fff;border-radius:0.25rem;padding:0.5rem}@media (prefers-color-scheme: dark){html:not([data-theme="light"]) div.output_subarea:has(.plotly-graph-div){background:#fff;border-radius:0.25rem;padding:0.5rem}}</style><script>if (!window.plotlySphinxGalleryResize) {window.plotlySphinxGalleryResize = true;window.addEventListener("load", function () {document.querySelectorAll(".plotly-graph-div").forEach(function (gd) { Plotly.Plots.resize(gd); });});}</script>[plotly figure stripped from llms output]
<br />
<br />

We can see that by increasing the radius of the Wasserstein ball, the weights get
closer to the equal weighted portfolio.

Let’s plot the portfolios cumulative returns:

```Python
fig = population.plot_cumulative_returns()
show(fig)
```

[plotly figure stripped from llms output]<style>html[data-theme="dark"] div.output_subarea:has(.plotly-graph-div){background:#fff;border-radius:0.25rem;padding:0.5rem}@media (prefers-color-scheme: dark){html:not([data-theme="light"]) div.output_subarea:has(.plotly-graph-div){background:#fff;border-radius:0.25rem;padding:0.5rem}}</style><script>if (!window.plotlySphinxGalleryResize) {window.plotlySphinxGalleryResize = true;window.addEventListener("load", function () {document.querySelectorAll(".plotly-graph-div").forEach(function (gd) { Plotly.Plots.resize(gd); });});}</script>

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

<a id="sphx-glr-download-auto-examples-distributionally-robust-cvar-plot-1-distributionally-robust-cvar-py"></a>
