<a id="skfolio-optimization-nestedclustersoptimization"></a>

# skfolio.optimization.NestedClustersOptimization

<a id="skfolio.optimization.NestedClustersOptimization"></a>

### *class* skfolio.optimization.NestedClustersOptimization(inner_estimator=None, outer_estimator=None, distance_estimator=None, clustering_estimator=None, cv=None, quantile=0.5, quantile_measure=Sharpe Ratio, n_jobs=None, verbose=0, portfolio_params=None, fallback=None, previous_weights=None, raise_on_failure=True)

Nested Clusters Optimization estimator.

Nested Clusters Optimization (NCO) is a portfolio optimization method developed by
Marcos Lopez de Prado.

It uses a distance matrix to compute clusters using a clustering algorithm (
Hierarchical Tree Clustering, KMeans, etc.). For each cluster, the inner-cluster
weights are computed by fitting the inner-estimator on each cluster using the whole
training data. Then the outer-cluster weights are computed by training the
outer-estimator using out-of-sample estimates of the inner-estimators with
cross-validation. Finally, the final assets weights are the dot-product of the
inner-weights and outer-weights.

#### NOTE
The original paper uses KMeans as the clustering algorithm, minimum Variance for
the inner-estimator and equal-weighted for the outer-estimator. Here we
generalize it to all `sklearn` and `skfolio` clustering algorithms
(HierarchicalClustering, KMeans, etc.), all portfolio optimizations
(Mean-Variance, HRP, etc.) and risk measures (Variance, CVaR, etc.).
To avoid data leakage at the outer-estimator, we use out-of-sample estimates to
fit the outer estimator.

