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

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
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. 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,) 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 Captured error message(s) when `fit` fails. For multi-portfolio outputs (`weights_` is 2D), this is a list aligned with portfolios. 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, ): 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): # noinspection PyTypeChecker 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 ) -> 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) # 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)) self.weights_ = w / sum(w) 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. 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,) 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 Captured error message(s) when `fit` fails. For multi-portfolio outputs (`weights_` is 2D), this is a list aligned with portfolios. 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, ): 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) -> 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. 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,) 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 Captured error message(s) when `fit` fails. For multi-portfolio outputs (`weights_` is 2D), this is a list aligned with portfolios. 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, ): 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): """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