HKUDS/Vibe-Trading · error · ValueError

x_col and y_col must differ; both are {x_col!r}. A series ca

Error message

x_col and y_col must differ; both are {x_col!r}. A series cannot Granger-cause itself and the test returns p=1.0 regardless.

What it means

granger_test refuses x_col == y_col because a series cannot Granger-cause itself; statsmodels would accept the duplicated column and the F-test trivially fails to reject, returning p=1.0 for every lag — a non-finding that looks like a result.

Source

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

        data: Frame containing both columns.
        x_col: Name of the candidate predictor column.
        y_col: Name of the predicted column.
        max_lag: Maximum lag order to test; every lag from 1 to this is reported.

    Returns:
        Dict mapping lag (int, 1..``max_lag``) to the SSR F-test p-value (float).
        A small p-value rejects "x does not Granger-cause y".

    Raises:
        ImportError: If ``statsmodels`` is not installed.
        KeyError: If either column is missing from ``data``.
        ValueError: If ``max_lag`` is below 1, or if ``x_col`` and ``y_col`` are
            the same column. Testing a series against itself hands statsmodels a
            duplicated column, where the F-test trivially fails to reject and
            every p-value comes back 1.0 -- an answer that looks like a finding.
    """
    if x_col == y_col:
        raise ValueError(
            f"x_col and y_col must differ; both are {x_col!r}. A series cannot "
            "Granger-cause itself and the test returns p=1.0 regardless."
        )
    stattools = _require("statsmodels.tsa.stattools", "statsmodels", "granger_test")
    if max_lag < 1:
        raise ValueError(f"granger_test needs max_lag >= 1, got {max_lag}")
    missing = [c for c in (y_col, x_col) if c not in data.columns]
    if missing:
        raise KeyError(f"granger_test: column(s) not in data: {missing}")

    # statsmodels >= 0.14 dropped the `verbose` kwarg and prints the full test
    # table to stdout unconditionally; swallow it so a library call stays quiet.
    with contextlib.redirect_stdout(io.StringIO()):
        results = stattools.grangercausalitytests(data[[y_col, x_col]].dropna(), maxlag=max_lag)
    return {lag: float(results[lag][0]["ssr_ftest"][1]) for lag in range(1, max_lag + 1)}


def fit_garch(returns: pd.Series, horizon: int = 5) -> dict:

View on GitHub (pinned to 80ffdda44c)

Solutions

  1. Pass two distinct column names
  2. When looping over candidate drivers, skip the target: if col != y_col
  3. Add an assertion/config check that x_col != y_col before calling

Example fix

# before
res = granger_test(df, x_col='ret', y_col='ret')
# after
for col in df.columns:
    if col == 'ret':
        continue
    res = granger_test(df, x_col=col, y_col='ret')
Defensive patterns

Strategy: type-guard

Validate before calling

assert x_col != y_col, 'cannot Granger-test a series against itself'

Type guard

def is_valid_granger_pair(x_col: str, y_col: str) -> bool:
    return x_col != y_col

Try / catch

try:
    granger_test(data, x_col, y_col)
except ValueError as e:
    if 'must differ' in str(e):
        continue  # skip self-pair in driver loops
    raise

Prevention

When it happens

Trigger: Calling granger_test(data, x_col='close', y_col='close'), or using a variable for both columns that resolves to the same name via a loop/config typo.

Common situations: Looping over candidate drivers and forgetting to exclude the target column, copy-paste of the same column name, or config-driven column selection that collapses to one name.

Related errors


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