virattt/ai-hedge-fund · critical · ValueError
{spec.name}: equity is {equity_before:.2f} as of {as_of} — c
Error message
{spec.name}: equity is {equity_before:.2f} as of {as_of} — cannot size positions against a non-positive book What it means
Raised in run_cycle (hedge_fund/pipeline/run_cycle.py:75) when the fund's equity (cash + marked value of held positions) is zero or negative at the start of a cycle. Position sizing scales targets against the current book, so a non-positive book makes sizing mathematically undefined — the run stops loudly instead of producing nonsense leverage.
Source
Thrown at hedge_fund/pipeline/run_cycle.py:75
The universe is an argument, not a mandate field: a fund is its desk —
strategies, staff, risk, capital — and can be pointed at any names. What
it was asked to trade this tick is recorded on the returned CycleRecord.
"""
spec = fund.spec
universe = normalize_universe(universe)
held = broker.positions()
marks, skipped = _mark_prices(
sorted(set(universe) | set(held)), as_of, held, data_client,
)
cash_before = broker.cash()
equity_before = cash_before + sum(
p.shares * marks[t] for t, p in held.items()
)
if equity_before <= 0:
raise ValueError(
f"{spec.name}: equity is {equity_before:.2f} as of {as_of} — "
"cannot size positions against a non-positive book"
)
tradeable = [t for t in universe if t in marks]
# Each strategy runs its own analysts and blends its own sleeve; the fund
# nets the sleeves by capital slice. A persona staffed into two strategies
# is asked twice, but the second ask is a prompt-cache hit, not spend.
total_slice = sum(s.weight for s, _ in fund.strategies)
strategy_records: list[StrategyRecord] = []
netted: dict[str, float] = {t: 0.0 for t in tradeable}
for strategy, staff in fund.strategies:
signals: list[Signal] = []
for ticker in tradeable:
for model in staff:
signals.append(model.predict(ticker, as_of, data_client))
blend = blend_signals(View on GitHub (pinned to eff8a7320f)
Solutions
- Inspect the CycleRecords leading up to the failure: the equity trajectory in prior cycles shows whether this is legitimate ruin or a marking/fill bug.
- If it's genuine ruin, tighten the mandate's risk limits (per-position caps, gross exposure) or increase spec.capital and re-run.
- If cash goes negative implausibly fast, audit broker fills/fees (SimBroker) and the marks in _mark_prices for stale or wrong prices.
- Start the backtest from a date where the book is positive, or reset the broker before the run.
Example fix
# before
# mandate.yaml
capital: 10000
risk: {max_gross: 5.0} # 5x leverage -> equity hits 0 mid-backtest -> ValueError
# after
capital: 100000
risk: {max_gross: 1.5} Defensive patterns
Strategy: validation
Validate before calling
def book_is_positive(broker, marks: dict[str, float]) -> bool:
"""Equity check identical to run_cycle's, runnable before the call."""
held = broker.positions()
equity = broker.cash() + sum(p.shares * marks[t] for t, p in held.items())
return equity > 0 Type guard
def is_ruined(equity: float) -> bool:
return not (equity > 0) # True for 0, negative, and NaN Try / catch
from datetime import date, timedelta
look = (date.fromisoformat(as_of) - timedelta(days=7)).isoformat()
marks, _ = _mark_like_prices(client, sorted(broker.positions()), as_of)
if broker.cash() + sum(p.shares * marks[t] for t, p in broker.positions().items()) <= 0:
raise SystemExit(f"book is non-positive before {as_of}; stopping run") Prevention
- Track equity per cycle from CycleRecords and stop the run at a drawdown threshold you choose, before it hits zero.
- Set mandate risk limits (per-position caps, gross exposure) so simulated ruin requires a real bug to reach.
- Sanity-check spec.capital > 0 at mandate load time.
- If equity crosses zero implausibly fast, audit SimBroker fills and fee modeling before re-running.
When it happens
Trigger: A SimBroker that has already lost everything (equity hit <= 0 through cumulative losses or fees in a long backtest); a negative cash state from a bug in fill simulation; a spec.capital of 0 combined with no positions. The check runs every cycle before strategy evaluation, using marks from _mark_prices for held tickers.
Common situations: High-leverage mandates bleeding to ruin mid-backtest (risk limits set too loose); a fill/fee model bug driving cash negative; spec.capital misconfigured as 0; short positions marked against the book across a crash.
Related errors
- {spec.name}: no {spec.benchmark} bars in [{start}, {end}] —
- unknown rebalance cadence {cadence!r}
- held position {ticker} has no price within {_MARK_LOOKBACK_D
- no {spec.benchmark} bars in [{start}, {end}] — cannot build
AI-assisted analysis of virattt/ai-hedge-fund@eff8a7320f (2026-08-15).
Data as JSON: /api/errors/744f925b6a56f5ce.
Report an issue: GitHub.