HKUDS/Vibe-Trading · error · ValueError

lgd must be in [0.0, 1.0], got {lgd}

Error message

lgd must be in [0.0, 1.0], got {lgd}

What it means

expected_loss requires lgd (loss given default) to be in [0.0, 1.0] since it represents the fractional loss on exposure when default occurs. Values outside that range are not valid fractions of a loss.

Source

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

        ead: Exposure at Default in currency units >= 0.
        pd: Probability of Default in [0.0, 1.0].
        lgd: Loss Given Default in [0.0, 1.0].

    Returns:
        Expected loss amount in currency units.

    Raises:
        ValueError: If ead < 0, pd not in [0, 1], or lgd not in [0, 1].
    """
    ead = _require_finite(ead, "ead")
    pd = _require_finite(pd, "pd")
    lgd = _require_finite(lgd, "lgd")
    if ead < 0.0:
        raise ValueError(f"ead must be non-negative, got {ead}")
    if not (0.0 <= pd <= 1.0):
        raise ValueError(f"pd must be in [0.0, 1.0], got {pd}")
    if not (0.0 <= lgd <= 1.0):
        raise ValueError(f"lgd must be in [0.0, 1.0], got {lgd}")
    return float(ead * pd * lgd)


def vasicek_credit_var(
    ead: float,
    pd: float,
    lgd: float,
    asset_correlation: float,
    confidence: float = 0.999,
) -> dict:
    """Vasicek single-factor asymptotic credit risk portfolio model (Basel II/III capital framework).

    Under the Asymptotic Single Risk Factor (ASRF) model, conditional default
    probability at confidence level alpha is:
        WCDR(alpha) = Phi( (Phi^{-1}(PD) + sqrt(rho) * Phi^{-1}(alpha)) / sqrt(1 - rho) )

    where rho is the pairwise asset return correlation.

View on GitHub (pinned to 80ffdda44c)

Solutions

  1. Use decimals: lgd = 0.60 not 60
  2. If you have recovery rate R, pass lgd = 1.0 - R
  3. Keep LGD and recovery clearly named in your config to avoid swaps

Example fix

# before
el = expected_loss(1_000_000, pd=0.02, lgd=60)

# after
el = expected_loss(1_000_000, pd=0.02, lgd=0.60)
Defensive patterns

Strategy: validation

Validate before calling

lgd = 1.0 - recovery_rate if using_recovery else lgd
if not 0.0 <= lgd <= 1.0:
    raise ValueError(f"lgd out of range: {lgd}")
el = expected_loss(ead, pd, lgd)

Type guard

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

Try / catch

try:
    el = expected_loss(ead, pd, lgd)
except ValueError as e:
    if 'lgd' in str(e):
        el = expected_loss(ead, pd, 0.45)  # regulatory fallback LGD
    else:
        raise

Prevention

When it happens

Trigger: Calling expected_loss with lgd = 1.2, lgd = -0.1, lgd = 60 (percent), or accidentally passing recovery rate (0.4) when a 60% loss was intended.

Common situations: Percent-vs-decimal confusion (60 vs 0.6); mixing up LGD with recovery rate (LGD = 1 - recovery); hardcoded workout assumptions above 100%.

Related errors


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