HKUDS/Vibe-Trading · error · ValueError

recovery_rate must be in [0.0, 1.0), got {recovery_rate}

Error message

recovery_rate must be in [0.0, 1.0), got {recovery_rate}

What it means

cds_price uses recovery_rate to compute loss-given-default (LGD = 1 - R), which must lie in [0.0, 1.0). A recovery of 1.0 would give zero LGD and make the hazard approximation s/LGD blow up, while negative recovery is invalid in this model.

Source

Thrown at agent/src/quantlib/credit.py:804

            * ``par_spread_bps`` (float): Model par spread in basis points.
            * ``upfront_pct`` (float): Upfront payment as decimal fraction of notional.
            * ``upfront_amount`` (float): Net upfront cash payment (positive = buyer pays seller).
            * ``buyer_mtm`` (float): Mark-to-market value for the protection buyer.

    Raises:
        ValueError: If spread_bps < 0, recovery_rate not in [0, 1), tenor_years <= 0, or notional <= 0.
    """
    spread_bps = _require_finite(spread_bps, "spread_bps")
    recovery_rate = _require_finite(recovery_rate, "recovery_rate")
    tenor_years = _require_finite(tenor_years, "tenor_years")
    risk_free_rate = _require_finite(risk_free_rate, "risk_free_rate")
    coupon_bps = _require_finite(coupon_bps, "coupon_bps")
    notional = _require_finite(notional, "notional")
    payment_frequency = _require_finite(payment_frequency, "payment_frequency")
    if spread_bps < 0.0:
        raise ValueError(f"spread_bps must be non-negative, got {spread_bps}")
    if not (0.0 <= recovery_rate < 1.0):
        raise ValueError(f"recovery_rate must be in [0.0, 1.0), got {recovery_rate}")
    if tenor_years <= 0.0:
        raise ValueError(f"tenor_years must be strictly positive, got {tenor_years}")
    if notional <= 0.0:
        raise ValueError(f"notional must be strictly positive, got {notional}")
    if payment_frequency <= 0:
        raise ValueError(f"payment_frequency must be positive, got {payment_frequency}")

    s_dec = spread_bps / 10_000.0
    c_dec = coupon_bps / 10_000.0
    lgd = 1.0 - recovery_rate

    # Implied hazard rate lambda ≈ s / LGD
    lambda_hazard = float(s_dec / lgd) if lgd > 0 else 0.0

    n_periods = max(1, int(round(tenor_years * payment_frequency)))
    t_grid = np.linspace(tenor_years / n_periods, tenor_years, n_periods)
    t_prev = np.r_[0.0, t_grid[:-1]]
    dts = t_grid - t_prev

View on GitHub (pinned to 80ffdda44c)

Solutions

  1. Express recovery_rate as a decimal in [0.0, 1.0), e.g. 0.40
  2. If you were given LGD, convert: recovery_rate = 1 - lgd
  3. Cap recovery strictly below 1.0; use e.g. 0.999 if modeling near-full recovery

Example fix

# before
pv = cds_price(spread_bps=250, recovery_rate=40)

# after
pv = cds_price(spread_bps=250, recovery_rate=0.40)
Defensive patterns

Strategy: validation

Validate before calling

if not (0.0 <= recovery_rate < 1.0):
    raise ValueError(f"recovery_rate out of range: {recovery_rate}")
pv = cds_price(250.0, recovery_rate=recovery_rate, tenor_years=5.0)

Type guard

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

Try / catch

try:
    pv = cds_price(250.0, recovery_rate=r, tenor_years=5.0)
except ValueError as e:
    if 'recovery_rate' in str(e):
        r = 0.40  # fallback to standard assumption
        pv = cds_price(250.0, recovery_rate=r, tenor_years=5.0)
    else:
        raise

Prevention

When it happens

Trigger: Calling cds_price with recovery_rate = 1.0, recovery_rate < 0, or a percentage such as 40 instead of 0.40.

Common situations: Passing 40 (percent) instead of 0.40 (decimal); using a 100% recovery assumption; confusing recovery rate with LGD (passing 0.6 when you mean 40% recovery).

Related errors


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