HKUDS/Vibe-Trading · error · ValueError

equity must be strictly positive to express drawdown as a fr

Error message

equity must be strictly positive to express drawdown as a fraction

What it means

drawdown_series expresses drawdown as a fraction of the running peak (values/peak - 1), so every equity value must be strictly positive. A zero or negative value (account blown up, short ledger sign, or bad data) would make the fraction undefined or nonsensical.

Source

Thrown at agent/src/quantlib/risk.py:281

    Raises:
        ValueError: If ``equity`` is not 1-D, has no finite observations, or contains values <= 0.
    """
    if not isinstance(equity, pd.Series):
        array = np.asarray(equity, dtype=float)
        if array.ndim > 1:
            raise ValueError(f"equity must be 1-D, got shape {array.shape}")
        series = pd.Series(array)
    else:
        series = equity.copy()

    series = series.astype(float)
    series = series[np.isfinite(series.to_numpy())]
    if series.empty:
        raise ValueError("equity contains no finite observation")
    values = series.to_numpy()
    if (values <= 0.0).any():
        raise ValueError("equity must be strictly positive to express drawdown as a fraction")

    running_peak = np.maximum.accumulate(values)
    dd = -(values / running_peak - 1.0)  # non-negative loss fraction
    return pd.Series(dd, index=series.index, name="drawdown")


def ulcer_index(equity: pd.Series | np.ndarray | Sequence[float]) -> float:
    """Calculate Peter Martin's Ulcer Index measuring downside drawdown volatility.

    Ulcer Index is the root-mean-square percentage drawdown:
        UI = sqrt( (1/N) * sum( (DD_t)^2 ) )

    Args:
        equity: Net-value / equity series, strictly positive.

    Returns:
        Ulcer Index as a positive decimal fraction.

View on GitHub (pinned to 80ffdda44c)

Solutions

  1. Convert PnL to an equity curve: equity = initial_capital + pnl.cumsum()
  2. Exponentiate log-equity: equity = np.exp(log_equity)
  3. Clean zeros/negatives from the raw data or use drawdown in currency terms with your own peak logic

Example fix

// before
dd = drawdown_series(pnl.cumsum())  # can go <= 0
// after
dd = drawdown_series(100_000 + pnl.cumsum())
Defensive patterns

Strategy: validation

Validate before calling

import numpy as np
assert (np.asarray(equity, dtype=float) > 0).all(), "equity must be strictly positive"

Type guard

import numpy as np

def is_positive_equity(x) -> bool:
    v = np.asarray(x, dtype=float)
    return bool(np.isfinite(v).all() and (v > 0).all())

Try / catch

try:
    dd = drawdown_series(equity)
except ValueError as e:
    if "strictly positive" in str(e):
        dd = drawdown_series(capital + pd.Series(equity).cumsum())
    else:
        raise

Prevention

When it happens

Trigger: drawdown_series([100, 0]), drawdown_series([100, -50]), or a PnL series (which crosses zero) passed where an equity/capital curve is expected.

Common situations: Passing cumulative PnL or log-equity instead of equity level; brokerage ledgers that go negative on margin; data errors with 0 placeholders for missing rows.

Related errors


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