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
- 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.
- Check that start <= end, both are YYYY-MM-DD, and the window contains at least one trading day (not all weekend/holidays).
- Widen the date window by a few days on each side so it straddles at least one benchmark trading day.
- 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
- Validate the benchmark ticker once per mandate with a direct get_prices probe before any long run.
- Keep start/end as canonical YYYY-MM-DD strings from a single date-parsing helper so the b.time[:10] compare can't silently fail.
- Never widen an empty window automatically — decide explicitly whether the ticker or the dates are wrong.
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
- no {spec.benchmark} bars in [{start}, {end}] — cannot build
- unknown rebalance cadence {cadence!r}
- {ticker} as of {as_of}: only {len(metrics)} filed periods (n
- {spec.name}: equity is {equity_before:.2f} as of {as_of} — c
AI-assisted analysis of virattt/ai-hedge-fund@eff8a7320f (2026-08-15).
Data as JSON: /api/errors/a74338a5495aeaa2.
Report an issue: GitHub.