<a id="sphx-glr-auto-examples-mean-risk-plot-5-weight-constraints-py"></a>

<a id="weight-constraints"></a>

# Weight Constraints

This tutorial shows how to incorporate weight constraints into the
[`MeanRisk`](https://skfolio.org/generated/skfolio.optimization.MeanRisk.html.md#skfolio.optimization.MeanRisk) optimization.

We will show how to use the below parameters:
: * min_weights
  * max_weights
  * budget
  * min_budget
  * max_budget
  * max_short
  * max_long
  * linear_constraints
  * groups
  * left_inequality
  * right_inequality

<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 1990-01-02 up to 2022-12-28.
We select only 3 assets to make the example more readable, which are Apple (AAPL),
General Electric (GE) and JPMorgan (JPM):

```Python
import numpy as np
from plotly.io import show

from skfolio.datasets import load_sp500_dataset
from skfolio.optimization import MeanRisk
from skfolio.preprocessing import prices_to_returns

prices = load_sp500_dataset()
prices = prices[["AAPL", "GE", "JPM"]]

X = prices_to_returns(prices)
```

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

## Model

In this tutorial, we will use a Minimum Variance model.
By default,  [`MeanRisk`](https://skfolio.org/generated/skfolio.optimization.MeanRisk.html.md#skfolio.optimization.MeanRisk) is long only (`min_weights=0`)
and fully invested (`budget=1`). In other terms, all weights are positive and sum to
one.

```Python
model = MeanRisk()
model.fit(X)
print(sum(model.weights_))
model.weights_
```

```none
1.0

array([0.22768876, 0.56566507, 0.20664617])
```

<a id="budget"></a>

## Budget

The budget is the sum of long positions and short positions (sum of all weights).
It can be `None` or a float. `None` means that there are no budget constraints.
The default is `1.0` (fully invested).

Examples:

> * budget = 1    -> fully invested portfolio
> * budget = 0    -> market neutral portfolio
> * budget = None -> no constraints on the sum of weights

```Python
model = MeanRisk(budget=0.5)
model.fit(X)
print(sum(model.weights_))
model.weights_
```

```none
0.5000000000000001

array([0.11391513, 0.28246101, 0.10362386])
```

You can also set a constraint on the minimum and maximum budget using `min_budget`
and `max_budget`, which are the lower and upper bounds of the sum of long and short
positions (sum of all weights). The default is `None`. If provided, you must set
`budget=None`.

```Python
model = MeanRisk(budget=None, min_budget=0.3, max_budget=0.5)
model.fit(X)
print(sum(model.weights_))
model.weights_
```

```none
0.3000003461791651

array([0.06832987, 0.16956647, 0.06210401])
```

<a id="lower-and-upper-bounds-on-weights"></a>

## Lower and Upper Bounds on Weights

The weights lower and upper bounds are controlled by the parameters `min_weights` and
`max_weights` respectively.
You can provide `None`, a float, an array-like or a dictionary.
`None` is equivalent to `-np.Inf` (no lower bounds).
If a float is provided, it is applied to each asset.
If a dictionary is provided, its (key/value) pair must be the (asset name/asset
weight bound) and the input `X` of the `fit` method must be a DataFrame with the
assets names in columns.
The default values are `min_weights=0.0` (no short selling) and `max_weights=1.0`
(each asset is below 100%). When using a dictionary, you don’t have to provide
constraints for all assets. If not provided, the default values (0.0 for min_weights
and 1.0 for max_weights) will be assigned to the assets not specified in the
dictionary.

#### NOTE
When incorporating a pre-selection transformer into a Pipeline, using a list for
weight constraints is not feasible, as we don’t know in advance which assets will
be selected by the pre-selection process. This is where the dictionary proves
useful.

Example:
: * min_weights = 0                     -> long only portfolio (no short selling).
  * min_weights = None                  -> no lower bound (same as -np.Inf).
  * min_weights = -2                    -> each weight must be above -200%.
  * min_weights = [0, -2, 0.5]          -> “AAPL”, “GE” and “JPM” must be above 0%, -200% and 50% respectively.
  * min_weights = {“AAPL”: 0, “GE”: -2} -> “AAPL”, “GE” and “JPM”  must be above 0%, -200% and 0% (default) respectively.
  * max_weights = 0                     -> no long position (short only portfolio).
  * max_weights = None                  -> no upper bound (same as +np.Inf).
  * max_weights = 2                     -> each weight must be below 200%.
  * max_weights = [1, 2, -0.5]          -> “AAPL”, “GE” and “JPM”  must be below 100%, 200% and -50% respectively.
  * max_weights = {“AAPL”: 1, “GE”: 2}  -> “AAPL”, “GE” and “JPM”  must be below 100%, 200% and 100% (default).

Let’s create a model that allows short positions with a budget of -100%:

```Python
model = MeanRisk(budget=-1, min_weights=-1)
model.fit(X)
print(sum(model.weights_))
model.weights_
```

```none
-1.0

array([-0.22770271, -0.56559255, -0.20670474])
```

Let’s add weight constraints on “AAPL”, “GE” and “JPM” to be above 0%, 50% and 10%
respectively:

```Python
model = MeanRisk(min_weights=[0, 0.5, 0.1])
model.fit(X)
print(sum(model.weights_))
model.weights_
```

```none
0.9999999999999998

array([0.22788246, 0.56548525, 0.20663228])
```

Let’s plot the composition:

```Python
portfolio = model.predict(X)
fig = portfolio.plot_composition()
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>

<br/>

Let’s create the same model as above but using partial dictionary:

```Python
model = MeanRisk(min_weights={"GE": 0.5, "JPM": 0.1})
model.fit(X)
print(sum(model.weights_))
model.weights_
```

```none
0.9999999999999998

array([0.22788246, 0.56548525, 0.20663228])
```

Let’s create a model with a leverage of 3 and every weights below 150%:

```Python
model = MeanRisk(budget=3, max_weights=1.5)
model.fit(X)
print(sum(model.weights_))
model.weights_
```

```none
3.0000000000000018

array([0.74197781, 1.49999867, 0.75802352])
```

<a id="short-and-long-position-constraints"></a>

## Short and Long Position Constraints

Constraints on the upper bound for short and long positions can be set using
`max_short` and `max_long`. The short position is defined as the sum of negative
weights (in absolute term) and the long position as the sum of positive weights.

Let’s create a fully invested long-short portfolio model with a total short position
less than 50%:

```Python
model = MeanRisk(min_weights=-1, max_short=0.5)
model.fit(X)
print(sum(model.weights_))
model.weights_
```

```none
0.9999999999999998

array([0.22770146, 0.56558315, 0.20671539])
```

<a id="group-and-linear-constraints"></a>

## Group and Linear Constraints

We can assign groups to each asset using the `groups` parameter and set
constraints on these groups using the `linear_constraint` parameter.
The `groups` parameter can be a 2D array-like or a dictionary. If a dictionary is
provided, its (key/value)  pair must be the (asset name/asset groups).
You can reference these groups and/or the asset names in `linear_constraint`, which
is a list of strings following the below patterns:

> * “2.5 \* ref1 + 0.10 \* ref2 + 0.0013 <= 2.5 \* ref3”
> * “ref1 >= 2.9 \* ref2”
> * “ref1 == ref2”
> * “ref1 >= ref1”

Let’s create a model with groups constraints on “industry sector” and
“capitalization”:

```Python
groups = {
    "AAPL": ["Technology", "Mega Cap"],
    "GE": ["Industrial", "Big Cap"],
    "JPM": ["Financial", "Big Cap"],
}
# You can also provide a 2D array-like:
# groups = [["Technology", "Industrial", "Financial"], ["Mega Cap", "Big Cap", "Big Cap"]]
linear_constraints = [
    "Technology + 1.5 * Industrial <= 2 * Financial",  # First group
    "Mega Cap >= 0.75 * Big Cap",  # Second group
    "Technology >= Big Cap",  # Mix of first and second groups
    "Mega Cap >= 2 * JPM",  # Mix of groups and assets
]
# Note that only the first constraint would be sufficient in that case.

model = MeanRisk(groups=groups, linear_constraints=linear_constraints)
model.fit(X)
model.weights_
```

```none
array([6.66666667e-01, 1.17341916e-11, 3.33333333e-01])
```

<a id="left-and-right-inequalities"></a>

## Left and Right Inequalities

Finally, you can also directly provide the matrix $A$ and the vector
$b$ of the linear constraint $A \cdot w \leq b$:

```Python
left_inequality = np.array(
    [[1.0, 1.5, -2.0], [-1.0, 0.75, 0.75], [-1.0, 1.0, 1.0], [-1.0, -0.0, 2.0]]
)
right_inequality = np.array([0.0, 0.0, 0.0, 0.0])

model = MeanRisk(left_inequality=left_inequality, right_inequality=right_inequality)
model.fit(X)
model.weights_
```

```none
array([6.66666667e-01, 1.17341916e-11, 3.33333333e-01])
```

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

<a id="sphx-glr-download-auto-examples-mean-risk-plot-5-weight-constraints-py"></a>
