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)[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.
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 MVO 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 (MVO) solution. The better the conditioning of the initial covariance matrix, the closer the allocation will get to the MVO 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.- portfolio_paramsdict, optional
Portfolio parameters passed to the portfolio evaluated by the
predict
andscore
methods. If not provided, thename
,transaction_costs
,management_fees
andprevious_weights
are copied from the optimization model and systematically passed to the portfolio.
- 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.
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
orPopulation
ofPortfolio
onX
based on the fitted weights.score
(X[, y])Prediction score.
set_params
(**params)Set the parameters of this estimator.
Notes
A poorly conditioned covariance matrix can prevent convergence to the MVO 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.- Parameters:
- Xarray-like of shape (n_observations, n_assets)
Price returns of the assets.
- Returns:
- predictionPortfolio | Population
Portfolio
orPopulation
ofPortfolio
estimated onX
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.
- predict(X)#
Predict the
Portfolio
orPopulation
ofPortfolio
onX
based on the fitted weights.Optimization estimators can return a 1D or a 2D array of
weights
. For a 1D array, the prediction returns aPortfolio
. For a 2D array, the prediction returns aPopulation
ofPortfolio
.If
name
is not provided in the portfolio arguments, we use the first 500 characters of the estimator name.- Parameters:
- Xarray-like of shape (n_observations, n_assets)
Price returns of the assets.
- Returns:
- predictionPortfolio | Population
Portfolio
orPopulation
ofPortfolio
estimated onX
based on the fittedweights
.
- score(X, y=None)#
Prediction score. If the prediction is a single
Portfolio
, the score is the Sharpe Ratio. If the prediction is aPopulation
ofPortfolio
, the score is the mean of all the portfolios Sharpe Ratios in the population.- 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.