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.

BatchStreamingKindNotes
ThurstonePortfolioStreamingThurstonenovelability tilt; common-seed transport
SchurBridgeStreamingSchurBridgenovelbridges 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
SchurComplementaryStreamingSchurnovel(ish)collapsed encoding of the HRP bridge, skfolio parity; Fiedler seriation
HierarchicalRiskParityStreamingHRPstandardHRP; the gamma=0 case of Schur
RiskParityStreamingRiskParitystandardequal risk contribution; interior, warm-started
MinimumVarianceStreamingMinimumVariancestandardΣ⁻¹1; shrinkage; signed
MaximumDiversificationStreamingMaximumDiversificationstandardΣ⁻¹σ; signed
MaximumDecorrelationStreamingMaximumDecorrelationstandardmin-variance on the correlation; signed
MeanVarianceStreamingMeanVariancestandardtangency Σ⁻¹μ; μ from EWMA mean or supplied
EqualWeightStreamingEqualWeightstandard1/n
InverseVarianceStreamingInverseVariancestandardw ∝ 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.