skfolio.alpha.EWSharpeOptimalAlpha#

class skfolio.alpha.EWSharpeOptimalAlpha(*, descriptors, half_life=20, ridge_scale=1e-06, horizon=1, signal_lag=1, neutralize_against=None, outlier_transformer=None, scoring_transformer=None, transform_by_group=None, forecast_unit=IDIO_RETURN, forecast_scale=1.0, normalize_weights=True, n_jobs=1)[source]#

Exponentially weighted least-squares Sharpe-optimal alpha estimator.

This estimator aggregates multiple cross-sectional signals from descriptors into a single alpha forecast by estimating their joint contribution to forward idiosyncratic returns. Coefficients are estimated with exponentially weighted least squares.

The estimator supports two forecast units. With the default forecast_unit=ForecastUnit.IDIO_RETURN, descriptors are fitted directly to forward idiosyncratic returns. When descriptor scores linearly forecast idiosyncratic returns and residual noise is proportional to idiosyncratic variance, the learned signal blend is Sharpe-optimal in idiosyncratic return space for an unconstrained long-short strategy.

With forecast_unit=ForecastUnit.IDIO_SHARPE, descriptors are fitted to forward idiosyncratic return divided by idiosyncratic volatility, with unit regression weights. Dividing the target by \(\sigma_i\) transforms the inverse-variance GLS objective in return units into OLS in idiosyncratic-Sharpe units.

Signals are first transformed into cross-sectional scores (e.g., z-scores, ranks), then optionally neutralized against factors and re-transformed into cross-sectional scores and finally combined linearly:

\[\alpha_i = \sum_{k=1}^{K} \beta_k \, S_{k,i}\]

where \(S_{k,i}\) denotes the cross-sectional score of signal \(k\) for asset \(i\) and \(\beta_k\) is the estimated signal coefficient.

By default, coefficients map descriptor scores directly into expected return units. With forecast_unit=ForecastUnit.IDIO_SHARPE, coefficients map descriptor scores into idiosyncratic-Sharpe units and the final forecast is multiplied by current idiosyncratic volatility so alpha_ remains in expected return units.

This generalizes IC-based signal weighting by:

  • accounting for cross-signal correlations (multivariate estimation)

  • incorporating asset-specific risk, either through inverse idiosyncratic variance weights or through volatility-scaled targets

  • producing an alpha forecast in expected return units, which is required whenever the optimizer is trading off alpha against real costs and constraints (e.g. transaction costs, market impact, borrow costs, turnover constraints).

For an individual signal with constant idiosyncratic variance, the estimator reduces to a scaled IC-like weighting.

The estimator uses the following regression target:

\[\begin{split}y_t = \begin{cases} \epsilon_t, & \text{if } \texttt{forecast\_unit=ForecastUnit.IDIO\_RETURN} \\ \epsilon_t / \sigma_t, & \text{if } \texttt{forecast\_unit=ForecastUnit.IDIO\_SHARPE} \end{cases}\end{split}\]

and regression weights:

\[\begin{split}W_t = \begin{cases} \operatorname{diag}(1 / \sigma_{t,i}^2), & \text{if } \texttt{forecast\_unit=ForecastUnit.IDIO\_RETURN} \\ I, & \text{if } \texttt{forecast\_unit=ForecastUnit.IDIO\_SHARPE} \end{cases}\end{split}\]

where:

  • \(\epsilon_{t,i}\) is the forward mean idiosyncratic return over the chosen horizon

  • \(S_{t,i} \in \mathbb{R}^K\) is the vector of cross-sectional scores

  • \(\sigma_{t,i}^2\) is the forecast idiosyncratic variance

If normalize_weights=True, the positive diagonal entries of \(W_t\) are divided by their cross-sectional average before computing the normal-equation statistics.

With forecast_unit=ForecastUnit.IDIO_SHARPE, the return model is instead:

\[\epsilon_{t,i} = \sigma_{t,i} S_{t,i}^\top \beta + \eta_{t,i}, \quad \operatorname{Var}(\eta_{t,i}) \propto \sigma_{t,i}^2\]

The corresponding inverse-variance GLS objective is:

