HKUDS/Vibe-Trading · error · ValueError

tenor_years must be strictly positive, got {tenor_years}

Error message

tenor_years must be strictly positive, got {tenor_years}

What it means

survival_probability_to_hazard_rate divides -ln(survival_prob) by tenor_years, so the tenor must be a strictly positive number of years. A zero or negative horizon would cause division by zero or a meaningless negative hazard rate, so it is rejected up front.

Source

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

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,
    and mark-to-market (MTM) upfront cash payment.

View on GitHub (pinned to 80ffdda44c)

Solutions

  1. Ensure tenor_years > 0; convert months/days to years before calling
  2. Fix upstream date math that produces 0 or negative horizons
  3. If a zero horizon is legitimate in your flow, skip the call or handle it as a special case

Example fix

# before
h = survival_probability_to_hazard_rate(0.95, 0.0)

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

Strategy: validation

Validate before calling

if tenor_years <= 0.0:
    raise ValueError(f"tenor_years must be > 0, got {tenor_years}")
h = survival_probability_to_hazard_rate(survival_prob, tenor_years)

Type guard

def is_valid_tenor(t: float) -> bool:
    return isinstance(t, (int, float)) and float(t) > 0.0 and math.isfinite(t)

Try / catch

try:
    h = survival_probability_to_hazard_rate(sp, t)
except ValueError as e:
    if 'tenor_years' in str(e):
        h = 0.0  # degenerate horizon
    else:
        raise

Prevention

When it happens

Trigger: Calling survival_probability_to_hazard_rate with tenor_years = 0.0, a negative number, or after _require_finite passes a value that is finite but non-positive (e.g. -1.0).

Common situations: Passing tenor in months (e.g. 6) where years are expected is fine, but passing 0 for an at-trade-date calculation, or a negative offset from a date arithmetic bug, triggers it.

Related errors


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