Skip to content

markowitz.views.view_specs

markowitz.views.view_specs

User-facing view specifications and the :class:Views container.

A Black-Litterman investor expresses subjective opinions as a collection of views. Each view is either

  • an absolute view on a single asset (E[r_i] = q), or
  • a relative view that pits one basket of assets against another (sum(P_long) * r - sum(P_short) * r = q).

The :class:Views class collects a sequence of these dataclasses, validates them against the asset universe, and assembles the (P, Q) matrices used downstream by the Theil mixed-estimation form.

AbsoluteView(asset: str, expected_return: float, confidence: float | None = None) dataclass

A point estimate on a single asset's expected excess return.

RelativeView(long_leg: Mapping[str, float], short_leg: Mapping[str, float], spread: float, confidence: float | None = None) dataclass

A spread view between a long and a short basket of assets.

The basket weights specify how each leg is constructed: long_leg maps asset names to non-negative coefficients and likewise short_leg. The resulting pick row is long_leg - short_leg and must sum to zero (this is verified at construction time).

Views(views: Sequence[ViewSpec], assets: Sequence[str])

Container that validates and serialises a list of :class:ViewSpec.

Source code in src/markowitz/views/view_specs.py
def __init__(
    self,
    views: Sequence[ViewSpec],
    assets: Sequence[str],
) -> None:
    self._views: tuple[ViewSpec, ...] = tuple(views)
    self._assets: tuple[str, ...] = tuple(assets)
    self._asset_index: dict[str, int] = {a: i for i, a in enumerate(self._assets)}
    self._validate()

build_omega_he_litterman(tau: float, cov: np.ndarray) -> np.ndarray

Return the He-Litterman diagonal Omega = diag(P tau Sigma P^T).

Source code in src/markowitz/views/view_specs.py
def build_omega_he_litterman(self, tau: float, cov: np.ndarray) -> np.ndarray:
    """Return the He-Litterman diagonal ``Omega = diag(P tau Sigma P^T)``."""
    if tau <= 0.0:
        raise ViewValidationError(f"tau must be > 0, got {tau}.")
    if cov.shape[0] != cov.shape[1] or cov.shape[0] != len(self._assets):
        raise ViewValidationError(
            f"Covariance shape {cov.shape} incompatible with universe "
            f"of size {len(self._assets)}."
        )
    p_mat, _ = self.build_pq()
    diag = np.einsum("ki,ij,kj->k", p_mat, tau * cov, p_mat)
    return np.diag(diag)

build_pq() -> tuple[np.ndarray, np.ndarray]

Return the (P, Q) matrices for the current view set.

Source code in src/markowitz/views/view_specs.py
def build_pq(self) -> tuple[np.ndarray, np.ndarray]:
    """Return the ``(P, Q)`` matrices for the current view set."""
    k = len(self._views)
    n = len(self._assets)
    p_mat = np.zeros((k, n), dtype=float)
    q_vec = np.zeros(k, dtype=float)

    for i, view in enumerate(self._views):
        if isinstance(view, AbsoluteView):
            p_mat[i, self._asset_index[view.asset]] = 1.0
            q_vec[i] = float(view.expected_return)
        else:
            for asset, w in view.long_leg.items():
                p_mat[i, self._asset_index[asset]] += float(w)
            for asset, w in view.short_leg.items():
                p_mat[i, self._asset_index[asset]] -= float(w)
            q_vec[i] = float(view.spread)
    return p_mat, q_vec

confidences() -> np.ndarray | None

Return per-view confidences as an array, or None if unspecified.

Source code in src/markowitz/views/view_specs.py
def confidences(self) -> np.ndarray | None:
    """Return per-view confidences as an array, or ``None`` if unspecified."""
    if not self._views or self._views[0].confidence is None:
        return None
    return np.asarray([v.confidence for v in self._views], dtype=float)