HKUDS/Vibe-Trading · error · ValueError

factor_cov matrix must be symmetric

Error message

factor_cov matrix must be symmetric

What it means

factor_risk_decomposition uses eigvalsh and a quadratic form that both assume a symmetric covariance; if F is not (numerically) symmetric within atol=1e-8 the math is invalid, so symmetry is enforced first.

Source

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

            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]

    # Align factors
    factors = X.columns.intersection(factor_cov.index).intersection(factor_cov.columns)
    if factors.empty:
        raise ValueError(
            f"No matching factors between exposures ({sorted(X.columns)}) and factor_cov ({sorted(factor_cov.index)})"
        )

    X = X[factors]
    F = factor_cov.loc[factors, factors]
    F_mat = F.to_numpy(dtype=float)
    if not np.allclose(F_mat, F_mat.T, atol=1e-8):
        raise ValueError("factor_cov matrix must be symmetric")
    eigvals = np.linalg.eigvalsh(F_mat)
    if np.min(eigvals) < -1e-8:
        raise ValueError("factor_cov matrix must be positive semi-definite")

    # Align specific variances
    if specific_variances is not None:
        spec_var_s = pd.Series(specific_variances, dtype=float)
        if not np.isfinite(spec_var_s.values).all():
            raise ValueError("specific_variances contains non-finite values")
        d = spec_var_s.reindex(assets, fill_value=0.0).clip(lower=0.0)
    else:
        d = pd.Series(0.0, index=assets, dtype=float)

    # Portfolio factor exposure: x_p = X^T w (K x 1)
    x_p = X.T.dot(w)

    # Factor variance: x_p^T F x_p
    F_x_p = F.dot(x_p)

View on GitHub (pinned to 80ffdda44c)

Solutions

  1. Symmetrize: F = (F + F.T) / 2 before calling
  2. Rebuild the matrix from a symmetric source
  3. Check for duplicated index labels causing misaligned .loc slicing

Example fix

# before
risk = factor_risk_decomposition(w, X, F)
# after
F = (F + F.T) / 2
risk = factor_risk_decomposition(w, X, F)
Defensive patterns

Strategy: validation

Validate before calling

F = (F + F.T) / 2
assert np.allclose(F.values, F.values.T, atol=1e-8)

Prevention

When it happens

Trigger: A hand-built covariance with F[i,j] != F[j,i], or asymmetric input after loc-based slicing with duplicated labels.

Common situations: Covariance patched cell-by-cell (e.g. overriding one off-diagonal); data read from a long-format table that lost symmetry.

Related errors


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