virattt/ai-hedge-fund · error · ValueError
duplicate strategy names: {sorted(duplicates)}
Error message
duplicate strategy names: {sorted(duplicates)} What it means
Raised by the _unique_strategy_names field validator on FundSpec (hedge_fund/fund/spec.py:139) when two or more strategies in the mandate share the same name. Strategy names are the join key between the FundSpec and the instantiated models (Fund wires models by models[strategy.name]), so duplicates would make that lookup ambiguous — hence the spec refuses to construct.
Source
Thrown at hedge_fund/fund/spec.py:139
)
benchmark: str = Field(
default="SPY",
description="what the fund measures itself against; also the source "
"of the backtest's trading-day grid",
)
@field_validator("benchmark")
@classmethod
def _uppercase_benchmark(cls, ticker: str) -> str:
return ticker.upper()
@field_validator("strategies")
@classmethod
def _unique_strategy_names(cls, strategies: list[StrategySpec]) -> list[StrategySpec]:
names = [s.name for s in strategies]
duplicates = {n for n in names if names.count(n) > 1}
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 universeView on GitHub (pinned to eff8a7320f)
Solutions
- Rename the duplicate strategies in the mandate YAML so every name is unique (e.g. 'value_core' and 'value_tilt').
- If you meant the strategies to be identical, delete the duplicate instead of renaming it.
- Check for YAML merge-key accidents: an anchor like <<: *value-strategy combined with an explicit name that collides.
Example fix
# before (mandate.yaml)
strategies:
- name: momentum
weight: 0.6
...
- name: momentum # duplicate -> ValueError: duplicate strategy names: ['momentum']
weight: 0.4
# after
strategies:
- name: momentum-core
weight: 0.6
- name: momentum-tilt
weight: 0.4 Defensive patterns
Strategy: validation
Validate before calling
def validate_mandate_spec(spec) -> list[str]:
names = [s.name for s in spec.strategies]
seen, dupes = set(), set()
for n in names:
(dupes if n in seen else seen).add(n)
return sorted(dupes) # empty list == ok (FundSpec enforces this too) Type guard
from hedge_fund.fund.spec import FundSpec
def has_unique_strategy_names(spec: FundSpec) -> bool:
names = [s.name for s in spec.strategies]
return len(names) == len(set(names)) Try / catch
from pydantic import ValidationError
try:
spec = load_spec("mandate.yaml")
except ValidationError as e:
if "duplicate strategy names" in str(e):
raise SystemExit("fix the mandate YAML: " + str(e)) from e
raise Prevention
- Let FundSpec validation run at load time — never construct strategies by hand from raw YAML.
- Lint shipped mandates in CI by loading them with load_spec.
- After copy-pasting a strategy block in YAML, the first edit is always the name.
When it happens
Trigger: Creating FundSpec(strategies=[...]) where two StrategySpec entries have name='value' (copy-paste of a YAML block without renaming); loading a YAML mandate with duplicated strategy keys after manual editing. Pydantic runs this during model validation, so the error surfaces at FundSpec(**data) / load_spec time, before any backtest work starts.
Common situations: Copy-pasting a strategy block in the mandate YAML and forgetting to change the name; YAML anchors/merge keys accidentally producing two identically-named entries; merging two mandate files that each define a 'momentum' strategy.
Related errors
- unknown rebalance cadence {cadence!r}
- unknown model {m.name!r} in strategy {strategy.name!r}; avai
- universe is empty — a run needs at least one ticker
- No v2 client for {provider} (model {model}). Supported: {',
- confidence out of range: {confidence}
AI-assisted analysis of virattt/ai-hedge-fund@eff8a7320f (2026-08-15).
Data as JSON: /api/errors/bf88e506d1401327.
Report an issue: GitHub.