HKUDS/Vibe-Trading · error · ValueError
holdings is empty
Error message
holdings is empty
What it means
Thrown by portfolio_style_exposure when the holdings mapping converts to an empty weight series after dropping NaNs. The function cannot compute a weighted sum of factor exposures with no positions, so it refuses immediately.
Source
Thrown at agent/src/quantlib/factormodel.py:506
used as supplied, so a book that is 60% invested reports a 60%-scaled
exposure, which is the honest reading.
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
View on GitHub (pinned to 80ffdda44c)
Solutions
- Check the holdings source: log its keys/values right before the call
- Skip or short-circuit reporting when the portfolio has no positions
- Coerce holdings with pd.Series(holdings, dtype=float).dropna() yourself and branch on .empty
Example fix
// before exp = portfolio_style_exposure(holdings, exposures) // after w = pd.Series(holdings, dtype=float).dropna() exp = portfolio_style_exposure(w, exposures) if not w.empty else None
Defensive patterns
Strategy: validation
Validate before calling
w = pd.Series(holdings, dtype=float).dropna() assert not w.empty, 'no positions to analyze'
Prevention
- Short-circuit empty portfolios before any analytics call
- Centralize a portfolio_sanity_check(weights) helper used by every report
When it happens
Trigger: Calling portfolio_style_exposure({}, exposures) or with a dict/Series whose values are all NaN; any holdings input that becomes an empty float Series.
Common situations: Upstream filtering removed all tickers before the call; an empty portfolio object passed straight into risk reporting; all-NaN weights from bad data joins.
Related errors
- exposures has no factor columns
- 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/da160acbfbc01cd7.
Report an issue: GitHub.