Source code for skfolio.portfolio._failed_portfolio

"""FailedPortfolio sentinel class."""

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

from __future__ import annotations

import skfolio.typing as skt
from skfolio.portfolio._portfolio import Portfolio
from skfolio.typing import ArrayLike, FloatArray


[docs] class FailedPortfolio(Portfolio): r""" Portfolio object returned when an optimization step fails. It acts as a sentinel value that marks the failure and stores failure diagnostics (`optimization_error`, `fallback_chain`). `FailedPortfolio` preserves full API compatibility with `Portfolio` so it can seamlessly pass through risk measures, aggregation, rolling computations and plotting without raising. All returns, weights, composition, and derived measures are NaN. .. note:: In backtesting workflows, when an optimization estimator is configured with `raise_on_failure=False`, a `FailedPortfolio` is returned on failed rebalancings. This lets the process complete without raising while preserving the full timeline for downstream analysis and diagnostics. Parameters ---------- X : array-like of shape (n_observations, n_assets) Price returns of the assets. If `X` is a DataFrame, the columns will be considered as assets names and the indices will be considered as observations. Otherwise, we use `["x0", "x1", ..., "x(n_assets - 1)"]` as asset names and `[0, 1, ..., n_observations]` as observations. optimization_error : str, optional Stringified error message explaining why the optimization failed. Propagated from the optimization estimator when `raise_on_failure=False`. `None` means the reason is unknown or not provided. name : str, optional Name of the portfolio. The default (`None`) is to use the object id. tag : str, optional Tag given to the portfolio. Tags are used to manipulate groups of Portfolios from a `Population`. fallback_chain : list[tuple[str, str]] | None, optional 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"`). * `outcome` is `"success"` if that step produced a valid solution, otherwise the stringified error message. For successful fits without any fallback, this is `None`. When fallbacks are provided and the primary fails, the chain starts with `(primary_repr, primary_error)` and is followed by one entry per fallback that was attempted, ending with the first `"success"` or the last error if all fail. This is set by the optimization estimator and propagated to the resulting portfolio objects (including `FailedPortfolio`). previous_weights : float | dict[str, float] | array-like of shape (n_assets, ), optional Accepted for API compatibility with `Portfolio` but not used by `FailedPortfolio`. transaction_costs : float | dict[str, float] | array-like of shape (n_assets, ), optional Accepted for API compatibility with `Portfolio` but not used by `FailedPortfolio`. management_fees : float | dict[str, float] | array-like of shape (n_assets, ), optional Accepted for API compatibility with `Portfolio` but not used by `FailedPortfolio`. risk_free_rate : float, default=0.0 Accepted for API compatibility with `Portfolio` but not used by `FailedPortfolio`. annualization_factor : float, default=252.0 Accepted for API compatibility with `Portfolio` but not used by `FailedPortfolio`. fitness_measures : list[measures], optional Accepted for API compatibility with `Portfolio` but not used by `FailedPortfolio`. compounded : bool, default=False Accepted for API compatibility with `Portfolio` but not used by `FailedPortfolio`. sample_weight : ndarray of shape (n_observations, ), optional Accepted for API compatibility with `Portfolio` but not used by `FailedPortfolio`. min_acceptable_return : float | None, optional Accepted for API compatibility with `Portfolio` but not used by `FailedPortfolio`. value_at_risk_beta : float, default=0.95 Accepted for API compatibility with `Portfolio` but not used by `FailedPortfolio`. entropic_risk_measure_theta : float, default=1.0 Accepted for API compatibility with `Portfolio` but not used by `FailedPortfolio`. entropic_risk_measure_beta : float, default=0.95 Accepted for API compatibility with `Portfolio` but not used by `FailedPortfolio`. cvar_beta : float, default=0.95 Accepted for API compatibility with `Portfolio` but not used by `FailedPortfolio`. evar_beta : float, default=0.95 Accepted for API compatibility with `Portfolio` but not used by `FailedPortfolio`. drawdown_at_risk_beta : float, default=0.95 Accepted for API compatibility with `Portfolio` but not used by `FailedPortfolio`. cdar_beta : float, default=0.95 Accepted for API compatibility with `Portfolio` but not used by `FailedPortfolio`. edar_beta : float, default=0.95 Accepted for API compatibility with `Portfolio` but not used by `FailedPortfolio`. Notes ----- All performance, risk, and contribution measures are computed from NaN returns and NaN weights in a `FailedPortfolio`. As a result, these parameters do not affect the outcome: NaNs are carried over to metrics, contributions, plots, and rolling computations. This class exists solely to preserve API and type compatibility while signaling a failed optimization. """ __slots__ = { # read-write "optimization_error", } def __init__( self, X: ArrayLike, name: str | None = None, tag: str | None = None, optimization_error: str | None = None, fallback_chain: list[tuple[str, str]] | None = None, previous_weights: skt.MultiInput = None, transaction_costs: skt.MultiInput = None, management_fees: skt.MultiInput = None, risk_free_rate: float = 0, annualization_factor: float | None = None, fitness_measures: list[skt.Measure] | None = None, compounded: bool = False, sample_weight: FloatArray | None = None, min_acceptable_return: float | None = None, value_at_risk_beta: float = 0.95, entropic_risk_measure_theta: float = 1, entropic_risk_measure_beta: float = 0.95, cvar_beta: float = 0.95, evar_beta: float = 0.95, drawdown_at_risk_beta: float = 0.95, cdar_beta: float = 0.95, edar_beta: float = 0.95, **kwargs, ): super().__init__( X=X, weights=None, previous_weights=previous_weights, transaction_costs=transaction_costs, management_fees=management_fees, risk_free_rate=risk_free_rate, name=name, tag=tag, annualization_factor=annualization_factor, fitness_measures=fitness_measures, compounded=compounded, sample_weight=sample_weight, min_acceptable_return=min_acceptable_return, value_at_risk_beta=value_at_risk_beta, entropic_risk_measure_theta=entropic_risk_measure_theta, entropic_risk_measure_beta=entropic_risk_measure_beta, cvar_beta=cvar_beta, evar_beta=evar_beta, drawdown_at_risk_beta=drawdown_at_risk_beta, cdar_beta=cdar_beta, edar_beta=edar_beta, fallback_chain=fallback_chain, **kwargs, ) self.optimization_error = optimization_error