{"record":{"id":"c24a666db739440d","repo":"HKUDS/Vibe-Trading","slug":"portfolio-exposures-and-factor-returns-share-no-fa","errorCode":null,"errorMessage":"portfolio_exposures and factor_returns share no factor; exposures={sorted(exposures.index)} returns={sorted(returns.index)}","messagePattern":"portfolio_exposures and factor_returns share no factor; exposures=(.+?) returns=(.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"agent/src/quantlib/factormodel.py","lineNumber":585,"sourceCode":"        portfolio_return: The realised portfolio return being explained.\n\n    Returns:\n        Contribution per factor (exposure times factor return), plus a\n        ``specific`` entry holding the unexplained remainder and a ``total``\n        entry equal to ``portfolio_return``. The parts sum to the total by\n        construction: the residual is defined as what is left, never estimated\n        separately, so no reconciliation gap can appear.\n\n    Raises:\n        ValueError: If the two inputs share no factor.\n    \"\"\"\n    exposures = pd.Series(portfolio_exposures, dtype=float).drop(\n        labels=[\"unmatched_weight\"], errors=\"ignore\"\n    )\n    returns = pd.Series(factor_returns, dtype=float)\n    shared = exposures.index.intersection(returns.index)\n    if shared.empty:\n        raise ValueError(\n            \"portfolio_exposures and factor_returns share no factor; \"\n            f\"exposures={sorted(exposures.index)} returns={sorted(returns.index)}\"\n        )\n\n    contributions = exposures.loc[shared] * returns.loc[shared]\n    explained = float(contributions.sum())\n    contributions[\"specific\"] = portfolio_return - explained\n    contributions[\"total\"] = portfolio_return\n    return contributions\n\n\ndef factor_risk_decomposition(\n    portfolio_weights: pd.Series | Mapping[str, float],\n    exposures: pd.DataFrame,\n    factor_cov: pd.DataFrame,\n    specific_variances: pd.Series | Mapping[str, float] | None = None,\n) -> FactorRiskDecomposition:\n    \"\"\"Decompose portfolio risk into systematic factor and idiosyncratic components.","sourceCodeStart":567,"sourceCodeEnd":603,"githubUrl":"https://github.com/HKUDS/Vibe-Trading/blob/80ffdda44c5c4db0dd84d70e051cca591cea67df/agent/src/quantlib/factormodel.py#L567-L603","documentation":"factor_return_attribution multiplies portfolio factor exposures by factor returns element-wise on shared factor names; if the two Series' indexes are disjoint there is no overlap to attribute, and the error names both sets to make the mismatch visible.","triggerScenarios":"portfolio_exposures indexed by {'value','momentum'} while factor_returns is indexed by {'Value','MOM'} (case/name mismatch), or completely disjoint factor taxonomies.","commonSituations":"Factor name normalization differs between the risk model and the returns feed; one side uses prefixed names like 'fctr.value'; renaming after a merge dropped shared names.","solutions":["Compare sorted indexes of both Series as the message displays","Normalize/uppercase factor names on both sides before the call","Reindex factor_returns to the exposure index after verifying the mapping"],"exampleFix":"# before\nattr = factor_return_attribution(exp, rets)\n# after\nrets = rets.rename(dict(zip(rets.index, exp.index)))  # fix mapping\nattr = factor_return_attribution(exp, rets)","handlingStrategy":"validation","validationCode":"shared = exposures.index.intersection(returns.index)\nassert not shared.empty","typeGuard":null,"tryCatchPattern":"try:\n    attr = factor_return_attribution(exp, rets)\nexcept ValueError as e:\n    if 'share no factor' in str(e):\n        logger.error('factor taxonomy mismatch: %s', e)\n    raise","preventionTips":["Enforce one canonical factor-naming map across the codebase","Diff sorted factor names in CI for both feeds"],"tags":["quantlib","factormodel","index-alignment","attribution"],"backgroundTag":"disjoint-index-alignment","analyzedSha":"80ffdda44c5c4db0dd84d70e051cca591cea67df","analyzedAt":"2026-08-28T12:46:38.989Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}