HKUDS/Vibe-Trading · error · ValueError

threshold_pct must be in (0, 100), got {threshold_pct}

Error message

threshold_pct must be in (0, 100), got {threshold_pct}

What it means

fit_gpd_tail fits a Generalized Pareto Distribution to loss exceedances beyond a percentile threshold, so threshold_pct must be strictly between 0 and 100. A value of 0 or 100 would select the sample min/max (degenerate threshold), and out-of-range values are meaningless percentiles.

Source

Thrown at agent/src/quantlib/risk.py:669

            shape_stderr (float): Asymptotic standard error of ``shape_xi``,
                ``|1 + xi| / sqrt(n_exceedances)``. Only valid for ``xi > -0.5``;
                below that the GPD likelihood is non-regular and the figure is
                indicative at best.
            scale_sigma (float): GPD scale, in units of loss magnitude.
            tail_type (str): ``"fat"`` when ``shape_xi`` clears
                ``GPD_SHAPE_SIGNIFICANCE_SIGMAS * shape_stderr``, ``"bounded"``
                when it clears it on the negative side, otherwise
                ``"exponential"`` -- i.e. a shape indistinguishable from zero at
                this sample size is reported as exponential rather than being
                rounded into one of the two extremes.

    Raises:
        ValueError: If ``threshold_pct`` is outside (0, 100), ``returns`` has no
            finite observation, or the threshold leaves fewer than 2 exceedances
            to fit.
    """
    if not 0.0 < threshold_pct < 100.0:
        raise ValueError(f"threshold_pct must be in (0, 100), got {threshold_pct}")
    values = _clean_returns(returns)
    threshold = float(np.percentile(values, threshold_pct))
    # Exceedances are loss magnitudes, hence non-negative: a positive number is
    # "how far below the threshold this return fell".
    exceedances = threshold - values[values < threshold]
    if exceedances.size < 2:
        raise ValueError(
            f"need at least 2 exceedances to fit a GPD, got {exceedances.size} "
            f"at threshold_pct={threshold_pct}"
        )

    shape, _loc, scale = genpareto.fit(exceedances, floc=0.0)
    # Asymptotic MLE standard error of the GPD shape. Empirically checked
    # against the spread of 12 refits per shape at n=2000: predicted
    # 0.0291/0.0246/0.0224/0.0179 vs observed 0.0313/0.0244/0.0212/0.0162 for
    # true xi of +0.30/+0.10/0.00/-0.20.
    shape_stderr = float(abs(1.0 + shape) / np.sqrt(exceedances.size))
    band = GPD_SHAPE_SIGNIFICANCE_SIGMAS * shape_stderr

View on GitHub (pinned to 80ffdda44c)

Solutions

  1. Pass the percentile: 95, not 0.95
  2. If your config stores a fraction, multiply by 100 before the call
  3. Typical values are 90–99 for tail fitting

Example fix

// before
res = fit_gpd_tail(returns, threshold_pct=0.95)
// after
res = fit_gpd_tail(returns, threshold_pct=95)
Defensive patterns

Strategy: validation

Validate before calling

assert 0.0 < threshold_pct < 100.0, "threshold_pct is a percentile in (0, 100), e.g. 95"

Type guard

def is_valid_threshold_pct(x) -> bool:
    return isinstance(x, (int, float)) and not isinstance(x, bool) and 0.0 < x < 100.0

Try / catch

try:
    res = fit_gpd_tail(returns, threshold_pct=t)
except ValueError as e:
    if "threshold_pct" in str(e):
        res = fit_gpd_tail(returns, threshold_pct=t * 100 if t < 1 else 95)
    else:
        raise

Prevention

When it happens

Trigger: fit_gpd_tail(returns, threshold_pct=95) is valid, but threshold_pct=0, 100, 101, or a fraction like 0.95 (confusing the confidence convention) fails; also negative values.

Common situations: Reusing a confidence fraction (0.95) where a percentile (95) is expected — the opposite confusion of the VaR confidence argument; config validation gaps.

Related errors


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