Portfolio Views

class pyvallocation.views.BlackLittermanProcessor(*, prior_cov: numpy.ndarray | pandas.DataFrame, prior_mean: numpy.ndarray | pandas.Series | None = None, market_weights: numpy.ndarray | pandas.Series | None = None, risk_aversion: float = 1.0, tau: float = 0.05, idzorek_use_tau: bool = True, pi: numpy.ndarray | pandas.Series | None = None, mean_views: Any | None = None, view_confidences: Any | None = None, omega: Any | None = None, verbose: bool = False)[source]

Bases: object

Bayesian Black–Litterman (BL) updater for equality mean views.

The processor combines a prior distribution of excess returns \(\mathcal N(\boldsymbol\pi,\;\boldsymbol\Sigma)\) with user-supplied views

\[\mathbf P\,\boldsymbol\mu \;=\; \mathbf Q\;+\;\boldsymbol\varepsilon, \qquad \boldsymbol\varepsilon \sim \mathcal N\!\bigl(\mathbf 0,\, \boldsymbol\Omega\bigr),\]

where

  • P (self._p) is the \(K\times N\) pick matrix selecting linear combinations of the N asset means that are subject to views,

  • Q (self._q) is the \(K\times1\) view target vector,

  • \(\boldsymbol\Omega\) encodes view confidence as in He & Litterman [He and Litterman, 2002], or via the Idzorek diagonal construction [Idzorek, 2020].

With the shrinkage scalar \(\tau>0\) the posterior moments follow immediately from Bayesian mixed estimation [Black and Litterman, 1992]

(1)\[\begin{split}\begin{aligned} \boldsymbol\mu^{\star} &= \boldsymbol\pi + \tau\boldsymbol\Sigma\,\mathbf P^\top \bigl(\mathbf P\,\tau\boldsymbol\Sigma\,\mathbf P^\top +\boldsymbol\Omega\bigr)^{-1} \bigl(\mathbf Q - \mathbf P\,\boldsymbol\pi\bigr),\\ \boldsymbol\Sigma^{\star} &= \boldsymbol\Sigma + \tau\boldsymbol\Sigma - \tau\boldsymbol\Sigma\,\mathbf P^\top \bigl(\mathbf P\,\tau\boldsymbol\Sigma\,\mathbf P^\top +\boldsymbol\Omega\bigr)^{-1} \mathbf P\,\tau\boldsymbol\Sigma. \end{aligned}\end{split}\]

Only equality mean views (absolute or relative) are implemented; the entropy-pooling framework in FlexibleViewsProcessor should be used for inequalities or higher-order moment constraints.

Prior specification

Exactly one of the following mutually exclusive inputs must be supplied to initialise \(\boldsymbol\pi\):

  1. pi – direct numeric vector.

  2. market_weights – reverse-optimised \(\boldsymbol\pi=\delta\boldsymbol\Sigma\mathbf w\) (CAPM equilibrium) with risk-aversion \(\delta>0\) [Black and Litterman, 1992].

  3. prior_mean – treat the sample mean as \(\boldsymbol\pi\).

param prior_cov:

Prior covariance \(\boldsymbol\Sigma\).

type prior_cov:

(N, N) array_like

param prior_mean:

Prior mean vector (exclusive with pi / market_weights).

type prior_mean:

(N,) array_like, optional

param market_weights:

Market-cap weights used for CAPM reverse optimisation.

type market_weights:

(N,) array_like, optional

param risk_aversion:

Risk-aversion coefficient \(\delta\;(>0)\).

type risk_aversion:

float, default 1.0

param tau:

Shrinkage scalar \(\tau\) controlling the weight on the prior covariance; typical values 0.01–0.10 [He and Litterman, 2002].

type tau:

float, default 0.05

param idzorek_use_tau:

If True the Idzorek confidence rule scales by \(\tau\boldsymbol\Sigma\) (original He–Litterman convention); if False it uses \(\boldsymbol\Sigma\) instead.

type idzorek_use_tau:

bool, default True

param pi:

Direct prior mean (exclusive with the other two options above).

type pi:

(N,) array_like, optional

param mean_views:

Equality mean views:

  • {'Asset': 0.02} (absolute)

  • {('A','B'): 0.00} (relative \(\mu_A-\mu_B = 0\))

  • length-N array (per-asset absolute views).

type mean_views:

mapping or array_like, optional

param view_confidences:

Idzorek confidences \(c_k\in(0,1]\) per view.

type view_confidences:

