HKUDS/Vibe-Trading · error · ValueError

cross-sectional regression needs at least {MIN_CROSS_SECTION

Error message

cross-sectional regression needs at least {MIN_CROSS_SECTION} assets with both a return and full exposures, got {len(common)}

What it means

cross_sectional_factor_returns regresses returns on exposures per date; it requires at least MIN_CROSS_SECTION assets that simultaneously have a non-NaN return and a complete (no NaN in any column) exposure row. Below that floor the factor return estimates are too noisy, so it refuses.

Source

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

        returns: Asset returns for the date being explained, indexed by asset.
        exposures: Exposure matrix from the *previous* date, rows indexed by
            asset. Using the same date's exposures would be a look-ahead: the
            characteristic and the return would share information.
        market_caps: Market capitalisation by asset for the regression weights.
            When None the regression is unweighted.
        date: Label recorded on the result. Purely informational.

    Returns:
        A :class:`FactorReturnFit`.

    Raises:
        ValueError: If fewer than :data:`MIN_CROSS_SECTION` assets are common to
            the inputs, if the design matrix has more columns than rows, or if
            the exposures are perfectly collinear.
    """
    common = returns.dropna().index.intersection(exposures.dropna(how="any").index)
    if len(common) < MIN_CROSS_SECTION:
        raise ValueError(
            f"cross-sectional regression needs at least {MIN_CROSS_SECTION} assets "
            f"with both a return and full exposures, got {len(common)}"
        )

    y = returns.loc[common].to_numpy(dtype=float)
    factor_names = list(exposures.columns)
    design = np.column_stack(
        [np.ones(len(common)), exposures.loc[common].to_numpy(dtype=float)]
    )
    names = [MARKET_FACTOR, *factor_names]

    if design.shape[1] > design.shape[0]:
        raise ValueError(
            f"{design.shape[1]} regressors but only {design.shape[0]} assets; "
            "the fit would be exactly determined and meaningless"
        )

    if market_caps is None:

View on GitHub (pinned to 80ffdda44c)

Solutions

  1. Diagnose the intersection: common = returns.dropna().index.intersection(exposures.dropna(how='any').index); print(len(common)).
  2. Fill or drop sparse exposure columns (build_style_exposures already fills missing cells with 0 and counts them), or restrict the universe to names with full data.

Example fix

# before
fr = cross_sectional_factor_returns(returns, exposures_with_nans)
# after
exposures_filled = exposures_with_nans.fillna(0.0)
fr = cross_sectional_factor_returns(returns, exposures_filled)
Defensive patterns

Strategy: validation

Validate before calling

common = returns.dropna().index.intersection(exposures.dropna(how="any").index)
assert len(common) >= MIN_CROSS_SECTION, len(common)

Prevention

When it happens

Trigger: An inner join of returns.dropna() and exposures.dropna(how='any') shrinking below the minimum — e.g. one characteristic mostly NaN wipes out complete rows even though returns are fine.

Common situations: Sparse characteristics (analyst estimates coverage), small pilot universes, exposures built with fillna(0) removed by a strictness change, or a date where many tickers lack returns.

Related errors


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