HKUDS/Vibe-Trading · error · ValueError

label_end_times holds a non-finite value

Error message

label_end_times holds a non-finite value

What it means

_as_label_spans casts array-based label_end_times to float then int positions; NaN or inf values would silently cast to garbage integers (e.g. NaN -> platform-dependent int), corrupting every purge decision. All values are therefore required to be finite.

Source

Thrown at agent/src/quantlib/crossvalidation.py:149

    if isinstance(label_end_times, pd.Series):
        if label_end_times.empty:
            raise ValueError("label_end_times is empty")
        starts = label_end_times.index
        ends = label_end_times.to_numpy()
        # searchsorted on the start index converts label end *times* into label
        # end *positions*; the right insertion point minus one keeps a label
        # that ends between two observations attached to the earlier one.
        positions = np.searchsorted(starts, ends, side="right") - 1
        positions = np.clip(positions, np.arange(len(starts)), len(starts) - 1)
        span_ends = positions.astype(int)
    else:
        span_ends = np.asarray(label_end_times, dtype=float)
        if span_ends.ndim != 1:
            raise ValueError(f"label_end_times must be 1-D, got shape {span_ends.shape}")
        if span_ends.size == 0:
            raise ValueError("label_end_times is empty")
        if not np.isfinite(span_ends).all():
            raise ValueError("label_end_times holds a non-finite value")
        span_ends = span_ends.astype(int)
        if (span_ends < np.arange(span_ends.size)).any():
            raise ValueError(
                "a label cannot end before the observation it belongs to starts"
            )

    if n_samples is not None and span_ends.size != n_samples:
        raise ValueError(
            f"label_end_times has {span_ends.size} entries but the sample has {n_samples}"
        )
    return span_ends


def _apply_purge_and_embargo(
    label_ends: np.ndarray,
    test_mask: np.ndarray,
    embargo_size: int,
) -> tuple[np.ndarray, int, int]:

View on GitHub (pinned to 80ffdda44c)

Solutions

  1. Drop or fill non-finite entries first: ends = ends[np.isfinite(ends)] or use fillna before calling
  2. Fix the merge/reindex that introduced NaNs by aligning on observation ids
  3. Add an assertion np.isfinite(ends).all() in your data pipeline

Example fix

# before
folds = purged_kfold_splits(np.array([2.0, np.nan, 5.0]), n_splits=2)

# after
ends = np.nan_to_num(np.array([2.0, np.nan, 5.0]), nan=3.0)
folds = purged_kfold_splits(ends, n_splits=2)
Defensive patterns

Strategy: validation

Validate before calling

ends = np.asarray(label_end_times, dtype=float)
if not np.isfinite(ends).all():
    bad = np.where(~np.isfinite(ends))[0]
    raise ValueError(f'non-finite label end times at positions {bad}')
folds = purged_kfold_splits(ends, n_splits=5)

Type guard

def all_finite(x) -> bool:
    return bool(np.isfinite(np.asarray(x, dtype=float)).all())

Try / catch

try:
    folds = purged_kfold_splits(ends, n_splits=5)
except ValueError as e:
    if 'non-finite' in str(e):
        ends = ends[np.isfinite(ends)]
        folds = purged_kfold_splits(ends, n_splits=5)
    else:
        raise

Prevention

When it happens

Trigger: Passing an array containing np.nan, np.inf, or -inf as label end positions, e.g. from a merge that introduced NaNs or an unfilled mask.

Common situations: NaNs introduced by left joins or reindexing on misaligned indexes; sentinel values like -999 replaced later but not here; inf from division by zero when computing end positions.

Related errors


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