float | sequence | dict, optional

param omega:

View covariance \(\boldsymbol\Omega\). * "idzorek" – derive from confidences. * vector length K – treated as diagonal. * full \(K\times K\) matrix – used verbatim.

type omega:

{“idzorek”} | array_like, optional

param verbose:

Print intermediate diagnostics.

type verbose:

bool, default False

posterior_mean

\(\boldsymbol\mu^{\star}\) from Eq. (1).

Type:

ndarray or pandas.Series

posterior_cov

\(\boldsymbol\Sigma^{\star}\) from Eq. (1).

Type:

ndarray or pandas.DataFrame

get_posterior() (posterior_mean, posterior_cov)[source]

Return the posterior moments in the same NumPy / Pandas flavour as the inputs.

Examples

>>> bl = BlackLittermanProcessor(
...         prior_cov=cov,
...         market_weights=cap_weights,
...         risk_aversion=2.5,
...         mean_views={('EM', 'DM'): 0.03},
...         view_confidences={'EM,DM': 0.60},
...         omega='idzorek')
>>> mu_bl, sigma_bl = bl.get_posterior()
get_posterior() Tuple[numpy.ndarray | pandas.Series, numpy.ndarray | pandas.DataFrame][source]
No-index:

class pyvallocation.views.FlexibleViewsProcessor(prior_returns: numpy.ndarray | pandas.DataFrame | None = None, prior_probabilities: numpy.ndarray | pandas.Series | None = None, *, prior_mean: numpy.ndarray | pandas.Series | None = None, prior_cov: numpy.ndarray | pandas.DataFrame | None = None, distribution_fn: Callable[[numpy.ndarray, numpy.ndarray, int, Any], numpy.ndarray] | None = None, num_scenarios: int = 10000, random_state: Any | None = None, mean_views: Any | None = None, vol_views: Any | None = None, corr_views: Any | None = None, skew_views: Any | None = None, sequential: bool = False)[source]

Bases: object

Entropy‑pooling engine with fully flexible moment views.

The FlexibleViewsProcessor class provides a robust framework for adjusting a discrete prior distribution of multivariate asset returns to incorporate user-specified views. This is achieved by minimizing the relative entropy (Kullback-Leibler divergence) between the prior and the new, “posterior” distribution. The class implements the Fully Flexible Views methodology proposed by Attilio Meucci, supporting views on various statistical moments, including means, variances, skewnesses, and correlations. It supports both simultaneous (“one‑shot”) and iterated (block‑wise) entropy pooling.

Mathematical background

Let \(R \in \mathbb{R}^{S\times N}\) be a scenario matrix representing \(S\) scenarios for \(N\) asset returns, with associated prior probabilities \(p^{(0)} \in (0,1)^S\). The entropy pooling problem is formally stated as:

\[\begin{split}\min_{q \in \Delta_S} &\; D_{\mathrm{KL}}(q\,\|\,p^{(0)}) \\ \text{s.t.} &\; Eq = b, \\ &\; Gq \le h,\end{split}\]

where \(\Delta_S\) denotes the probability simplex (i.e., \(q_s > 0\) for all \(s\) and \(\sum_{s=1}^S q_s = 1\)). The matrices \(E\) and \(G\), along with vectors \(b\) and \(h\), encode the user’s views as linear constraints on the posterior probabilities \(q\). The minimization of this problem is efficiently performed by minimizing its dual formulation, handled by the internal helper function _entropy_pooling_dual_objective().

For practitioners

  • Plug‑and‑play Scenario Handling: Users can either provide historical

    returns directly or specify prior mean and covariance, allowing the processor to synthesize scenarios. The output includes posterior moments (mean, covariance) and the adjusted probabilities.

  • Sequential Updating (Iterated EP): By setting sequential=True, the

    class applies view blocks (e.g., mean views, then volatility views) in a predefined order (mean → vol → skew → corr). This iterated approach, also known as Sequential Entropy Pooling (SeqEP), can lead to significantly better solutions and ensure logical consistency, especially when views on higher-order moments (like variance or skewness) implicitly depend on lower-order moments (like the mean). The original EP approach might introduce strong implicit views by fixing parameters to their prior values.

  • Flexible Inequality Views: Views can be specified not only as equalities

    but also as inequalities (e.g., ‘>=’, ‘<=’, ‘>’, ‘<’). For instance, vol_views={‘Equity US’: (‘<=’, 0.20)} sets an upper bound on the volatility. Equality is the default if no operator is specified.

