quantlab.factor.kunquant_cs.CrossSectionalNeutralize#

class quantlab.factor.kunquant_cs.CrossSectionalNeutralize(y, size, industry, w=None, universe=None, use_industry=True, use_size=True)[source]#

Bases: _IndustrySizeRegression

Neutralize y against industry, size or both: its residual from a per-bar regression.

The regression is CrossSectionalIndustrySizeFill’s: on each bar, over the symbols where universe > 0, y is finite, w is finite and positive and the regressors used are present (industry an integer code from 0 to 4096; any other code counts as missing), one intercept per industry plus a common slope on size, by weighted least squares. Every symbol with a finite y, inside the universe or not, gets:

out = y - intercept[industry] - b * size

The residual has zero weighted sum within each industry and zero weighted covariance with size over the fit sample. It is NaN where y is missing or infinite, where a regressor used is missing, and where the symbol’s industry has no fit member: a neutralized panel has no value that was left as it was. use_size=False neutralizes against industry alone (the weighted industry mean is subtracted); use_industry=False against size alone, with one common intercept.

Unlike CrossSectionalWLSResidual, a bar where size does not vary (within the industries, below 1e-10 of its spread) is not NaN: the residual of a least-squares fit is unique even then, and the slope is taken as 0. A symbol alone in its industry in the fit sample is left with a residual of 0. The output is not re-standardized; follow it with CrossSectionalZScore for that.

w and universe are optional: without w the fit is ordinary least squares, without universe it uses every symbol. Leave them out rather than passing a constant: KunQuant merges equal expressions, so the same node passed twice to one cross-sectional op (a constant 1 as both w and universe) reaches the C++ as one input and the op does not compile. Each set of flags and optional inputs gets a class of its own (CrossSectionalNeutralize_industry_size, CrossSectionalNeutralize_industry_size_w_universe); isinstance still holds. The batch-start and SIMD-width notes of CrossSectionalZScore apply.

Parameters:
yOpBase

The values to neutralize, such as an alpha factor.

sizeOpBase

The size regressor, such as the log of market cap. Ignored, but still required, with use_size=False.

industryOpBase

The industry code, a non-negative integer stored as a float. Ignored, but still required, with use_industry=False.

wOpBase, optional

The regression weights, such as the square root of market cap as a Barra-style model weighs. Without it, every symbol weighs 1.

universeOpBase, optional

A mask, positive for the symbols the fit uses. Without it, the fit uses every symbol.

use_industry, use_sizebool, default True

Which regressors the model has; at least one.

Raises:
ValueError

If both flags are false.

Examples

Industry and size neutral by ordinary least squares over every symbol, then size neutral alone:

>>> cap = Input("marketcap")
>>> Output(CrossSectionalNeutralize(alpha, Log(cap), Input("industry")), "alpha_neutral")
>>> Output(CrossSectionalNeutralize(alpha, Log(cap), ConstantOp(0.0), use_industry=False),
...        "alpha_size_neutral")

Weighted by the square root of market cap and fitted on an estimation universe, as Barra does:

>>> Output(CrossSectionalNeutralize(alpha, Log(cap), Input("industry"), Sqrt(cap), estu), "alpha_barra_neutral")
generate_body()[source]#

Return the C++ that fits one bar and writes its residuals.