virattt/ai-hedge-fund · critical · ValueError

{spec.name}: no {spec.benchmark} bars in [{start}, {end}] —

Error message

{spec.name}: no {spec.benchmark} bars in [{start}, {end}] — cannot build the trading grid

What it means

Raised by the backtest runner when the benchmark ticker returned zero price bars inside the requested [start, end] window. The benchmark's closes define the 'trading grid' — the set of dates on which cycles fire — so an empty grid means the backtest cannot run at all. The library treats this as an infrastructure failure (bad ticker, wrong dates, or a data outage) rather than an empty result, per its fail-loud policy documented in the docstring at hedge_fund/backtesting/fund.py.

Source

Thrown at hedge_fund/backtesting/fund.py:96

    on_cycle: Callable[[int, int, CycleRecord], None] | None = None,
) -> FundBacktestResult:
    """Run *fund* over *universe* through history from *start* to *end*.

    One run_cycle per grid date against a persistent SimBroker — positions
    and cash carry across ticks, so the fund rebalances rather than
    restarts. `on_cycle(i, n, record)` fires after each tick (progress UIs).
    The universe is the study's input, not the mandate's: the same fund can
    be backtested over different names.

    Fail loud: no benchmark bars in the window raises — a backtest with no
    trading grid is an infrastructure problem, not an empty result.
    """
    spec = fund.spec
    universe = normalize_universe(universe)
    bars = data_client.get_prices(spec.benchmark, start, end)
    closes = {b.time[:10]: b.close for b in bars if start <= b.time[:10] <= end}
    if not closes:
        raise ValueError(
            f"{spec.name}: no {spec.benchmark} bars in [{start}, {end}] — "
            "cannot build the trading grid"
        )
    grid = rebalance_grid(sorted(closes), spec.rebalance)

    broker = SimBroker(cash=spec.capital)
    records: list[CycleRecord] = []
    nav: list[float] = []
    benchmark_nav: list[float] = []
    base_close = closes[grid[0]]
    for i, as_of in enumerate(grid):
        record = run_cycle(fund, as_of, broker, data_client, universe)
        records.append(record)
        nav.append(record.nav)
        benchmark_nav.append(spec.capital * closes[as_of] / base_close)
        if on_cycle is not None:
            on_cycle(i, len(grid), record)

View on GitHub (pinned to eff8a7320f)

Solutions

  1. Verify the benchmark ticker returns bars: call data_client.get_prices(spec.benchmark, start, end) directly and confirm the list is non-empty; fix the ticker in the mandate YAML if it returns nothing.
  2. Check that start <= end, both are YYYY-MM-DD, and the window contains at least one trading day (not all weekend/holidays).
  3. Widen the date window by a few days on each side so it straddles at least one benchmark trading day.
  4. If bars exist but the filter start <= b.time[:10] <= end still yields empty, confirm the bar timestamps are ISO strings whose first 10 chars are the date — a different time format in cached data breaks the slice.

Example fix

# before
result = run_backtest(fund, client, start="2024-01-06", end="2024-01-07")  # weekend-only window -> raises

# after
# include a trading day in the window
result = run_backtest(fund, client, start="2024-01-05", end="2024-01-09")
Defensive patterns

Strategy: validation

Validate before calling

from datetime import date, timedelta

def window_has_trading_days(client, benchmark: str, start: str, end: str) -> bool:
    """True if at least one benchmark bar exists in [start, end] (includes a
    weekend/holiday sanity check so an empty range never reaches the engine)."""
    d0, d1 = date.fromisoformat(start), date.fromisoformat(end)
    if d0 > d1:
        return False
    # a window with zero weekdays certainly has no trading days
    days = 0
    d = d0
    while d <= d1 and days < 1:
        if d.weekday() < 5:
            days += 1
        d += timedelta(days=1)
    if days == 0:
        return False
    bars = client.get_prices(benchmark, start, end)
    return any(start <= b.time[:10] <= end for b in bars)

Try / catch

try:
    result = run_backtest(fund, client, start, end, universe)
except ValueError as e:
    if "cannot build the trading grid" in str(e):
        # fix ticker/dates, surface to user; do NOT treat as empty result
        raise SystemExit(f"bad benchmark/window: {e}") from e
    raise

Prevention

When it happens

Trigger: Calling run_backtest (or the backtest entry in hedge_fund/backtesting/fund.py) with: (1) a spec.benchmark ticker that doesn't exist or is misspelled (get_prices returns empty/404s), (2) a start/end window where the benchmark has no trading days (weekend-only window, market holiday range), (3) start/end dates inverted or outside the cached data range, (4) a data-client outage that returns empty lists instead of raising.

Common situations: Typo'd benchmark ticker in the YAML mandate (e.g. '^SPX' vs 'SPY' vs the provider's symbol format); backtest window requested over a long weekend or holiday closure; dates passed as full timestamps vs YYYY-MM-DD so the string compare start <= b.time[:10] <= end never matches; local price cache populated for a different date range than requested.

Related errors


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