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_stderrView on GitHub (pinned to 80ffdda44c)
Solutions
- Pass the percentile: 95, not 0.95
- If your config stores a fraction, multiply by 100 before the call
- 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
- Percentiles here are 0-100, unlike the 0-1 confidence argument
- Name config keys threshold_pct vs confidence explicitly
- Use 90-99 for EVT tail fitting
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
- returns contains no finite observation
- confidence must be in (0, 1), got {confidence}
- horizon must be >= 1, got {horizon}
- s0 must be > 0, got {s0}
- parametric_var needs at least 2 observations for a std estim
AI-assisted analysis of HKUDS/Vibe-Trading@80ffdda44c (2026-08-28).
Data as JSON: /api/errors/b40ffed52e0d1948.
Report an issue: GitHub.