\[\beta_t = \arg\min_\beta \sum_i \frac{(\epsilon_{t,i} - \sigma_{t,i} S_{t,i}^\top \beta)^2} {\sigma_{t,i}^2}\]

which is equivalent to ordinary least squares on the volatility-scaled target \(\epsilon_{t,i}/\sigma_{t,i}\):

\[\beta_t = \arg\min_\beta \sum_i \left(\frac{\epsilon_{t,i}}{\sigma_{t,i}} - S_{t,i}^\top \beta\right)^2\]

The final forecast is converted back to expected return units:

\[\alpha_i = \sigma_i S_i^\top \beta\]

This is useful when signals are assumed to forecast idiosyncratic Sharpe rather than raw idiosyncratic return. For the same scaled signal forecast, higher-volatility assets receive larger return alpha because the forecast is converted back from idiosyncratic-Sharpe units to return units.

To reduce estimation noise and turnover, the estimator maintains exponentially weighted least-squares statistics:

\[A_t^{EW} = \lambda A_{t-1}^{EW} + (1 - \lambda) S_t^\top W_t S_t\]
\[b_t^{EW} = \lambda b_{t-1}^{EW} + (1 - \lambda) S_t^\top W_t y_t, \quad \lambda = 2^{-1/\text{half-life}}\]

Coefficients are obtained by ridge-stabilized normal equations:

\[\beta_t = (A_t^{EW} + \rho_t I)^{-1} b_t^{EW}\]

With forecast_unit=ForecastUnit.IDIO_RETURN, the final alpha forecast is:

\[\alpha_i = S_i^\top \beta\]

With forecast_unit=ForecastUnit.IDIO_SHARPE, the forecast is:

\[\alpha_i = \sigma_i S_i^\top \beta\]

No intercept is included to avoid absorbing cross-sectional means, making the resulting alpha suitable for long-short strategies.

The estimator supports latest-alpha fitting with fit and partial_fit, and historical alpha forecasts with fit_transform and partial_fit_transform. Historical rows are computed as-of each observation: for horizon \(h\) and signal lag \(\ell\), alpha at observation \(t\) uses coefficient updates from signal observations up to \(t - \ell - h + 1\).

Parameters:
descriptorslist of (name, estimator) tuples

List of descriptors that compute signals from characteristics. Each tuple contains a string name and a descriptor estimator. Multiple descriptors are aggregated into a single alpha using multivariate regression. The descriptors are evaluated in parallel if n_jobs > 1.

half_lifefloat, default=20

Half-life of the exponential weights in number of observations.

  • Larger half-life: More stable alpha estimates, slower adaptation

  • Smaller half-life: More responsive estimates, faster adaptation

horizonint, default=1

Number of forward periods to average for the target idiosyncratic return. Must be >= 1. The target for observation \(t\) is mean(idio_returns[t+signal_lag : t+signal_lag+horizon]).

  • horizon=1: Predicts one-period idiosyncratic return starting after signal_lag

  • horizon>1: Predicts the mean of horizon idiosyncratic returns starting after signal_lag.

signal_lagint, default=1

Number of periods between the signal observation and the first return in the target window. Must be >= 1. Under skfolio’s as-of time-indexing convention, signal_lag=0 would use information observed at the end of \(t\) to predict return at \(t\), which is look-ahead. Values larger than 1 can model conservative data availability or execution delays.

neutralize_againstlist of str, optional

Factor names or families to neutralize scores against. If provided, scores are orthogonalized with respect to the specified factor exposures before regression.

outlier_transformerBaseCSTransformer or “passthrough”, optional

Cross-sectional transformer for descriptor outlier handling. If None, defaults to CSWinsorizer(). Use “passthrough” to skip.

scoring_transformerBaseCSTransformer or “passthrough”, optional

Cross-sectional transformer for descriptor scoring applied after outlier handling. If None, defaults to CSStandardScaler(). Use “passthrough” to skip.

transform_by_groupstr, optional

Name of a categorical characteristic in the AssetPanel to use for group-wise transformations. If provided, outlier and scoring transformations are applied within each group separately.

forecast_unitForecastUnit, default=ForecastUnit.IDIO_RETURN

