HKUDS/Vibe-Trading · error · ValueError

fit_ornstein_uhlenbeck needs a series that varies; this one

Error message

fit_ornstein_uhlenbeck needs a series that varies; this one is constant

What it means

fit_ornstein_uhlenbeck rejects input series whose lagged values have zero (ddof=0) standard deviation, i.e. a constant series. Mean-reversion estimation via OLS on lag pairs is degenerate when the regressor never varies, so the library raises instead of returning meaningless parameters.

Source

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

    Raises:
        ImportError: If ``statsmodels`` is not installed.
        ValueError: If ``dt <= 0``, fewer than 3 valid lag pairs remain, or series is constant.
    """
    if dt <= 0:
        raise ValueError(f"dt must be strictly positive, got {dt}")

    sm = _require("statsmodels.api", "statsmodels", "fit_ornstein_uhlenbeck")
    s = pd.Series(series, dtype=float).dropna()
    if not np.isfinite(s.values).all():
        raise ValueError("series contains non-finite values")

    lagged = s.shift(1)
    frame = pd.concat({"curr": s, "lag": lagged}, axis=1).dropna()
    if len(frame) < 3:
        raise ValueError(f"fit_ornstein_uhlenbeck needs at least 3 lag pairs, got {len(frame)}")
    if frame["lag"].std(ddof=0) == 0:
        raise ValueError("fit_ornstein_uhlenbeck needs a series that varies; this one is constant")

    exog = sm.add_constant(frame[["lag"]])
    params = _ols_params(frame["curr"], exog)
    a = float(params[0])
    b = float(params[1])

    residuals = frame["curr"] - (a + b * frame["lag"])
    n = len(frame)
    dof = max(1, n - 2)
    sigma_eps_sq = float(np.sum(residuals**2) / dof)
    sigma_eps = float(np.sqrt(sigma_eps_sq))

    if 0.0 < b < 1.0:
        theta = float(-np.log(b) / dt)
        mu = float(a / (1.0 - b))
        half_life = float(np.log(2.0) / theta)
        one_minus_b2 = float(1.0 - b**2)
        stat_var = float(sigma_eps_sq / one_minus_b2)

View on GitHub (pinned to 80ffdda44c)

Solutions

  1. Check the series varies before calling: s.std(ddof=0) > 0
  2. Inspect upstream data ingestion for a stuck feed or repeated fill-forward values
  3. If constant data is legitimate, skip the OU fit or handle it as zero-volatility case

Example fix

# before
res = fit_ornstein_uhlenbeck(pd.Series([5.0] * 100))
# after
if s.std(ddof=0) == 0:
    raise ValueError("series is constant; cannot fit OU")
res = fit_ornstein_uhlenbeck(s)
Defensive patterns

Strategy: validation

Validate before calling

s = pd.Series(data, dtype=float).dropna()
if len(s) < 4 or s.shift(1).std(ddof=0) == 0:
    raise ValueError('series must vary and have >= 3 lag pairs')

Type guard

def is_fittable_ou(s: pd.Series) -> bool:
    s = s.dropna()
    return len(s) >= 4 and s.shift(1).dropna().std(ddof=0) > 0

Try / catch

try:
    fit_ornstein_uhlenbeck(s)
except ValueError as e:
    if 'series that varies' in str(e):
        logger.warning('constant series; skipping OU fit: %s', e)
    else:
        raise

Prevention

When it happens

Trigger: Calling fit_ornstein_uhlenbeck with a series of identical values (e.g. np.full(100, 42.0)), or data that becomes constant after dropna of the lag frame (first N-1 values equal).

Common situations: Feeding prices from illiquid assets pinned at a fix, dummy/placeholder data in tests, or a mis-scaled series that rounds to a single value.

Related errors


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