Source code for skfolio.optimization.naive._naive

"""Naive estimators."""

# Copyright (c) 2023-2026
# Author: Hugo Delatte <hugo.delatte@skfoliolabs.com>
# SPDX-License-Identifier: BSD-3-Clause

from __future__ import annotations

from typing import Any

import numpy as np
import sklearn.utils.metadata_routing as skm
import sklearn.utils.validation as skv

import skfolio.typing as skt
from skfolio.optimization._base import BaseOptimization, _check_finite_weights
from skfolio.prior import BasePrior, EmpiricalPrior
from skfolio.typing import ArrayLike
from skfolio.utils.stats import rand_weights_dirichlet
from skfolio.utils.tools import check_estimator


[docs] class InverseVolatility(BaseOptimization): """Inverse Volatility estimator. Each asset weight is computed using the inverse of its volatility and rescaled to have a sum of weights equal to one. The assets volatilities are derived from the prior estimator's covariance matrix. Parameters ---------- prior_estimator : BasePrior, optional :ref:`Prior estimator <prior>`. The prior estimator is used to estimate the :class:`~skfolio.prior.ReturnDistribution` containing estimates of expected asset returns, covariance matrix, returns and Cholesky decomposition of the covariance. The default (`None`) is to use :class:`~skfolio.prior.EmpiricalPrior`. 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. See :ref:`optimization_fallbacks`. 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 and no fallback succeeds. If True, the estimator raises the final error. If False, the estimator emits a warning and sets `weights_` to None, so subsequent calls to `predict` return a :class:`~skfolio.portfolio.FailedPortfolio`. During `fit`, `raise_on_failure` applies to any fitting error, including errors raised by the prior estimator. See :ref:`optimization_failure_handling`. Attributes ---------- weights_ : ndarray of shape (n_assets,) or (n_optimizations, n_assets) Weights of the assets. prior_estimator_ : BasePrior Fitted `prior_estimator`. 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] | None For a single portfolio, this is the recorded error message, or None after a successful allocation or fallback. For multiple portfolios, it is a list with one entry per row of `weights_`, containing an error message for each failed portfolio and None for each successful portfolio. Notes ----- All estimators should specify all parameters as explicit keyword arguments in `__init__` (no `*args` or `**kwargs`), following scikit-learn conventions. """ prior_estimator_: BasePrior def __init__( self, prior_estimator: BasePrior | None = None, portfolio_params: dict | None = None, fallback: skt.Fallback = None, previous_weights: skt.MultiInput | None = None, raise_on_failure: bool = True, ) -> None: super().__init__( portfolio_params=portfolio_params, fallback=fallback, previous_weights=previous_weights, raise_on_failure=raise_on_failure, ) self.prior_estimator = prior_estimator
[docs] def get_metadata_routing(self) -> skm.MetadataRouter: """Get metadata routing for this estimator. Routes metadata passed to `fit` to the `fit` method of `prior_estimator`. Returns ------- routing : MetadataRouter Metadata routing configuration. """ router = skm.MetadataRouter(owner=self.__class__.__name__).add( prior_estimator=self.prior_estimator, method_mapping=skm.MethodMapping().add(caller="fit", callee="fit"), ) return router
[docs] def fit( self, X: ArrayLike, y: ArrayLike | None = None, **fit_params: Any ) -> InverseVolatility: """Fit the Inverse Volatility 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 :ref:`Metadata Routing User Guide <metadata_routing>` for more details. Returns ------- self : InverseVolatility Fitted estimator. """ routed_params = skm.process_routing(self, "fit", **fit_params) _ = skv.validate_data(self, X, skip_check_array=True) # fitting prior estimator self.prior_estimator_ = check_estimator( self.prior_estimator, default=EmpiricalPrior(), check_type=BasePrior, ) self.prior_estimator_.fit(X, y, **routed_params.prior_estimator.fit) covariance = self.prior_estimator_.return_distribution_.covariance w = 1 / np.sqrt(np.diag(covariance)) weights = w / sum(w) _check_finite_weights(weights) self.weights_ = weights return self
[docs] class EqualWeighted(BaseOptimization): """Equally Weighted estimator. Each asset weight is equal to `1/n_assets`. Parameters ---------- 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. See :ref:`optimization_fallbacks`. 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 and no fallback succeeds. If True, the estimator raises the final error. If False, the estimator emits a warning and sets `weights_` to None, so subsequent calls to `predict` return a :class:`~skfolio.portfolio.FailedPortfolio`. During `fit`, `raise_on_failure` applies to any fitting error, including errors raised by the prior estimator. See :ref:`optimization_failure_handling`. Attributes ---------- weights_ : ndarray of shape (n_assets,) or (n_optimizations, n_assets) Weights of the assets. 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] | None For a single portfolio, this is the recorded error message, or None after a successful allocation or fallback. For multiple portfolios, it is a list with one entry per row of `weights_`, containing an error message for each failed portfolio and None for each successful portfolio. Notes ----- All estimators should specify all parameters as explicit keyword arguments in `__init__` (no `*args` or `**kwargs`), following scikit-learn conventions. """ def __init__( self, portfolio_params: dict | None = None, fallback: skt.Fallback = None, previous_weights: skt.MultiInput | None = None, raise_on_failure: bool = True, ) -> None: super().__init__( portfolio_params=portfolio_params, fallback=fallback, previous_weights=previous_weights, raise_on_failure=raise_on_failure, )
[docs] def fit(self, X: ArrayLike, y: None = None) -> EqualWeighted: """Fit the Equal Weighted estimator. Parameters ---------- X : array-like of shape (n_observations, n_assets) Price returns of the assets. y : Ignored Not used, present for API consistency by convention. Returns ------- self : EqualWeighted Fitted estimator. """ X = skv.validate_data(self, X) n_assets = X.shape[1] self.weights_ = np.ones(n_assets) / n_assets return self
[docs] class Random(BaseOptimization): """Random weight estimator. The asset weights are drawn from a Dirichlet distribution and sum to one. Parameters ---------- 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. See :ref:`optimization_fallbacks`. 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 and no fallback succeeds. If True, the estimator raises the final error. If False, the estimator emits a warning and sets `weights_` to None, so subsequent calls to `predict` return a :class:`~skfolio.portfolio.FailedPortfolio`. During `fit`, `raise_on_failure` applies to any fitting error, including errors raised by the prior estimator. See :ref:`optimization_failure_handling`. Attributes ---------- weights_ : ndarray of shape (n_assets,) or (n_optimizations, n_assets) Weights of the assets. 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] | None For a single portfolio, this is the recorded error message, or None after a successful allocation or fallback. For multiple portfolios, it is a list with one entry per row of `weights_`, containing an error message for each failed portfolio and None for each successful portfolio. Notes ----- All estimators should specify all parameters as explicit keyword arguments in `__init__` (no `*args` or `**kwargs`), following scikit-learn conventions. """ def __init__( self, portfolio_params: dict | None = None, fallback: skt.Fallback = None, previous_weights: skt.MultiInput | None = None, raise_on_failure: bool = True, ) -> None: super().__init__( portfolio_params=portfolio_params, fallback=fallback, previous_weights=previous_weights, raise_on_failure=raise_on_failure, )
[docs] def fit(self, X: ArrayLike, y: None = None) -> Random: """Fit the Random Weighted estimator. Parameters ---------- X : array-like of shape (n_observations, n_assets) Price returns of the assets. y : Ignored Not used, present for API consistency by convention. Returns ------- self : EqualWeighted Fitted estimator. """ X = skv.validate_data(self, X) n_assets = X.shape[1] self.weights_ = rand_weights_dirichlet(n=n_assets) return self