{"record":{"id":"0d1eb9e5903b65cf","repo":"HKUDS/Vibe-Trading","slug":"brinson-fachler-needs-at-least-one-sector","errorCode":null,"errorMessage":"brinson_fachler needs at least one sector","messagePattern":"brinson_fachler needs at least one sector","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"agent/src/quantlib/attribution.py","lineNumber":255,"sourceCode":"\n        The tie-out is exactly as good as ``weight_sum_tolerance`` allows: a\n        weight-sum gap ``dW`` leaves a residual of ``-R_b * dW``, because the\n        ``R_b * sum(w_p - w_b)`` term of the allocation effect no longer vanishes.\n        At the default tolerance that residual is below 1e-10 and invisible.\n        Loosen the tolerance and it becomes visible in basis points -- a 2%\n        weight-sum gap against a 5% benchmark return is a 10bp residual -- so\n        loosen it only to absorb rounding in the weights, never to force through\n        two vectors that genuinely disagree.\n\n    Raises:\n        ValueError: If no sectors were supplied, if the two weight vectors do not\n            sum to the same total within ``weight_sum_tolerance``, or if a sector\n            carries a non-zero weight on a side but no return on that side.\n    \"\"\"\n    ordered: list[str] = list(portfolio_weights)\n    ordered.extend(sector for sector in benchmark_weights if sector not in portfolio_weights)\n    if not ordered:\n        raise ValueError(\"brinson_fachler needs at least one sector\")\n\n    portfolio_total = math.fsum(portfolio_weights.values())\n    benchmark_total = math.fsum(benchmark_weights.values())\n    if abs(portfolio_total - benchmark_total) > weight_sum_tolerance:\n        raise ValueError(\n            \"portfolio and benchmark weights must sum to the same total for the Brinson \"\n            f\"identity to hold; got {portfolio_total!r} and {benchmark_total!r} \"\n            f\"(difference {portfolio_total - benchmark_total!r} exceeds {weight_sum_tolerance!r})\"\n        )\n\n    resolved: list[tuple[str, float, float, float, float]] = []\n    for sector in ordered:\n        w_p = float(portfolio_weights.get(sector, 0.0))\n        w_b = float(benchmark_weights.get(sector, 0.0))\n        r_p = portfolio_returns.get(sector)\n        r_b = benchmark_returns.get(sector)\n        if r_p is None:\n            if w_p != 0.0:","sourceCodeStart":237,"sourceCodeEnd":273,"githubUrl":"https://github.com/HKUDS/Vibe-Trading/blob/80ffdda44c5c4db0dd84d70e051cca591cea67df/agent/src/quantlib/attribution.py#L237-L273","documentation":"brinson_fachler performs Brinson-Fachler performance attribution and requires at least one sector; it builds the union of portfolio and benchmark weight keys and raises ValueError when that union is empty.","triggerScenarios":"Calling brinson_fachler({}, {}, portfolio_returns, benchmark_returns) or passing dicts whose keys are all empty; upstream code filtering sectors down to nothing before the call.","commonSituations":"Date-range filters or screens removing every sector; empty CSV/DataFrame slices feeding the weights; mis-wired dict comprehension producing empty mappings.","solutions":["Check the sector union is non-empty before calling: set(portfolio_weights) | set(benchmark_weights)","Fix the upstream data pipeline that produced empty weight dicts","Default missing side to {} only when the other side genuinely has sectors"],"exampleFix":"# before\nresult = brinson_fachler({}, {}, {}, {})\n\n# after\nif not (portfolio_weights or benchmark_weights):\n    raise ValueError(\"no sector data in period\")\nresult = brinson_fachler(portfolio_weights, benchmark_weights, portfolio_returns, benchmark_returns)","handlingStrategy":"validation","validationCode":"if not (set(portfolio_weights) | set(benchmark_weights)):\n    raise ValueError('no sectors provided for attribution')","typeGuard":"def has_sectors(pw: Mapping[str, float], bw: Mapping[str, float]) -> bool:\n    return bool(set(pw) | set(bw))","tryCatchPattern":null,"preventionTips":["Assert non-empty universe before attribution","Test pipelines with realistic fixtures that always contain sectors"],"tags":["quantlib","attribution","validation","empty-input"],"backgroundTag":"empty-input-validation","analyzedSha":"80ffdda44c5c4db0dd84d70e051cca591cea67df","analyzedAt":"2026-08-28T12:46:38.989Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}