HKUDS/Vibe-Trading · error · ValueError

a label cannot end before the observation it belongs to star

Error message

a label cannot end before the observation it belongs to starts

What it means

Raised by _as_label_spans in the purged cross-validation module when a label's end time is smaller than the index of the observation it belongs to, i.e. a label span ends before the observation's own start time. Since observation i is assumed to start at time i, label_end_times[i] < i is temporally impossible for forward-looking labels. The library rejects it because purging/embargo logic assumes labels extend at least to their observation start.

Source

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

        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]:
    """Build a training mask that is purged of overlap and embargoed after.

    Args:

View on GitHub (pinned to 80ffdda44c)

Solutions

  1. Verify label_end_times[i] >= i for every row; regenerate labels from the actual horizon end dates
  2. If labels are stored as timestamps, convert them with the same time-origin/index mapping used for the observations
  3. Check for row drops/reordering: recompute label_end_times on the same dataframe you pass as the sample
  4. Pass label_end_times=None to fall back to the identity span np.arange(n_samples) while debugging

Example fix

// before
label_ends = df['label_start'].to_numpy()  # starts, not ends
splits = list(purged_kfold_splits(X, n_folds=5, label_end_times=label_ends))
// after
label_ends = df['label_end'].to_numpy()
assert (label_ends >= np.arange(len(label_ends))).all()
splits = list(purged_kfold_splits(X, n_folds=5, label_end_times=label_ends))
Defensive patterns

Strategy: validation

Validate before calling

import numpy as np
label_ends = np.asarray(label_end_times)
assert label_ends.size == len(X)
assert np.isfinite(label_ends.astype(float)).all()
assert (label_ends >= np.arange(label_ends.size)).all(), 'label ends before observation start'

Prevention

When it happens

Trigger: Calling purged_kfold_splits, purged_walk_forward_splits, combinatorial_purged_splits, or detect_boundary_leakage with a label_end_times array (integer or datetime-converted-to-int) where some entry is less than its positional index, e.g. label_end_times=[5, 0, 7].

Common situations: Misaligned label arrays after slicing/dropping rows without reindexing, using label start times instead of end times, off-by-one when converting timestamps to integer indices, or sorting the sample without sorting label_end_times alongside.

Related errors


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