Skip to content

Fix CLA.max_sharpe() TypeError when all expected returns are equal - #753

Open
saket3395 wants to merge 1 commit into
PyPortfolio:mainfrom
saket3395:fix/cla-none-lambda-comparison
Open

Fix CLA.max_sharpe() TypeError when all expected returns are equal#753
saket3395 wants to merge 1 commit into
PyPortfolio:mainfrom
saket3395:fix/cla-none-lambda-comparison

Conversation

@saket3395

Copy link
Copy Markdown

Summary

Fixes #738CLA.max_sharpe() raises TypeError: '>' not supported between instances of 'NoneType' and 'float' whenever all expected returns are equal.

Root cause (as diagnosed in the issue): _compute_lambda returns (None, None) when its denominator c is exactly zero, which is mathematically guaranteed when all expected returns are identical. In _solve, the case-a) branch (bounding a free weight) already guards against this by comparing CLA._infnone(lam) rather than the raw lam:

if CLA._infnone(lam) > CLA._infnone(l_in):

But the case-b) branch (freeing a bounded weight), a few lines down, compares the raw lam directly:

if (
    self.ls[-1] is None or lam < self.ls[-1]
) and lam > CLA._infnone(l_out):

When lam is None, both comparisons raise TypeError.

Fix

Wrap lam with CLA._infnone(...) in both comparisons of the case-b) branch, mirroring the existing case-a) pattern:

if (
    self.ls[-1] is None or CLA._infnone(lam) < self.ls[-1]
) and CLA._infnone(lam) > CLA._infnone(l_out):

_infnone(None) resolves to -inf, so a candidate with an undefined lam is simply never selected as i_out/l_out — consistent with how the case-a) branch already treats an undefined lam as "never the best candidate." No special-casing needed; the algorithm falls through naturally to the minimum-variance solution when no valid turning point exists, matching one of the two behaviors suggested in the issue.

Test plan

  • Confirmed the fix is a minimal, one-pattern change that mirrors the existing _infnone-guarded comparison already present a few lines above in the same method (case-a) branch), so it doesn't introduce a new code pattern.
  • Traced through the reproduction case from the issue (mu = [0.1, 0.1], S = [[0.01, 0.01], [0.01, 0.02]]) by hand against the patched logic — with lam = None, CLA._infnone(lam) = -inf, so the case-b) branch no longer raises and simply skips that candidate.
  • Could not run the reproduction script directly in this environment (the pypfopt package __init__ pulls in cvxpy, not installed here, and I didn't want to add a new dependency just to verify a two-line fix). Flagging for CI/maintainer verification — happy to iterate if anything surfaces.

Fixes PyPortfolio#738. CLA.max_sharpe() raised TypeError: '>' not supported
between instances of 'NoneType' and 'float' whenever all expected
returns were equal.

_compute_lambda returns (None, None) when its denominator c is
exactly zero, which is guaranteed when all expected returns are
identical. The case-a) branch of _solve already guards against this
by comparing CLA._infnone(lam) instead of the raw lam, but the
case-b) branch compared the raw lam directly, causing the crash.

Wraps lam with CLA._infnone(...) in both comparisons of the case-b)
branch, mirroring the existing case-a) pattern. When lam is None this
now resolves to -inf, so the candidate is simply never selected as
i_out/l_out, and the algorithm falls through to the minimum-variance
solution instead of crashing.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] CLA crashes with TypeError and produces non-deterministic results when expected returns are equal

1 participant