markowitz.core¶
markowitz.core
¶
Mathematical core for Markowitz mean-variance optimization.
This subpackage provides:
- Numerically stable linear algebra primitives (
linalg). - Closed-form Merton scalars and analytic frontier (
merton_scalars,frontier). - An immutable
Portfoliovalue object (portfolio). - A taxonomy of domain-specific exceptions (
exceptions).
AnalyticFrontier(mu: npt.ArrayLike, Sigma: npt.ArrayLike)
¶
Closed-form mean-variance frontier for (mu, Sigma).
Construction is the only point at which Sigma is factored; all
subsequent queries (gmv, tangency, efficient_return,
frontier) are O(n) once the cached basis vectors are available.
Source code in src/markowitz/core/frontier.py
abcd: MertonABCD
property
¶
The Merton scalars associated with (mu, Sigma).
is_degenerate: bool
property
¶
True when D is essentially zero.
A degenerate frontier collapses to a single point in mean-
variance space because mu is collinear with 1 (every
asset has the same expected return). Efficient-return queries
are still well-defined at the GMV expected return but are
infeasible for any other target.
n_assets: int
property
¶
Number of assets in the investment universe.
efficient_return(mu_p: float) -> Portfolio
¶
Return the minimum-variance portfolio with expected return mu_p.
Source code in src/markowitz/core/frontier.py
frontier(n_points: int = 100, *, mu_min: float | None = None, mu_max: float | None = None) -> list[Portfolio]
¶
Return n_points portfolios spanning [mu_min, mu_max].
Defaults straddle the GMV expected return symmetrically: if no
bounds are supplied, the frontier extends from mu_GMV to
mu_GMV + 2 * (max(mu) - mu_GMV). Callers that need a
specific range should pass it explicitly.
Source code in src/markowitz/core/frontier.py
gmv() -> Portfolio
¶
Return the global minimum-variance portfolio.
Source code in src/markowitz/core/frontier.py
tangency(rf: float) -> Portfolio
¶
Return the tangency portfolio for risk-free rate rf.
Raises:
| Type | Description |
|---|---|
InfeasibleFrontierError
|
If |
Source code in src/markowitz/core/frontier.py
CoreError
¶
Bases: Exception
Base class for all errors raised by markowitz.core.
InfeasibleFrontierError
¶
Bases: CoreError
Raised when the requested frontier point does not exist.
Examples include requesting the tangency portfolio at a risk-free rate that coincides with the GMV expected return, or asking for an efficient portfolio at a return that lies outside the attainable band of a degenerate frontier.
MertonABCD
¶
Bases: NamedTuple
The four Merton scalars characterising an analytic frontier.
NumericalError
¶
Bases: CoreError
Raised for numerical failures that are not covered by the more specific subclasses (e.g. NaN inputs, non-finite intermediate results, or breakdowns of an iterative solver).
Portfolio(weights: FloatArray, expected_return: float, volatility: float, sharpe: float | None = None)
dataclass
¶
A snapshot of a portfolio's weights and analytic performance.
Attributes:
| Name | Type | Description |
|---|---|---|
weights |
FloatArray
|
Length- |
expected_return |
float
|
|
volatility |
float
|
|
sharpe |
float | None
|
Sharpe ratio relative to whatever risk-free rate the caller
chose; |
SingularCovarianceError(msg: str = '', *, condition_number: float = float('inf'), min_eigenvalue: float = 0.0)
¶
Bases: CoreError
Raised when a covariance matrix is not positive definite.
The instance attaches diagnostic information that is helpful for downstream regularization heuristics or user-facing error messages.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
msg
|
str
|
Human-readable explanation of the failure. |
''
|
condition_number
|
float
|
Ratio |
float('inf')
|
min_eigenvalue
|
float
|
Smallest eigenvalue of the offending matrix (possibly negative). |
0.0
|
Source code in src/markowitz/core/exceptions.py
cholesky_solve(Sigma: npt.ArrayLike, b: npt.ArrayLike, *, check_finite: bool = True, overwrite_b: bool = False) -> FloatArray
¶
Solve Sigma x = b for a symmetric positive definite Sigma.
The Cholesky factorization is preferred over forming the explicit inverse: it is twice as fast for SPD systems and avoids the catastrophic cancellation associated with inverting near-singular matrices.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
Sigma
|
ArrayLike
|
Symmetric positive definite matrix of shape |
required |
b
|
ArrayLike
|
Right-hand side of shape |
required |
check_finite
|
bool
|
Forwarded to SciPy; when |
True
|
overwrite_b
|
bool
|
Permit SciPy to overwrite |
False
|
Returns:
| Name | Type | Description |
|---|---|---|
x |
FloatArray
|
Solution array with the same shape as |
Raises:
| Type | Description |
|---|---|
SingularCovarianceError
|
If |
NumericalError
|
If |
Source code in src/markowitz/core/linalg.py
compute_abcd(mu: npt.ArrayLike, Sigma: npt.ArrayLike) -> MertonABCD
¶
Compute Merton's (A, B, C, D) scalars.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mu
|
ArrayLike
|
Expected-return vector of length |
required |
Sigma
|
ArrayLike
|
Symmetric positive definite covariance matrix of shape |
required |
Returns:
| Name | Type | Description |
|---|---|---|
A |
class:`MertonABCD` named tuple.
|
|
Raises:
| Type | Description |
|---|---|
NumericalError
|
If shapes are inconsistent or |
SingularCovarianceError
|
Propagated from :func: |
Source code in src/markowitz/core/merton_scalars.py
condition_number(Sigma: npt.ArrayLike) -> float
¶
Return lambda_max / lambda_min for Sigma.
Returns float('inf') if the matrix is not positive definite,
contains non-finite entries, or has a non-positive smallest
eigenvalue. No exception is raised.
Source code in src/markowitz/core/linalg.py
portfolio_performance(weights: npt.ArrayLike, mu: npt.ArrayLike, Sigma: npt.ArrayLike, *, rf: float | None = None) -> Portfolio
¶
Compute analytic performance for a given weight vector.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
weights
|
ArrayLike
|
Length- |
required |
mu
|
ArrayLike
|
Expected returns. |
required |
Sigma
|
ArrayLike
|
Covariance matrix. |
required |
rf
|
float | None
|
Optional risk-free rate. When supplied, |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
A |
class:`Portfolio` instance.
|
|
Raises:
| Type | Description |
|---|---|
NumericalError
|
If shapes are inconsistent or inputs contain non-finite values. |
Source code in src/markowitz/core/portfolio.py
psd_check(Sigma: npt.ArrayLike, *, tol: float = 1e-10, require_symmetric: bool = True, symmetry_tol: float = 1e-10) -> None
¶
Validate that Sigma is symmetric positive definite.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
Sigma
|
ArrayLike
|
Candidate covariance matrix. |
required |
tol
|
float
|
Lower bound for the smallest eigenvalue. Matrices whose
minimum eigenvalue is below |
1e-10
|
require_symmetric
|
bool
|
Reject matrices that are not symmetric within |
True
|
symmetry_tol
|
float
|
Absolute tolerance for the |
1e-10
|
Raises:
| Type | Description |
|---|---|
SingularCovarianceError
|
If the matrix is not symmetric (when required), not positive definite, or contains non-finite entries. |
ValueError
|
If the matrix is not 2-D and square. |
Source code in src/markowitz/core/linalg.py
regularize(Sigma: npt.ArrayLike, *, eps: float | Literal['auto'] = 'auto', method: Literal['ridge'] = 'ridge') -> FloatArray
¶
Return a regularized copy of Sigma that is safely SPD.
The default ridge method adds eps * I to a symmetrized copy
of Sigma. When eps='auto', a scale-aware value is chosen
based on the trace of Sigma and machine precision.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
Sigma
|
ArrayLike
|
Input matrix. |
required |
eps
|
float | Literal['auto']
|
Either an explicit non-negative scalar or the string |
'auto'
|
method
|
Literal['ridge']
|
Currently only |
'ridge'
|
Returns:
| Type | Description |
|---|---|
A new ``(n, n)`` ``float64`` array.
|
|