{"record":{"id":"b30a5077b13d655e","repo":"HKUDS/Vibe-Trading","slug":"exposures-has-no-factor-columns","errorCode":null,"errorMessage":"exposures has no factor columns","messagePattern":"exposures has no factor columns","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"agent/src/quantlib/factormodel.py","lineNumber":508,"sourceCode":"        exposures: Exposure matrix, rows indexed by asset.\n        benchmark: Benchmark weights by asset. When supplied, the result is the\n            ACTIVE exposure (portfolio minus benchmark), which is what an\n            attribution conversation is about.\n\n    Returns:\n        Exposure per factor. Assets held but absent from ``exposures`` are\n        reported through the ``unmatched_weight`` entry rather than dropped, so\n        a portfolio half of whose weight had no exposure data cannot read as a\n        clean measurement.\n\n    Raises:\n        ValueError: If ``holdings`` is empty or ``exposures`` has no columns.\n    \"\"\"\n    weights = pd.Series(holdings, dtype=float).dropna()\n    if weights.empty:\n        raise ValueError(\"holdings is empty\")\n    if exposures.shape[1] == 0:\n        raise ValueError(\"exposures has no factor columns\")\n\n    matched = weights.index.intersection(exposures.index)\n    unmatched_weight = float(weights.drop(matched).abs().sum())\n    result = exposures.loc[matched].mul(weights.loc[matched], axis=0).sum()\n\n    if benchmark is not None:\n        bench = pd.Series(benchmark, dtype=float).dropna()\n        bench_matched = bench.index.intersection(exposures.index)\n        unmatched_weight += float(bench.drop(bench_matched).abs().sum())\n        result = result - exposures.loc[bench_matched].mul(\n            bench.loc[bench_matched], axis=0\n        ).sum()\n\n    result[\"unmatched_weight\"] = unmatched_weight\n    return result\n\n\ndef style_drift(exposure_history: pd.DataFrame) -> StyleDrift:","sourceCodeStart":490,"sourceCodeEnd":526,"githubUrl":"https://github.com/HKUDS/Vibe-Trading/blob/80ffdda44c5c4db0dd84d70e051cca591cea67df/agent/src/quantlib/factormodel.py#L490-L526","documentation":"portfolio_style_exposure requires a non-degenerate exposures DataFrame; shape[1]==0 means no factor columns exist, so there is nothing to aggregate weights over.","triggerScenarios":"Passing exposures=pd.DataFrame() or a frame with an empty column axis (e.g. a filtered frame where all factor columns were dropped).","commonSituations":"Factor data loader returned zero columns after rename/alignment; columns were consumed by a prior drop(columns=[...]) chain.","solutions":["Inspect exposures.columns before the call","Verify the factor data pipeline produced the expected factor names","Guard with 'if exposures.shape[1] == 0: skip/log' upstream"],"exampleFix":"// before\nexp = portfolio_style_exposure(w, exposures)\n// after\nexp = (portfolio_style_exposure(w, exposures)\n       if exposures.shape[1] else None)","handlingStrategy":"validation","validationCode":"assert isinstance(exposures, pd.DataFrame) and exposures.shape[1] > 0","typeGuard":"def has_factor_columns(x) -> bool:\n    return isinstance(x, pd.DataFrame) and x.shape[1] > 0","tryCatchPattern":null,"preventionTips":["Validate factor frame shape at load time","Log exposures.shape in ingestion pipelines"],"tags":["quantlib","factormodel","empty-input","validation"],"backgroundTag":"empty-input-validation","analyzedSha":"80ffdda44c5c4db0dd84d70e051cca591cea67df","analyzedAt":"2026-08-28T12:46:38.989Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}