HKUDS/Vibe-Trading · error · ValueError

survival_prob must be in (0.0, 1.0], got {survival_prob}

Error message

survival_prob must be in (0.0, 1.0], got {survival_prob}

What it means

survival_probability_to_hazard_rate converts a survival probability into a constant hazard rate via -ln(S)/T. The survival probability is a probability and only has meaning in (0.0, 1.0]: 0 implies certain default (infinite hazard), values above 1 are impossible, and negative values are nonsensical. The library validates this before taking the logarithm to avoid NaN/inf results.

Source

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


def survival_probability_to_hazard_rate(survival_prob: float, tenor_years: float) -> float:
    """Convert a survival probability Q(T) to implied constant hazard rate lambda = -ln(Q(T)) / T.

    Args:
        survival_prob: Survival probability in (0.0, 1.0].
        tenor_years: Time horizon in years > 0.

    Returns:
        Annualised hazard rate lambda.

    Raises:
        ValueError: If survival_prob is not in (0.0, 1.0] or tenor_years <= 0.
    """
    survival_prob = _require_finite(survival_prob, "survival_prob")
    tenor_years = _require_finite(tenor_years, "tenor_years")
    if survival_prob <= 0.0 or survival_prob > 1.0:
        raise ValueError(f"survival_prob must be in (0.0, 1.0], got {survival_prob}")
    if tenor_years <= 0.0:
        raise ValueError(f"tenor_years must be strictly positive, got {tenor_years}")
    return float(-np.log(survival_prob) / tenor_years)


def cds_price(
    spread_bps: float,
    recovery_rate: float = 0.40,
    tenor_years: float = 5.0,
    risk_free_rate: float = 0.03,
    coupon_bps: float = 100.0,
    notional: float = 10_000_000.0,
    payment_frequency: int = 4,
) -> dict:
    """Flat-hazard single-name Credit Default Swap (CDS) valuation engine.

    Computes the implied hazard rate, survival probability curve, Risky Present Value
    of a Basis Point (RPV01), protection leg PV, premium leg PV, fair par spread,

View on GitHub (pinned to 80ffdda44c)

Solutions

  1. Check that survival_prob is a decimal in (0.0, 1.0], not a percentage
  2. If the value comes from exp(-hazard*T), clamp tiny underflow results to a small epsilon like 1e-16
  3. Verify you are not passing a probability of default (PD) where survival probability S = 1 - PD is required

Example fix

# before
h = survival_probability_to_hazard_rate(95.0, 5.0)

# after
h = survival_probability_to_hazard_rate(0.95, 5.0)
Defensive patterns

Strategy: validation

Validate before calling

if not (0.0 < survival_prob <= 1.0):
    raise ValueError(f"invalid survival_prob: {survival_prob}")
h = survival_probability_to_hazard_rate(survival_prob, tenor_years)

Type guard

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

Try / catch

try:
    h = survival_probability_to_hazard_rate(sp, t)
except ValueError as e:
    logger.warning("bad survival probability input: %s", e)
    h = float('nan')

Prevention

When it happens

Trigger: Calling survival_probability_to_hazard_rate(survival_prob, tenor_years) with survival_prob <= 0.0 or > 1.0, e.g. 0.0, -0.2, 1.5, or a percentage like 95.0 instead of 0.95.

Common situations: Passing percentages (95.0) instead of decimals (0.95); passing a hazard rate where a survival probability was expected; chained computations that underflow to exactly 0.0 for long horizons.

Related errors


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