Skip to content

Add max diversification - #15

Merged
thinkjrs merged 6 commits into
developfrom
feature/max-diversification
Oct 27, 2026
Merged

thinkjrs merged 6 commits into
developfrom
feature/max-diversification

Conversation

@thinkjrs

@thinkjrs thinkjrs commented Oct 27, 2026 •

Copy link
Copy Markdown
Owner

Adds max diversification, the last of the 0.2.5 methods built on the active-set QP solver from #11.

max_diversification_allocation() and max_diversification_allocation_with_config(&CovarianceConfig)

The method maximizes the diversification ratio σᵀw / √(wᵀΣw) subject to 1ᵀw = 1 and w ≥ 0, where σ holds the asset volatilities from the adjusted covariance. It uses only the covariance, so its API matches long-only min variance. It has no long_only_ prefix because the method is long-only by definition.

The ratio has the same form as the Sharpe ratio, with σ in place of μ − r_f, so the same change of variables applies. Rescaling one asset doesn't change the ratio, so it's solved on the correlation matrix C rather than Σ:

minimize vᵀCv  subject to  1ᵀv = 1,  v ≥ 0,   then  w ∝ v / σ

Solving on Σ in raw units gave wrong weights, with no error, when volatilities were about eight orders of magnitude apart.

  • Shared helper: the solve and zero-variance check from long-only max Sharpe moved into long_only_max_ratio(cov, a, best), which both methods call. It returns None when the optimum is in the null space, so each caller names its own error. Long-only max Sharpe still solves on Σ.
  • Uncorrelated assets: the result is inverse volatility.
  • Singular covariance: if a long-only portfolio with zero variance exists, the ratio is unbounded and the method returns SingularCovariance, as risk parity does. The cutoff now runs on C, so it doesn't depend on units. Unbounded describes expected returns growing without limit, which doesn't fit a covariance-only method. Shrinkage or regularization gives weights.

Tests

  • Unit tests:
    • It matches the closed form w ∝ C⁻¹1 / σ on three assets with unequal correlations.
    • Two uncorrelated assets with volatilities 1e4, 1e8 and 1e12 apart give inverse volatility.
    • It returns SingularCovariance on the rank-one covariance and weights once shrinkage is added. It returns weights on the collinear pair, whose zero-variance hedge needs a short.
    • Bad shrinkage and a zero-variance asset are rejected.
  • Properties:
    • Optimality: the gradient, proportional to σ·wᵀΣw − σᵀw·Σw, is zero on held assets and no higher on the others.
    • Best long-only: its diversification ratio is at least 1 and at least that of every other long-only method. A shared long_only_methods list now feeds all three "best long-only" properties, so max diversification is also checked against long-only min variance and long-only max Sharpe.
    • Units: multiplying one asset's returns by k ∈ [1e-6, 1e6] divides its weight by k, then the weights are rescaled.
    • Invariants: it's added to the column-order, return-scale, never-short and sums-to-one checks, with and without shrinkage.
    • Runs: three runs at PROPTEST_CASES=50000 pass.
  • Reference fixtures: 173 → 196 cases, and the drift check reports 0 of 196 drifted.
    • The Python long-only max Sharpe code is now a general long_only_max_ratio(cov, a). Max diversification calls it on the correlation matrix. The long-only max Sharpe fixtures are unchanged.
    • New dataset: scales4 has four correlated assets with volatilities from about 1e-2 to 1e-10. Min variance and risk parity skip it, since they solve in raw units, where its covariance is numerically singular.
    • The new cases cover the full-rank datasets, scales4, the singular datasets with shrinkage, regularization on near_singular4, and rank_deficient6 without shrinkage, which expects SingularCovariance.
  • Probe: 2,000 random short histories with singular covariances, each with and without shrinkage, gave weights or SingularCovariance (2,979 and 1,021) and no other errors. Every set of weights was nonnegative and summed to 1.
  • Suite: all suites, clippy, docs and fmt pass on stable and on MSRV 1.78.

Docs

  • The CHANGELOG has entries for the method and its tests.
  • The README method list, feature list, method table and testing paragraph mention the method.
  • reference/README.md describes the correlation-matrix reference and scales4.
  • The compare example adds a row.

@thinkjrs thinkjrs self-assigned this Oct 27, 2026
@thinkjrs thinkjrs added the enhancement New feature or request label Oct 27, 2026
@thinkjrs thinkjrs changed the title Feature/max diversification Add max diversification Oct 27, 2026
Comment thread src/lib.rs
Comment thread src/lib.rs
Comment thread reference/generate.py
Comment thread tests/properties.rs Outdated
Comment thread src/lib.rs Outdated
Comment thread src/lib.rs Outdated
@thinkjrs
thinkjrs merged commit 48d65e0 into develop Oct 27, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant