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 universe

View on GitHub (pinned to eff8a7320f)

Solutions

  1. Rename the duplicate strategies in the mandate YAML so every name is unique (e.g. 'value_core' and 'value_tilt').
  2. If you meant the strategies to be identical, delete the duplicate instead of renaming it.
  3. 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

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


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