HKUDS/Vibe-Trading · error · ValueError

catch_up_rate must be in [0, 1], got {catch_up_rate!r}

Error message

catch_up_rate must be in [0, 1], got {catch_up_rate!r}

What it means

The catch-up rate must be in [0, 1] — it is the fraction of catch-up tier dollars the GP receives during the catch-up phase. Values outside that range have no meaning in the tier math.

Source

Thrown at agent/src/quantlib/fundmath.py:1177

    Returns:
        A :class:`WaterfallResult` whose tiers sum exactly to ``distributable``.

    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:

View on GitHub (pinned to 80ffdda44c)

Solutions

  1. Pass a fraction in [0,1]: 1.0 for full catch-up, 0.5 for 50%
  2. Convert percentages at the boundary

Example fix

# before
waterfall_split(..., catch_up_rate=100)

# after
waterfall_split(..., catch_up_rate=1.0)
Defensive patterns

Strategy: validation

Validate before calling

assert 0.0 <= catch_up_rate <= 1.0, catch_up_rate

Type guard

def is_unit_interval(x: float) -> bool:
    return isinstance(x, (int, float)) and 0.0 <= x <= 1.0

Prevention

When it happens

Trigger: Calling waterfall_split(catch_up_rate=1.5) or -0.1, e.g. passing 100 instead of 1.0 for a full catch-up.

Common situations: Percent-vs-fraction confusion (100 vs 1.0); copying a carry rate into the catch-up field with a different convention.

Related errors


AI-assisted analysis of HKUDS/Vibe-Trading@80ffdda44c (2026-08-28). Data as JSON: /api/errors/ba6a208262b53137. Report an issue: GitHub.