Risk Models & Solve Helpers¶
Supporting data structures used internally by the engine and exposed for advanced usage.
FactorModel¶
FactorModel
dataclass
¶
Frozen dataclass for a factor risk model decomposition (Section 4.1).
Encapsulates the three components of the factor model
.. math::
\bm{\Sigma} = \mathbf{B}\mathbf{F}\mathbf{B}^\top + \mathbf{D}
where
- :math:
\mathbf{B} \in \mathbb{R}^{n \times k}is the factor loading matrix: column :math:jgives the sensitivity of each asset to factor :math:j. - :math:
\mathbf{F} \in \mathbb{R}^{k \times k}is the factor covariance matrix (positive definite), capturing how the :math:kfactors co-vary. - :math:
\mathbf{D} = \operatorname{diag}(d_1, \dots, d_n)with :math:d_i > 0is the idiosyncratic variance diagonal, capturing the asset-specific variance unexplained by the common factors.
The central assumption is :math:k \ll n: the dominant systematic sources
of risk are captured by a handful of factors while the idiosyncratic
component is, by construction, uncorrelated across assets.
Attributes:
| Name | Type | Description |
|---|---|---|
factor_loadings |
ndarray
|
Factor loading matrix :math: |
factor_covariance |
ndarray
|
Factor covariance matrix :math: |
idiosyncratic_var |
ndarray
|
Idiosyncratic variance vector
:math: |
Examples:
>>> import numpy as np
>>> loadings = np.eye(3, 2)
>>> cov = np.eye(2) * 0.5
>>> idio = np.array([0.5, 0.5, 1.0])
>>> fm = FactorModel(factor_loadings=loadings, factor_covariance=cov, idiosyncratic_var=idio)
>>> fm.n_assets
3
>>> fm.n_factors
2
>>> fm.covariance.shape
(3, 3)
factor_loadings: np.ndarray
instance-attribute
¶
factor_covariance: np.ndarray
instance-attribute
¶
idiosyncratic_var: np.ndarray
instance-attribute
¶
n_assets: int
property
¶
Number of assets n (rows of factor_loadings).
n_factors: int
property
¶
Number of factors k (columns of factor_loadings).
covariance: np.ndarray
property
¶
Reconstruct the full :math:n \times n covariance matrix.
Computes :math:\bm{\Sigma} = \mathbf{B}\mathbf{F}\mathbf{B}^\top +
\mathbf{D} by combining the low-rank systematic component with the
diagonal idiosyncratic component.
Returns:
| Type | Description |
|---|---|
ndarray
|
np.ndarray: Shape |
Examples:
>>> import numpy as np
>>> loadings = np.array([[1.0, 0.0], [0.0, 1.0], [0.0, 0.0]])
>>> cov = np.eye(2)
>>> idio = np.ones(3)
>>> fm = FactorModel(factor_loadings=loadings, factor_covariance=cov, idiosyncratic_var=idio)
>>> fm.covariance.diagonal().tolist()
[2.0, 2.0, 1.0]
woodbury_condition_number: float
property
¶
Condition number of the inner :math:k \times k Woodbury matrix.
Returns the condition number of the matrix
.. math::
\mathbf{M} = \mathbf{F}^{-1} + \mathbf{B}^\top\mathbf{D}^{-1}\mathbf{B}
which is the matrix actually inverted during :meth:solve. A large
value (above _DEFAULT_COND_THRESHOLD ≈ 1e12) indicates that the
Woodbury solve is numerically unreliable.
This property gives callers a way to inspect the numerical health of
the model without performing a full solve. Unlike the condition number
of the full :math:n \times n covariance matrix, this measure is
specific to the :math:k \times k inner system solved inside the
Woodbury identity.
Returns:
| Name | Type | Description |
|---|---|---|
float |
float
|
Condition number :math: |
float
|
|
|
float
|
singular or indefinite), as the Cholesky decomposition used to |
|
form |
float
|
math: |
Examples:
>>> import numpy as np
>>> loadings = np.eye(3, 1)
>>> cov = np.eye(1)
>>> idio = np.ones(3)
>>> fm = FactorModel(factor_loadings=loadings, factor_covariance=cov, idiosyncratic_var=idio)
>>> fm.woodbury_condition_number > 0
True
__post_init__() -> None
¶
Validate shape consistency and strict positivity after initialization.
Raises:
| Type | Description |
|---|---|
FactorModelError
|
If |
FactorModelError
|
If |
FactorModelError
|
If |
FactorModelError
|
If any element of
|
solve(rhs: np.ndarray, cond_threshold: float = _DEFAULT_COND_THRESHOLD) -> np.ndarray
¶
Solve :math:\bm{\Sigma}\,\mathbf{x} = \mathbf{b} via the Woodbury identity.
Applies the Sherman--Morrison--Woodbury formula (Section 4.3 of
basanos.pdf) to avoid forming or factorising the full
:math:n \times n covariance matrix:
.. math::
(\mathbf{D} + \mathbf{B}\mathbf{F}\mathbf{B}^\top)^{-1}
= \mathbf{D}^{-1}
- \mathbf{D}^{-1}\mathbf{B}
\bigl(\mathbf{F}^{-1} + \mathbf{B}^\top\mathbf{D}^{-1}\mathbf{B}\bigr)^{-1}
\mathbf{B}^\top\mathbf{D}^{-1}.
Because :math:\mathbf{D} is diagonal, :math:\mathbf{D}^{-1} is
free. The inner matrix is :math:k \times k with cost
:math:O(k^3), and the surrounding multiplications cost
:math:O(kn). Total cost is :math:O(k^3 + kn) rather than
:math:O(n^3).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rhs
|
ndarray
|
Right-hand side vector :math: |
required |
cond_threshold
|
float
|
Condition-number threshold above which an
:class: |
_DEFAULT_COND_THRESHOLD
|
Returns:
| Type | Description |
|---|---|
ndarray
|
np.ndarray: Solution vector :math: |
Raises:
| Type | Description |
|---|---|
DimensionMismatchError
|
If |
SingularMatrixError
|
If the inner :math: |
Examples:
>>> import numpy as np
>>> loadings = np.eye(3, 1)
>>> cov = np.eye(1)
>>> idio = np.ones(3)
>>> fm = FactorModel(factor_loadings=loadings, factor_covariance=cov, idiosyncratic_var=idio)
>>> rhs = np.array([1.0, 2.0, 3.0])
>>> x = fm.solve(rhs)
>>> np.allclose(fm.covariance @ x, rhs)
True
from_returns(returns: np.ndarray, k: int) -> FactorModel
classmethod
¶
Fit a rank-k factor model from a return matrix via truncated SVD.
Extracts latent factors from the return matrix
:math:\mathbf{R} \in \mathbb{R}^{T \times n} using the Singular
Value Decomposition (SVD). The top-k singular triplets define the
factor model components:
.. math::
\mathbf{B} = \mathbf{V}_k, \quad
\mathbf{F} = \bm{\Sigma}_k^2 / T, \quad
\hat{d}_i = 1 - \bigl(\mathbf{B}\mathbf{F}\mathbf{B}^\top\bigr)_{ii}
where :math:\mathbf{V}_k and :math:\bm{\Sigma}_k are the top-k
right singular vectors and singular values of :math:\mathbf{R}
respectively. When returns contains unit-variance columns (as
produced by :func:~basanos.math._signal.vol_adj), the sample
covariance has unit diagonal; the idiosyncratic term
:math:\hat{d}_i = 1 - (\mathbf{B}\mathbf{F}\mathbf{B}^\top)_{ii}
absorbs the residual so the full covariance :math:\hat{\mathbf{C}}^{(k)}
also has unit diagonal. Each :math:\hat{d}_i is clamped from below
at machine epsilon to guarantee strict positivity.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
returns
|
ndarray
|
Return matrix of shape |
required |
k
|
int
|
Number of factors to retain. Must satisfy
|
required |
Returns:
| Name | Type | Description |
|---|---|---|
FactorModel |
FactorModel
|
Fitted factor model with |
Raises:
| Type | Description |
|---|---|
FactorModelError
|
If returns is not 2-D. |
FactorModelError
|
If k is outside the range |
Examples:
>>> import numpy as np
>>> rng = np.random.default_rng(0)
>>> ret = rng.standard_normal((50, 5))
>>> fm = FactorModel.from_returns(ret, k=2)
>>> fm.n_factors
2
>>> fm.n_assets
5
>>> fm.covariance.shape
(5, 5)
__init__(factor_loadings: np.ndarray, factor_covariance: np.ndarray, idiosyncratic_var: np.ndarray) -> None
¶
MatrixBundle¶
MatrixBundle
dataclass
¶
Container for the covariance matrix and any mode-specific auxiliary state.
Wrapping the covariance matrix in a dataclass decouples
:meth:_SolveMixin._compute_position from the raw array so that future
covariance modes (e.g. DCC-GARCH, RMT-cleaned) can carry additional fields
through the same interface without changing the method signature.
Attributes:
| Name | Type | Description |
|---|---|---|
matrix |
ndarray
|
The |
WarmupState¶
WarmupState
dataclass
¶
Final state produced by a full batch solve; consumed by :meth:BasanosStream.from_warmup.
Returned by :meth:BasanosEngine.warmup_state and used by
:meth:BasanosStream.from_warmup to initialise the streaming state without
coupling to the private :meth:~_SolveMixin._iter_solve generator.
Attributes:
| Name | Type | Description |
|---|---|---|
prev_cash_pos |
ndarray
|
Cash positions at the last warmup row, shape
|
corr_iir_state |
_EwmCorrState | None
|
Final IIR filter memory from the EWM correlation pass,
or |
SolveStatus¶
SolveStatus
¶
Bases: StrEnum
Solver outcome labels for each timestamp.
Since :class:SolveStatus inherits from :class:str via StrEnum,
values compare equal to their string equivalents (e.g.
SolveStatus.VALID == "valid"), preserving backward compatibility
with code that matches on string literals.
Attributes:
| Name | Type | Description |
|---|---|---|
WARMUP |
Insufficient history for the sliding-window covariance mode. |
|
ZERO_SIGNAL |
The expected-return vector was all-zero; positions zeroed. |
|
DEGENERATE |
Normalisation denominator was non-finite, solve failed, or no asset had a finite price; positions zeroed for safety. |
|
VALID |
Linear system solved successfully; positions are non-trivially non-zero. |