HKUDS/Vibe-Trading · error · ValueError

tier {self.name!r} has a negative {label} of {value!r}

Error message

tier {self.name!r} has a negative {label} of {value!r}

What it means

Each waterfall tier's allocation dataclass validates in __post_init__ that amount, lp_amount, and gp_amount are non-negative (within _ALLOCATION_TOLERANCE). A negative allocation would corrupt the LP/GP split downstream.

Source

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

    amount: float
    lp_amount: float
    gp_amount: float

    def __post_init__(self) -> None:
        """Check that the tier's two shares account for the whole tier.

        Raises:
            ValueError: If ``lp_amount + gp_amount`` differs from ``amount``, or
                if any figure is negative. A waterfall that does not conserve
                cash is a bug, never a rounding artefact worth tolerating.
        """
        for label, value in (
            ("amount", self.amount),
            ("lp_amount", self.lp_amount),
            ("gp_amount", self.gp_amount),
        ):
            if value < -_ALLOCATION_TOLERANCE:
                raise ValueError(
                    f"tier {self.name!r} has a negative {label} of {value!r}"
                )
        if not math.isclose(
            self.lp_amount + self.gp_amount,
            self.amount,
            rel_tol=_ALLOCATION_TOLERANCE,
            abs_tol=_ALLOCATION_TOLERANCE,
        ):
            raise ValueError(
                f"tier {self.name!r} does not conserve cash: lp {self.lp_amount!r} "
                f"+ gp {self.gp_amount!r} != {self.amount!r}"
            )


@dataclass(frozen=True)
class WaterfallResult:
    """Outcome of a European whole-of-fund distribution waterfall.

View on GitHub (pinned to 80ffdda44c)

Solutions

  1. Clamp allocations to >= 0 (max(value, 0.0)) where a partial distribution legitimately yields zero
  2. Audit the upstream split computation for overshoot (remaining cash exhausted before a tier fills)
  3. Fix fixture data sign errors

Example fix

# before
tier = Tier(name="catch_up", amount=amount, lp_amount=lp, gp_amount=gp)  # gp < 0 on partial catch-up

# after
clamped = max(0.0, gp)
tier = Tier(name="catch_up", amount=lp + clamped, lp_amount=lp, gp_amount=clamped)
Defensive patterns

Strategy: validation

Validate before calling

def safe_tier(name, lp, gp, tol=1e-9):
    lp, gp = max(0.0, lp), max(0.0, gp)
    return Tier(name=name, amount=lp + gp, lp_amount=lp, gp_amount=gp)

Prevention

When it happens

Trigger: Constructing a tier (e.g. WaterfallTier(name="catch-up", amount=-100.0, lp_amount=-60.0, gp_amount=-40.0)) with any negative field, or a builder that computes allocations which go negative on partial distributions.

Common situations: Rounding drift in generated tier splits; a carry/catch-up computation overshooting into negative remainders; hand-built fixtures with wrong signs.

Related errors


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