HKUDS/Vibe-Trading · error · ValueError

face_value must be positive, got {self.face_value!r}

Error message

face_value must be positive, got {self.face_value!r}

What it means

FixedIncome/Bond.__post_init__ requires face_value, when provided, to be strictly positive after float coercion, since a bond's par value cannot be zero or negative.

Source

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

    face_value: float | None = None
    coupon_rate: float | None = None
    coupon_frequency: int = 2
    maturity_date: date | None = None
    day_count: str = "30/360"

    def __post_init__(self) -> None:
        """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:

View on GitHub (pinned to 80ffdda44c)

Solutions

  1. Supply the actual positive par amount (commonly 1000 or 100)
  2. If par is unknown, pass face_value=None rather than 0
  3. Fix ingestion defaults so absent par fields become None, not zero

Example fix

# before
Bond(instrument_id='b1', face_value=0)

# after
Bond(instrument_id='b1', face_value=1000)
Defensive patterns

Strategy: validation

Validate before calling

face = row.get('face_value')
face = float(face) if face not in (None, '', 0) else None
if face is not None and face <= 0:
    raise ValueError(f'bad face_value {face}')
Bond(..., face_value=face)

Type guard

def is_valid_face_value(f) -> bool:
    return f is None or float(f) > 0

Prevention

When it happens

Trigger: Bond(..., face_value=0), face_value=-1000, or face_value supplied as 0.0 by a default/placeholder. Values are float()-coerced, so '1000' as a string works but 0/-5 does not.

Common situations: Missing par values defaulted to 0 in source files; a percent-of-par convention (0.98) confused with absolute par and multiplied out incorrectly; sign errors from short positions applied to the face field.

Related errors


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