virattt/ai-hedge-fund · critical · ValueError

held position {ticker} has no price within {_MARK_LOOKBACK_D

Error message

held position {ticker} has no price within {_MARK_LOOKBACK_DAYS} days of {as_of} — cannot value the book

What it means

Raised by _mark_prices (hedge_fund/pipeline/run_cycle.py:163) when a ticker the broker currently HOLDS has no price bar within the last _MARK_LOOKBACK_DAYS (=7) calendar days of as_of. The policy is asymmetric by design: a universe ticker with no recent bar is merely skipped (TickerSkip, 'missing data reads as no signal'), but a held ticker cannot be valued honestly, so the run raises rather than mark the book at zero or stale prices.

Source

Thrown at hedge_fund/pipeline/run_cycle.py:163

    held: dict,
    data_client: DataClient,
) -> tuple[dict[str, float], list[TickerSkip]]:
    """Last close on or before *as_of* for each ticker, within the lookback.

    No bar and not held -> TickerSkip (the caller then never runs analysts
    on it). No bar but HELD -> raise: the book cannot be honestly valued.
    """
    start = (_date.fromisoformat(as_of) - timedelta(days=_MARK_LOOKBACK_DAYS)).isoformat()
    marks: dict[str, float] = {}
    skipped: list[TickerSkip] = []

    for ticker in tickers:
        prices = data_client.get_prices(ticker, start, as_of)
        bars = [p for p in prices if p.time[:10] <= as_of]
        if bars:
            marks[ticker] = max(bars, key=lambda p: p.time).close
        elif ticker in held:
            raise ValueError(
                f"held position {ticker} has no price within "
                f"{_MARK_LOOKBACK_DAYS} days of {as_of} — cannot value the book"
            )
        else:
            skipped.append(TickerSkip(
                ticker=ticker,
                reason=f"no close within {_MARK_LOOKBACK_DAYS} days of {as_of}",
            ))

    return marks, skipped

View on GitHub (pinned to eff8a7320f)

Solutions

  1. Verify data coverage: check get_prices(ticker, as_of-7d, as_of) actually returns bars; if the cache is truncated for that ticker, refresh/re-fetch it.
  2. If the position is legitimately stale (halt/delisting), have the cycle logic liquidate or write off the position before the mark step instead of holding it into a valuation.
  3. Use more liquid tickers in the universe, or accept that halted names abort the run and wrap run_cycle per cycle with recovery logic.
  4. As a last resort, widen _MARK_LOOKBACK_DAYS — but understand this marks the book at up-to-N-day-old prices, changing valuation honesty.

Example fix

# before
record = run_cycle(fund, as_of, broker, client, universe)  # held MEgas halt -> ValueError mid-run

# after
from datetime import date, timedelta
look = (date.fromisoformat(as_of) - timedelta(days=7)).isoformat()
stale = [t for t in broker.positions() if not client.get_prices(t, look, as_of)]
if stale:
    # liquidate/flag stale names before the cycle
    for t in stale:
        broker.close(t)  # or record a write-off
record = run_cycle(fund, as_of, broker, client, universe)
Defensive patterns

Strategy: validation

Validate before calling

from datetime import date, timedelta

def stale_held_positions(client, broker, as_of: str, lookback_days: int = 7) -> list[str]:
    """Held tickers with no bar in the mark window — the exact raise condition."""
    start = (date.fromisoformat(as_of) - timedelta(days=lookback_days)).isoformat()
    return [
        t for t, _ in broker.positions().items()
        if not any(p.time[:10] <= as_of for p in client.get_prices(t, start, as_of))
    ]

Type guard

def position_is_markable(bars: list, as_of: str) -> bool:
    return any(b.time[:10] <= as_of for b in bars)

Try / catch

try:
    record = run_cycle(fund, as_of, broker, client, universe)
except ValueError as e:
    if "cannot value the book" in str(e):
        # deliberate policy: either liquidate the stale name and restart the
        # cycle, or abort the backtest. Do NOT mark it at zero silently.
        raise SystemExit(f"unmarkable position: {e}") from e
    raise

Prevention

When it happens

Trigger: A held ticker is delisted, halted, or thinly traded (no close in the 7-day window ending at as_of): e.g. an earnings halt, a suspension, or a backtest grid date that ran past the ticker's last trade. Also possible: the cached price data simply ends before end of the backtest window. Only fires when ticker in held — universe-only names never trigger it.

Common situations: Backtests that hold small-caps through halts; a delisting mid-window (data stops); using as_of dates beyond the last cached bar; data provider gaps of >7 days for OTC names.

Related errors


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