Source code for skfolio.moments.expected_returns._equilibrium_mu

"""Equilibrium Expected Returns (Mu) Estimators."""

# Copyright (c) 2023-2026
# Author: Hugo Delatte <hugo.delatte@skfoliolabs.com>
# SPDX-License-Identifier: BSD-3-Clause
# Implementation derived from:
# scikit-learn, Copyright (c) 2007-2010 David Cournapeau, Fabian Pedregosa, Olivier
# Grisel Licensed under 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

from skfolio.moments.covariance import BaseCovariance, EmpiricalCovariance
from skfolio.moments.expected_returns._base import BaseMu
from skfolio.typing import ArrayLike, FloatArray
from skfolio.utils.tools import check_estimator


[docs] class EquilibriumMu(BaseMu): r"""Equilibrium Expected Returns (Mu) estimator. The Equilibrium is defined as: .. math:: risk\_aversion \times \Sigma \cdot w^T For Market Cap Equilibrium, the weights are the assets Market Caps. For Equal-weighted Equilibrium, the weights are equal-weighted (1/N). Parameters ---------- risk_aversion : float, default=1.0 Risk aversion factor. The default value is `1.0`. weights : array-like of shape (n_assets,), optional Reference portfolio weights used to compute the implied excess returns. Supplied weights are used as given, without normalization. They may contain negative values and need not sum to one. Their scale is preserved, so changing the portfolio exposure changes the estimated returns. For a market-cap-weighted equilibrium, provide normalized market capitalizations: `market_caps / market_caps.sum()`. Other weights define a reverse-optimization prior for the chosen reference portfolio, which may be leveraged, long-short, or market-neutral. The default (`None`) is to use equal weights (1/N). covariance_estimator : BaseCovariance, optional :ref:`Covariance estimator <covariance_estimator>` used to estimate the covariance in the equilibrium formula. The default (`None`) is to use :class:`~skfolio.moments.EmpiricalCovariance`. Attributes ---------- mu_ : ndarray of shape (n_assets,) Estimated expected returns of the assets. covariance_estimator_ : BaseCovariance Fitted `covariance_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. """ covariance_estimator_: BaseCovariance def __init__( self, risk_aversion: float = 1, weights: FloatArray | None = None, covariance_estimator: BaseCovariance | None = None, ) -> None: self.risk_aversion = risk_aversion self.weights = weights self.covariance_estimator = covariance_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 `covariance_estimator`. Returns ------- routing : MetadataRouter Metadata routing configuration. """ router = skm.MetadataRouter(owner=self.__class__.__name__).add( covariance_estimator=self.covariance_estimator, method_mapping=skm.MethodMapping().add(caller="fit", callee="fit"), ) return router
[docs] def fit(self, X: ArrayLike, y: None = None, **fit_params: Any) -> EquilibriumMu: """Fit the EquilibriumMu estimator model. 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. **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 : EquilibriumMu Fitted estimator. """ routed_params = skm.process_routing(self, "fit", **fit_params) # fitting estimators self.covariance_estimator_ = check_estimator( self.covariance_estimator, default=EmpiricalCovariance(), check_type=BaseCovariance, ) self.covariance_estimator_.fit(X, y, **routed_params.covariance_estimator.fit) # we validate and convert to numpy after all models have been fitted to keep # features names information. X = skv.validate_data(self, X) n_assets = X.shape[1] if self.weights is None: weights = np.ones(n_assets) / n_assets else: weights = np.asarray(self.weights) self.mu_ = self.risk_aversion * self.covariance_estimator_.covariance_ @ weights return self