skfolio.descriptor.EWDownsideBeta#

class skfolio.descriptor.EWDownsideBeta(half_life=60.0, min_acceptable_return=0.0, min_periods=None, eps=1e-12)[source]#

Exponentially weighted downside beta descriptor.

Measures the sensitivity of each asset to market downturns using lower partial co-moments. Unlike standard beta, which treats up-moves and down-moves symmetrically, downside beta captures how much an asset tends to drop when the market drops [1] [2].

The lower partial co-moment formulation is:

\[ \begin{aligned} D_i(t) &= \min(r_i(t) - \text{mar},\; 0) \\[0.75em] D_m(t) &= \min(r_m(t) - \text{mar},\; 0) \\[0.75em] \beta^{\text{down}}_i(t) &= \frac{\text{EWMA}(D_i \cdot D_m)} {\text{EWMA}(D_m^2)} \end{aligned} \]

where \(\text{mar}\) is the minimum acceptable return threshold and the EWMA uses decay \(\lambda = \exp(-\ln(2) / \text{half\_life})\).

The EWMA is updated at every observation. Returns above mar add zero downside co-moment for that observation, while previous downside co-moments still decay. This avoids freezing the estimator during calm periods, unlike a conditional estimator that updates only on down-market days.

Parameters:
half_lifefloat, default=60.0

EWMA half-life in observations. Controls how fast old observations decay. The default of 60 trading days (~3 months) balances responsiveness and stability. Adjust for other frequencies (e.g. half_life=12 for weekly data).

min_acceptable_returnfloat, default=0.0

Threshold below which returns are considered “downside”. The default of 0.0 defines downside as negative returns (losses).

min_periodsint, optional

Minimum number of market observations and valid asset returns required before computing downside betas. Until both counts reach this value, the asset’s output is NaN. This warm-up period avoids exposing early EWMA values before the downside beta estimate has sufficiently converged from its zero initialization. If None, defaults to \(\lceil\text{half\_life}\rceil\), with a minimum of 1.

epsfloat, default=1e-12

Small constant for numerical stability in \(1 / \text{EWMA}(D_m^2)\).

Attributes:
n_assets_int

Number of assets seen during fitting.

asset_names_ndarray of shape (n_assets,)

Asset names seen during fitting.

downside_beta_ndarray of shape (n_assets,)

Last fitted downside beta value for each asset.

Methods

fit_transform(X[, y])

Compute exponentially weighted downside betas.

get_metadata_routing()

Get metadata routing of this object.

get_params([deep])

Get parameters for this estimator.

partial_fit_transform(X[, y])

Update EWMA state and return downside betas for this batch.

set_params(**params)

Set the parameters of this estimator.

Notes

The EWMA is initialized to zero (no bias correction). Since the initialization bias is identical across all assets at each time step, cross-sectional rankings are unaffected.

The market downside variance is updated at every observation. Asset co-moments are updated only for assets with valid (non-NaN) returns and each asset’s valid-observation count controls when its output starts. This avoids emitting initialized values for late-listed or sparsely observed assets. The active_mask property of AssetPanel distinguishes holidays from delistings.

Market returns are computed from the estimation universe (estimation_mask of AssetPanel). If no estimable asset has both finite returns and finite market_cap at an observation, the market return is undefined and a ValueError is raised.

References

[1]

“Downside risk” The Review of Financial Studies. Ang, A., Chen, J., & Xing, Y. (2006).

[2]

“Systematic risk in emerging markets: the D-CAPM”. Emerging Markets Review. Estrada, J. (2002).

Examples

>>> from skfolio.datasets import make_synthetic_characteristics
>>> from skfolio.descriptor import EWDownsideBeta
>>>
>>> X = make_synthetic_characteristics()
>>>
>>> # Standard downside beta (losses only)
>>> descriptor = EWDownsideBeta()
>>> downside_beta = descriptor.fit_transform(X)
>>>
>>> # Custom threshold
>>> descriptor = EWDownsideBeta(min_acceptable_return=-0.01)
>>> downside_beta = descriptor.fit_transform(X)
fit_transform(X, y=None, **fit_params)[source]#

Compute exponentially weighted downside betas.

Parameters:
XAssetPanel

Input panel containing returns and market_cap.

yNone

Ignored. Present for compatibility with scikit-learn’s API.

**fit_paramsdict

Additional fit parameters. Ignored.

Returns:
downside_betandarray of shape (n_observations, n_assets)

Downside beta for each observation and asset.

get_metadata_routing()#

Get metadata routing of this object.

Please check User Guide on how the routing mechanism works.

Returns:
routingMetadataRequest

A MetadataRequest encapsulating routing information.

get_params(deep=True)#

Get parameters for this estimator.

Parameters:
deepbool, default=True

If True, will return the parameters for this estimator and contained subobjects that are estimators.

Returns:
paramsdict

Parameter names mapped to their values.

partial_fit_transform(X, y=None, **fit_params)[source]#

Update EWMA state and return downside betas for this batch.

This method supports online updates by continuing from the current fitted state. Use fit_transform to start from a clean state.

Parameters:
XAssetPanel

Input panel containing returns and market_cap.

yNone

Ignored. Present for compatibility with scikit-learn’s API.

**fit_paramsdict

Additional fit parameters. Ignored.

Returns:
downside_betandarray of shape (n_observations, n_assets)

Downside beta for each observation and asset.

set_params(**params)#

Set the parameters of this estimator.

The method works on simple estimators as well as on nested objects (such as Pipeline). The latter have parameters of the form <component>__<parameter> so that it’s possible to update each component of a nested object.

Parameters:
**paramsdict

Estimator parameters.

Returns:
selfestimator instance

Estimator instance.