virattt/ai-hedge-fund · error · ValueError
universe is empty — a run needs at least one ticker
Error message
universe is empty — a run needs at least one ticker
What it means
Raised by normalize_universe (hedge_fund/fund/spec.py:156) — the single normalizer for every entry point (CLI flag, TUI input, API) — when the ticker list is empty after trimming, upper-casing, and de-duping. A cycle with nothing to trade is defined as a caller mistake: the library raises instead of returning an empty result.
Source
Thrown at hedge_fund/fund/spec.py:156
if duplicates:
raise ValueError(f"duplicate strategy names: {sorted(duplicates)}")
return strategies
def normalize_universe(tickers: list[str]) -> list[str]:
"""Clean a run's ticker list: upper-cased, de-duped, order preserved.
The single normalizer for every entry point (CLI flag, TUI input, a future
API), so what the engine trades can't drift by caller. Empty raises: a
cycle with nothing to trade is a caller mistake, not an empty result.
"""
universe: list[str] = []
for ticker in tickers:
upper = ticker.strip().upper()
if upper and upper not in universe:
universe.append(upper)
if not universe:
raise ValueError("universe is empty — a run needs at least one ticker")
return universe
def load_spec(path: str | Path) -> FundSpec:
"""Load a mandate from YAML. Validation errors carry the pydantic detail."""
with open(path) as f:
data = yaml.safe_load(f)
# Mandates used to carry a `universe`. Tickers are a run-time input now
# (see FundSpec), so drop the legacy key rather than fail extra='forbid'
# on funds saved by an older build.
data.pop("universe", None)
return FundSpec(**data)
def load_strategy(path: str | Path) -> StrategySpec:
"""Load one strategy (a library file under hedge_fund/strategies/) from YAML."""
with open(path) as f:
data = yaml.safe_load(f)View on GitHub (pinned to eff8a7320f)
Solutions
- Check the ticker list before invoking the run: if not tickers or all entries are blank, exit with a usage message instead of calling the engine.
- Fix the upstream producer: make the CLI flag required (nargs='+'), guard the TUI submit handler on non-empty input.
- Strip/split carefully: [t.strip().upper() for t in raw.split(',') if t.strip()] so empty comma segments don't create phantom entries.
Example fix
# before
result = run_backtest(fund, client, start, end, universe=arg_universe) # arg_universe == [] -> raises
# after
if not arg_universe:
raise SystemExit("pass at least one ticker, e.g. --universe AAPL MSFT")
result = run_backtest(fund, client, start, end, universe=arg_universe) Defensive patterns
Strategy: validation
Validate before calling
def clean_universe_arg(raw: str | list[str]) -> list[str]:
"""Parse/normalize before the engine sees it; None/empty -> explicit exit."""
if isinstance(raw, str):
items = [t.strip() for t in raw.split(",")]
else:
items = [t.strip() for t in raw]
tickers = [t.upper() for t in items if t]
if not tickers:
raise SystemExit("--universe needs at least one ticker, e.g. AAPL,MSFT")
return tickers Type guard
def is_non_empty_universe(u: object) -> bool:
return (
isinstance(u, (list, tuple))
and len(u) > 0
and all(isinstance(t, str) and t.strip() for t in u)
) Try / catch
try:
result = run_backtest(fund, client, start, end, universe)
except ValueError as e:
if "universe is empty" in str(e):
raise SystemExit("pass at least one ticker") from e
raise Prevention
- Make the universe CLI flag required (argparse nargs='+'), so argparse rejects empty input before the engine does.
- Guard TUI submit handlers on non-blank input.
- Split comma lists with a filter for empty segments: [t for t in raw.split(',') if t.strip()].
When it happens
Trigger: Passing [] as the universe argument to run_cycle or the backtest runner; passing [' ', ''] (only whitespace entries — all filtered out); a CLI invocation whose --universe flag ends up empty after parsing; the TUI submitting an empty ticker input box. Note the FundSpec itself no longer carries a universe (load_spec pops the legacy key), so this is purely a run-time input.
Common situations: Script iterates a filtered ticker list and the filter removes everything; CLI arg parsing bug dropping the universe values; TUI submitted before the user typed tickers; empty string in a config fed through split(',') producing [''].
Related errors
- unknown rebalance cadence {cadence!r}
- duplicate strategy names: {sorted(duplicates)}
- unknown model {m.name!r} in strategy {strategy.name!r}; avai
- confidence out of range: {confidence}
AI-assisted analysis of virattt/ai-hedge-fund@eff8a7320f (2026-08-15).
Data as JSON: /api/errors/f2f812e48bd03afa.
Report an issue: GitHub.