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 soalpha_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
fitandpartial_fit, and historical alpha forecasts withfit_transformandpartial_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 aftersignal_laghorizon>1: Predicts the mean ofhorizonidiosyncratic returns starting aftersignal_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=0would 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. WithForecastUnit.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. Setnormalize_weights=Falsefor 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
-1for 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
Noneduring warmup phase (fewer thansignal_lag + horizonobservations).- 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.
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
estimatorsparameter.- 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 inestimators.- 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.