Unit of the intermediate forecast learned from descriptor scores. With ForecastUnit.IDIO_RETURN, the target is the forward mean idiosyncratic return and WLS weights are inverse idiosyncratic variance. With ForecastUnit.IDIO_SHARPE, the target is divided by forecast idiosyncratic volatility and fitted with unit weights. The resulting idiosyncratic-Sharpe forecast is converted back to return units by multiplying by current idiosyncratic volatility.

forecast_scalefloat, default=1.0

Multiplicative scale applied to the final alpha forecast after the learned coefficients have been converted to expected return units. This controls alpha strength without changing the EWLS coefficient estimates.

normalize_weightsbool, default=True

If True, regression weights are normalized within each observation to have an average of one across valid assets. This removes changes in aggregate weight caused by the scale of idiosyncratic variances, while preserving the greater statistical weight of observations with more valid assets. In practice, this prevents calm, low-volatility regimes from mechanically dominating the EWLS statistics just because inverse-variance weights are larger in those regimes. Set normalize_weights=False for the unnormalized GLS estimator (which is BLUE under the usual assumptions).

ridge_scalefloat, default=1e-6

Relative ridge penalty applied to the exponentially weighted normal matrix.

n_jobsint, default=1

Number of parallel jobs for descriptor computation. Use -1 for all available cores.

Attributes:
alpha_ndarray of shape (n_assets,) or None

Estimated alpha (expected idiosyncratic return) for each asset. This is the aggregated prediction from all signals. Returns None during warmup phase (fewer than signal_lag + horizon observations).

coef_ndarray of shape (n_descriptors,)

Estimated descriptor coefficients.

descriptors_list of BaseDescriptor

Fitted descriptor estimators.

named_descriptors_dict of {str: BaseDescriptor}

Dictionary mapping descriptor names to fitted estimators.

outlier_transformer_BaseCSTransformer or str

The fitted outlier transformer.

scoring_transformer_BaseCSTransformer or str

The fitted scoring transformer.

n_assets_int

Number of assets seen during fitting.

asset_names_ndarray of shape (n_assets,)

Asset names seen during fitting.

Methods

fit(X[, y])

Fit the alpha model.

fit_transform(X[, y])

Fit the alpha model and return historical alpha forecasts.

get_metadata_routing()

Return metadata routing for descriptor estimators.

get_params([deep])

Get the parameters of an estimator from the ensemble.

partial_fit(X[, y])

Incrementally fit the alpha model with new observations.

partial_fit_transform(X[, y])

Incrementally fit the alpha model and return new historical alpha forecasts.

set_params(**params)

Set the parameters of a factor from the ensemble.

Notes

The Information Ratio (IR) of a strategy is approximately [1]:

\[\text{IR} \approx \text{IC} \times \sqrt{\text{Breadth}}\]

This estimator generalizes single-signal IC weighting by estimating multivariate, risk-weighted signal payoffs. The exponential weighting and ridge stabilization reduce turnover and estimation noise in the coefficients.

References

[1]

“Active Portfolio Management: A Quantitative Approach for Producing Superior Returns and Controlling Risk”, McGraw-Hill, Grinold & Kahn (1999).

Examples

