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

  1. Inspect exposures.columns before the call
  2. Verify the factor data pipeline produced the expected factor names
  3. 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

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


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