HKUDS/Vibe-Trading · error · ValueError

find_hedge_ratio needs at least 3 aligned observations, got

Error message

find_hedge_ratio needs at least 3 aligned observations, got {len(frame)}

What it means

After aligning y and x and dropping NaN rows, fewer than 3 complete observations remain — not enough to fit the two-parameter OLS hedge regression. The library enforces a minimum sample size rather than emitting an unstable beta.

Source

Thrown at agent/src/quantlib/timeseries.py:362

        ``spread_mean`` (float), ``spread_std`` (float, sample ddof=1) and
        ``half_life`` (float, in observation periods).

    Raises:
        ImportError: If ``statsmodels`` is not installed.
        ValueError: If the series differ in length, carry different indices,
            fewer than 3 aligned non-NaN observations remain, or ``x`` is
            constant. A flat hedging leg is refused rather than fitted:
            ``sm.add_constant`` leaves an already-constant column alone, so the
            design would silently collapse to one column and the β lookup would
            be a bare ``IndexError``.
    """
    sm = _require("statsmodels.api", "statsmodels", "find_hedge_ratio")
    frame = pd.concat({"y": pd.Series(y, dtype=float), "x": pd.Series(x, dtype=float)}, axis=1)
    if len(frame) != len(pd.Series(y)) or len(frame) != len(pd.Series(x)):
        raise ValueError("find_hedge_ratio needs y and x sharing one index")
    frame = frame.dropna()
    if len(frame) < 3:
        raise ValueError(f"find_hedge_ratio needs at least 3 aligned observations, got {len(frame)}")
    if frame["x"].std(ddof=0) == 0:
        raise ValueError("find_hedge_ratio needs an x that varies; this one is constant")

    params = _ols_params(frame["y"], sm.add_constant(frame[["x"]]))
    intercept, beta = float(params[0]), float(params[1])
    spread = frame["y"] - beta * frame["x"]

    return {
        "hedge_ratio": beta,
        "intercept": intercept,
        "spread_mean": float(spread.mean()),
        "spread_std": float(spread.std()),
        "half_life": compute_half_life(spread),
    }


def granger_test(data: pd.DataFrame, x_col: str, y_col: str, max_lag: int = 5) -> dict:
    """Test whether ``x`` Granger-causes ``y``.

View on GitHub (pinned to 80ffdda44c)

Solutions

  1. Check the aligned, NaN-dropped overlap length before calling
  2. Extend the shared date range of the two inputs
  3. Drop or impute NaNs upstream so more complete pairs survive

Example fix

# before
ratio = find_hedge_ratio(y_short, x_short)
# after
frame = pd.concat({'y': y, 'x': x}, axis=1).dropna()
assert len(frame) >= 3, f"only {len(frame)} aligned observations"
ratio = find_hedge_ratio(y, x)
Defensive patterns

Strategy: validation

Validate before calling

frame = pd.concat({'y': pd.Series(y, dtype=float), 'x': pd.Series(x, dtype=float)}, axis=1).dropna()
if len(frame) < 3:
    raise ValueError(f'insufficient overlap: {len(frame)} rows')

Type guard

def has_enough_overlap(y, x, minimum: int = 3) -> bool:
    frame = pd.concat({'y': pd.Series(y, dtype=float), 'x': pd.Series(x, dtype=float)}, axis=1)
    return len(frame.dropna()) >= minimum

Try / catch

try:
    find_hedge_ratio(y, x)
except ValueError as e:
    if 'at least 3 aligned' in str(e):
        # widen date range or fetch more data
        raise
    raise

Prevention

When it happens

Trigger: Passing very short series (<3 points), series whose overlap after index alignment is under 3 rows, or series with many NaNs that leave <3 complete pairs after dropna.

Common situations: Mismatched date ranges where only 1-2 dates overlap, series with heavy missing data, or unit tests built from tiny hand-made arrays.

Related errors


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