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.

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.

Appendix: 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)