allocation 0.0.1__tar.gz → 0.0.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- allocation-0.0.2/PKG-INFO +248 -0
- allocation-0.0.2/README.md +224 -0
- allocation-0.0.2/allocation/__init__.py +91 -0
- allocation-0.0.2/allocation/_schur/bridge.py +561 -0
- {allocation-0.0.1 → allocation-0.0.2}/allocation/_schur/coupling.py +60 -14
- {allocation-0.0.1 → allocation-0.0.2}/allocation/_schur/seriation.py +5 -3
- allocation-0.0.2/allocation/_thurstone/ability.py +127 -0
- allocation-0.0.2/allocation/_thurstone/calibrate.py +99 -0
- {allocation-0.0.1 → allocation-0.0.2}/allocation/_thurstone/covariance.py +49 -0
- allocation-0.0.2/allocation/_thurstone/diagonal.py +37 -0
- allocation-0.0.2/allocation/_thurstone/factor.py +63 -0
- allocation-0.0.2/allocation/_thurstone/transport.py +289 -0
- allocation-0.0.2/allocation/backtest.py +302 -0
- {allocation-0.0.1 → allocation-0.0.2}/allocation/base.py +14 -0
- allocation-0.0.2/allocation/baselines.py +153 -0
- allocation-0.0.2/allocation/bridge.py +249 -0
- allocation-0.0.2/allocation/constraints.py +342 -0
- allocation-0.0.2/allocation/convex.py +260 -0
- allocation-0.0.2/allocation/costs.py +116 -0
- allocation-0.0.2/allocation/diagnostics.py +80 -0
- allocation-0.0.2/allocation/factor.py +154 -0
- allocation-0.0.2/allocation/keyed.py +561 -0
- allocation-0.0.2/allocation/moments.py +192 -0
- {allocation-0.0.1 → allocation-0.0.2}/allocation/schur.py +57 -11
- allocation-0.0.2/allocation/thurstone.py +275 -0
- allocation-0.0.2/allocation/tilt.py +215 -0
- allocation-0.0.2/allocation.egg-info/PKG-INFO +248 -0
- allocation-0.0.2/allocation.egg-info/SOURCES.txt +65 -0
- {allocation-0.0.1 → allocation-0.0.2}/allocation.egg-info/requires.txt +4 -1
- {allocation-0.0.1 → allocation-0.0.2}/pyproject.toml +6 -4
- allocation-0.0.2/tests/test_backtest.py +101 -0
- allocation-0.0.2/tests/test_baselines.py +153 -0
- allocation-0.0.2/tests/test_bridge.py +353 -0
- allocation-0.0.2/tests/test_bridge_properties.py +304 -0
- allocation-0.0.2/tests/test_constraints.py +122 -0
- allocation-0.0.2/tests/test_convex.py +180 -0
- allocation-0.0.2/tests/test_costs.py +103 -0
- allocation-0.0.2/tests/test_diagnostics.py +43 -0
- allocation-0.0.2/tests/test_diagonal_zero_variance.py +29 -0
- allocation-0.0.2/tests/test_downside.py +61 -0
- allocation-0.0.2/tests/test_factor.py +94 -0
- allocation-0.0.2/tests/test_issue_49.py +56 -0
- allocation-0.0.2/tests/test_issue_50.py +66 -0
- allocation-0.0.2/tests/test_issue_51.py +72 -0
- allocation-0.0.2/tests/test_issue_58.py +43 -0
- allocation-0.0.2/tests/test_issue_60.py +70 -0
- allocation-0.0.2/tests/test_issue_79.py +52 -0
- allocation-0.0.2/tests/test_issue_85.py +78 -0
- allocation-0.0.2/tests/test_issue_89.py +38 -0
- allocation-0.0.2/tests/test_issue_91.py +77 -0
- {allocation-0.0.1 → allocation-0.0.2}/tests/test_schur.py +33 -0
- allocation-0.0.2/tests/test_thurstone_factor.py +82 -0
- allocation-0.0.2/tests/test_thurstone_factors.py +108 -0
- allocation-0.0.2/tests/test_thurstone_samplers.py +252 -0
- allocation-0.0.2/tests/test_tilt_public.py +168 -0
- allocation-0.0.2/tests/test_winning_contract.py +71 -0
- allocation-0.0.1/PKG-INFO +0 -89
- allocation-0.0.1/README.md +0 -68
- allocation-0.0.1/allocation/__init__.py +0 -30
- allocation-0.0.1/allocation/_thurstone/ability.py +0 -45
- allocation-0.0.1/allocation/_thurstone/calibrate.py +0 -105
- allocation-0.0.1/allocation/_thurstone/diagonal.py +0 -26
- allocation-0.0.1/allocation/_thurstone/transport.py +0 -53
- allocation-0.0.1/allocation/keyed.py +0 -223
- allocation-0.0.1/allocation/moments.py +0 -62
- allocation-0.0.1/allocation/thurstone.py +0 -142
- allocation-0.0.1/allocation.egg-info/PKG-INFO +0 -89
- allocation-0.0.1/allocation.egg-info/SOURCES.txt +0 -29
- {allocation-0.0.1 → allocation-0.0.2}/LICENSE +0 -0
- {allocation-0.0.1 → allocation-0.0.2}/allocation/_schur/__init__.py +0 -0
- {allocation-0.0.1 → allocation-0.0.2}/allocation/_thurstone/__init__.py +0 -0
- {allocation-0.0.1 → allocation-0.0.2}/allocation/universe.py +0 -0
- {allocation-0.0.1 → allocation-0.0.2}/allocation.egg-info/dependency_links.txt +0 -0
- {allocation-0.0.1 → allocation-0.0.2}/allocation.egg-info/top_level.txt +0 -0
- {allocation-0.0.1 → allocation-0.0.2}/setup.cfg +0 -0
- {allocation-0.0.1 → allocation-0.0.2}/tests/test_schur_vs_skfolio.py +0 -0
- {allocation-0.0.1 → allocation-0.0.2}/tests/test_skfolio.py +0 -0
- {allocation-0.0.1 → allocation-0.0.2}/tests/test_streaming.py +0 -0
- {allocation-0.0.1 → allocation-0.0.2}/tests/test_thurstone.py +0 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: allocation
|
|
3
|
+
Version: 0.0.2
|
|
4
|
+
Summary: Online (streaming) portfolio construction: scikit-learn / skfolio-compatible estimators over a keyed dynamic universe
|
|
5
|
+
Author-email: Peter Cotton <peter.cotton@microprediction.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://allocation.microprediction.org
|
|
8
|
+
Project-URL: Source, https://github.com/microprediction/allocation
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: numpy>=1.23
|
|
13
|
+
Requires-Dist: winning>=1.5.0
|
|
14
|
+
Provides-Extra: fast
|
|
15
|
+
Requires-Dist: winning[fast]>=1.5.0; extra == "fast"
|
|
16
|
+
Provides-Extra: scale
|
|
17
|
+
Requires-Dist: scipy>=1.10; extra == "scale"
|
|
18
|
+
Provides-Extra: skfolio
|
|
19
|
+
Requires-Dist: skfolio>=0.5; extra == "skfolio"
|
|
20
|
+
Provides-Extra: test
|
|
21
|
+
Requires-Dist: pytest>=7.0; extra == "test"
|
|
22
|
+
Requires-Dist: scipy>=1.10; extra == "test"
|
|
23
|
+
Dynamic: license-file
|
|
24
|
+
|
|
25
|
+
# allocation
|
|
26
|
+
|
|
27
|
+
**Online (streaming) portfolio construction.** A home for portfolio methods as
|
|
28
|
+
scikit-learn / [skfolio](https://skfolio.org)-compatible estimators
|
|
29
|
+
(`fit` / `partial_fit` / `predict` / `weights_`), on top of a keyed
|
|
30
|
+
dynamic-universe layer so they survive reconstituting universes.
|
|
31
|
+
|
|
32
|
+
📖 **[allocation.microprediction.org](https://allocation.microprediction.org)**
|
|
33
|
+
· 🤖 **[SKILL.md](SKILL.md)** — when to reach for `allocation` (for LLMs / code review)
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from allocation import ThurstonePortfolio
|
|
37
|
+
|
|
38
|
+
est = ThurstonePortfolio(calib="market", phi=1.0)
|
|
39
|
+
est.fit(returns) # returns: (n_obs, n_assets)
|
|
40
|
+
est.weights_ # long-only weights on the simplex
|
|
41
|
+
|
|
42
|
+
for batch in stream: # streaming / rebalancing
|
|
43
|
+
est.partial_fit(batch) # transports a fixed seed ensemble -> low turnover
|
|
44
|
+
w = est.weights_
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`score(X)` returns the portfolio's Sharpe ratio (skfolio's scoring convention),
|
|
48
|
+
and `to_portfolio(X)` wraps the fitted weights as a skfolio `Portfolio`
|
|
49
|
+
(`pip install allocation[skfolio]`) so the output drops into skfolio's metrics,
|
|
50
|
+
`Population`, and cross-validation.
|
|
51
|
+
|
|
52
|
+
## Why a separate package
|
|
53
|
+
|
|
54
|
+
Actually Hugo (mostly) and I did move a *batch* version of schur into skfolio and maybe more things here
|
|
55
|
+
will be upstreamed into skfolio or river (Max Halford). Hugo made some improvements that are *not*
|
|
56
|
+
replicated in the streaming version of schur here, and the streaming versions of schur and HRP here also
|
|
57
|
+
deal with changing universes and weight stability.
|
|
58
|
+
|
|
59
|
+
To be clear
|
|
60
|
+
- `allocation` is the **online** complement in that sense — estimators with `partial_fit`
|
|
61
|
+
- It can also use a keyed dynamic universe (in the
|
|
62
|
+
spirit of [`precise`](https://github.com/microprediction/precise) but also river-ml) while staying API-compatible to both.
|
|
63
|
+
|
|
64
|
+
This is all MIT-licensed and anyone is welcome to take anything here.
|
|
65
|
+
|
|
66
|
+
## Methods
|
|
67
|
+
|
|
68
|
+
| Estimator | Status | Notes |
|
|
69
|
+
|-----------|--------|-------|
|
|
70
|
+
| `ThurstonePortfolio` | working | Ability tilt: weights are winning probabilities of a correlated race; calibrate to a benchmark under a reference correlation, tilt under the estimate; smooth common-seed transport for `partial_fit`. Calibration and racing run through [`winning`](https://github.com/microprediction/winning). |
|
|
71
|
+
| `SchurComplementary` | working | The **collapsed encoding** of the HRP bridge, matching skfolio to machine precision: HRP at `gamma=0`, moving *toward* min-variance as `gamma→1` when the min-variance portfolio is long-only, without reaching it (`SchurBridge` is the exact one). Over a smooth **Fiedler seriation** instead of a dendrogram, so `partial_fit` is low-turnover. |
|
|
72
|
+
| `SchurBridge` | working | **Bridges named by their endpoints, one engine underneath**: `hrp_to_min_variance(gamma)`, `hmv_to_min_variance`, `herc_to_min_variance(gamma, eta, n_clusters)`, `nco_to_min_variance(gamma, n_clusters)`, `inverse_variance_to_min_variance(eta)`, each exact at both ends with the default sibling conditioning; `companion='vol'`/`'mean'` lands on maximum diversification / tangency instead. See below. |
|
|
73
|
+
| `HierarchicalRiskParity` | working | Dynamic HRP — the `gamma=0` special case of the Schur construction (recursive-bisection risk parity over the Fiedler order); named for recognisability. |
|
|
74
|
+
| `RiskParity` | working | Equal-risk-contribution (ERC); interior convex solution, solved by coordinate descent **warm-started from the previous weights** so updates stay smooth. |
|
|
75
|
+
| `EqualWeight`, `InverseVariance` | working | Smooth baselines for benchmarking (`1/n`; `w ∝ 1/σ²`). |
|
|
76
|
+
| `MinimumVariance`, `MaximumDiversification` | working | Closed-form `Σ⁻¹1` / `Σ⁻¹σ` with optional `shrinkage` for conditioning. Unconstrained (signed) so they stay smooth — a long-only QP would kink. |
|
|
77
|
+
|
|
78
|
+
Each has a river-style streaming twin for a *changing* universe — `StreamingThurstone`, `StreamingSchur`, `StreamingSchurBridge`, `StreamingHRP`, `StreamingRiskParity`, `StreamingEqualWeight`, `StreamingInverseVariance`, `StreamingMinimumVariance`, `StreamingMaximumDiversification`, `StreamingMaximumDecorrelation`, `StreamingMeanVariance` — with `learn_one({id: ret})` / `predict_one() → {id: weight}`.
|
|
79
|
+
|
|
80
|
+
**On smoothness.** Each method is written so that `partial_fit` over a drifting covariance moves weights only as much as the covariance moved. It helps to see `weights = allocator(cov_estimator(data))` as a product of two factors:
|
|
81
|
+
|
|
82
|
+
- For the **closed-form linear allocators** (`MinimumVariance`, `MaximumDiversification`, `InverseVariance`) the allocator factor is already a smooth (rational) function of `Σ`, so weight-smoothness *is* covariance-smoothness — pair them with a smooth online covariance (the default EWMA, a shrinkage estimator, or a `precise` skater) and you're done. Caveats: keep `Σ` well-conditioned (use `shrinkage`, since `Σ⁻¹` swings near a vanishing eigenvalue) and avoid hard long-only QPs (they kink at the zero bound — the smooth long-only min-variance is `SchurComplementary` as `gamma→1`).
|
|
83
|
+
- The package's distinctive work is the **other** family, where the allocator factor itself is rough no matter how smooth `Σ` is. Three sources, three primitives: sampling noise → common-seed transport (Thurstone); combinatorial ordering → Fiedler seriation (Schur/HRP); active-set kinks → keep the solution interior (ERC is interior by construction).
|
|
84
|
+
|
|
85
|
+
### Bridges, named by their endpoints
|
|
86
|
+
|
|
87
|
+
Schur conditioning is a technique; what matters is what it connects. Block
|
|
88
|
+
inversion writes the global direction `Σ⁻¹u` cluster by cluster: each cluster's
|
|
89
|
+
block is a minimum-variance direction on its covariance *conditional on everything
|
|
90
|
+
else*, with budget the inverse of that conditional variance. Every hierarchical
|
|
91
|
+
or clustered heuristic drops conditioning somewhere. `SchurBridge` puts it back by
|
|
92
|
+
degrees with two dials, `gamma` (cluster on the outside) and `eta` (asset on its
|
|
93
|
+
cluster mates), and a bridge earns its name only if **both ends are exact**:
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from allocation import SchurBridge
|
|
97
|
+
|
|
98
|
+
SchurBridge.hrp_to_min_variance(gamma=0.5) # HRP -> min-variance, one dial
|
|
99
|
+
SchurBridge.hmv_to_min_variance(gamma=0.5) # hierarchical min-var (Cotton 2024) -> min-variance
|
|
100
|
+
SchurBridge.herc_to_min_variance(gamma=0.5, eta=0.5, n_clusters=8) # HERC -> min-variance, the square
|
|
101
|
+
SchurBridge.nco_to_min_variance(gamma=0.5, n_clusters=8) # NCO -> min-variance
|
|
102
|
+
SchurBridge.inverse_variance_to_min_variance(eta=0.5) # inverse variance -> min-variance (Stevens' path)
|
|
103
|
+
SchurBridge.hrp_to_min_variance(0.5, companion="vol") # ... -> maximum diversification
|
|
104
|
+
SchurBridge.hrp_to_min_variance(0.5, companion="mean") # ... -> tangency
|
|
105
|
+
est.endpoints_ # e.g. ('HRP', 'minimum variance')
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
The HRP bridge needs a split rule that moves with the dial: the variance of the
|
|
109
|
+
child's `eta`-damped naive portfolio on its conditioned pair (`split='dial'`). HRP's
|
|
110
|
+
own naive split (`split='hrp'`, the rule inside `SchurComplementary` and skfolio)
|
|
111
|
+
starts at HRP but does not reach minimum variance; the min-variance split
|
|
112
|
+
(`split='minvar'`) reaches it but starts at hierarchical minimum variance. Which
|
|
113
|
+
published "Schur" is which is tabulated on the
|
|
114
|
+
[taxonomy page](https://schur.microprediction.org/taxonomy.html).
|
|
115
|
+
|
|
116
|
+
Two facts make the bridge the streaming choice. Everything after the partition is a
|
|
117
|
+
closed-form, continuous function of the covariance. And at the far end the weights
|
|
118
|
+
do not depend on the partition at all, so the turnover caused by a cluster
|
|
119
|
+
membership change shrinks to zero as the dials approach it (checked in
|
|
120
|
+
`experiments/bridge_churn_and_scale.py`): the dials that damp estimation noise damp
|
|
121
|
+
reclustering churn. The partition is the Fiedler order, which changes only when two
|
|
122
|
+
coordinates cross, bisected
|
|
123
|
+
(`n_clusters=None`) or cut into contiguous blocks (`n_clusters=k`), or a fixed label
|
|
124
|
+
vector (`clusters=`) such as sectors.
|
|
125
|
+
|
|
126
|
+
`conditioning` picks the path: `'siblings'` composes down the bisection tree
|
|
127
|
+
(exact, one half-size solve at the root), `'all'` conditions on every other asset
|
|
128
|
+
(exact, expensive), and `'factor'` conditions on one factor-mimicking portfolio per
|
|
129
|
+
other cluster, exact when cross-cluster dependence runs through one latent factor
|
|
130
|
+
per cluster and costing only cluster-sized solves; on a generic covariance it is
|
|
131
|
+
approximate at the far end, so the named constructors default to `'siblings'`. `long_only=True` caps `eta` at
|
|
132
|
+
each cluster's closed-form long-only frontier, and raises rather than return a
|
|
133
|
+
short position when the companion or outer layer would still produce one. The exact-arithmetic theory behind
|
|
134
|
+
the corners is in the papers at [schur.microprediction.org](https://schur.microprediction.org).
|
|
135
|
+
|
|
136
|
+
### Very large universes (e.g. Russell 3000)
|
|
137
|
+
|
|
138
|
+
Where the covariance is rank-deficient, every inversion-based allocator (min-variance,
|
|
139
|
+
max-diversification, …) is *undefined* (`is_singular()` flags it; `strict=True` refuses).
|
|
140
|
+
The robust large-universe methods are the ones that never invert `Σ`: inverse-variance,
|
|
141
|
+
HRP/Schur, and the **Thurstone tilt** — benchmark-anchored, no inversion, low turnover.
|
|
142
|
+
`ThurstonePortfolio(factors=k)` runs the tilt with a `k`-factor (low-rank) correlation and
|
|
143
|
+
an `O(M·n·k)` race instead of the dense `O(M·n²)+O(n³)` one, so it scales to thousands of
|
|
144
|
+
names (a few seconds per rebalance at n=3000 with the default path budget; fewer paths
|
|
145
|
+
trade accuracy for speed).
|
|
146
|
+
|
|
147
|
+
To make *minimum-variance itself* well-posed at scale, `FactorMinimumVariance` /
|
|
148
|
+
`FactorMaximumDiversification` invert a factor covariance `Σ = BBᵀ + diag(ψ)` via the
|
|
149
|
+
**Woodbury identity** (`O(n·k²)`, condition number bounded by an idiosyncratic floor, so the
|
|
150
|
+
inverse always exists). Supply a factor covariance (`loadings_` / `idiosyncratic_`, e.g. an
|
|
151
|
+
adapted `precise.FactorCovariance`) for the `O(n·k)` path, or let it factor a dense covariance.
|
|
152
|
+
And `SchurComplementary(ridge=…)` regularizes the cross-block solve so `gamma>0` stays
|
|
153
|
+
well-posed (and self-tempers toward HRP) when blocks are rank-deficient.
|
|
154
|
+
|
|
155
|
+
### Trading costs
|
|
156
|
+
|
|
157
|
+
`TurnoverPenalty(estimator, cost=λ)` wraps any estimator with a quadratic trading
|
|
158
|
+
cost (market-impact model). Minimising `‖w − w*‖² + λ‖w − w_prev‖²` gives the smooth,
|
|
159
|
+
budget-preserving blend `w_t = α w* + (1−α) w_prev` with `α = 1/(1+λ)`, so each step's
|
|
160
|
+
turnover is scaled exactly by `α`. This explicit damping composes with the implicit
|
|
161
|
+
turnover control (smooth target) and has a streaming twin `StreamingTurnoverPenalty`.
|
|
162
|
+
|
|
163
|
+
### Linear constraints
|
|
164
|
+
|
|
165
|
+
`BoxConstrained(estimator, lower=, upper=, groups=, group_caps=)` imposes per-asset
|
|
166
|
+
bounds and (disjoint) group caps on any estimator via a **log-barrier**, not a QP.
|
|
167
|
+
The barrier's domain *is* the constraint set, so the result is feasible for
|
|
168
|
+
any `tau>0` (strictly, except for coordinates that a bound or cap pins exactly) —
|
|
169
|
+
feasibility never depends on tuning. Infeasible or malformed constraints raise. The operator stays C¹ (no
|
|
170
|
+
active-set kinks), so turnover stays low. Streaming twin: `StreamingBoxConstrained`.
|
|
171
|
+
|
|
172
|
+
## Comparing methods
|
|
173
|
+
|
|
174
|
+
`allocation.backtest` is a numpy-only walk-forward harness: it trades each
|
|
175
|
+
estimator forward (form weights → earn next period → update) and tabulates
|
|
176
|
+
out-of-sample Sharpe, **turnover**, concentration, and Sharpe net of a
|
|
177
|
+
proportional cost. Turnover and cost are charged on the trades needed to return
|
|
178
|
+
drifted holdings to each new target.
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
from allocation import SchurComplementary, TurnoverPenalty, MinimumVariance
|
|
182
|
+
from allocation.backtest import compare, format_table, make_panel
|
|
183
|
+
|
|
184
|
+
panel = make_panel() # synthetic; or any (n_obs, n_assets) array
|
|
185
|
+
print(format_table(compare({
|
|
186
|
+
"schur": lambda: SchurComplementary(gamma=0.5),
|
|
187
|
+
"schur+λ": lambda: TurnoverPenalty(SchurComplementary(gamma=0.5), cost=3.0),
|
|
188
|
+
"minvar": lambda: MinimumVariance(shrinkage=0.1),
|
|
189
|
+
}, panel)))
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
No data is shipped (to avoid bloat); `compare()` takes any array, and
|
|
193
|
+
`load_returns_csv(url)` can pull a raw CSV from a data repo at runtime.
|
|
194
|
+
|
|
195
|
+
For a robust, universe-independent comparison, `compare_random_subsets(...)` runs
|
|
196
|
+
a Monte-Carlo over random name-subsets and windows, reporting each method's mean
|
|
197
|
+
± sd net Sharpe, mean turnover, and **win rate** (fraction of trials it had the
|
|
198
|
+
best net Sharpe) — averaging out the single-backtest dependence on which names
|
|
199
|
+
and period you happened to pick.
|
|
200
|
+
|
|
201
|
+
## Design
|
|
202
|
+
|
|
203
|
+
```
|
|
204
|
+
allocation/
|
|
205
|
+
base.py # BaseOnlinePortfolio: fit / partial_fit / predict / weights_
|
|
206
|
+
moments.py # EwmaCovariance (default); any partial_fit/covariance_ estimator plugs in
|
|
207
|
+
baselines.py # EqualWeight / InverseVariance / RiskParity (ERC)
|
|
208
|
+
convex.py # MinimumVariance / MaximumDiversification (closed-form, signed)
|
|
209
|
+
thurstone.py # ThurstonePortfolio
|
|
210
|
+
_thurstone/ # calibration + transport (thin over winning)
|
|
211
|
+
schur.py # SchurComplementary / HierarchicalRiskParity
|
|
212
|
+
bridge.py # SchurBridge: the pair-form engine the other allocators are corners of
|
|
213
|
+
_schur/ # Fiedler seriation + Schur coupling engine + the bridge (pairs, dials, knots)
|
|
214
|
+
keyed.py # river-style streaming twins over a changing universe
|
|
215
|
+
universe.py # keyed dynamic-universe state for the batch estimators (planned)
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Covariance is pluggable: the default is a light EWMA, but any online estimator
|
|
219
|
+
exposing `partial_fit` and `covariance_` (e.g. a `precise` skater) can be passed
|
|
220
|
+
via `covariance=`.
|
|
221
|
+
|
|
222
|
+
## Papers
|
|
223
|
+
|
|
224
|
+
The two novel methods are written up as working papers.
|
|
225
|
+
|
|
226
|
+
- **Thurstone Portfolios: Allocation as Winning Probability** — long-only
|
|
227
|
+
allocation as the winning probabilities of a correlated race: free of the
|
|
228
|
+
duplication paradox, tail-consistent when the race is driven by a
|
|
229
|
+
downside-dependent simulation, with an implied regularized objective and a
|
|
230
|
+
smoothness/turnover bound. Draft in
|
|
231
|
+
[`papers/thurstone-portfolios/`](papers/thurstone-portfolios), built on
|
|
232
|
+
[`thurstone`](https://github.com/microprediction/thurstone).
|
|
233
|
+
- **Schur-complementary allocation** — robust, inversion-light allocation along a
|
|
234
|
+
spectral (Fiedler) seriation; background and bibliography at
|
|
235
|
+
[schur.microprediction.org](https://schur.microprediction.org).
|
|
236
|
+
|
|
237
|
+
## Status
|
|
238
|
+
|
|
239
|
+
Early. The Thurstone, Schur/HRP, and risk-parity estimators all work and are
|
|
240
|
+
tested, batch and streaming; the keyed dynamic-universe layer for the *batch*
|
|
241
|
+
estimators (so `partial_fit` survives a changing asset count, as the streaming
|
|
242
|
+
twins already do) is next. The theory behind the Thurstone method (feasibility,
|
|
243
|
+
redundancy consistency, the implied regularized objective, smoothness/turnover)
|
|
244
|
+
is written up in the accompanying paper.
|
|
245
|
+
|
|
246
|
+
## License
|
|
247
|
+
|
|
248
|
+
MIT © Peter Cotton
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# allocation
|
|
2
|
+
|
|
3
|
+
**Online (streaming) portfolio construction.** A home for portfolio methods as
|
|
4
|
+
scikit-learn / [skfolio](https://skfolio.org)-compatible estimators
|
|
5
|
+
(`fit` / `partial_fit` / `predict` / `weights_`), on top of a keyed
|
|
6
|
+
dynamic-universe layer so they survive reconstituting universes.
|
|
7
|
+
|
|
8
|
+
📖 **[allocation.microprediction.org](https://allocation.microprediction.org)**
|
|
9
|
+
· 🤖 **[SKILL.md](SKILL.md)** — when to reach for `allocation` (for LLMs / code review)
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
from allocation import ThurstonePortfolio
|
|
13
|
+
|
|
14
|
+
est = ThurstonePortfolio(calib="market", phi=1.0)
|
|
15
|
+
est.fit(returns) # returns: (n_obs, n_assets)
|
|
16
|
+
est.weights_ # long-only weights on the simplex
|
|
17
|
+
|
|
18
|
+
for batch in stream: # streaming / rebalancing
|
|
19
|
+
est.partial_fit(batch) # transports a fixed seed ensemble -> low turnover
|
|
20
|
+
w = est.weights_
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`score(X)` returns the portfolio's Sharpe ratio (skfolio's scoring convention),
|
|
24
|
+
and `to_portfolio(X)` wraps the fitted weights as a skfolio `Portfolio`
|
|
25
|
+
(`pip install allocation[skfolio]`) so the output drops into skfolio's metrics,
|
|
26
|
+
`Population`, and cross-validation.
|
|
27
|
+
|
|
28
|
+
## Why a separate package
|
|
29
|
+
|
|
30
|
+
Actually Hugo (mostly) and I did move a *batch* version of schur into skfolio and maybe more things here
|
|
31
|
+
will be upstreamed into skfolio or river (Max Halford). Hugo made some improvements that are *not*
|
|
32
|
+
replicated in the streaming version of schur here, and the streaming versions of schur and HRP here also
|
|
33
|
+
deal with changing universes and weight stability.
|
|
34
|
+
|
|
35
|
+
To be clear
|
|
36
|
+
- `allocation` is the **online** complement in that sense — estimators with `partial_fit`
|
|
37
|
+
- It can also use a keyed dynamic universe (in the
|
|
38
|
+
spirit of [`precise`](https://github.com/microprediction/precise) but also river-ml) while staying API-compatible to both.
|
|
39
|
+
|
|
40
|
+
This is all MIT-licensed and anyone is welcome to take anything here.
|
|
41
|
+
|
|
42
|
+
## Methods
|
|
43
|
+
|
|
44
|
+
| Estimator | Status | Notes |
|
|
45
|
+
|-----------|--------|-------|
|
|
46
|
+
| `ThurstonePortfolio` | working | Ability tilt: weights are winning probabilities of a correlated race; calibrate to a benchmark under a reference correlation, tilt under the estimate; smooth common-seed transport for `partial_fit`. Calibration and racing run through [`winning`](https://github.com/microprediction/winning). |
|
|
47
|
+
| `SchurComplementary` | working | The **collapsed encoding** of the HRP bridge, matching skfolio to machine precision: HRP at `gamma=0`, moving *toward* min-variance as `gamma→1` when the min-variance portfolio is long-only, without reaching it (`SchurBridge` is the exact one). Over a smooth **Fiedler seriation** instead of a dendrogram, so `partial_fit` is low-turnover. |
|
|
48
|
+
| `SchurBridge` | working | **Bridges named by their endpoints, one engine underneath**: `hrp_to_min_variance(gamma)`, `hmv_to_min_variance`, `herc_to_min_variance(gamma, eta, n_clusters)`, `nco_to_min_variance(gamma, n_clusters)`, `inverse_variance_to_min_variance(eta)`, each exact at both ends with the default sibling conditioning; `companion='vol'`/`'mean'` lands on maximum diversification / tangency instead. See below. |
|
|
49
|
+
| `HierarchicalRiskParity` | working | Dynamic HRP — the `gamma=0` special case of the Schur construction (recursive-bisection risk parity over the Fiedler order); named for recognisability. |
|
|
50
|
+
| `RiskParity` | working | Equal-risk-contribution (ERC); interior convex solution, solved by coordinate descent **warm-started from the previous weights** so updates stay smooth. |
|
|
51
|
+
| `EqualWeight`, `InverseVariance` | working | Smooth baselines for benchmarking (`1/n`; `w ∝ 1/σ²`). |
|
|
52
|
+
| `MinimumVariance`, `MaximumDiversification` | working | Closed-form `Σ⁻¹1` / `Σ⁻¹σ` with optional `shrinkage` for conditioning. Unconstrained (signed) so they stay smooth — a long-only QP would kink. |
|
|
53
|
+
|
|
54
|
+
Each has a river-style streaming twin for a *changing* universe — `StreamingThurstone`, `StreamingSchur`, `StreamingSchurBridge`, `StreamingHRP`, `StreamingRiskParity`, `StreamingEqualWeight`, `StreamingInverseVariance`, `StreamingMinimumVariance`, `StreamingMaximumDiversification`, `StreamingMaximumDecorrelation`, `StreamingMeanVariance` — with `learn_one({id: ret})` / `predict_one() → {id: weight}`.
|
|
55
|
+
|
|
56
|
+
**On smoothness.** Each method is written so that `partial_fit` over a drifting covariance moves weights only as much as the covariance moved. It helps to see `weights = allocator(cov_estimator(data))` as a product of two factors:
|
|
57
|
+
|
|
58
|
+
- For the **closed-form linear allocators** (`MinimumVariance`, `MaximumDiversification`, `InverseVariance`) the allocator factor is already a smooth (rational) function of `Σ`, so weight-smoothness *is* covariance-smoothness — pair them with a smooth online covariance (the default EWMA, a shrinkage estimator, or a `precise` skater) and you're done. Caveats: keep `Σ` well-conditioned (use `shrinkage`, since `Σ⁻¹` swings near a vanishing eigenvalue) and avoid hard long-only QPs (they kink at the zero bound — the smooth long-only min-variance is `SchurComplementary` as `gamma→1`).
|
|
59
|
+
- The package's distinctive work is the **other** family, where the allocator factor itself is rough no matter how smooth `Σ` is. Three sources, three primitives: sampling noise → common-seed transport (Thurstone); combinatorial ordering → Fiedler seriation (Schur/HRP); active-set kinks → keep the solution interior (ERC is interior by construction).
|
|
60
|
+
|
|
61
|
+
### Bridges, named by their endpoints
|
|
62
|
+
|
|
63
|
+
Schur conditioning is a technique; what matters is what it connects. Block
|
|
64
|
+
inversion writes the global direction `Σ⁻¹u` cluster by cluster: each cluster's
|
|
65
|
+
block is a minimum-variance direction on its covariance *conditional on everything
|
|
66
|
+
else*, with budget the inverse of that conditional variance. Every hierarchical
|
|
67
|
+
or clustered heuristic drops conditioning somewhere. `SchurBridge` puts it back by
|
|
68
|
+
degrees with two dials, `gamma` (cluster on the outside) and `eta` (asset on its
|
|
69
|
+
cluster mates), and a bridge earns its name only if **both ends are exact**:
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
from allocation import SchurBridge
|
|
73
|
+
|
|
74
|
+
SchurBridge.hrp_to_min_variance(gamma=0.5) # HRP -> min-variance, one dial
|
|
75
|
+
SchurBridge.hmv_to_min_variance(gamma=0.5) # hierarchical min-var (Cotton 2024) -> min-variance
|
|
76
|
+
SchurBridge.herc_to_min_variance(gamma=0.5, eta=0.5, n_clusters=8) # HERC -> min-variance, the square
|
|
77
|
+
SchurBridge.nco_to_min_variance(gamma=0.5, n_clusters=8) # NCO -> min-variance
|
|
78
|
+
SchurBridge.inverse_variance_to_min_variance(eta=0.5) # inverse variance -> min-variance (Stevens' path)
|
|
79
|
+
SchurBridge.hrp_to_min_variance(0.5, companion="vol") # ... -> maximum diversification
|
|
80
|
+
SchurBridge.hrp_to_min_variance(0.5, companion="mean") # ... -> tangency
|
|
81
|
+
est.endpoints_ # e.g. ('HRP', 'minimum variance')
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The HRP bridge needs a split rule that moves with the dial: the variance of the
|
|
85
|
+
child's `eta`-damped naive portfolio on its conditioned pair (`split='dial'`). HRP's
|
|
86
|
+
own naive split (`split='hrp'`, the rule inside `SchurComplementary` and skfolio)
|
|
87
|
+
starts at HRP but does not reach minimum variance; the min-variance split
|
|
88
|
+
(`split='minvar'`) reaches it but starts at hierarchical minimum variance. Which
|
|
89
|
+
published "Schur" is which is tabulated on the
|
|
90
|
+
[taxonomy page](https://schur.microprediction.org/taxonomy.html).
|
|
91
|
+
|
|
92
|
+
Two facts make the bridge the streaming choice. Everything after the partition is a
|
|
93
|
+
closed-form, continuous function of the covariance. And at the far end the weights
|
|
94
|
+
do not depend on the partition at all, so the turnover caused by a cluster
|
|
95
|
+
membership change shrinks to zero as the dials approach it (checked in
|
|
96
|
+
`experiments/bridge_churn_and_scale.py`): the dials that damp estimation noise damp
|
|
97
|
+
reclustering churn. The partition is the Fiedler order, which changes only when two
|
|
98
|
+
coordinates cross, bisected
|
|
99
|
+
(`n_clusters=None`) or cut into contiguous blocks (`n_clusters=k`), or a fixed label
|
|
100
|
+
vector (`clusters=`) such as sectors.
|
|
101
|
+
|
|
102
|
+
`conditioning` picks the path: `'siblings'` composes down the bisection tree
|
|
103
|
+
(exact, one half-size solve at the root), `'all'` conditions on every other asset
|
|
104
|
+
(exact, expensive), and `'factor'` conditions on one factor-mimicking portfolio per
|
|
105
|
+
other cluster, exact when cross-cluster dependence runs through one latent factor
|
|
106
|
+
per cluster and costing only cluster-sized solves; on a generic covariance it is
|
|
107
|
+
approximate at the far end, so the named constructors default to `'siblings'`. `long_only=True` caps `eta` at
|
|
108
|
+
each cluster's closed-form long-only frontier, and raises rather than return a
|
|
109
|
+
short position when the companion or outer layer would still produce one. The exact-arithmetic theory behind
|
|
110
|
+
the corners is in the papers at [schur.microprediction.org](https://schur.microprediction.org).
|
|
111
|
+
|
|
112
|
+
### Very large universes (e.g. Russell 3000)
|
|
113
|
+
|
|
114
|
+
Where the covariance is rank-deficient, every inversion-based allocator (min-variance,
|
|
115
|
+
max-diversification, …) is *undefined* (`is_singular()` flags it; `strict=True` refuses).
|
|
116
|
+
The robust large-universe methods are the ones that never invert `Σ`: inverse-variance,
|
|
117
|
+
HRP/Schur, and the **Thurstone tilt** — benchmark-anchored, no inversion, low turnover.
|
|
118
|
+
`ThurstonePortfolio(factors=k)` runs the tilt with a `k`-factor (low-rank) correlation and
|
|
119
|
+
an `O(M·n·k)` race instead of the dense `O(M·n²)+O(n³)` one, so it scales to thousands of
|
|
120
|
+
names (a few seconds per rebalance at n=3000 with the default path budget; fewer paths
|
|
121
|
+
trade accuracy for speed).
|
|
122
|
+
|
|
123
|
+
To make *minimum-variance itself* well-posed at scale, `FactorMinimumVariance` /
|
|
124
|
+
`FactorMaximumDiversification` invert a factor covariance `Σ = BBᵀ + diag(ψ)` via the
|
|
125
|
+
**Woodbury identity** (`O(n·k²)`, condition number bounded by an idiosyncratic floor, so the
|
|
126
|
+
inverse always exists). Supply a factor covariance (`loadings_` / `idiosyncratic_`, e.g. an
|
|
127
|
+
adapted `precise.FactorCovariance`) for the `O(n·k)` path, or let it factor a dense covariance.
|
|
128
|
+
And `SchurComplementary(ridge=…)` regularizes the cross-block solve so `gamma>0` stays
|
|
129
|
+
well-posed (and self-tempers toward HRP) when blocks are rank-deficient.
|
|
130
|
+
|
|
131
|
+
### Trading costs
|
|
132
|
+
|
|
133
|
+
`TurnoverPenalty(estimator, cost=λ)` wraps any estimator with a quadratic trading
|
|
134
|
+
cost (market-impact model). Minimising `‖w − w*‖² + λ‖w − w_prev‖²` gives the smooth,
|
|
135
|
+
budget-preserving blend `w_t = α w* + (1−α) w_prev` with `α = 1/(1+λ)`, so each step's
|
|
136
|
+
turnover is scaled exactly by `α`. This explicit damping composes with the implicit
|
|
137
|
+
turnover control (smooth target) and has a streaming twin `StreamingTurnoverPenalty`.
|
|
138
|
+
|
|
139
|
+
### Linear constraints
|
|
140
|
+
|
|
141
|
+
`BoxConstrained(estimator, lower=, upper=, groups=, group_caps=)` imposes per-asset
|
|
142
|
+
bounds and (disjoint) group caps on any estimator via a **log-barrier**, not a QP.
|
|
143
|
+
The barrier's domain *is* the constraint set, so the result is feasible for
|
|
144
|
+
any `tau>0` (strictly, except for coordinates that a bound or cap pins exactly) —
|
|
145
|
+
feasibility never depends on tuning. Infeasible or malformed constraints raise. The operator stays C¹ (no
|
|
146
|
+
active-set kinks), so turnover stays low. Streaming twin: `StreamingBoxConstrained`.
|
|
147
|
+
|
|
148
|
+
## Comparing methods
|
|
149
|
+
|
|
150
|
+
`allocation.backtest` is a numpy-only walk-forward harness: it trades each
|
|
151
|
+
estimator forward (form weights → earn next period → update) and tabulates
|
|
152
|
+
out-of-sample Sharpe, **turnover**, concentration, and Sharpe net of a
|
|
153
|
+
proportional cost. Turnover and cost are charged on the trades needed to return
|
|
154
|
+
drifted holdings to each new target.
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
from allocation import SchurComplementary, TurnoverPenalty, MinimumVariance
|
|
158
|
+
from allocation.backtest import compare, format_table, make_panel
|
|
159
|
+
|
|
160
|
+
panel = make_panel() # synthetic; or any (n_obs, n_assets) array
|
|
161
|
+
print(format_table(compare({
|
|
162
|
+
"schur": lambda: SchurComplementary(gamma=0.5),
|
|
163
|
+
"schur+λ": lambda: TurnoverPenalty(SchurComplementary(gamma=0.5), cost=3.0),
|
|
164
|
+
"minvar": lambda: MinimumVariance(shrinkage=0.1),
|
|
165
|
+
}, panel)))
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
No data is shipped (to avoid bloat); `compare()` takes any array, and
|
|
169
|
+
`load_returns_csv(url)` can pull a raw CSV from a data repo at runtime.
|
|
170
|
+
|
|
171
|
+
For a robust, universe-independent comparison, `compare_random_subsets(...)` runs
|
|
172
|
+
a Monte-Carlo over random name-subsets and windows, reporting each method's mean
|
|
173
|
+
± sd net Sharpe, mean turnover, and **win rate** (fraction of trials it had the
|
|
174
|
+
best net Sharpe) — averaging out the single-backtest dependence on which names
|
|
175
|
+
and period you happened to pick.
|
|
176
|
+
|
|
177
|
+
## Design
|
|
178
|
+
|
|
179
|
+
```
|
|
180
|
+
allocation/
|
|
181
|
+
base.py # BaseOnlinePortfolio: fit / partial_fit / predict / weights_
|
|
182
|
+
moments.py # EwmaCovariance (default); any partial_fit/covariance_ estimator plugs in
|
|
183
|
+
baselines.py # EqualWeight / InverseVariance / RiskParity (ERC)
|
|
184
|
+
convex.py # MinimumVariance / MaximumDiversification (closed-form, signed)
|
|
185
|
+
thurstone.py # ThurstonePortfolio
|
|
186
|
+
_thurstone/ # calibration + transport (thin over winning)
|
|
187
|
+
schur.py # SchurComplementary / HierarchicalRiskParity
|
|
188
|
+
bridge.py # SchurBridge: the pair-form engine the other allocators are corners of
|
|
189
|
+
_schur/ # Fiedler seriation + Schur coupling engine + the bridge (pairs, dials, knots)
|
|
190
|
+
keyed.py # river-style streaming twins over a changing universe
|
|
191
|
+
universe.py # keyed dynamic-universe state for the batch estimators (planned)
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Covariance is pluggable: the default is a light EWMA, but any online estimator
|
|
195
|
+
exposing `partial_fit` and `covariance_` (e.g. a `precise` skater) can be passed
|
|
196
|
+
via `covariance=`.
|
|
197
|
+
|
|
198
|
+
## Papers
|
|
199
|
+
|
|
200
|
+
The two novel methods are written up as working papers.
|
|
201
|
+
|
|
202
|
+
- **Thurstone Portfolios: Allocation as Winning Probability** — long-only
|
|
203
|
+
allocation as the winning probabilities of a correlated race: free of the
|
|
204
|
+
duplication paradox, tail-consistent when the race is driven by a
|
|
205
|
+
downside-dependent simulation, with an implied regularized objective and a
|
|
206
|
+
smoothness/turnover bound. Draft in
|
|
207
|
+
[`papers/thurstone-portfolios/`](papers/thurstone-portfolios), built on
|
|
208
|
+
[`thurstone`](https://github.com/microprediction/thurstone).
|
|
209
|
+
- **Schur-complementary allocation** — robust, inversion-light allocation along a
|
|
210
|
+
spectral (Fiedler) seriation; background and bibliography at
|
|
211
|
+
[schur.microprediction.org](https://schur.microprediction.org).
|
|
212
|
+
|
|
213
|
+
## Status
|
|
214
|
+
|
|
215
|
+
Early. The Thurstone, Schur/HRP, and risk-parity estimators all work and are
|
|
216
|
+
tested, batch and streaming; the keyed dynamic-universe layer for the *batch*
|
|
217
|
+
estimators (so `partial_fit` survives a changing asset count, as the streaming
|
|
218
|
+
twins already do) is next. The theory behind the Thurstone method (feasibility,
|
|
219
|
+
redundancy consistency, the implied regularized objective, smoothness/turnover)
|
|
220
|
+
is written up in the accompanying paper.
|
|
221
|
+
|
|
222
|
+
## License
|
|
223
|
+
|
|
224
|
+
MIT © Peter Cotton
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""allocation -- online (streaming) portfolio construction.
|
|
2
|
+
|
|
3
|
+
A home for online/streaming portfolio estimators that follow scikit-learn /
|
|
4
|
+
skfolio conventions (``fit`` / ``partial_fit`` / ``weights_`` / ``predict``) on
|
|
5
|
+
top of a keyed dynamic-universe layer, so they survive reconstituting universes.
|
|
6
|
+
|
|
7
|
+
Estimators:
|
|
8
|
+
from allocation import ThurstonePortfolio
|
|
9
|
+
w = ThurstonePortfolio(calib="market", phi=1.0).fit(returns).weights_
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from .base import BaseOnlinePortfolio
|
|
13
|
+
from .bridge import SchurBridge
|
|
14
|
+
from .baselines import EqualWeight, InverseVariance, RiskParity
|
|
15
|
+
from .constraints import BoxConstrained, StreamingBoxConstrained
|
|
16
|
+
from .convex import (
|
|
17
|
+
MaximumDecorrelation,
|
|
18
|
+
MaximumDiversification,
|
|
19
|
+
MeanVariance,
|
|
20
|
+
MinimumVariance,
|
|
21
|
+
)
|
|
22
|
+
from .diagnostics import negative_weight_diagnostic
|
|
23
|
+
from .factor import FactorMaximumDiversification, FactorMinimumVariance
|
|
24
|
+
from .costs import StreamingTurnoverPenalty, TurnoverPenalty
|
|
25
|
+
from .keyed import (
|
|
26
|
+
KeyedEwmaCovariance,
|
|
27
|
+
StreamingEqualWeight,
|
|
28
|
+
StreamingHRP,
|
|
29
|
+
StreamingInverseVariance,
|
|
30
|
+
StreamingMaximumDecorrelation,
|
|
31
|
+
StreamingMaximumDiversification,
|
|
32
|
+
StreamingMeanVariance,
|
|
33
|
+
StreamingMinimumVariance,
|
|
34
|
+
StreamingRiskParity,
|
|
35
|
+
StreamingSchur,
|
|
36
|
+
StreamingSchurBridge,
|
|
37
|
+
StreamingThurstone,
|
|
38
|
+
)
|
|
39
|
+
from .moments import DownsideSemicovariance, EwmaCovariance
|
|
40
|
+
from .schur import HierarchicalRiskParity, SchurComplementary
|
|
41
|
+
from .thurstone import ThurstonePortfolio
|
|
42
|
+
from .tilt import (
|
|
43
|
+
abilities_from_weights,
|
|
44
|
+
blend_correlation,
|
|
45
|
+
tilt_weights,
|
|
46
|
+
weights_from_abilities,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
__version__ = "0.0.2"
|
|
50
|
+
|
|
51
|
+
__all__ = [
|
|
52
|
+
"abilities_from_weights",
|
|
53
|
+
"weights_from_abilities",
|
|
54
|
+
"tilt_weights",
|
|
55
|
+
"blend_correlation",
|
|
56
|
+
# batch / skfolio-style
|
|
57
|
+
"BaseOnlinePortfolio",
|
|
58
|
+
"ThurstonePortfolio",
|
|
59
|
+
"SchurComplementary",
|
|
60
|
+
"SchurBridge",
|
|
61
|
+
"HierarchicalRiskParity",
|
|
62
|
+
"EqualWeight",
|
|
63
|
+
"InverseVariance",
|
|
64
|
+
"RiskParity",
|
|
65
|
+
"MinimumVariance",
|
|
66
|
+
"MaximumDiversification",
|
|
67
|
+
"MaximumDecorrelation",
|
|
68
|
+
"MeanVariance",
|
|
69
|
+
"FactorMinimumVariance",
|
|
70
|
+
"FactorMaximumDiversification",
|
|
71
|
+
"negative_weight_diagnostic",
|
|
72
|
+
"TurnoverPenalty",
|
|
73
|
+
"BoxConstrained",
|
|
74
|
+
"EwmaCovariance",
|
|
75
|
+
"DownsideSemicovariance",
|
|
76
|
+
# streaming / river-style (changing universe)
|
|
77
|
+
"StreamingThurstone",
|
|
78
|
+
"StreamingSchur",
|
|
79
|
+
"StreamingSchurBridge",
|
|
80
|
+
"StreamingHRP",
|
|
81
|
+
"StreamingEqualWeight",
|
|
82
|
+
"StreamingInverseVariance",
|
|
83
|
+
"StreamingRiskParity",
|
|
84
|
+
"StreamingMinimumVariance",
|
|
85
|
+
"StreamingMaximumDiversification",
|
|
86
|
+
"StreamingMaximumDecorrelation",
|
|
87
|
+
"StreamingMeanVariance",
|
|
88
|
+
"StreamingTurnoverPenalty",
|
|
89
|
+
"StreamingBoxConstrained",
|
|
90
|
+
"KeyedEwmaCovariance",
|
|
91
|
+
]
|