HKUDS/Vibe-Trading · error · ValueError
tier {self.name!r} does not conserve cash: lp {self.lp_amoun
Error message
tier {self.name!r} does not conserve cash: lp {self.lp_amount!r} + gp {self.gp_amount!r} != {self.amount!r} What it means
A waterfall tier must conserve cash: lp_amount + gp_amount must equal amount within _ALLOCATION_TOLERANCE. This invariant catches split computations that leak or create dollars between the LP and GP sleeves.
Source
Thrown at agent/src/quantlib/fundmath.py:1035
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.
Attributes:
distributable: Cash entering the waterfall.
contributed_capital: LP capital that must be returned first.
preferred_amount: Preferred return owed before the GP earns anything.
tiers: The tiers in payment order.
lp_total: Everything allocated to the limited partners.
gp_total: Everything allocated to the general partner.
unreturned_capital: Contributed capital still outstanding, positive when
the distributable amount did not cover the return of capital.View on GitHub (pinned to 80ffdda44c)
Solutions
- Derive one sleeve from the other: gp_amount = round(amount - lp_amount, 2)
- Round amount and both sleeves consistently to the same precision
- Recompute the tier from a single split rate (gp_amount = amount * carry_rate)
Example fix
# before tier = Tier(name="carry", amount=100.0, lp_amount=80.0, gp_amount=15.0) # after amount = 100.0 lp = round(amount * 0.8, 2) tier = Tier(name="carry", amount=amount, lp_amount=lp, gp_amount=round(amount - lp, 2))
Defensive patterns
Strategy: validation
Validate before calling
import math assert math.isclose(lp + gp, amount, rel_tol=1e-9, abs_tol=1e-9), (lp, gp, amount)
Prevention
- Never round lp and gp independently; derive one from amount minus the other
- Use one rounding precision throughout the tier pipeline
When it happens
Trigger: Constructing a tier with amount=100.0, lp_amount=80.0, gp_amount=15.0 (sums to 95, not 100), typically from independent rounding of lp/gp shares.
Common situations: Rounding each sleeve to cents separately; carry split computed from a rate applied to a different base than amount; assembling tiers from separate data sources.
Related errors
- tier {self.name!r} has a negative {label} of {value!r}
- waterfall does not reconcile on {label}: tiers give {compute
- {label} must be non-negative, got {value!r}
- carry_rate must be in [0, 1), got {carry_rate!r}
- catch_up_rate must be in [0, 1], got {catch_up_rate!r}
AI-assisted analysis of HKUDS/Vibe-Trading@80ffdda44c (2026-08-28).
Data as JSON: /api/errors/e36bdd7282e2fa99.
Report an issue: GitHub.