Source code for skfolio.descriptor._volatility._ew_downside_volatility

"""Exponentially weighted downside return volatility descriptors."""

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

from __future__ import annotations

from skfolio.descriptor._volatility._base import _BaseEWVolatility


[docs] class EWDownsideVolatility(_BaseEWVolatility): r"""Exponentially weighted downside return volatility descriptor. Computes the downside semi-deviation of asset returns using EWMA estimation [1]_. Only returns below the `min_acceptable_return` threshold contribute to the variance estimate: .. math:: :nowrap: \[ \begin{aligned} D_i(t) &= \min(r_i(t) - \text{mar},\; 0) \\[0.75em] S_{\text{down},i}(t) &= \lambda \cdot S_{\text{down},i}(t-1) + (1 - \lambda) \cdot D_i(t)^2 \\[0.75em] \text{output}_i(t) &= \sqrt{\frac{S_{\text{down},i}(t)} {1 - \lambda^{n_i(t)}}} \end{aligned} \] where :math:`\lambda = \exp(-\ln(2)/\text{half\_life})` and :math:`n_i(t)` is the number of valid returns for asset :math:`i`. Parameters ---------- half_life : float, default=40.0 EWMA half-life in observations. min_acceptable_return : float, default=0.0 Threshold below which returns are considered "downside". The default of `0.0` defines downside as negative returns (losses). min_periods : int, optional Minimum number of valid returns required for each asset. Until an asset reaches this count, its output is NaN. This warm-up period avoids exposing early EWMA values before the downside volatility estimate has sufficiently converged from its zero initialization. If `None`, defaults to :math:`\lceil\text{half\_life}\rceil`, with a minimum of 1. Attributes ---------- n_assets_ : int Number of assets seen during fitting. asset_names_ : ndarray of shape (n_assets,) Asset names seen during fitting. volatility_ : ndarray of shape (n_assets,) Last computed EWMA downside volatility. Contains NaN for inactive assets and assets that have not reached `min_periods` valid returns. Notes ----- The EWMA variance accumulator is initialized to zero and bias-corrected at output time using each asset's valid observation count, matching :class:`~skfolio.moments.EWVariance`. NaNs are treated as missing observations. Active assets with missing returns keep their previous EWMA state and inactive assets output NaN and restart their warm-up period when they become active again. Non-missing returns must be finite. References ---------- .. [1] "The cross-section of volatility and expected returns" The Journal of Finance. Ang, A., Hodrick, R. J., Xing, Y., & Zhang, X. (2006). See Also -------- EWVolatility : Total (non-downside) variant. EWResidualDownsideVolatility : Downside CAPM residual volatility. Examples -------- >>> from skfolio.datasets import make_synthetic_characteristics >>> from skfolio.descriptor import EWDownsideVolatility >>> >>> X = make_synthetic_characteristics() >>> >>> descriptor = EWDownsideVolatility() >>> downside_volatility = descriptor.fit_transform(X) >>> >>> # Custom threshold >>> descriptor = EWDownsideVolatility(min_acceptable_return=-0.01) >>> downside_volatility = descriptor.fit_transform(X) """ def __init__( self, half_life: float = 40.0, min_acceptable_return: float = 0.0, min_periods: int | None = None, ): super().__init__( half_life=half_life, min_acceptable_return=min_acceptable_return, min_periods=min_periods, )