HKUDS/Vibe-Trading · error · ValueError

coupon_rate must be a decimal fraction (0.05 means 5%), got

Error message

coupon_rate must be a decimal fraction (0.05 means 5%), got {rate!r}; a value above 1.0 is almost certainly a percentage

What it means

Bond.__post_init__ enforces coupon_rate <= 1.0 with a decimal-fraction convention (0.05 = 5%). Values above 1.0 are assumed to be percentages mistakenly passed as-is (e.g. 5.0 meaning 5%) and are rejected.

Source

Thrown at agent/src/entities/models.py:369

        """Validate the base fields plus bond-specific ranges and consistency.

        Raises:
            ValueError: If ``face_value`` is non-positive, ``coupon_rate`` is
                negative or expressed as a percentage, the coupon frequency
                contradicts the coupon rate, or maturity precedes inception.
        """
        super().__post_init__()
        if self.face_value is not None:
            face = float(self.face_value)
            if face <= 0:
                raise ValueError(f"face_value must be positive, got {self.face_value!r}")
            object.__setattr__(self, "face_value", face)
        if self.coupon_rate is not None:
            rate = float(self.coupon_rate)
            if rate < 0:
                raise ValueError(f"coupon_rate cannot be negative, got {rate!r}")
            if rate > 1.0:
                raise ValueError(
                    f"coupon_rate must be a decimal fraction (0.05 means 5%), "
                    f"got {rate!r}; a value above 1.0 is almost certainly a "
                    "percentage"
                )
            object.__setattr__(self, "coupon_rate", rate)
        frequency = int(self.coupon_frequency)
        if frequency < 0:
            raise ValueError(
                f"coupon_frequency cannot be negative, got {self.coupon_frequency!r}"
            )
        if self.coupon_rate and frequency == 0:
            raise ValueError(
                f"coupon_frequency=0 marks a zero-coupon bond, but coupon_rate="
                f"{self.coupon_rate!r} is non-zero"
            )
        object.__setattr__(self, "coupon_frequency", frequency)
        if self.maturity_date is not None:
            object.__setattr__(

View on GitHub (pinned to 80ffdda44c)

Solutions

  1. Divide percent values by 100 before construction (4.25 -> 0.0425)
  2. If the source is basis points, divide by 10_000
  3. Centralize the conversion in the ingestion layer and document the fraction convention

Example fix

# before
Bond(instrument_id='b1', coupon_rate=5.0)  # meant 5%

# after
Bond(instrument_id='b1', coupon_rate=0.05)
Defensive patterns

Strategy: validation

Validate before calling

cr = float(row['coupon_rate'])
if cr > 1.0:
    cr /= 100.0  # percent -> decimal fraction
Bond(..., coupon_rate=cr)

Type guard

def is_decimal_fraction(r) -> bool:
    return r is None or 0.0 <= float(r) <= 1.0

Prevention

When it happens

Trigger: Bond(..., coupon_rate=5.0) meaning 5%, coupon_rate=650 for 6.5% stored as 6.5*100, or basis points passed raw. Values in [0, 1] pass; anything > 1.0 raises.

Common situations: Vendor feeds and spreadsheets quoting coupons in percent; users copying 'Coupon: 4.25%' values directly; mixed conventions across teams where one stores fractions and another percent.

Related errors


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