quantlab.portfolio.base.LowRankCovarianceEstimate#

class quantlab.portfolio.base.LowRankCovarianceEstimate(symbols, exposures, specific_variance)[source]#

Bases: object

A covariance estimate B B' + diag(D) of low rank plus a diagonal, without risk factors.

A shrunk sample covariance of a T-bar window is one (Ledoit-Wolf: B the scaled demeaned window, k = T columns): its factor_form() is (B, I, D), so an optimiser prices risk without the dense [n, n] matrix, when there are enough symbols for that to be faster (LOW_RANK_MIN_RATIO). The columns of B are not risk factors, so factor_exposure refuses as a dense estimate’s does.

Attributes:
symbolsnp.ndarray

The n symbols covered, the order of exposures’ rows and specific_variance.

exposuresnp.ndarray

B, [n, k].

specific_variancenp.ndarray

D, [n].

Examples

>>> low_rank = LowRankCovarianceEstimate(
...     symbols=np.array(["AAA", "BBB"]),
...     exposures=np.array([[0.1], [0.05]]),
...     specific_variance=np.array([0.01, 0.02]),
... )
>>> low_rank.covariance
array([[0.02  , 0.005 ],
       [0.005 , 0.0225]])
>>> low_rank.variance
array([0.02  , 0.0225])
property covariance#

The dense [n, n] covariance B B' + diag(D), built on each call.

Examples

>>> low_rank.covariance.shape
(2, 2)
factor_exposure(name, symbols)[source]#

Refuse: the columns of B are not risk factors.

Raises:
ValueError

Always.

Examples

>>> low_rank.factor_exposure("market", np.array(["AAA"]))
Traceback (most recent call last):
...
ValueError: the covariance estimate has no risk factors, so no exposure to 'market'
factor_form()[source]#

Return (B, I, D), the identity as the columns’ covariance, when it is the cheaper form.

An optimiser’s low-rank risk term costs about n k^2 and the dense one about n^2: with CLARABEL on a long-only problem, k = 252 took 4.5 s against 5.6 s dense at 3,000 symbols and 0.44 s against 0.15 s at 500. The form is returned when n >= LOW_RANK_MIN_RATIO * k, else None and the optimiser prices the dense covariance.

Examples

>>> low_rank.factor_form() is None  # 2 symbols, rank 1: dense is cheaper
True
>>> wide = LowRankCovarianceEstimate(
...     symbols=np.arange(6), exposures=np.full((6, 1), 0.1),
...     specific_variance=np.full(6, 0.01))
>>> exposures, factor_covariance, specific = wide.factor_form()
>>> factor_covariance
array([[1.]])
scaled(factor)[source]#

Return the estimate with every entry multiplied by factor (B by its square root).

Examples

>>> low_rank.scaled(5).variance
array([0.1   , 0.1125])
subset(rows)[source]#

Return the estimate over the symbols at positions rows, in that order.

Examples

>>> low_rank.subset(np.array([1])).variance
array([0.0225])
property variance#

Each covered symbol’s variance, |B_i|^2 + D_i, without the dense matrix.

Examples

>>> low_rank.variance
array([0.02  , 0.0225])