* **Parameters:**
  **inner_estimator** *BaseOptimization, optional*
  : [Optimization estimator](https://skfolio.org/user_guide/optimization.html.md#optimization) used to estimate the inner-weights
    (also called intra-weights) which are the assets weights inside each cluster.
    The default `None` is to use [`MeanRisk`](https://skfolio.org/generated/skfolio.optimization.MeanRisk.html.md#skfolio.optimization.MeanRisk).

  **outer_estimator** *BaseOptimization, optional*
  : [Optimization estimator](https://skfolio.org/user_guide/optimization.html.md#optimization) used to estimate the outer-weights
    (also called inter-weights) which are the weights applied to each cluster.
    The default `None` is to use [`MeanRisk`](https://skfolio.org/generated/skfolio.optimization.MeanRisk.html.md#skfolio.optimization.MeanRisk).

  **distance_estimator** *BaseDistance, optional*
  : [Distance estimator](https://skfolio.org/user_guide/distance.html.md#distance).
    The distance estimator is used to estimate the codependence and the distance
    matrix needed for the computation of the linkage matrix.
    The default (`None`) is to use [`PearsonDistance`](https://skfolio.org/generated/skfolio.distance.PearsonDistance.html.md#skfolio.distance.PearsonDistance).

  **clustering_estimator** *BaseEstimator, optional*
  : Clustering estimator. Must expose a `labels_` attribute after fitting.
    The clustering estimator is used to compute the clusters of the assets based on
    the distance matrix. The default (`None`) is to use
    [`HierarchicalClustering`](https://skfolio.org/generated/skfolio.cluster.HierarchicalClustering.html.md#skfolio.cluster.HierarchicalClustering).
    <br/>
    #### NOTE
    Clustering estimators from `sklearn` are also supported. For example:
    `sklearn.cluster.KMeans`.

  **cv** *BaseCrossValidator | BaseCombinatorialCV | int | “ignore”, optional*
  : Determines the cross-validation splitting strategy.
    The default (`None`) is to use the 5-fold cross validation `KFold()`.
    It is applied to the inner-estimators. Its out-of-sample outputs are used to
    train the outer-estimator.
    Possible inputs for `cv` are:
    > * “ignore”: no cross-validation is used (note that it will likely lead to data leakage with a high risk of overfitting)
    > * Integer, to specify the number of folds in a `sklearn.model_selection.KFold`
    > * An object to be used as a cross-validation generator
    > * An iterable yielding train, test splits
    > * A [`CombinatorialPurgedCV`](https://skfolio.org/generated/skfolio.model_selection.CombinatorialPurgedCV.html.md#skfolio.model_selection.CombinatorialPurgedCV)
    <br/>
    If a `CombinatorialCV` cross-validator is used, each cluster out-of-sample
    outputs becomes a collection of multiple paths instead of one single path. The
    selected out-of-sample path among this collection of paths is chosen according
    to the `quantile` and `quantile_measure` parameters.

  **n_jobs** *int, optional*
  : The number of jobs to run in parallel for `fit` of all `estimators`.
    The value `-1` means using all processors.
    The default (`None`) means 1 unless in a `joblib.parallel_backend` context.

  **quantile** *float, default=0.5*
  : Quantile for a given measure (`quantile_measure`) of the out-of-sample
    inner-estimator paths when the `cv` parameter is a
    [`CombinatorialPurgedCV`](https://skfolio.org/generated/skfolio.model_selection.CombinatorialPurgedCV.html.md#skfolio.model_selection.CombinatorialPurgedCV) cross-validator.
    The default value is `0.5` corresponding to the path with the median measure.
    (see `cv`)

  **quantile_measure** *PerfMeasure or RatioMeasure or RiskMeasure or ExtraRiskMeasure, default=RatioMeasure.SHARPE_RATIO*
  : Measure used for the quantile path selection (see `quantile` and `cv`).
    The default is `RatioMeasure.SHARPE_RATIO`.

  **verbose** *int, default=0*
  : The verbosity level. The default value is `0`.

  **portfolio_params** *dict, optional*
  : Portfolio parameters forwarded to the resulting `Portfolio` in `predict`.
    If not provided and if available on the estimator, the following
    attributes are propagated to the portfolio by default: `name` and
    `previous_weights`.

  **fallback** *BaseOptimization | “previous_weights” | list[BaseOptimization | “previous_weights”], optional*
  : Fallback estimator or a list of estimators to try, in order, when the primary
    optimization raises during `fit`. Alternatively, use `"previous_weights"`
    (alone or in a list) to fall back to the estimator’s `previous_weights`.
    When a fallback succeeds, its fitted `weights_` are copied back to the primary
    estimator so that `fit` still returns the original instance. For traceability,
    `fallback_` stores the successful estimator (or the string `"previous_weights"`)
    and `fallback_chain_` stores each attempt with the associated outcome.

  **previous_weights** *float | dict[str, float] | array-like of shape (n_assets,), optional*
  : When `fallback="previous_weights"`, failures will fall back to these weights
    if provided.

  **raise_on_failure** *bool, default=True*
  : Controls error handling when fitting fails.
    If True, any failure during `fit` is raised immediately, no `weights_` are
    set and subsequent calls to `predict` will raise a `NotFittedError`.
    If False, errors are not raised; instead, a warning is emitted, `weights_`
    is set to `None` and subsequent calls to `predict` will return a
    `FailedPortfolio`. When fallbacks are specified, this behavior applies only
    after all fallbacks have been exhausted.
* **Attributes:**
  **weights_** *ndarray of shape (n_assets,)*
  : Weights of the assets.

  **distance_estimator_** *BaseDistance*
  : Fitted `distance_estimator`.

  **inner_estimators_** *list[BaseOptimization]*
  : List of fitted `inner_estimator`. One per cluster for clusters containing more
    than one asset.

  **outer_estimator_** *BaseOptimization*
  : Fitted `outer_estimator`.

  **clustering_estimator_** *BaseEstimator*
  : Fitted `clustering_estimator`.

  **n_features_in_** *int*
  : Number of assets seen during `fit`.

  **feature_names_in_** *ndarray of shape (`n_features_in_`,)*
  : Names of assets seen during `fit`. Defined only when `X`
    has assets names that are all strings.

  **fallback_** *BaseOptimization | “previous_weights” | None*
  : The fallback estimator instance, or the string `"previous_weights"`, that
    produced the final result. `None` if no fallback was used.

  **fallback_chain_** *list[tuple[str, str]] | None*
  : Sequence describing the optimization fallback attempts. Each element is a
    pair `(estimator_repr, outcome)` where `estimator_repr` is the string
    representation of the primary estimator or a fallback (e.g. `"EqualWeighted()"`,
    `"previous_weights"`), and `outcome` is `"success"` if that step produced
    a valid solution, otherwise the stringified error message. For successful
    fits without any fallback, this is `None`.

  **error_** *str | list[str] | None*
  : Captured error message(s) when `fit` fails. For multi-portfolio outputs
    (`weights_` is 2D), this is a list aligned with portfolios.

### Methods

| [`fit`](#skfolio.optimization.NestedClustersOptimization.fit)(X[, y])            | Fit the Nested Clusters Optimization estimator.                                                                                 |
|-------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------|
| [`fit_predict`](#skfolio.optimization.NestedClustersOptimization.fit_predict)(X)         | Perform `fit` on `X` and returns the predicted `Portfolio` or `Population` of `Portfolio` on `X` based on the fitted `weights`. |
| [`get_metadata_routing`](#skfolio.optimization.NestedClustersOptimization.get_metadata_routing)() | Get metadata routing of this object.                                                                                            |
| [`get_params`](#skfolio.optimization.NestedClustersOptimization.get_params)([deep])     | Get parameters for this estimator.                                                                                              |
| [`predict`](#skfolio.optimization.NestedClustersOptimization.predict)(X)             | Predict the `Portfolio` or a `Population` of portfolios on `X`.                                                                 |
| [`score`](#skfolio.optimization.NestedClustersOptimization.score)(X[, y])          | Prediction score using the Sharpe Ratio.                                                                                        |
| [`set_params`](#skfolio.optimization.NestedClustersOptimization.set_params)(\*\*params) | Set the parameters of this estimator.                                                                                           |

### Notes

All estimators should specify all parameters as explicit keyword arguments in
`__init__` (no `*args` or `**kwargs`), following scikit-learn conventions.

### References

* <a id='r28b9d31cd9a7-1'>**[1]**</a> “Building diversified portfolios that outperform out of sample”, The Journal of Portfolio Management, Marcos López de Prado (2016)
* <a id='r28b9d31cd9a7-2'>**[2]**</a> “A robust estimator of the efficient frontier”, SSRN Electronic Journal, Marcos López de Prado (2019)
* <a id='r28b9d31cd9a7-3'>**[3]**</a> “Machine Learning for Asset Managers”, Elements in Quantitative Finance. Cambridge University Press, Marcos López de Prado (2020)

### Examples

For complete tutorials on nested clusters optimization, see the
[Hierarchical Clustering and NCO](https://skfolio.org/auto_examples/clustering/index.html.md#cluster-examples) gallery.

```pycon
>>> from skfolio import RiskMeasure
>>> from skfolio.datasets import load_sp500_dataset
>>> from skfolio.optimization import (
...     MeanRisk,
...     NestedClustersOptimization,
...     ObjectiveFunction,
...     RiskBudgeting,
... )
>>> from skfolio.preprocessing import prices_to_returns
>>>
>>> # Load historical prices and convert them to returns
>>> prices = load_sp500_dataset()
>>> X = prices_to_returns(prices)
>>>
>>> # Maximize the Sharpe ratio within each cluster and minimize the CVaR
>>> # computed from the out-of-sample predicted returns of the clusters
>>> inner_estimator = MeanRisk(
...     objective_function=ObjectiveFunction.MAXIMIZE_RATIO,
...     risk_measure=RiskMeasure.STANDARD_DEVIATION,
... )
>>> outer_estimator = RiskBudgeting(risk_measure=RiskMeasure.CVAR)
>>> model = NestedClustersOptimization(
...     inner_estimator=inner_estimator,
...     outer_estimator=outer_estimator,
... )
>>> model.fit(X)
NestedClustersOptimization(...)
>>> print(model.weights_)
[0.0327 0.0029 0.     ... 0.0864 0.0236 0.0736]
>>> print(model.clustering_estimator_.labels_)
[4 4 3 4 2 3 4 1 3 0 1 1 4 0 1 0 2 1 4 2]
```

<a id="skfolio.optimization.NestedClustersOptimization.fit"></a>

#### fit(X, y=None, \*\*fit_params)

Fit the Nested Clusters Optimization estimator.

* **Parameters:**
  **X** *array-like of shape (n_observations, n_assets)*
  : Price returns of the assets.

  **y** *array-like of shape (n_observations, n_targets), optional*
  : Price returns of factors or a target benchmark.
    The default is `None`.

  **\*\*fit_params** *dict*
  : Parameters to pass to the underlying estimators.
    Only available if `enable_metadata_routing=True`, which can be
    set by using `sklearn.set_config(enable_metadata_routing=True)`.
    See [Metadata Routing User Guide](https://skfolio.org/user_guide/metadata_routing.html.md#metadata-routing) for
    more details.
* **Returns:**
  **self** *NestedClustersOptimization*
  : Fitted estimator.

<a id="skfolio.optimization.NestedClustersOptimization.fit_predict"></a>

#### fit_predict(X)

Perform `fit` on `X` and returns the predicted `Portfolio` or
`Population` of `Portfolio` on `X` based on the fitted `weights`.
For factor models, use `fit(X, factors=...)` then `predict(X)` separately.

If fitting fails and `raise_on_failure=False`, this returns a
`FailedPortfolio`.

* **Parameters:**
  **X** *array-like of shape (n_observations, n_assets)*
  : Price returns of the assets.
* **Returns:**
  Portfolio | Population
  : The predicted `Portfolio` or `Population` based on the fitted `weights`.

<a id="skfolio.optimization.NestedClustersOptimization.get_metadata_routing"></a>

#### get_metadata_routing()

Get metadata routing of this object.

Please check [User Guide](https://skfolio.org/user_guide/metadata_routing.html.md#metadata-routing) on how the routing
mechanism works.

* **Returns:**
  **routing** *MetadataRequest*
  : A `MetadataRequest` encapsulating
    routing information.

<a id="skfolio.optimization.NestedClustersOptimization.get_params"></a>

#### get_params(deep=True)

Get parameters for this estimator.

* **Parameters:**
  **deep** *bool, default=True*
  : If True, will return the parameters for this estimator and
    contained subobjects that are estimators.
* **Returns:**
  **params** *dict*
  : Parameter names mapped to their values.

<a id="skfolio.optimization.NestedClustersOptimization.needs_previous_weights"></a>

#### *property* needs_previous_weights

Whether `previous_weights` must be propagated between folds/rebalances.

Used by `cross_val_predict` and `online_predict` to decide whether to run
sequentially and pass the weights from the previous rebalancing to the next.
This is `True` when `portfolio_params` sets `weight_drift=True`, or when
transaction costs, a maximum turnover, or a fallback depending on
`previous_weights` are present.

<a id="skfolio.optimization.NestedClustersOptimization.predict"></a>

#### predict(X)

Predict the `Portfolio` or a `Population` of portfolios on `X`.

Optimization estimators can return a 1D or a 2D array of `weights`.
For a 1D array, the prediction is a single `Portfolio`.
For a 2D array, the prediction is a `Population` of `Portfolio`.

If `name` is not provided in the portfolio parameters, the estimator
class name is used.

* **Parameters:**
  **X** *array-like of shape (n_observations, n_assets) | ReturnDistribution*
  : Asset returns or a `ReturnDistribution` carrying returns and optional
    sample weights.
* **Returns:**
  Portfolio | Population
  : The predicted `Portfolio` or `Population` based on the fitted `weights`.

<a id="skfolio.optimization.NestedClustersOptimization.score"></a>

#### score(X, y=None)

Prediction score using the Sharpe Ratio.
If the prediction is a single `Portfolio`, the score is its Sharpe Ratio.
If the prediction is a `Population`, the score is the mean Sharpe Ratio
across portfolios.

* **Parameters:**
  **X** *array-like of shape (n_observations, n_assets)*
  : Price returns of the assets.

  **y** *Ignored*
  : Not used, present here for API consistency by convention.
* **Returns:**
  **score** *float*
  : The Sharpe Ratio of the portfolio if the prediction is a single `Portfolio`
    or the mean of all the portfolio Sharpe Ratios if the prediction is a
    `Population` of `Portfolio`.

<a id="skfolio.optimization.NestedClustersOptimization.set_params"></a>

#### set_params(\*\*params)

Set the parameters of this estimator.

The method works on simple estimators as well as on nested objects
(such as `Pipeline`). The latter have
parameters of the form `<component>__<parameter>` so that it’s
possible to update each component of a nested object.

* **Parameters:**
  **\*\*params** *dict*
  : Estimator parameters.
* **Returns:**
  **self** *estimator instance*
  : Estimator instance.

