HKUDS/Vibe-Trading · error · ValueError

exposures must be a non-empty DataFrame

Error message

exposures must be a non-empty DataFrame

What it means

The exposures argument must be a non-empty pandas DataFrame (assets x factors). Passing something else — a Series, ndarray, dict, or empty frame — trips this guard before any math runs.

Source

Thrown at agent/src/quantlib/factormodel.py:639

            Defaults to zero if omitted.

    Returns:
        :class:`FactorRiskDecomposition` containing total/factor/specific
        variances, volatilities, marginal contributions to risk (MCR), and
        percentage contributions to risk (PCR) per factor and per asset.

    Raises:
        ValueError: If weights or matrices are empty, contain non-finite values,
            or share no common assets or factors.
    """
    w_series = pd.Series(portfolio_weights, dtype=float)
    if w_series.empty:
        raise ValueError("portfolio_weights cannot be empty")
    if not np.isfinite(w_series.values).all():
        raise ValueError("portfolio_weights contains non-finite values")

    if not isinstance(exposures, pd.DataFrame) or exposures.empty:
        raise ValueError("exposures must be a non-empty DataFrame")
    if not np.isfinite(exposures.values).all():
        raise ValueError("exposures contains non-finite values")

    if not isinstance(factor_cov, pd.DataFrame) or factor_cov.empty:
        raise ValueError("factor_cov must be a non-empty DataFrame")
    if not np.isfinite(factor_cov.values).all():
        raise ValueError("factor_cov contains non-finite values")

    # Align assets
    assets = w_series.index.intersection(exposures.index)
    if assets.empty:
        raise ValueError(
            f"No matching assets between weights ({sorted(w_series.index)}) and exposures ({sorted(exposures.index)})"
        )

    unmatched_weight = float(w_series.drop(index=assets, errors="ignore").abs().sum())
    w = w_series.loc[assets]
    X = exposures.loc[assets]

View on GitHub (pinned to 80ffdda44c)

Solutions

  1. Pass exposures.loc[assets, factors] as a real 2-D DataFrame
  2. Fix the upstream loader to return a DataFrame
  3. Construct explicitly: pd.DataFrame(data, index=assets, columns=factors)

Example fix

# before
risk = factor_risk_decomposition(w, exposures_dict, F)
# after
X = pd.DataFrame(exposures_dict).T  # or load as DataFrame
risk = factor_risk_decomposition(w, X, F)
Defensive patterns

Strategy: type-guard

Validate before calling

assert isinstance(exposures, pd.DataFrame) and not exposures.empty

Type guard

def is_exposure_frame(x) -> bool:
    return isinstance(x, pd.DataFrame) and not x.empty and x.shape[1] > 0

Prevention

When it happens

Trigger: exposures=pd.Series(...), np.array(...), {}, or pd.DataFrame() passed as the second argument.

Common situations: Refactor changed the exposure loader to return a Series; a mock/stub returned a dict; empty frame after slicing all rows away.

Understand the failure class

Background: "Wrong argument type", "must be a string", "expected Array or Prism::Scope": TypeError and ArgumentError when a library receives a value of the wrong type — this error's family across 28 libraries.

Related errors


AI-assisted analysis of HKUDS/Vibe-Trading@80ffdda44c (2026-08-28). Data as JSON: /api/errors/36a563fe3d74c743. Report an issue: GitHub.