allocation
Online (streaming) portfolio construction.
scikit-learn / skfolio-compatible estimators
(fit / partial_fit / weights_ / predict)
on a keyed dynamic-universe layer, so they survive reconstituting universes where assets
come and go. Two methods are novel; the rest are the standard
toolkit, re-engineered to stay smooth under streaming updates.
Methods
Novel
Thurstone portfolio
Weights are the winning probabilities of a Thurstonian race. Abilities calibrate the race to a benchmark; the tilt re-runs it under the estimated correlation. A common-seed transport keeps streaming updates low-turnover.
Schur bridges, named by their endpoints
One engine of which HERC, HRP, hierarchical minimum variance, NCO, minimum variance,
maximum diversification and tangency are corners. A partition, two Schur dials
(gamma: cluster on the outside, eta: asset on its cluster mates),
a companion vector, and an inverse-variance budget rule. A bridge earns its name only if
both ends are exact, which the named constructors are with their default sibling conditioning: SchurBridge.hrp_to_min_variance(gamma) is HRP at 0 and
minimum variance at 1, to machine precision, which no earlier variant achieved. At the far
end the weights do not depend on the partition, so reclustering churn is damped by the
same dials that damp estimation noise. Which published “Schur” is which:
the taxonomy;
one picture: all roads lead to minimum variance.
Schur-complementary, collapsed encoding
The port that matches skfolio's SchurComplementary to machine precision: HRP at gamma=0,
moving toward minimum variance as gamma→1 when the minimum-variance portfolio is long-only, without reaching it, ordered
by Fiedler seriation instead of a dendrogram, which reorders only when two Fiedler
coordinates cross (each crossing a finite jump; lower turnover is an empirical result, not a continuity theorem). Kept for parity; the exact bridge is SchurBridge.
Background and bibliography at
schur.microprediction.org.
Standard, kept smooth
Read weights = allocator(cov(data)) as two factors. For the closed-form linear
allocators (minimum variance, max diversification / decorrelation, inverse variance,
mean–variance) the allocator is already smooth in the covariance — pair them with a
smooth online covariance and you are done. The novelty in Thurstone and Schur is the cases
where the allocator itself is rough: sampling noise (common-seed transport), combinatorial
ordering (Fiedler seriation), and long-only kinks (kept interior).
Estimators
Every method has a batch estimator and a streaming twin
(Streaming…) for a changing universe.
| Batch | Streaming | Kind | Notes |
|---|---|---|---|
| ThurstonePortfolio | StreamingThurstone | novel | ability tilt; common-seed transport |
| SchurBridge | StreamingSchurBridge | novel | bridges named by endpoints: hrp_to_min_variance, hmv_to_min_variance, herc_to_min_variance, nco_to_min_variance, inverse_variance_to_min_variance; companion 'vol'/'mean'; paths siblings/all/factor |
| SchurComplementary | StreamingSchur | novel(ish) | collapsed encoding of the HRP bridge, skfolio parity; Fiedler seriation |
| HierarchicalRiskParity | StreamingHRP | standard | HRP; the gamma=0 case of Schur |
| RiskParity | StreamingRiskParity | standard | equal risk contribution; interior, warm-started |
| MinimumVariance | StreamingMinimumVariance | standard | Σ⁻¹1; shrinkage; signed |
| MaximumDiversification | StreamingMaximumDiversification | standard | Σ⁻¹σ; signed |
| MaximumDecorrelation | StreamingMaximumDecorrelation | standard | min-variance on the correlation; signed |
| MeanVariance | StreamingMeanVariance | standard | tangency Σ⁻¹μ; μ from EWMA mean or supplied |
| EqualWeight | StreamingEqualWeight | standard | 1/n |
| InverseVariance | StreamingInverseVariance | standard | w ∝ 1/σ² |
Usage
pip install allocation
from allocation import SchurBridge, SchurComplementary, RiskParity, MinimumVariance
SchurBridge.hrp_to_min_variance(0.5).fit(returns).weights_ # HRP -> min-variance, exact at both ends
SchurBridge.herc_to_min_variance(0.5, 0.5, n_clusters=8).fit(returns).weights_ # the HERC square
SchurComplementary(gamma=0.5).fit(returns).weights_ # collapsed encoding (skfolio parity)
RiskParity().fit(returns).weights_ # standard
MinimumVariance(shrinkage=0.1).fit(returns).weights_
# streaming, over a changing universe
from allocation import StreamingSchur
est = StreamingSchur(gamma=0.5)
for x in stream: # x = {asset_id: return}
est.learn_one(x) # assets may enter / leave
w = est.predict_one() # {asset_id: weight}
Why a separate package
Hi Max and Hugo. skfolio is the scikit-learn-native portfolio library, but
it is batch: a changing universe is handled by NaN/incomplete-data mechanics, not by
carrying per-asset state across streaming updates. allocation is the online
complement — partial_fit, a keyed dynamic universe (in the spirit of
precise), and turnover control by
construction — while staying API-compatible so each estimator can be contributed upstream.
Previously I worked with Hugo to put the original Schur method into skfolio, so maybe we'll do
the same here; and note also the river-compatible
methods (learn_one / predict_one) for changing universes. It is all
MIT-licensed and meant to be upstreamed — take anything here and improve on it.