Repository navigation
✨ Add long-only max Sharpe - #14
Conversation
…ramming and its KKT conditions
| highest_return, | ||
| }); | ||
| } | ||
| let cov = self.adjusted_covariance(&config.covariance)?; |
There was a problem hiding this comment.
Unlike max_sharpe_allocation, which builds the covariance before checking the rate, this validates the covariance config after the NoPositiveExcessReturn check. With an invalid config (e.g. shrinkage outside [0, 1]) or a zero-variance asset, and a rate at or above the highest return, the caller gets NoPositiveExcessReturn instead of InvalidConfig or ZeroVariance. So the same inputs report different errors from the two max Sharpe methods. If the early rejection is meant (as in long_only_mean_variance_allocation), say in the # Errors docs which error comes first.
There was a problem hiding this comment.
Keeping this order on purpose. The rate check doesn't need Σ, so it runs before the covariance is built. That matches long_only_mean_variance_allocation, where review on #13 asked for an unreachable target to be rejected before adjusted_covariance, and it matches the Python reference. max_sharpe_allocation builds Σ first only because its threshold, the minimum variance return, needs Σ.
Adds long-only max Sharpe, the second of the 0.2.5 methods built on the active-set QP solver from #11. It takes the same
MaxSharpeConfigasmax_sharpe_allocation.long_only_max_sharpe_allocation(&MaxSharpeConfig)The method maximizes
(μᵀw − r_f) / √(wᵀΣw)subject to1ᵀw = 1andw ≥ 0. The ratio isn't a quadratic objective, but the change of variablesy = w / (μ − r_f)ᵀwturns it into one (Cornuejols and Tütüncü, 2007, §8.2):solve_nonnegativewithQ = Σ,c = 0and the single constraintE = μ − r_f.long_only_weightshelper from ✨ Add long-only mean-variance #13 already rescales the solution to sum to 1, which is exactly the map fromyback tow. Its doc now says so.When Σ is invertible and the tangency portfolio has no short positions, the two methods agree. When Σ is singular, several long-only portfolios can share the maximum, and the docs point to shrinkage, as for the other long-only methods.
When it exists
A long-only portfolio has a positive Sharpe ratio only if some asset's expected return is above the risk-free rate. So the long-only maximum needs
r_fbelow the highest expected return. That's a weaker condition than the unconstrained one, which needsr_fbelow the minimum variance portfolio's return. For rates between the two,max_sharpe_allocationreturnsRiskFreeRateTooHighand this method returns weights.CutupError::NoPositiveExcessReturn { risk_free_rate, highest_return }: returned when no asset's expected return is above the rate. A rate within1e-10 · (|r_f| + max |μᵢ|)of the highest return counts as at it, the same tolerance max Sharpe uses.RiskFreeRateTooHighisn't reused, because itsmin_variance_returnfield would be the wrong number here.CutupErroris#[non_exhaustive], so the new variant isn't breaking.Unbounded: the error when the optimum has zero variance, to withinn·εof Σ's largest entry, the same scale as max Sharpe's pseudo-inverse cutoff. The optimum's excess return is positive by construction, so zero variance means a riskless long-only portfolio beats the rate and the Sharpe ratio has no upper bound. That can only happen when Σ is singular.Tests
max_sharpe_allocationreturnsRiskFreeRateTooHigh, and this method holds only that asset.NoPositiveExcessReturn.Unboundedon the rank-one covariance with a zero-variance long-only portfolio, and weights once shrinkage is added.wis scaled. So at the optimum its gradient, proportional to(μ − r_f)·σ² − (μ − r_f)ᵀw·Σw, is zero on held assets and no higher on the others.PROPTEST_CASES=50000pass.scipy.optimize.linprogmaximizes the excess return over long-only portfolios withΣw = 0, and a positive maximum means the Sharpe ratio is unbounded.prices10x60, between the minimum variance return and the highest, where only the long-only maximum exists;Unboundedonrank_deficient6and weights oncollinear2, whose zero-variance hedge isn't long-only;NoPositiveExcessReturncases.Unbounded(1,138 and 818) and no unexpected errors.Docs
reference/README.mddescribes the transformed support enumeration and the linear program.compareexample adds a row.Next
Max diversification follows in its own PR, with the same change of variables and the asset volatilities in place of
μ − r_f.