Portfolio Views

Investment view tooling resides in pyvallocation.views. It covers both Bayesian updates and entropy-based scenario reweighting:

  • pyvallocation.views.BlackLittermanProcessor - classical Black-Litterman equality views with Idzorek confidences.

  • pyvallocation.views.FlexibleViewsProcessor - entropy pooling with inequalities on means, volatilities, correlations, and higher moments.

  • pyvallocation.views.entropy_pooling() - low-level solver returning reweighted scenario probabilities.

class pyvallocation.views.BlackLittermanProcessor(*, prior_cov, prior_mean=None, market_weights=None, risk_aversion=1.0, tau=0.05, idzorek_use_tau=True, pi=None, mean_views=None, view_confidences=None, omega=None, verbose=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\).

Parameters:
  • prior_cov (array-like) – Prior covariance \(\\boldsymbol\\Sigma\), shape (N, N).

  • prior_mean (array-like, optional) – Prior mean vector, exclusive with pi / market_weights.

  • market_weights (array-like, optional) – Market-cap weights used for CAPM reverse optimisation.

  • risk_aversion (float, optional) – Risk-aversion coefficient \(\\delta (>0)\). Defaults to 1.0.

  • tau (float, optional) – Shrinkage scalar \(\\tau\) for the prior covariance. Typical values 0.010.10. Defaults to 0.05. [He and Litterman, 2002].

  • idzorek_use_tau (bool, optional) – If True, the Idzorek rule scales by \(\\tau\\boldsymbol\\Sigma\); otherwise it uses \(\\boldsymbol\\Sigma\). Defaults to True.

  • pi (array-like, optional) – Direct prior mean, exclusive with the other options above.

  • mean_views (mapping or array-like, optional) – Equality mean views. Examples: {'Asset': 0.02} (absolute), {('A','B'): 0.00} (relative), or length-N array (per-asset absolute views).

  • view_confidences (float | sequence | dict, optional) – Idzorek confidences \(c_k \\in (0,1]\) per view.

  • omega ({“idzorek”} | array-like, optional) – View covariance \(\\boldsymbol\\Omega\). "idzorek" derives it from confidences; vector length K is diagonal; full K x K matrices are used verbatim.

  • verbose (bool, optional) – If True, prints intermediate diagnostics. Defaults to False.

posterior_mean

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

Type:

np.ndarray or pd.Series

posterior_cov

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

Type:

np.ndarray or pd.DataFrame

get_posterior()[source]

Return posterior moments in the same

Return type:

Tuple[numpy.ndarray | pandas.Series, numpy.ndarray | pandas.DataFrame]

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()[source]
No-index:

Return type:

Tuple[numpy.ndarray | pandas.Series, numpy.ndarray | pandas.DataFrame]

Return posterior mean and covariance.

Returns:

Tuple[ArrayLike, ArrayLike] – Posterior mean and covariance.

Return type:

Tuple[numpy.ndarray | pandas.Series, numpy.ndarray | pandas.DataFrame]

Parameters:
  • prior_cov (numpy.ndarray | pd.DataFrame)

  • prior_mean (numpy.ndarray | pd.Series | None)

  • market_weights (numpy.ndarray | pd.Series | None)

  • risk_aversion (float)

  • tau (float)

  • idzorek_use_tau (bool)

  • pi (numpy.ndarray | pd.Series | None)

  • mean_views (Any)

  • view_confidences (Any)

  • omega (Any)

  • verbose (bool)

class pyvallocation.views.FlexibleViewsProcessor(prior_risk_drivers=None, prior_probabilities=None, *, prior_mean=None, prior_cov=None, distribution_fn=None, num_scenarios=10000, random_state=None, mean_views=None, vol_views=None, var_views=None, corr_views=None, skew_views=None, cvar_views=None, quantile_views=None, rank_mean=None, sequential=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.

Parameters:
  • prior_risk_drivers (np.ndarray or pd.DataFrame, optional) – Prior risk-driver scenarios (returns, factors, spreads, vol surfaces, etc.). If supplied, prior_mean and prior_cov are ignored.

  • prior_probabilities (array-like, optional) – Prior scenario weights. If omitted, uniform probabilities 1/S are used.

  • prior_mean (array-like, optional) – Mean vector used to synthesize scenarios when prior_risk_drivers is not supplied.

  • prior_cov (array-like, optional) – Covariance matrix used to synthesize scenarios when prior_risk_drivers is not supplied.

  • distribution_fn (callable, optional) – Custom scenario generator with signature f(mu, cov, n, rng) -> np.ndarray returning shape (n, N). Defaults to numpy.random.Generator.multivariate_normal.

  • num_scenarios (int, optional) – Number of synthetic scenarios when prior_risk_drivers is missing. Defaults to 10000.

  • random_state (int or np.random.Generator, optional) – Seed or RNG used by distribution_fn / multivariate_normal.

  • mean_views, vol_views, corr_views, skew_views (mapping or array-like, optional) – View payloads. Keys are asset labels (or pairs for correlations). Values are scalars (equalities) or tuples like ('>=', x).

  • sequential (bool, optional) – If True, apply views sequentially (mean → vol → skew → corr). Defaults to False (single entropy pooling solve).

posterior_probabilities

Optimal posterior probabilities \(q\), shape (S, 1).

Type:

np.ndarray

posterior_returns

Posterior mean.

Type:

np.ndarray or pd.Series

posterior_cov

Posterior covariance.

Type:

np.ndarray or pd.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_risk_drivers=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},  # Correlation view 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()[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:

Tuple[Union[np.ndarray, pd.Series], Union[np.ndarray, pd.DataFrame]] – 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[numpy.ndarray | pandas.Series, numpy.ndarray | pandas.DataFrame]

get_posterior_probabilities()[source]

Return the (S x 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:

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

Return type:

numpy.ndarray

get_scenarios()[source]

Return the scenario matrix used for entropy pooling.

If prior_risk_drivers was provided, returns a DataFrame (or array) with the same structure. If scenarios were synthesised from prior_mean and prior_cov, returns the synthetic matrix.

Use this to pass the exact scenarios to from_invariants() together with get_posterior_probabilities().

Return type:

numpy.ndarray | pandas.DataFrame

Parameters:
  • prior_risk_drivers (numpy.ndarray | pd.DataFrame | None)

  • prior_probabilities (numpy.ndarray | pd.Series | None)

  • prior_mean (numpy.ndarray | pd.Series | None)

  • prior_cov (numpy.ndarray | pd.DataFrame | None)

  • distribution_fn (Callable[[numpy.ndarray, numpy.ndarray, int, Any], numpy.ndarray] | None)

  • num_scenarios (int)

  • random_state (Any)

  • mean_views (Any)

  • vol_views (Any)

  • var_views (Any)

  • corr_views (Any)

  • skew_views (Any)

  • cvar_views (Any)

  • quantile_views (Any)

  • rank_mean (List | None)

  • sequential (bool)

pyvallocation.views.above(value)[source]

View target: strictly greater than value.

Examples

>>> mean_views = {"SPY": above(0.0)}  # E[SPY] > 0 (positive return)
pyvallocation.views.at_least(value)[source]

View target: greater than or equal to value.

Examples

>>> mean_views = {"SPY": at_least(0.05)}   # E[SPY] >= 5%
>>> vol_views  = {"TLT": at_least(0.08)}   # sigma_TLT >= 8%
pyvallocation.views.at_most(value)[source]

View target: less than or equal to value.

Examples

>>> vol_views  = {"SPY": at_most(0.20)}   # sigma_SPY <= 20%
>>> corr_views = {("SPY", "TLT"): at_most(0.0)}  # non-positive correlation
pyvallocation.views.below(value)[source]

View target: strictly less than value.

Examples

>>> mean_views = {"SPY": below(0.0)}  # E[SPY] < 0 (negative return)
pyvallocation.views.between(lo, hi)[source]

View target: lo <= target <= hi (range view).

Expands to two constraints internally (a >= and a <=).

Examples

>>> vol_views = {"SPY": between(0.12, 0.20)}  # 12% <= sigma_SPY <= 20%
>>> mean_views = {"GLD": between(0.01, 0.06)}  # 1% <= E[GLD] <= 6%
pyvallocation.views.entropy_pooling(p, A, b, G=None, h=None, method=None)[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 (np.ndarray) – Prior probability vector \(p^{(0)} \\in (0,1)^S\), shape (S,) or (S, 1). Must sum to 1 with strictly positive entries.

  • A (np.ndarray) – Equality constraint matrix \(E\) in \(Eq=b\), shape (K_eq, S).

  • b (np.ndarray) – Equality targets \(b\), shape (K_eq,).

  • G (np.ndarray, optional) – Inequality constraint matrix \(G\) in \(Gq \\le h\), shape (K_ineq, S). Defaults to None.

  • h (np.ndarray, optional) – Inequality targets \(h\), shape (K_ineq,). Defaults to None.

  • method (str, optional) – Optimizer passed to scipy.optimize.minimize. Supported values: "TNC" or "L-BFGS-B". Defaults to "TNC".

Returns:

np.ndarray – Posterior probability column vector \(q\) with shape (S, 1) satisfying the constraints while minimizing relative entropy.

Raises:

ValueError – If an unsupported method is specified.

Parameters:
  • p (numpy.ndarray)

  • A (numpy.ndarray)

  • b (numpy.ndarray)

  • G (numpy.ndarray | None)

  • h (numpy.ndarray | None)

  • method (str | None)

Return type:

numpy.ndarray

Notes

Adapted from fortitudo.tech (https://github.com/fortitudo-tech/fortitudo.tech). The core methodology is described in Meucci (2008). The dual problem is minimized and the posterior probabilities are recovered from the optimal Lagrange multipliers.