param prior_returns:

A matrix or DataFrame of historical or simulated prior scenarios. S is the number of scenarios, and N is the number of assets/risk factors. If provided, prior_mean and prior_cov are ignored.

type prior_returns:

(S, N) ndarray or DataFrame, optional

param prior_probabilities:

A vector of prior scenario weights. If not provided, a uniform distribution (i.e., 1/S for each scenario) is assumed.

type prior_probabilities:

(S,) array_like, optional

param prior_mean:

The mean vector used for synthesizing scenarios when prior_returns is not supplied.

type prior_mean:

(N,) array_like, optional

param prior_cov:

The covariance matrix used for synthesizing scenarios when prior_returns is not supplied.

type prior_cov:

(N, N) array_like, optional

param distribution_fn:

A custom function for generating synthetic scenarios when prior_returns is not supplied. The function signature should be f(mu: np.ndarray, cov: np.ndarray, n: int, rng: np.random.Generator) -> np.ndarray returning an (n, N) array. If omitted, numpy.random.Generator.multivariate_normal() is used.

type distribution_fn:

callable, optional

param num_scenarios:

The number of synthetic scenarios to generate if prior_returns is not provided. Defaults to 10,000.

type num_scenarios:

int, default 10000

param random_state:

A seed or a numpy.random.Generator instance to ensure reproducibility when generating synthetic scenarios via distribution_fn or numpy.random.multivariate_normal.

type random_state:

int or numpy.random.Generator, optional

param mean_views:

View payloads. Keys are asset labels (or pairs for correlations); values are either a scalar x (equality) or a 2‑tuple such as ('>=', x).

type mean_views:

mapping or array_like, optional

param vol_views:

View payloads. Keys are asset labels (or pairs for correlations); values are either a scalar x (equality) or a 2‑tuple such as ('>=', x).

type vol_views:

mapping or array_like, optional

param corr_views:

View payloads. Keys are asset labels (or pairs for correlations); values are either a scalar x (equality) or a 2‑tuple such as ('>=', x).

type corr_views:

mapping or array_like, optional

param skew_views:

View payloads. Keys are asset labels (or pairs for correlations); values are either a scalar x (equality) or a 2‑tuple such as ('>=', x).

type skew_views:

mapping or array_like, optional

param sequential:

If True, views are applied sequentially (iterated EP). The views are processed in the order mean → vol → skew → corr. If False, all views are processed simultaneously in a single EP optimization.

type sequential:

bool, default False

posterior_probabilities

The optimal probability vector \(q\).

Type:

(S, 1) ndarray

posterior_returns

The posterior mean.

Type:

ndarray or Series

posterior_cov

The posterior covariance.

Type:

ndarray or DataFrame

Examples

>>> import numpy as np
>>> import pandas as pd
>>> from scipy.stats import multivariate_normal
>>>
>>> # Example with historical returns
>>> np.random.seed(42)
>>> returns_data = np.random.randn(100, 3) # S=100 scenarios, N=3 assets
>>> return_df = pd.DataFrame(returns_data, columns=['Asset A', 'Asset B', 'Asset C'])
>>>
>>> # Initialize with historical returns and views
>>> fp_hist = FlexibleViewsProcessor(
...     prior_returns=return_df,
...     mean_views={'Asset A': 0.05, 'Asset B': ('>=', 0.01)},
...     vol_views={'Asset A': ('<=', 0.15)},
...     sequential=True, # Apply views sequentially
... )
>>> q_hist = fp_hist.get_posterior_probabilities()
>>> mu_post_hist, cov_post_hist = fp_hist.get_posterior()
>>> print("Posterior Mean (Historical):", mu_post_hist)
>>> print("Posterior Covariance (Historical):\n", cov_post_hist)
>>>
>>> # Example with synthesized scenarios
>>> prior_mu = np.array([0.01, 0.02])
>>> prior_sigma = np.array([[0.01, 0.005], [0.005, 0.015]])
>>>
>>> # Initialize with mean and covariance, synthesizing 5000 scenarios
>>> fp_synth = FlexibleViewsProcessor(
...     prior_mean=prior_mu,
...     prior_cov=prior_sigma,
...     num_scenarios=5000,
...     random_state=123,
...     corr_views={('0', '1'): 0.8}, # View on correlation between asset 0 and 1
... )
>>> q_synth = fp_synth.get_posterior_probabilities()
>>> mu_post_synth, cov_post_synth = fp_synth.get_posterior()
>>> print("\nPosterior Mean (Synthesized):", mu_post_synth)
>>> print("Posterior Covariance (Synthesized):\n", cov_post_synth)
get_posterior() Tuple[numpy.ndarray | pandas.Series, numpy.ndarray | pandas.DataFrame][source]