>>> import numpy as np
>>> from skfolio.datasets import make_synthetic_characteristics
>>> from skfolio.alpha import EWSharpeOptimalAlpha, ForecastUnit
>>> from skfolio.descriptor import EWMomentum, BookToPrice, Reversal, Passthrough
>>>
>>> X = make_synthetic_characteristics()
>>> rng = np.random.default_rng(0)
>>>
>>> # Alpha models regress forward idiosyncratic returns. In production these
>>> # come from a fitted CharacteristicsFactorModel.
>>> idio_returns = rng.standard_normal((X.n_observations, X.n_assets))
>>> idio_returns[~X.active_mask] = np.nan
>>> X["idio_returns"] = idio_returns
>>>
>>> # Required when forecast_unit=ForecastUnit.IDIO_SHARPE to scale targets and alphas.
>>> idio_variances = rng.uniform(0.01, 0.05, (X.n_observations, X.n_assets))
>>> idio_variances[~X.active_mask] = np.nan
>>> X["idio_variances"] = idio_variances
>>>
>>> # Required when neutralize_against is set. In production these are factor
>>> # exposures from the characteristics factor model.
>>> exposures = rng.standard_normal((X.n_observations, X.n_assets, 3))
>>> exposures[~X.active_mask] = np.nan
>>> X.add_3d_field(
...     "exposures",
...     exposures,
...     third_axis_name="factors",
...     third_axis_labels=["market", "beta", "size"],
... )
>>>
>>> alpha_model = EWSharpeOptimalAlpha(
...     descriptors=[
...         ("momentum", EWMomentum()),
...         ("book_to_price", BookToPrice()),
...         ("reversal", Reversal()),
...         ("eps_ntm", Passthrough("eps_ntm")),
...     ],
...     horizon=5,      # one-week forward idiosyncratic return
...     half_life=21,    # one-month EWLS half-life
...     neutralize_against=["market", "beta", "size"],
...     forecast_unit=ForecastUnit.IDIO_SHARPE,
... )
>>>
>>> # Latest alpha forecast for the current rebalance.
>>> alpha_model.fit(X)
>>> print(alpha_model.alpha_)
>>>
>>> # Online learning with partial_fit
>>> alpha_model.partial_fit(X[-5:])
>>> print(alpha_model.alpha_)
>>>
>>> # Historical as-of alpha forecasts with fit_transform
>>> alphas = alpha_model.fit_transform(X)
fit(X, y=None, **fit_params)[source]#

Fit the alpha model.

Resets all internal state, processes the provided panel and stores the latest alpha forecast in alpha_.

Parameters:
XAssetPanel

Input panel containing “idio_returns”, “idio_variances”, descriptor fields and optionally “exposures” for score neutralization.

yNone

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

**fit_paramsdict

Additional fit parameters passed to descriptors through metadata routing.

Returns:
selfEWSharpeOptimalAlpha

Fitted estimator.

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

Fit the alpha model and return historical alpha forecasts.

The returned alpha at observation \(t\) only uses coefficient updates whose forward-return target is observable by \(t\). Warmup rows are NaN.

Parameters:
XAssetPanel

Input panel containing “idio_returns”, “idio_variances”, descriptor fields and optionally “exposures” for score neutralization.

yNone

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

**fit_paramsdict

Additional fit parameters passed to descriptors through metadata routing.

Returns:
alphasndarray of shape (n_observations, n_assets)

Historical alpha forecasts for the input panel.

get_metadata_routing()#

Return metadata routing for descriptor estimators.

get_params(deep=True)#

Get the parameters of an estimator from the ensemble.

Returns the parameters given in the constructor as well as the estimators contained within the estimators parameter.

Parameters:
deepbool, default=True

Setting it to True gets the various estimators and the parameters of the estimators as well.

Returns:
paramsdict

Parameter and estimator names mapped to their values or parameter names mapped to their values.

property named_descriptors#

Dictionary to access any fitted factors by name.

Returns:
Bunch
partial_fit(X, y=None, **fit_params)[source]#

Incrementally fit the alpha model with new observations.

This method supports streaming/online updates. It maintains internal buffers to compute forward returns across partial_fit calls.

Parameters:
XAssetPanel

Input panel containing “idio_returns”, “idio_variances”, descriptor fields and optionally “exposures” for score neutralization.

yNone

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

**fit_paramsdict

Additional fit parameters passed to descriptors through metadata routing.

Returns:
selfEWSharpeOptimalAlpha

Fitted estimator.

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

Incrementally fit the alpha model and return new historical alpha forecasts.

Only rows corresponding to the newly supplied observations are returned.

Parameters:
XAssetPanel

Input panel containing “idio_returns”, “idio_variances”, descriptor fields and optionally “exposures” for score neutralization.

yNone

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

**fit_paramsdict

Additional fit parameters passed to descriptors through metadata routing.

Returns:
alphasndarray of shape (n_observations, n_assets)

Historical alpha forecasts for the new observations.

set_params(**params)#

Set the parameters of a factor from the ensemble.

Valid parameter keys can be listed with get_params(). Note that you can directly set the parameters of the estimators contained in estimators.

Parameters:
**paramskeyword arguments

Specific parameters using e.g. set_params(parameter_name=new_value). In addition, to setting the parameters of the estimator, the individual estimator of the estimators can also be set, or can be removed by setting them to ‘drop’.

Returns:
selfobject

Estimator instance.