HKUDS/Vibe-Trading · error · ValueError
performance must be 2-D, got shape {matrix.shape}
Error message
performance must be 2-D, got shape {matrix.shape} What it means
probability_of_backtest_overfitting converts the performance input to a 2-D (rows x strategies) matrix via pandas and requires exactly two dimensions. Passing a flat list of returns, a 1-D array, or a 3-D structure yields a matrix whose ndim != 2, which the CSCV splitting logic cannot index, so it raises this error naming the offending shape.
Source
Thrown at agent/src/quantlib/multipletesting.py:483
ddof: Delta degrees of freedom for the Sharpe standard deviation,
forwarded to :func:`sharpe_ratio`.
Returns:
A :class:`CSCVResult`.
Raises:
ValueError: If ``n_splits`` is odd or below 4, if fewer than 2
strategies are supplied (a rank needs competitors), if the sample
cannot give each subset at least 2 rows, or if every strategy has
zero variance so no Sharpe is defined.
"""
if n_splits < 4 or n_splits % 2 != 0:
raise ValueError(f"n_splits must be an even number >= 4, got {n_splits}")
frame = pd.DataFrame(performance)
matrix = frame.to_numpy(dtype=float)
if matrix.ndim != 2:
raise ValueError(f"performance must be 2-D, got shape {matrix.shape}")
n_rows, n_strategies = matrix.shape
if n_strategies < 2:
raise ValueError(
f"CSCV ranks strategies against each other and needs at least 2, "
f"got {n_strategies}"
)
subset_size = n_rows // n_splits
if subset_size < 2:
raise ValueError(
f"{n_rows} rows split {n_splits} ways gives {subset_size} row(s) per "
"subset; each subset needs at least 2 for a Sharpe"
)
used_rows = subset_size * n_splits
dropped = n_rows - used_rows
trimmed = matrix[:used_rows]View on GitHub (pinned to 80ffdda44c)
Solutions
- Reshape to (n_rows, n_strategies): np.asarray(performance).reshape(len(performance), -1) or pass a wide DataFrame with one column per strategy.
- If you truly have one strategy, PBO is undefined (see the >= 2 strategies error) — add competitors or skip the analysis.
- Check performance.squeeze() to drop accidental singleton dimensions before calling.
Example fix
# before pbo = probability_of_backtest_overfitting(returns_1d, n_splits=16) # raises # after wide = np.asarray(returns_list_of_strategies).T # shape (rows, strategies) pbo = probability_of_backtest_overfitting(wide, n_splits=16)
Defensive patterns
Strategy: type-guard
Validate before calling
perf = np.asarray(performance)
if perf.ndim != 2:
perf = perf.reshape(-1, perf.shape[-1]) if perf.ndim == 1 else perf.squeeze() Type guard
def is_2d_performance(p) -> bool:
return np.asarray(p).ndim == 2 Try / catch
try:
pbo = probability_of_backtest_overfitting(performance, n_splits)
except ValueError as e:
if 'must be 2-D' in str(e):
pbo = probability_of_backtest_overfitting(np.asarray(performance).reshape(-1, 1 if np.asarray(performance).ndim == 1 else -1).T, n_splits)
else:
raise Prevention
- Always build a (rows, strategies) wide DataFrame at the pipeline boundary.
- Assert .ndim == 2 in your data loader.
- Document the expected orientation in config comments.
When it happens
Trigger: Passing a 1-D list/array of returns for a single strategy, or a stacked 3-D array (trials x strategies x metrics), or a dict whose DataFrame conversion collapses to one dimension.
Common situations: Running PBO for a single strategy while prototyping; passing a list-of-lists-of-lists of per-trade returns; converting from a numpy tensor or xarray object that keeps extra dimensions.
Related errors
- n_splits must be an even number >= 4, got {n_splits}
- CSCV ranks strategies against each other and needs at least
- {n_rows} rows split {n_splits} ways gives {subset_size} row(
- no split produced a usable Sharpe; every strategy may have z
- {alpha_id}: output shape {result.shape} != close shape {ref.
AI-assisted analysis of HKUDS/Vibe-Trading@80ffdda44c (2026-08-28).
Data as JSON: /api/errors/7cb9bc36162f7069.
Report an issue: GitHub.