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

  1. 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.
  2. If it's genuine ruin, tighten the mandate's risk limits (per-position caps, gross exposure) or increase spec.capital and re-run.
  3. If cash goes negative implausibly fast, audit broker fills/fees (SimBroker) and the marks in _mark_prices for stale or wrong prices.
  4. 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

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


AI-assisted analysis of virattt/ai-hedge-fund@eff8a7320f (2026-08-15). Data as JSON: /api/errors/744f925b6a56f5ce. Report an issue: GitHub.