HKUDS/Vibe-Trading · error · ValueError
exposures has no factor columns
Error message
exposures has no factor columns
What it means
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.
Source
Thrown at agent/src/quantlib/factormodel.py:508
exposures: Exposure matrix, rows indexed by asset.
benchmark: Benchmark weights by asset. When supplied, the result is the
ACTIVE exposure (portfolio minus benchmark), which is what an
attribution conversation is about.
Returns:
Exposure per factor. Assets held but absent from ``exposures`` are
reported through the ``unmatched_weight`` entry rather than dropped, so
a portfolio half of whose weight had no exposure data cannot read as a
clean measurement.
Raises:
ValueError: If ``holdings`` is empty or ``exposures`` has no columns.
"""
weights = pd.Series(holdings, dtype=float).dropna()
if weights.empty:
raise ValueError("holdings is empty")
if exposures.shape[1] == 0:
raise ValueError("exposures has no factor columns")
matched = weights.index.intersection(exposures.index)
unmatched_weight = float(weights.drop(matched).abs().sum())
result = exposures.loc[matched].mul(weights.loc[matched], axis=0).sum()
if benchmark is not None:
bench = pd.Series(benchmark, dtype=float).dropna()
bench_matched = bench.index.intersection(exposures.index)
unmatched_weight += float(bench.drop(bench_matched).abs().sum())
result = result - exposures.loc[bench_matched].mul(
bench.loc[bench_matched], axis=0
).sum()
result["unmatched_weight"] = unmatched_weight
return result
def style_drift(exposure_history: pd.DataFrame) -> StyleDrift:View on GitHub (pinned to 80ffdda44c)
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
Example fix
// before
exp = portfolio_style_exposure(w, exposures)
// after
exp = (portfolio_style_exposure(w, exposures)
if exposures.shape[1] else None) Defensive patterns
Strategy: validation
Validate before calling
assert isinstance(exposures, pd.DataFrame) and exposures.shape[1] > 0
Type guard
def has_factor_columns(x) -> bool:
return isinstance(x, pd.DataFrame) and x.shape[1] > 0 Prevention
- Validate factor frame shape at load time
- Log exposures.shape in ingestion pipelines
When it happens
Trigger: Passing exposures=pd.DataFrame() or a frame with an empty column axis (e.g. a filtered frame where all factor columns were dropped).
Common situations: Factor data loader returned zero columns after rename/alignment; columns were consumed by a prior drop(columns=[...]) chain.
Related errors
- holdings is empty
- portfolio_weights cannot be empty
- brinson_fachler needs at least one sector
- style drift needs at least 2 dates, got {exposure_history.sh
- portfolio_weights contains non-finite values
AI-assisted analysis of HKUDS/Vibe-Trading@80ffdda44c (2026-08-28).
Data as JSON: /api/errors/b30a5077b13d655e.
Report an issue: GitHub.