{"record":{"id":"e44eee259243cd46","repo":"HKUDS/Vibe-Trading","slug":"equity-must-be-strictly-positive-to-express-drawdo","errorCode":null,"errorMessage":"equity must be strictly positive to express drawdown as a fraction","messagePattern":"equity must be strictly positive to express drawdown as a fraction","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"agent/src/quantlib/risk.py","lineNumber":281,"sourceCode":"\n    Raises:\n        ValueError: If ``equity`` is not 1-D, has no finite observations, or contains values <= 0.\n    \"\"\"\n    if not isinstance(equity, pd.Series):\n        array = np.asarray(equity, dtype=float)\n        if array.ndim > 1:\n            raise ValueError(f\"equity must be 1-D, got shape {array.shape}\")\n        series = pd.Series(array)\n    else:\n        series = equity.copy()\n\n    series = series.astype(float)\n    series = series[np.isfinite(series.to_numpy())]\n    if series.empty:\n        raise ValueError(\"equity contains no finite observation\")\n    values = series.to_numpy()\n    if (values <= 0.0).any():\n        raise ValueError(\"equity must be strictly positive to express drawdown as a fraction\")\n\n    running_peak = np.maximum.accumulate(values)\n    dd = -(values / running_peak - 1.0)  # non-negative loss fraction\n    return pd.Series(dd, index=series.index, name=\"drawdown\")\n\n\ndef ulcer_index(equity: pd.Series | np.ndarray | Sequence[float]) -> float:\n    \"\"\"Calculate Peter Martin's Ulcer Index measuring downside drawdown volatility.\n\n    Ulcer Index is the root-mean-square percentage drawdown:\n        UI = sqrt( (1/N) * sum( (DD_t)^2 ) )\n\n    Args:\n        equity: Net-value / equity series, strictly positive.\n\n    Returns:\n        Ulcer Index as a positive decimal fraction.\n","sourceCodeStart":263,"sourceCodeEnd":299,"githubUrl":"https://github.com/HKUDS/Vibe-Trading/blob/80ffdda44c5c4db0dd84d70e051cca591cea67df/agent/src/quantlib/risk.py#L263-L299","documentation":"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.","triggerScenarios":"drawdown_series([100, 0]), drawdown_series([100, -50]), or a PnL series (which crosses zero) passed where an equity/capital curve is expected.","commonSituations":"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.","solutions":["Convert PnL to an equity curve: equity = initial_capital + pnl.cumsum()","Exponentiate log-equity: equity = np.exp(log_equity)","Clean zeros/negatives from the raw data or use drawdown in currency terms with your own peak logic"],"exampleFix":"// before\ndd = drawdown_series(pnl.cumsum())  # can go <= 0\n// after\ndd = drawdown_series(100_000 + pnl.cumsum())","handlingStrategy":"validation","validationCode":"import numpy as np\nassert (np.asarray(equity, dtype=float) > 0).all(), \"equity must be strictly positive\"","typeGuard":"import numpy as np\n\ndef is_positive_equity(x) -> bool:\n    v = np.asarray(x, dtype=float)\n    return bool(np.isfinite(v).all() and (v > 0).all())","tryCatchPattern":"try:\n    dd = drawdown_series(equity)\nexcept ValueError as e:\n    if \"strictly positive\" in str(e):\n        dd = drawdown_series(capital + pd.Series(equity).cumsum())\n    else:\n        raise","preventionTips":["Always pass equity levels (capital + cumsum(PnL)), not PnL","Exponentiate log-equity before analysis","Scrub zero sentinels from exported data"],"tags":["quantlib","risk","drawdown","positive-values","valueerror"],"backgroundTag":"invalid-domain-value","analyzedSha":"80ffdda44c5c4db0dd84d70e051cca591cea67df","analyzedAt":"2026-08-28T12:46:38.989Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}