Return (posterior mean, posterior covariance).

This method provides the key outputs of the entropy pooling process: the mean vector and covariance matrix of asset returns under the new, posterior probability distribution. These moments reflect the impact of the incorporated views.

Returns:

A tuple containing: * The posterior mean vector (as np.ndarray or pd.Series). * The posterior covariance matrix (as np.ndarray or pd.DataFrame). The type of return object (NumPy or Pandas) depends on the input type provided during the initialization of the FlexibleViewsProcessor.

Return type:

Tuple[Union[np.ndarray, pd.Series], Union[np.ndarray, pd.DataFrame]]

get_posterior_probabilities() numpy.ndarray[source]

Return the (S × 1) posterior probability vector.

This method provides access to the final probability distribution q computed by the entropy pooling process, which incorporates all specified views while remaining as close as possible to the original prior distribution.

Returns:

The optimal posterior probability vector, q, in column-vector form.

Return type:

np.ndarray

pyvallocation.views.entropy_pooling(p: numpy.ndarray, A: numpy.ndarray, b: numpy.ndarray, G: numpy.ndarray | None = None, h: numpy.ndarray | None = None, method: str | None = None) numpy.ndarray[source]

Return posterior probabilities via the entropy‑pooling algorithm.

This function serves as a wrapper around scipy.optimize.minimize() to solve the dual optimization problem of Entropy Pooling (EP), a method developed by Attilio Meucci. EP aims to find a new probability distribution that is as “close” as possible to a given prior distribution, while satisfying a set of linear constraints (views). The “closeness” is measured by the Kullback-Leibler (KL) divergence, also known as relative entropy.

The problem is formulated as minimizing the relative entropy \(D_{\mathrm{KL}}(q\,\|\,p^{(0)})\) subject to linear equality constraints \(Eq = b\) and inequality constraints \(Gq \le h\). This function solves the dual of this problem using numerical optimization.

Equality constraints are represented by the matrix A and vector b. Inequality constraints are represented by G and h. For inequality constraints \(Gq \le h\), the corresponding Lagrange multipliers are constrained to be non-negative.

The optimization is performed using quasi-Newton methods from scipy.optimize.minimize. Only 'TNC' (Truncated Newton) and 'L-BFGS-B' (Limited-memory Broyden–Fletcher–Goldfarb–Shanno algorithm with box constraints) are supported, as they allow for box bounds on the Lagrange multipliers.

Parameters:
  • p ((S,) or (S, 1) ndarray) – The prior probability vector, \(p^{(0)} \in (0,1)^S\), where \(S\) is the number of scenarios. This vector is typically uniform but can be any valid probability distribution (i.e., elements sum to 1 and are positive).

  • A (ndarray) – The matrix \(E\) for the equality constraints, \(Eq = b\). Its shape is \((K_{eq}, S)\), where \(K_{eq}\) is the number of equality constraints.

  • b (ndarray) – The target vector \(b\) for the equality constraints. Its shape is \((K_{eq},)\).

  • G (ndarray, optional) – The matrix \(G\) for the inequality constraints, \(Gq \le h\). Its shape is \((K_{ineq}, S)\), where \(K_{ineq}\) is the number of inequality constraints. Defaults to None if no inequality constraints are present.

  • h (ndarray, optional) – The target vector \(h\) for the inequality constraints. Its shape is \((K_{ineq},)\). Defaults to None if no inequality constraints are present.

  • method ({'TNC', 'L-BFGS-B'}, optional) – The optimization method to be used by scipy.optimize.minimize. Supported options are ‘TNC’ and ‘L-BFGS-B’. If None, the function defaults to ‘TNC’, which is generally faster for the current SciPy build.

Returns:

q – The posterior probability vector, \(q\), in column-vector form, satisfying the given constraints while minimizing relative entropy to p.

Return type:

(S, 1) ndarray

Raises:

ValueError – If an unsupported method is specified.

Notes

This function is an adaptation from the fortitudo.tech open-source package (https://github.com/fortitudo-tech/fortitudo.tech). The core methodology is described in Meucci (2008). The dual problem is minimized, and the primal solution (posterior probabilities) is recovered from the optimal Lagrange multipliers.