skfolio.optimization.SchurComplementary#
- class skfolio.optimization.SchurComplementary(gamma=0.5, keep_monotonic=True, prior_estimator=None, distance_estimator=None, hierarchical_clustering_estimator=None, min_weights=0.0, max_weights=1.0, transaction_costs=0.0, management_fees=0.0, previous_weights=None, portfolio_params=None, fallback=None, raise_on_failure=True)[source]#
Schur Complementary Allocation estimator.
Schur Complementary Allocation is a portfolio allocation method developed by Peter Cotton [1].
It uses Schur-complement-inspired augmentation of sub-covariance matrices, revealing a link between Hierarchical Risk Parity (HRP) and minimum-variance portfolios (MVP).
By tuning the regularization factor
gamma
, which governs how much off-diagonal information is incorporated into the augmented covariance blocks, the method smoothly interpolates from the heuristic divide-and-conquer allocation of HRP (gamma = 0
) to the MVP solution (gamma -> 1
).The algorithm begins by computing a distance matrix and performing hierarchical clustering, then applies seriation to reorder assets in the dendrogram so that adjacent leaves have minimal distance.
Next, it uses recursive bisection: starting with the top-level cluster, each cluster is split into two sub-clusters in a top-down traversal.
For each sub-cluster, an augmented covariance matrix is built based on the Schur complement to incorporate off-diagonal block information. From this matrix, the total cluster variance under an inverse-variance allocation is computed, and a weighting factor derived from the variances of the two sub-clusters is used to update their cluster weights.
- Parameters:
- gammafloat
Regularization factor in [0, 1]. When gamma is zero, no off-diagonal information is used (equivalent to HRP). As gamma approaches one, the allocation moves toward the minimum variance solution. The better the conditioning of the initial covariance matrix, the closer the allocation will get to the MVP solution when gamma is near one.
- keep_monotonicbool, default=True
If True, ensures that the portfolio variance decreases monotonically with respect to gamma. This is achieved by capping gamma at its maximum permissible value (
effective_gamma_
). This constraint guarantees that the solution remains variance-bounded by the HRP portfolio (variance(Schur) <= variance(HRP)
), even in the presence of ill-conditioned covariance matrices. If False, no monotonicity enforcement or gamma capping is applied. For more details, see: skfolio/skfolio#3- prior_estimatorBasePrior, optional
Prior estimator. The prior estimator is used to estimate the
ReturnDistribution
containing the estimation of assets expected returns, covariance matrix and returns. The moments and returns estimations are used for the risk computation and the returns estimation are used by the distance matrix estimator. The default (None
) is to useEmpiricalPrior
.- distance_estimatorBaseDistance, optional
Distance estimator. The distance estimator is used to estimate the codependence and the distance matrix needed for the computation of the linkage matrix. The default (
None
) is to usePearsonDistance
.- hierarchical_clustering_estimatorHierarchicalClustering, optional
Hierarchical Clustering estimator. The hierarchical clustering estimator is used to compute the linkage matrix and the hierarchical clustering of the assets based on the distance matrix. The default (
None
) is to useHierarchicalClustering
.- min_weightsfloat | dict[str, float] | array-like of shape (n_assets, ), default=0.0
Minimum assets weights (weights lower bounds). The default is 0.0 (no short selling). Negative weights are not allowed. If a float is provided, it is applied to each asset.
None
is equivalent to the default0.0
. If a dictionary is provided, its (key/value) pair must be the (asset name/asset minimum weight) and the inputX
of thefit
methods must be a DataFrame with the asset names in columns. When using a dictionary, assets values that are not provided are assigned the default minimum weight of0.0
.Example:
min_weights = 0.0
–> long only portfolio (default).min_weights = {"SX5E": 0.1, "SPX": 0.2}
min_weights = [0.1, 0.2]
- max_weightsfloat | dict[str, float] | array-like of shape (n_assets, ), default=1.0
Maximum assets weights (weights upper bounds). The default is 1.0 (each asset is below 100%). Weights above 1.0 are not allowed. If a float is provided, it is applied to each asset.
None
is equivalent to the default1.0
. If a dictionary is provided, its (key/value) pair must be the (asset name/asset maximum weight) and the inputX
of thefit
method must be a DataFrame with the asset names in columns. When using a dictionary, assets values that are not provided are assigned the default maximum weight of1.0
.Example:
max_weights = 1.0
–> each weight must be below 100% (default).max_weights = 0.5
–> each weight must be below 50%.max_weights = {"SX5E": 0.8, "SPX": 0.9}
max_weights = [0.8, 0.9]
- transaction_costsfloat | dict[str, float] | array-like of shape (n_assets, ), default=0.0
Transaction costs of the assets. If a float is provided, it is applied to each asset. If a dictionary is provided, its (key/value) pair must be the (asset name/asset cost) and the input
X
of thefit
method must be a DataFrame with the asset names in columns. The default value is0.0
.- management_feesfloat | dict[str, float] | array-like of shape (n_assets, ), default=0.0
Management fees of the assets. If a float is provided, it is applied to each asset. If a dictionary is provided, its (key/value) pair must be the (asset name/asset fee) and the input
X
of thefit
method must be a DataFrame with the asset names in columns. The default value is0.0
.- previous_weightsfloat | dict[str, float] | array-like of shape (n_assets, ), optional
Previous weights of the assets. Previous weights are used to compute the portfolio total cost. If a float is provided, it is applied to each asset. If a dictionary is provided, its (key/value) pair must be the (asset name/asset previous weight) and the input
X
of thefit
method must be a DataFrame with the asset names in columns. The default (None
) means no previous weights. Additionally, whenfallback="previous_weights"
, failures will fall back to these weights if provided.- portfolio_paramsdict, optional
Portfolio parameters forwarded to the resulting
Portfolio
inpredict
. If not provided and if available on the estimator, the following attributes are propagated to the portfolio by default:name
,transaction_costs
,management_fees
,previous_weights
andrisk_free_rate
.- fallbackBaseOptimization | “previous_weights” | list[BaseOptimization | “previous_weights”], optional
Fallback estimator or a list of estimators to try, in order, when the primary optimization raises during
fit
. Alternatively, use"previous_weights"
(alone or in a list) to fall back to the estimator’sprevious_weights
. When a fallback succeeds, its fittedweights_
are copied back to the primary estimator so thatfit
still returns the original instance. For traceability,fallback_
stores the successful estimator (or the string"previous_weights"
)and
fallback_chain_
stores each attempt with the associated outcome.- raise_on_failurebool, default=True
Controls error handling when fitting fails. If True, any failure during
fit
is raised immediately, noweights_
are set and subsequent calls topredict
will raise aNotFittedError
. If False, errors are not raised; instead, a warning is emitted,weights_
is set toNone
and subsequent calls topredict
will return aFailedPortfolio
. When fallbacks are specified, this behavior applies only after all fallbacks have been exhausted.
- Attributes:
- weights_ndarray of shape (n_assets,)
Weights of the assets.
- effective_gamma_float
If
keep_monotonic
is True, the highest permissiblegamma
that preserves monotonic variance decrease; otherwise, equal to the inputgamma
.- distance_estimator_BaseDistance
Fitted
distance_estimator
.- hierarchical_clustering_estimator_HierarchicalClustering
Fitted
hierarchical_clustering_estimator
.- n_features_in_int
Number of assets seen during
fit
.- feature_names_in_ndarray of shape (
n_features_in_
,) Names of assets seen during
fit
. Defined only whenX
has asset names that are all strings.- fallback_BaseOptimization | “previous_weights” | None
The fallback estimator instance, or the string
"previous_weights"
, that produced the final result.None
if no fallback was used.- fallback_chain_list[tuple[str, str]] | None
Sequence describing the optimization fallback attempts. Each element is a pair
(estimator_repr, outcome)
whereestimator_repr
is the string representation of the primary estimator or a fallback (e.g."EqualWeighted()"
,"previous_weights"
), andoutcome
is"success"
if that step produced a valid solution, otherwise the stringified error message. For successful fits without any fallback, this isNone
.- error_str | list[str] | None
Captured error message(s) when
fit
fails. For multi-portfolio outputs (weights_
is 2D), this is a list aligned with portfolios.
Methods
fit
(X[, y])Fit the Schur Complementary estimator.
fit_predict
(X)Perform
fit
onX
and returns the predictedPortfolio
orPopulation
ofPortfolio
onX
based on the fittedweights
.Get metadata routing of this object.
get_params
([deep])Get parameters for this estimator.
predict
(X)Predict the
Portfolio
or aPopulation
of portfolios onX
.score
(X[, y])Prediction score using the Sharpe Ratio.
set_params
(**params)Set the parameters of this estimator.
Notes
A poorly conditioned covariance matrix can prevent convergence to the MVP solution as gamma approaches one. Setting
keep_monotonic=True
(the default) ensures that the portfolio variance decreases monotonically with respect to gamma and remains bounded by the variance of the HRP portfolio (variance(Schur) <= variance(HRP)
), even in the presence of ill-conditioned covariance matrices. Additionally, you can apply shrinkage or other conditioning techniques via theprior_estimator
parameter to improve numerical stability and estimation accuracy.References
[1]“Schur Complementary Allocation: A Unification of Hierarchical Risk Parity and Minimum Variance Portfolios”. Peter Cotton (2024).
[2]“Portfolio Optimization. Theory and Application”. Chapter 12.3.4 “From Portfolio Risk Minimization to Hierarchical Portfolios” Daniel P. Palomar (2025).
[3]“Building diversified portfolios that outperform out of sample”, The Journal of Portfolio Management, Marcos López de Prado (2016).
[4]“A robust estimator of the efficient frontier”, SSRN Electronic Journal, Marcos López de Prado (2019).
[5]“Machine Learning for Asset Managers”, Elements in Quantitative Finance. Cambridge University Press, Marcos López de Prado (2020).
[6]“A review of two decades of correlations, hierarchies, networks and clustering in financial markets”, Gautier Marti, Frank Nielsen, Mikołaj Bińkowski, Philippe Donnat (2020).
Examples
For a full tutorial on Schur Complementary Allocation, see Schur Complementary Allocation.
>>> from skfolio import RiskMeasure >>> from skfolio.cluster import HierarchicalClustering, LinkageMethod >>> from skfolio.datasets import load_sp500_dataset >>> from skfolio.distance import KendallDistance >>> from skfolio.moments import LedoitWolf >>> from skfolio.optimization import SchurComplementary >>> from skfolio.preprocessing import prices_to_returns >>> from skfolio.prior import EmpiricalPrior >>> >>> prices = load_sp500_dataset() >>> X = prices_to_returns(prices) >>> >>> # Default Schur Complementary allocation >>> model = SchurComplementary(gamma=0.5) >>> model.fit(X) >>> print(model.weights_) >>> >>> # Advanced model: >>> # * Ledoit-Wolf covariance shrinkage >>> # * Kendall's tau distance (absolute) for asset co-dependence >>> # * Hierarchical clustering with Ward's linkage >>> model = SchurComplementary( ... gamma=0.5, ... prior_estimator=EmpiricalPrior(covariance_estimator=LedoitWolf()), ... distance_estimator=KendallDistance(absolute=True), ... hierarchical_clustering_estimator=HierarchicalClustering( ... linkage_method=LinkageMethod.WARD, ... ) >>> model.fit(X) >>> print(model.weights_)
- fit(X, y=None, **fit_params)[source]#
Fit the Schur Complementary estimator.
- Parameters:
- Xarray-like of shape (n_observations, n_assets)
Price returns of the assets.
- yIgnored
Not used, present for API consistency by convention.
- Returns:
- selfSchurComplementary
Fitted estimator.
- fit_predict(X)#
Perform
fit
onX
and returns the predictedPortfolio
orPopulation
ofPortfolio
onX
based on the fittedweights
. For factor models, usefit(X, y)
thenpredict(X)
separately.If fitting fails and
raise_on_failure=False
, this returns aFailedPortfolio
.- Parameters:
- Xarray-like of shape (n_observations, n_assets)
Price returns of the assets.
- Returns:
- Portfolio | Population
The predicted
Portfolio
orPopulation
based on the fittedweights
.
- 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.
- property needs_previous_weights#
Whether
previous_weights
must be propagated between folds/rebalances.Used by
cross_val_predict
to decide whether to run sequentially and pass the weights from the previous rebalancing to the next. This isTrue
when transaction costs, a maximum turnover, or a fallback depending onprevious_weights
are present.
- predict(X)#
Predict the
Portfolio
or aPopulation
of portfolios onX
.Optimization estimators can return a 1D or a 2D array of
weights
. For a 1D array, the prediction is a singlePortfolio
. For a 2D array, the prediction is aPopulation
ofPortfolio
.If
name
is not provided in the portfolio parameters, the estimator class name is used.- Parameters:
- Xarray-like of shape (n_observations, n_assets) | ReturnDistribution
Asset returns or a
ReturnDistribution
carrying returns and optional sample weights.
- Returns:
- Portfolio | Population
The predicted
Portfolio
orPopulation
based on the fittedweights
.
- score(X, y=None)#
Prediction score using the Sharpe Ratio. If the prediction is a single
Portfolio
, the score is its Sharpe Ratio. If the prediction is aPopulation
, the score is the mean Sharpe Ratio across portfolios.- Parameters:
- Xarray-like of shape (n_observations, n_assets)
Price returns of the assets.
- yIgnored
Not used, present here for API consistency by convention.
- Returns:
- scorefloat
The Sharpe Ratio of the portfolio if the prediction is a single
Portfolio
or the mean of all the portfolios Sharpe Ratios if the prediction is aPopulation
ofPortfolio
.
- 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.