cs_neutralize¶
Defined in fynance.features.cross_section
- cs_neutralize(X, exposures)[source]
Per-bar cross-sectional OLS neutralization, NaN-aware.
At each bar
t, regresses the valid entries ofXon the corresponding valid rows ofexposures(plus an intercept) by ordinary least squares, and returns the residual — the part ofXorthogonal to the exposure(s). This is the standard way to strip an unwanted factor (e.g. sector, beta, size) out of a raw signal before ranking it.- Parameters:
- Xarray_like
Panel, shape
(T, N).- exposuresarray_like
Exposure(s) to neutralize against:
(T, N)for a single factor, or(T, N, K)forKfactors.
- Returns:
- np.ndarray
(T, N)residual panel.NaNwhereX(or the exposure) isNaN, and for every asset of a bar with fewer thanK + 1valid assets.
See also
cs_demean,cs_zscore
Notes
For bar
twith valid-asset design matrix \(D_t = [\mathbb{1}, F_t] \in \mathbb{R}^{n_t \times (K+1)}\) (an intercept column plus theKexposure columns) and valid target \(y_t\), the fit solves the least-squares problem\[\hat\beta_t = \arg\min_\beta \lVert y_t - D_t \beta \rVert_2^2\]via
numpy.linalg.lstsq, and the output is \(y_t - D_t \hat\beta_t\). A bar needs at least \(K + 1\) valid assets to identify the \(K + 1\) parameters (intercept +Kexposures); bars with fewer valid assets outputNaNfor every asset. An asset is valid at bartonly if bothX[t]and every column ofexposures[t]are non-NaNthere.Examples
A single factor that is an exact linear driver of
Xneutralizes to (numerically) zero:>>> import numpy as np >>> f = np.array([[1., 2., 3., 4.]]) >>> X = 2. + 3. * f >>> np.round(cs_neutralize(X, f), 8) array([[0., 0., 0., 0.]])
A bar with fewer valid assets than parameters needed (here 1 valid asset, 2 needed for an intercept + 1 factor) is entirely NaN:
>>> X = np.array([[1., np.nan, np.nan]]) >>> f = np.array([[1., 2., 3.]]) >>> cs_neutralize(X, f) array([[nan, nan, nan]])