virattt/ai-hedge-fund · critical · ValueError

no {spec.benchmark} bars in [{start}, {end}] — cannot build

Error message

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

What it means

Raised in the TUI's off-thread backtest worker (_run in hedge_fund/tui/app.py:1778) under the same policy as error [0]: the benchmark returned no bars within [start, end], so no trading grid can be built. The TUI fetches benchmark bars first (via CachedDataClient wrapping FDClient) to size its progress UI before warming agents and replaying cycles — this check is the earliest failure point of a TUI-launched run.

Source

Thrown at hedge_fund/tui/app.py:1778

    def _after_done(self, event: OptionList.OptionSelected) -> None:
        # "Back to home" means home: a ctrl+b backtest sits on top of the run
        # screen, so popping once would land on its stale ticker input.
        while not isinstance(self.app.screen, HomeScreen):
            self.app.pop_screen()

    # ---- the worker (everything below the UI runs off-thread) -------------

    @work(thread=True, exclusive=True)
    def _run(self, spec: FundSpec, start: str, end: str,
             universe: list[str]) -> None:
        app = self.app
        try:
            with FDClient() as raw:
                bars = CachedDataClient(raw).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"no {spec.benchmark} bars in [{start}, {end}] — "
                    "cannot build the trading grid"
                )
            grid = rebalance_grid(sorted(closes), spec.rebalance)

            app.call_from_thread(self._begin_warm, spec, universe, len(grid))
            self._warm_market(spec, universe, grid)
            app.call_from_thread(self._begin_agents, spec)
            self._warm_agents(spec, universe, grid)
            app.call_from_thread(self._begin_replay, spec, closes, len(grid))

            fund = Fund(spec)

            def tick(i: int, n: int, record: CycleRecord) -> None:
                started = time.time()
                app.call_from_thread(self._board_tick, record)
                dwell = _CYCLE_DWELL - (time.time() - started)
                if dwell > 0:

View on GitHub (pinned to eff8a7320f)

Solutions

  1. Verify the benchmark bars before launching the run: a quick shell call to get_prices(spec.benchmark, start, end) tells you whether the ticker/window is the problem.
  2. Correct the benchmark ticker in the mandate YAML to a symbol the provider actually returns (check casing/format).
  3. Adjust the date range so it contains at least one benchmark trading day and lies within your cached data coverage.
  4. If the cache is stale/truncated for the benchmark, clear or refresh it, then retry from the TUI.

Example fix

# before
# TUI run with benchmark: 'spx' (unrecognized symbol) -> ValueError: no spx bars in [...]

# after
# mandate.yaml
benchmark: SPY   # a symbol the provider returns bars for
Defensive patterns

Strategy: validation

Validate before calling

def tui_run_is_launchable(client, spec, start: str, end: str) -> str | None:
    """None if ok, else a user-facing reason — call before starting the worker."""
    bars = client.get_prices(spec.benchmark, start, end)
    closes = [b.time[:10] for b in bars if start <= b.time[:10] <= end]
    if not closes:
        return (f"benchmark {spec.benchmark} has no bars in [{start}, {end}]; "
                "check the symbol and pick a range with trading days")
    return None

Try / catch

try:
    ...  # inside the @work(thread=True) worker
except ValueError as e:
    if "cannot build the trading grid" in str(e):
        app.call_from_thread(self.notify, f"Cannot start: {e}", severity="error")
        return
    raise

Prevention

When it happens

Trigger: Starting a backtest in the TUI with: a benchmark ticker that doesn't resolve (typo, wrong symbol format for the provider); a date range entirely on non-trading days; end date before the cached data begins or after it ends; the same string-compare pitfall where start/end aren't plain YYYY-MM-DD. Because it runs inside a @work(thread=True) worker, the ValueError surfaces through the TUI's error display rather than a console traceback.

Common situations: User types the benchmark symbol in a format the provider rejects; picks a holiday-week range in the date pickers; local cache was built for a different window so get_prices returns nothing; typo in the mandate's benchmark field surfaced only when the run starts.

Related errors


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