HKUDS/Vibe-Trading · error · ValueError

sector {sector!r} has portfolio weight {w_p!r} but no portfo

Error message

sector {sector!r} has portfolio weight {w_p!r} but no portfolio return

What it means

A sector carries a non-zero portfolio weight but portfolio_returns has no entry for it. Attribution cannot compute a return for a held sector, so the function refuses rather than silently treating it as zero return.

Source

Thrown at agent/src/quantlib/attribution.py:274

    portfolio_total = math.fsum(portfolio_weights.values())
    benchmark_total = math.fsum(benchmark_weights.values())
    if abs(portfolio_total - benchmark_total) > weight_sum_tolerance:
        raise ValueError(
            "portfolio and benchmark weights must sum to the same total for the Brinson "
            f"identity to hold; got {portfolio_total!r} and {benchmark_total!r} "
            f"(difference {portfolio_total - benchmark_total!r} exceeds {weight_sum_tolerance!r})"
        )

    resolved: list[tuple[str, float, float, float, float]] = []
    for sector in ordered:
        w_p = float(portfolio_weights.get(sector, 0.0))
        w_b = float(benchmark_weights.get(sector, 0.0))
        r_p = portfolio_returns.get(sector)
        r_b = benchmark_returns.get(sector)
        if r_p is None:
            if w_p != 0.0:
                raise ValueError(f"sector {sector!r} has portfolio weight {w_p!r} but no portfolio return")
            if r_b is None:
                raise ValueError(f"sector {sector!r} has no portfolio return and no benchmark return")
            r_p = r_b
        if r_b is None:
            if w_b != 0.0:
                raise ValueError(f"sector {sector!r} has benchmark weight {w_b!r} but no benchmark return")
            r_b = r_p
        resolved.append((sector, w_p, w_b, float(r_p), float(r_b)))

    total_portfolio_return = math.fsum(w_p * r_p for _, w_p, _, r_p, _ in resolved)
    total_benchmark_return = math.fsum(w_b * r_b for _, _, w_b, _, r_b in resolved)

    effects: list[SectorEffect] = []
    for sector, w_p, w_b, r_p, r_b in resolved:
        active_weight = w_p - w_b
        effects.append(
            SectorEffect(
                sector=sector,

View on GitHub (pinned to 80ffdda44c)

Solutions

  1. Align sector keys between weights and returns (normalize taxonomy/labels)
  2. Fetch returns for the same universe/date as the weights
  3. If the sector genuinely has no return, either drop it from both sides or explicitly supply a return (e.g. 0.0) with a documented rationale

Example fix

# before
brinson_fachler({"tech": 1.0}, {"tech": 1.0}, {}, {"tech": 0.05})

# after
rp = {s: portfolio_returns.get(s, benchmark_returns[s]) for s in portfolio_weights}
brinson_fachler({"tech": 1.0}, {"tech": 1.0}, rp, {"tech": 0.05})
Defensive patterns

Strategy: validation

Validate before calling

missing = {s for s, w in portfolio_weights.items() if w != 0 and s not in portfolio_returns}
if missing:
    raise ValueError(f'portfolios sectors without returns: {sorted(missing)}')

Type guard

def returns_cover_weights(weights: Mapping[str, float], returns: Mapping[str, float]) -> bool:
    return all(w == 0 or s in returns for s, w in weights.items())

Prevention

When it happens

Trigger: Weight dict includes 'energy' at 5% but the returns mapping lacks 'energy' (data join gap, different sector taxonomies, or missing return for one date).

Common situations: Sector naming mismatches ('IT' vs 'Information Technology'); returns fetched for a different date than weights; a newly added holding not yet in the returns feed.

Related errors


AI-assisted analysis of HKUDS/Vibe-Trading@80ffdda44c (2026-08-28). Data as JSON: /api/errors/bac502642d7b3b45. Report an issue: GitHub.