HKUDS/Vibe-Trading · error · ValueError

{n_samples} samples cannot make {n_folds} folds

Error message

{n_samples} samples cannot make {n_folds} folds

What it means

purged_kfold_splits cannot produce n_folds non-empty test blocks from fewer than n_folds samples, so n_samples < n_folds is rejected. Each fold needs at least one observation for the test set.

Source

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

        n_samples: Number of observations.
        label_end_times: Where each label's outcome window ends. When None, each
            label is assumed to resolve within its own bar, so purging removes
            only the boundary and the embargo does the remaining work.
        n_folds: Number of folds, at least :data:`MIN_FOLDS`.
        embargo_fraction: Fraction of the sample embargoed after each test block.

    Yields:
        One :class:`Split` per fold, in chronological order of the test block.

    Raises:
        ValueError: If ``n_folds`` is below :data:`MIN_FOLDS`, exceeds the
            sample size, if ``embargo_fraction`` is negative or at least 1, or
            if ``label_end_times`` does not match the sample.
    """
    if n_folds < MIN_FOLDS:
        raise ValueError(f"n_folds must be at least {MIN_FOLDS}, got {n_folds}")
    if n_samples < n_folds:
        raise ValueError(f"{n_samples} samples cannot make {n_folds} folds")
    if not 0.0 <= embargo_fraction < 1.0:
        raise ValueError(
            f"embargo_fraction must be in [0, 1), got {embargo_fraction}"
        )

    if label_end_times is None:
        label_ends = np.arange(n_samples)
    else:
        label_ends = _as_label_spans(label_end_times, n_samples)

    embargo_size = int(round(n_samples * embargo_fraction))
    boundaries = np.linspace(0, n_samples, n_folds + 1).astype(int)

    for fold in range(n_folds):
        start, stop = int(boundaries[fold]), int(boundaries[fold + 1])
        if stop <= start:
            continue
        test_mask = np.zeros(n_samples, dtype=bool)

View on GitHub (pinned to 80ffdda44c)

Solutions

  1. Lower n_folds to at most n_samples (ideally much smaller)
  2. Increase the sample size / reduce filtering so n_samples >= n_folds
  3. Add an upfront check: if len(X) < n_folds: skip or reduce folds

Example fix

// before
splits = list(purged_kfold_splits(X_small, n_folds=10, label_end_times=le))
// after
n_folds = min(10, len(X_small))
splits = list(purged_kfold_splits(X_small, n_folds=n_folds, label_end_times=le))
Defensive patterns

Strategy: validation

Validate before calling

assert len(X) >= n_folds, f'{len(X)} samples cannot make {n_folds} folds'

Prevention

When it happens

Trigger: Calling purged_kfold_splits on a tiny sample (e.g. 3 rows with n_folds=5), or after heavy filtering reduced the dataframe below the configured fold count.

Common situations: Unit-test fixtures with a handful of rows, per-symbol or per-month slicing that leaves tiny samples, or a large n_folds (e.g. 20) inherited from a big dataset config.

Related errors


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