{"record":{"id":"a74338a5495aeaa2","repo":"virattt/ai-hedge-fund","slug":"spec-name-no-spec-benchmark-bars-in-start","errorCode":null,"errorMessage":"{spec.name}: no {spec.benchmark} bars in [{start}, {end}] — cannot build the trading grid","messagePattern":"(.+?): no (.+?) bars in \\[(.+?), (.+?)\\] — cannot build the trading grid","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"critical","filePath":"hedge_fund/backtesting/fund.py","lineNumber":96,"sourceCode":"    on_cycle: Callable[[int, int, CycleRecord], None] | None = None,\n) -> FundBacktestResult:\n    \"\"\"Run *fund* over *universe* through history from *start* to *end*.\n\n    One run_cycle per grid date against a persistent SimBroker — positions\n    and cash carry across ticks, so the fund rebalances rather than\n    restarts. `on_cycle(i, n, record)` fires after each tick (progress UIs).\n    The universe is the study's input, not the mandate's: the same fund can\n    be backtested over different names.\n\n    Fail loud: no benchmark bars in the window raises — a backtest with no\n    trading grid is an infrastructure problem, not an empty result.\n    \"\"\"\n    spec = fund.spec\n    universe = normalize_universe(universe)\n    bars = data_client.get_prices(spec.benchmark, start, end)\n    closes = {b.time[:10]: b.close for b in bars if start <= b.time[:10] <= end}\n    if not closes:\n        raise ValueError(\n            f\"{spec.name}: no {spec.benchmark} bars in [{start}, {end}] — \"\n            \"cannot build the trading grid\"\n        )\n    grid = rebalance_grid(sorted(closes), spec.rebalance)\n\n    broker = SimBroker(cash=spec.capital)\n    records: list[CycleRecord] = []\n    nav: list[float] = []\n    benchmark_nav: list[float] = []\n    base_close = closes[grid[0]]\n    for i, as_of in enumerate(grid):\n        record = run_cycle(fund, as_of, broker, data_client, universe)\n        records.append(record)\n        nav.append(record.nav)\n        benchmark_nav.append(spec.capital * closes[as_of] / base_close)\n        if on_cycle is not None:\n            on_cycle(i, len(grid), record)\n","sourceCodeStart":78,"sourceCodeEnd":114,"githubUrl":"https://github.com/virattt/ai-hedge-fund/blob/eff8a7320fcf0b473b135690fa1a5b0d9b022a83/hedge_fund/backtesting/fund.py#L78-L114","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"# before\nresult = run_backtest(fund, client, start=\"2024-01-06\", end=\"2024-01-07\")  # weekend-only window -> raises\n\n# after\n# include a trading day in the window\nresult = run_backtest(fund, client, start=\"2024-01-05\", end=\"2024-01-09\")","handlingStrategy":"validation","validationCode":"from datetime import date, timedelta\n\ndef window_has_trading_days(client, benchmark: str, start: str, end: str) -> bool:\n    \"\"\"True if at least one benchmark bar exists in [start, end] (includes a\n    weekend/holiday sanity check so an empty range never reaches the engine).\"\"\"\n    d0, d1 = date.fromisoformat(start), date.fromisoformat(end)\n    if d0 > d1:\n        return False\n    # a window with zero weekdays certainly has no trading days\n    days = 0\n    d = d0\n    while d <= d1 and days < 1:\n        if d.weekday() < 5:\n            days += 1\n        d += timedelta(days=1)\n    if days == 0:\n        return False\n    bars = client.get_prices(benchmark, start, end)\n    return any(start <= b.time[:10] <= end for b in bars)","typeGuard":null,"tryCatchPattern":"try:\n    result = run_backtest(fund, client, start, end, universe)\nexcept ValueError as e:\n    if \"cannot build the trading grid\" in str(e):\n        # fix ticker/dates, surface to user; do NOT treat as empty result\n        raise SystemExit(f\"bad benchmark/window: {e}\") from e\n    raise","preventionTips":["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."],"tags":["backtesting","data-validation","benchmark","date-range"],"backgroundTag":null,"analyzedSha":"eff8a7320fcf0b473b135690fa1a5b0d9b022a83","analyzedAt":"2026-08-15T00:22:46.567Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}