HKUDS/Vibe-Trading · error · ValueError
catch_up_rate={catch_up_rate!r} must exceed carry_rate={carr
Error message
catch_up_rate={catch_up_rate!r} must exceed carry_rate={carry_rate!r}, otherwise the catch-up tier can never complete. Pass catch_up_rate=0.0 for a fund with no catch-up. What it means
For the catch-up tier to ever complete, the GP's share during catch up (catch_up_rate) must strictly exceed the ongoing carry rate; otherwise the GP can never reach its carry level. If the fund has no catch-up, the API requires the explicit sentinel 0.0 to opt out.
Source
Thrown at agent/src/quantlib/fundmath.py:1179
Raises:
ValueError: If any amount is negative, if ``carry_rate`` is outside
``[0, 1)``, if ``catch_up_rate`` is outside ``[0, 1]``, or if a
non-zero ``catch_up_rate`` does not exceed ``carry_rate``.
"""
for label, value in (
("distributable", distributable),
("contributed_capital", contributed_capital),
("preferred_amount", preferred_amount),
):
if value < 0.0:
raise ValueError(f"{label} must be non-negative, got {value!r}")
if not 0.0 <= carry_rate < 1.0:
raise ValueError(f"carry_rate must be in [0, 1), got {carry_rate!r}")
if not 0.0 <= catch_up_rate <= 1.0:
raise ValueError(f"catch_up_rate must be in [0, 1], got {catch_up_rate!r}")
if catch_up_rate > 0.0 and catch_up_rate <= carry_rate:
raise ValueError(
f"catch_up_rate={catch_up_rate!r} must exceed carry_rate="
f"{carry_rate!r}, otherwise the catch-up tier can never complete. "
"Pass catch_up_rate=0.0 for a fund with no catch-up."
)
remaining = float(distributable)
return_of_capital = min(remaining, float(contributed_capital))
remaining -= return_of_capital
preferred_paid = min(remaining, float(preferred_amount))
remaining -= preferred_paid
if catch_up_rate > 0.0 and carry_rate > 0.0:
catch_up_target = carry_rate * preferred_paid / (catch_up_rate - carry_rate)
else:
catch_up_target = 0.0
catch_up = min(remaining, catch_up_target)View on GitHub (pinned to 80ffdda44c)
Solutions
- Pass catch_up_rate=0.0 for a fund with no catch-up tier
- Pass a full catch-up as 1.0 (the common '100% catch-up' term)
- Otherwise pass a rate strictly greater than carry_rate per the LPA
Example fix
# before waterfall_split(..., carry_rate=0.2, catch_up_rate=0.2) # after waterfall_split(..., carry_rate=0.2, catch_up_rate=0.0) # no catch-up # or full catch-up: waterfall_split(..., carry_rate=0.2, catch_up_rate=1.0)
Defensive patterns
Strategy: validation
Validate before calling
if catch_up_rate > 0.0:
assert catch_up_rate > carry_rate, "catch-up must exceed carry; use 0.0 to disable" Type guard
def catch_up_is_consistent(carry: float, catch_up: float) -> bool:
return catch_up == 0.0 or catch_up > carry Prevention
- Default catch_up_rate to 0.0, not to carry_rate
- Map LPA '100% catch-up' to 1.0 explicitly
When it happens
Trigger: Calling waterfall_split(carry_rate=0.2, catch_up_rate=0.2) or catch_up_rate=0.15 < carry_rate with catch_up_rate > 0.
Common situations: Defaulting catch_up_rate to the same value as carry_rate 'for consistency'; misreading a term sheet where the 100% catch-up should be passed as 1.0.
Related errors
- paid-in capital is {paid_in!r}; a capital multiple is undefi
- tier {self.name!r} has a negative {label} of {value!r}
- tier {self.name!r} does not conserve cash: lp {self.lp_amoun
- waterfall does not reconcile on {label}: tiers give {compute
- {label} must be non-negative, got {value!r}
AI-assisted analysis of HKUDS/Vibe-Trading@80ffdda44c (2026-08-28).
Data as JSON: /api/errors/5771d5ac2187c459.
Report an issue: GitHub.