Portfolio API

The pyvallocation.portfolioapi module provides the public entry points used across the tutorials and examples:

  • pyvallocation.portfolioapi.AssetsDistribution stores parametric or scenario-based return descriptions while preserving labels.

  • pyvallocation.portfolioapi.PortfolioWrapper exposes efficient frontiers (mean-variance, CVaR, relaxed risk parity, robust optimisation) with a uniform interface for constraints, turnover costs, and selectors.

  • pyvallocation.portfolioapi.PortfolioFrontier encapsulates solved frontiers and offers convenience selectors such as tangency() and at_risk().

All helpers preserve pandas alignment and cooperate with the ensembling and discrete allocation utilities.

class pyvallocation.portfolioapi.AssetsDistribution(mu=None, cov=None, scenarios=None, probabilities=None, asset_names=None)[source]

Bases: object

An immutable container for asset return distributions.

This class validates and stores the statistical properties of assets, which can be represented either parametrically (mean and covariance) or non-parametrically (scenarios and their probabilities). It automatically handles both NumPy arrays and pandas Series/DataFrames, ensuring data consistency.

Parameters:
  • mu (Optional[Union[npt.NDArray[np.floating], pd.Series]])

  • cov (Optional[Union[npt.NDArray[np.floating], pd.DataFrame]])

  • scenarios (Optional[Union[npt.NDArray[np.floating], pd.DataFrame]])

  • probabilities (Optional[Union[npt.NDArray[np.floating], pd.Series]])

  • asset_names (Optional[List[str]])

mu

A 1D array or pandas.Series of expected returns for each asset (N,).

Type:

Optional[Union[npt.NDArray[np.floating], pd.Series]]

cov

A 2D covariance matrix of asset returns (N, N).

Type:

Optional[Union[npt.NDArray[np.floating], pd.DataFrame]]

scenarios

A 2D array or pandas.DataFrame of shape (T, N), where each row is a market scenario.

Type:

Optional[Union[npt.NDArray[np.floating], pd.DataFrame]]

probabilities

A 1D array or pandas.Series of probabilities corresponding to each scenario (T,).

Type:

Optional[Union[npt.NDArray[np.floating], pd.Series]]

asset_names

A list of names for the assets. If not provided, inferred from pandas inputs.

Type:

Optional[List[str]]

N

The number of assets, inferred from the input data.

Type:

int

T

The number of scenarios, inferred from the input data. None if parametric distribution is used.

Type:

Optional[int]

Assumptions & Design Choices:
  • If “scenarios” are provided without “probabilities”, probabilities are assumed to be uniform across all scenarios.

  • If scenarios are provided but mu and cov are not, the mean and covariance will be estimated from the scenarios, accompanied by a warning.

  • If provided “probabilities” do not sum to 1.0, they are automatically normalized with a warning. This choice ensures downstream solvers receive valid probability distributions.

  • If pandas objects are used for inputs, asset names are inferred from their indices or columns. It is assumed that the order and names are consistent across all provided pandas objects.

N: int
T: int | None
asset_names: List[str] | None = None
cov: numpy.typing.NDArray.numpy.floating | pandas.DataFrame | None = None
mu: numpy.typing.NDArray.numpy.floating | pandas.Series | None = None
probabilities: numpy.typing.NDArray.numpy.floating | pandas.Series | None = None
scenarios: numpy.typing.NDArray.numpy.floating | pandas.DataFrame | None = None
class pyvallocation.portfolioapi.PortfolioFrontier(weights, returns, risks, risk_measure, asset_names=None, metadata=None, alternate_risks=<factory>)[source]

Bases: object

Represents an efficient frontier of optimal portfolios.

This immutable container holds the results of an optimization run that generates a series of efficient portfolios. It provides methods to easily query and analyze specific portfolios on the frontier.

Parameters:
  • weights (npt.NDArray[np.floating])

  • returns (npt.NDArray[np.floating])

  • risks (npt.NDArray[np.floating])

  • risk_measure (str)

  • asset_names (Optional[List[str]])

  • metadata (Optional[List[Dict[str, Any]]])

  • alternate_risks (Dict[str, np.ndarray])

weights

A 2D NumPy array of shape (N, M), where N is the number of assets and M is the number of portfolios on the frontier. Each column represents the weights of an optimal portfolio.

Type:

npt.NDArray[np.floating]

returns

A 1D NumPy array of shape (M,) containing the expected returns for each portfolio on the frontier.

Type:

npt.NDArray[np.floating]

risks

Primary risk vector of length M (e.g., volatility, CVaR, estimation risk).

Type:

npt.NDArray[np.floating]

risk_measure

Label describing the primary risk measure.

Type:

str

asset_names

An optional list of names for the assets. If provided, enables pandas Series/DataFrame output for portfolio weights.

Type:

Optional[List[str]]

metadata

Optional per-portfolio diagnostics. Each entry maps diagnostic field names (e.g., target_multiplier) to their values.

Type:

Optional[List[Dict[str, Any]]]

alternate_risks

Optional auxiliary risk grids keyed by label (e.g., "Volatility" when the frontier is built on CVaR). Shapes must match (M,). Access via risk_label=... parameters.

Type:

Dict[str, np.ndarray]

alternate_risks: Dict[str, numpy.ndarray]
as_discrete_allocation(column, latest_prices, total_value, *, method='greedy', lot_sizes=None, **kwargs)[source]

Convert a selected frontier portfolio into a discrete allocation.

Parameters:
  • column – Frontier column index to discretize.

  • latest_prices – Series or mapping of asset prices.

  • total_value – Total portfolio value available.

  • method – Discrete allocation method (default "greedy").

  • lot_sizes – Optional lot sizes per asset.

  • **kwargs – Extra arguments forwarded to the allocator.

Returns:

DiscreteAllocationResult – Allocation result with share counts.

Parameters:
  • column (int)

  • latest_prices (pandas.Series | Mapping[str, float])

  • total_value (float)

  • method (str)

  • lot_sizes (pandas.Series | Mapping[str, int] | None)

Return type:

DiscreteAllocationResult

asset_names: List[str] | None = None
at_percentile(percentile, *, risk_label=None)[source]

Select the portfolio closest to a risk percentile (0-1 or 0-100).

Parameters:
  • percentile – Percentile in [0, 1] or [0, 100].

  • risk_label – Risk label to use for ranking.

Returns:

Tuple[pd.Series, float, float] – Weights, return, and risk value.

Parameters:
  • percentile (float)

  • risk_label (str | None)

Return type:

Tuple[pandas.Series, float, float]

at_return(min_return, *, risk_label=None)[source]

Finds the portfolio that minimizes risk for a given expected return target.

This method identifies the portfolio on the frontier that has the lowest risk, subject to its return being greater than or equal to min_return.

Parameters:
  • min_return (float) – The minimum required expected return.

  • risk_label – Optional alternate risk measure used for the minimisation.

Returns:

Tuple[pd.Series, float, float]

A tuple containing:
  • weights (pandas.Series): The weights of the portfolio.

  • returns (float): The expected return of the portfolio.

  • risk (float): The risk of the portfolio.

Parameters:
  • min_return (float)

  • risk_label (str | None)

Return type:

Tuple[pandas.Series, float, float]

at_risk(max_risk, *, risk_label=None)[source]

Finds the portfolio that maximizes return for a given risk tolerance.

This method identifies the portfolio on the frontier that has the highest return, subject to its risk being less than or equal to max_risk.

Parameters:
  • max_risk (float) – The maximum allowable risk.

  • risk_label – Optional alternate risk measure name (see available_risk_measures()). Defaults to the primary risk.

Returns:

Tuple[pd.Series, float, float]

A tuple containing:
  • weights (pandas.Series): The weights of the portfolio.

  • returns (float): The expected return of the portfolio.

  • risk (float): The risk of the portfolio.

Parameters:
  • max_risk (float)

  • risk_label (str | None)

Return type:

Tuple[pandas.Series, float, float]

available_risk_measures()[source]

Return list of risk measure labels carried by this frontier.

Returns:

list[str] – Primary and alternate risk labels.

Return type:

List[str]

closest_risk(target_risk, *, risk_label=None)[source]

Select the portfolio whose risk is nearest target_risk (L1 distance).

Useful for aligning risk levels across frontiers built with different models.

Parameters:
  • target_risk (float)

  • risk_label (str | None)

Return type:

Tuple[pandas.Series, float, float]

index_at_risk_percentile(percentile, *, risk_label=None)[source]

Return the column index closest to a risk percentile.

Parameters:
  • percentile – Percentile in [0, 1] or [0, 100].

  • risk_label – Risk label to use for ranking.

Returns:

int – Column index on the frontier.

Parameters:
  • percentile (float)

  • risk_label (str | None)

Return type:

int

metadata: List[Dict[str, Any]] | None = None
min_risk(*, risk_label=None)[source]

Finds the portfolio with the minimum risk on the efficient frontier.

Parameters:

risk_label – Optional alternate risk measure name (see available_risk_measures()). Defaults to the primary risk.

Returns:

Tuple[pd.Series, float, float]

A tuple containing:
  • weights (pandas.Series): The weights of the minimum risk portfolio.

  • returns (float): The expected return of the minimum risk portfolio.

  • risk (float): The risk of the minimum risk portfolio.

Parameters:

risk_label (str | None)

Return type:

Tuple[pandas.Series, float, float]

returns: numpy.typing.NDArray.numpy.floating
risk_measure: str
risk_percentiles(risk_label=None)[source]

Return per-portfolio risk percentiles on [0, 1].

Parameters:

risk_label – Risk label to use for ranking. Defaults to primary risk.

Returns:

np.ndarray – Percentile ranks in [0, 1].

Parameters:

risk_label (str | None)

Return type:

numpy.ndarray

risks: numpy.typing.NDArray.numpy.floating
tangency(risk_free_rate)[source]

Calculates the tangency portfolio, which represents the portfolio with the maximum Sharpe ratio.

The Sharpe ratio is defined as (portfolio_return - risk_free_rate) / portfolio_risk.

Parameters:

risk_free_rate (float) – The risk-free rate of return.

Returns:

Tuple[pd.Series, float, float]

A tuple containing:
  • weights (pandas.Series): The weights of the tangency portfolio.

  • returns (float): The expected return of the tangency portfolio.

  • risk (float): The risk of the tangency portfolio.

Parameters:

risk_free_rate (float)

Return type:

Tuple[pandas.Series, float, float]

to_frame(columns=None, *, column_labels=None)[source]

Return the frontier weights as a pandas DataFrame.

Parameters:
  • columns – Optional iterable of column indices selecting specific portfolios.

  • column_labels – Optional labels for the resulting DataFrame columns.

Returns:

A DataFrame whose rows correspond to assets and whose columns correspond to efficient portfolios.

Raises:

ValueError – If column_labels is supplied with a length that does not match the number of selected portfolios.

Parameters:
  • columns (Iterable[int] | None)

  • column_labels (Sequence[str] | None)

Return type:

pandas.DataFrame

to_samples(columns=None, *, as_frame=True)[source]

Return the frontier weights as either a DataFrame or raw NumPy samples.

Parameters:
  • columns – Optional iterable selecting specific portfolio indices.

  • as_frame – When True (default) return a pandas DataFrame. When False return (matrix, asset_names) suitable for downstream NumPy consumption.

Parameters:
  • columns (Iterable[int] | None)

  • as_frame (bool)

Return type:

pandas.DataFrame | Tuple[numpy.ndarray, List[str] | None]

class pyvallocation.portfolioapi.PortfolioWrapper(distribution, *, constraints=None, costs=None, long_only=True, total_weight=1.0, bounds=None)[source]

Bases: object

High-level interface for portfolio construction and optimization.

Factory classmethods cover every user level:

  • Newbie: PortfolioWrapper.from_prices(price_df)

  • Parametric: PortfolioWrapper.from_moments(mu, cov)

  • Scenario-based: PortfolioWrapper.from_scenarios(scenarios)

  • Meucci Prayer (P3→P4): PortfolioWrapper.from_invariants(invariants, reprice=..., horizon=52)

  • Bayesian robust: PortfolioWrapper.from_robust_posterior(posterior)

Then compute frontiers, select portfolios, and analyze:

frontier = port.variance_frontier()
w, ret, risk = frontier.tangency(risk_free_rate=0.04)

Constraints and costs can be passed to factories or per-method.

Parameters:
  • distribution (AssetsDistribution)

  • constraints (Union['Constraints', Dict[str, Any], None])

  • costs (Optional['TransactionCosts'])

  • long_only (bool)

  • total_weight (float)

  • bounds (Optional[Any])

assemble_ensembles(specs, **kwargs)[source]

Proxy to pyvallocation.ensembles.assemble_portfolio_ensemble().

Parameters:
  • specs – Sequence of ensemble specifications.

  • **kwargs – Forwarded to assemble_portfolio_ensemble().

Returns:

EnsembleResult – Aggregated ensemble output.

Parameters:
Return type:

EnsembleResult

cvar_frontier(num_portfolios=10, alpha=0.05, seed=None, *, constraints=None, costs=None, enforce_convexity=True)[source]

Compute the Mean-CVaR efficient frontier.

Implementation Notes:
  • This method requires scenarios. If only mu and cov are provided, it makes a strong modeling assumption to simulate scenarios from a multivariate normal distribution.

Parameters:
  • num_portfolios – The number of portfolios to compute. Defaults to 10.

  • alpha – The tail probability for CVaR. Defaults to 0.05.

  • seed – Random seed for scenario simulation reproducibility.

  • constraints – Optional Constraints or dict overriding the instance constraints.

  • costs – Optional TransactionCosts overriding instance costs.

  • enforce_convexity – Apply monotonicity and convex-envelope correction to the frontier (default True). Set False to return raw solver output.

Returns:

A PortfolioFrontier object whose columns are sorted by non-decreasing CVaR. An auxiliary alternate_risks['Volatility'] is attached for variance-based selection.

Parameters:
  • num_portfolios (int)

  • alpha (float)

  • seed (int | None)

  • enforce_convexity (bool)

Return type:

PortfolioFrontier

classmethod from_invariants(invariants, *, horizon=1, n_simulations=5000, p=None, reprice=None, seed=None, constraints=None, costs=None, long_only=True, total_weight=1.0, bounds=None)[source]

Create a wrapper via Meucci’s Prayer: project invariants, reprice to P&L.

Chains P3 Projection (bootstrap invariants over horizon steps) and P4 Pricing (reprice to P&L) to produce simple-return scenarios ready for optimisation (P6).

Parameters:
  • invariants – Historical invariant scenarios (T, K). DataFrame or array of risk-driver observations (log-returns, yield changes, implied-vol changes, …).

  • horizon – Investment horizon in invariant time steps.

  • n_simulations – Number of projected P&L scenarios.

  • p – Flexible probabilities for the invariants (e.g. posterior from FlexibleViewsProcessor).

  • repriceP4 repricing callable. Default reprice_exp() (exp(x)-1, suitable for log-return invariants).

    • callable: applied to the full (n_sim, K) projected matrix.

    • dict {col_name: callable}: per-column repricing (requires DataFrame invariants).

  • seed – Random seed for reproducibility.

  • constraints – Constraint specification.

  • costs – Transaction cost specification.

  • long_only – Shortcut for long-only constraint. Defaults to True.

  • total_weight – Shortcut for weight-sum constraint. Defaults to 1.0.

  • bounds – Shortcut for per-asset bounds.

Parameters:
  • invariants (Union[npt.NDArray[np.floating], pd.DataFrame])

  • horizon (int)

  • n_simulations (int)

  • p (Optional[Union[npt.NDArray[np.floating], pd.Series]])

  • reprice (Optional[Any])

  • seed (Optional[int])

  • constraints (Union[Constraints, Dict[str, Any], None])

  • costs (Optional[TransactionCosts])

  • long_only (bool)

  • total_weight (float)

  • bounds (Optional[Any])

Return type:

PortfolioWrapper

Examples

>>> # Equities (log-return invariants, default reprice_exp)
>>> log_rets = np.log(prices / prices.shift(1)).dropna()
>>> wrapper = PortfolioWrapper.from_invariants(log_rets, horizon=52, seed=42)
>>> frontier = wrapper.cvar_frontier()
>>> # Mixed portfolio (per-instrument repricing)
>>> wrapper = PortfolioWrapper.from_invariants(
...     invariants_df,
...     reprice={"SPY": reprice_exp,
...              "TLT": lambda dy: reprice_taylor(dy, delta=-7.2, gamma=55)},
...     horizon=52,
... )
classmethod from_moments(mu, cov, *, constraints=None, costs=None, long_only=True, total_weight=1.0, bounds=None, return_type='simple')[source]

Create a ready-to-use wrapper from mean and covariance.

Applies ensure_psd_matrix to the covariance and sets constraints in one call. When constraints is provided (as a Constraints object or dict), it overrides the long_only/total_weight/bounds shortcuts.

Parameters:

return_type"simple" (default) or "log". When "log", moments are converted to simple-return space via log2simple() so the optimizer works in P&L space per Meucci’s Prayer.

Parameters:
  • mu (Union[npt.NDArray[np.floating], pd.Series])

  • cov (Union[npt.NDArray[np.floating], pd.DataFrame])

  • constraints (Union[Constraints, Dict[str, Any], None])

  • costs (Optional[TransactionCosts])

  • long_only (bool)

  • total_weight (float)

  • bounds (Optional[Any])

  • return_type (str)

Return type:

PortfolioWrapper

Examples

>>> wrapper = PortfolioWrapper.from_moments(mu, cov)
>>> frontier = wrapper.variance_frontier()
classmethod from_prices(prices, *, constraints=None, costs=None, long_only=True, total_weight=1.0, bounds=None)[source]

Create a wrapper from a price history.

Computes simple returns (P_t / P_{t-1}) - 1 and builds the distribution from the resulting scenario matrix. This is the easiest entry point for users who have raw price data.

Parameters:
  • prices – Price matrix (T+1, N) as a 2-D array or DataFrame. Rows are chronological observations, columns are assets.

  • constraints – Constraint specification.

  • costs – Transaction cost specification.

  • long_only – Shortcut for long-only constraint. Defaults to True.

  • total_weight – Shortcut for weight-sum constraint. Defaults to 1.0.

  • bounds – Shortcut for per-asset bounds.

Raises:

ValueError – If prices contains fewer than 2 rows, NaN, or non-positive values.

Parameters:
  • prices (Union[npt.NDArray[np.floating], pd.DataFrame])

  • constraints (Union[Constraints, Dict[str, Any], None])

  • costs (Optional[TransactionCosts])

  • long_only (bool)

  • total_weight (float)

  • bounds (Optional[Any])

Return type:

PortfolioWrapper

Examples

>>> wrapper = PortfolioWrapper.from_prices(price_df)
>>> frontier = wrapper.variance_frontier()
classmethod from_robust_posterior(posterior, *, constraints=None, costs=None, long_only=True, total_weight=1.0, bounds=None, annualization_factor=1.0)[source]

Create a wrapper configured for robust optimisation from a Bayesian posterior.

Maps the RobustBayesPosterior fields to the robust optimiser inputs per Meucci (2005, Eq. 9.155):

  • dist.muposterior.mu (posterior mean \(\mu_1\))

  • dist.covposterior.sigma (return covariance \(\Sigma_{ce}\))

  • _uncertainty_covposterior.s_mu (mean-uncertainty scatter \(S_\mu\))

The resulting wrapper is ready for robust_lambda_frontier() or solve_robust_gamma_portfolio().

Parameters:
  • posterior – A RobustBayesPosterior (typically from RobustBayesPosterior.from_niw).

  • constraints – Constraint specification.

  • costs – Transaction cost specification.

  • long_only – Shortcut for long-only constraint. Defaults to True.

  • total_weight – Shortcut for weight-sum constraint. Defaults to 1.0.

  • bounds – Shortcut for per-asset bounds.

  • annualization_factor – Horizon scaling (e.g. 52 for weekly → annual). Mean scales by h, scatter scales by h^2.

Parameters:
  • posterior (RobustBayesPosterior)

  • constraints (Union[Constraints, Dict[str, Any], None])

  • costs (Optional[TransactionCosts])

  • long_only (bool)

  • total_weight (float)

  • bounds (Optional[Any])

  • annualization_factor (float)

Return type:

PortfolioWrapper

Examples

>>> from pyvallocation import PortfolioWrapper
>>> from pyvallocation.bayesian import RobustBayesPosterior
>>> posterior = RobustBayesPosterior.from_niw(...)
>>> wrapper = PortfolioWrapper.from_robust_posterior(posterior, annualization_factor=52)
>>> frontier = wrapper.robust_lambda_frontier()
classmethod from_scenarios(scenarios, *, probabilities=None, constraints=None, costs=None, long_only=True, total_weight=1.0, bounds=None, return_type='simple')[source]

Create a ready-to-use wrapper from scenario data.

Parameters:

return_type"simple" (default) or "log". When "log", scenarios are converted to simple returns via exp(x) - 1 so the optimizer works in P&L space per Meucci’s Prayer.

Parameters:
  • scenarios (Union[npt.NDArray[np.floating], pd.DataFrame])

  • probabilities (Optional[Union[npt.NDArray[np.floating], pd.Series]])

  • constraints (Union[Constraints, Dict[str, Any], None])

  • costs (Optional[TransactionCosts])

  • long_only (bool)

  • total_weight (float)

  • bounds (Optional[Any])

  • return_type (str)

Return type:

PortfolioWrapper

Examples

>>> wrapper = PortfolioWrapper.from_scenarios(returns_df)
>>> frontier = wrapper.variance_frontier()
make_ensemble_spec(name, *, optimiser='mean_variance', optimiser_kwargs=None, selector='tangency', selector_kwargs=None, frontier_selection=None, metadata=None)[source]

Convenience wrapper around pyvallocation.ensembles.make_portfolio_spec() that reuses the wrapper’s distribution.

Parameters:
  • name (str)

  • optimiser (Union[str, Callable[[PortfolioWrapper], PortfolioFrontier], Callable[..., PortfolioFrontier]])

  • optimiser_kwargs (Optional[Dict[str, Any]])

  • selector (Union[str, Callable[[PortfolioFrontier], Union[pd.Series, Tuple[Any, ...], np.ndarray]]])

  • selector_kwargs (Optional[Dict[str, Any]])

  • frontier_selection (Optional[Sequence[int]])

  • metadata (Optional[Dict[str, Any]])

Return type:

EnsembleSpec

min_cvar_at_return(return_target, alpha=0.05, seed=None, *, constraints=None, costs=None)[source]

Solve for the minimum CVaR portfolio that achieves a given expected return.

This method directly solves the optimization problem for a specific target return, rather than interpolating from a pre-computed frontier.

Parameters:
  • return_target (float) – The desired minimum expected return.

  • alpha (float) – The tail probability for CVaR. Defaults to 0.05.

  • seed – Random seed for scenario simulation reproducibility.

  • constraints – Optional Constraints or dict overriding the instance constraints.

  • costs – Optional TransactionCosts overriding instance costs.

Returns:

Tuple[pd.Series, float, float]

A tuple containing:
  • weights (pandas.Series): The weights of the optimal portfolio.

  • return (float): The expected return of the portfolio.

  • risk (float): The CVaR of the portfolio.

Raises:

ValueError – If scenarios cannot be used or generated.

Parameters:
  • return_target (float)

  • alpha (float)

  • seed (int | None)

Return type:

Tuple[pandas.Series, float, float]

min_variance_at_return(return_target, *, constraints=None, costs=None)[source]

Solve for the minimum-variance portfolio achieving the target return.

This method directly solves the optimization problem for a specific target return, rather than interpolating from a pre-computed frontier. This is more accurate and efficient if only a single portfolio is of interest.

Parameters:
  • return_target (float) – The desired minimum expected return.

  • constraints – Optional Constraints or dict overriding the instance constraints.

  • costs – Optional TransactionCosts overriding instance costs.

Returns:

Tuple[pd.Series, float, float]

A tuple containing:
  • weights (pandas.Series): The weights of the optimal portfolio.

  • return (float): The expected return of the portfolio.

  • risk (float): The volatility (standard deviation) of the portfolio.

Raises:

ValueError – If mu and cov are not available in the distribution.

Parameters:

return_target (float)

Return type:

Tuple[pandas.Series, float, float]

relaxed_risk_parity_frontier(num_portfolios=10, max_multiplier=1.6, *, lambda_reg=0.2, target_multipliers=None, include_risk_parity=True, risk_budgets=None, constraints=None, costs=None)[source]

Build a relaxed risk parity frontier by sweeping target-return multipliers.

Each frontier column corresponds to a distinct multiplier \(m\) applied to the benchmark risk parity return \(r_{RP}\) to generate the target \(R = m \cdot \max(r_{RP}, 0)\). The method solves the regulated RP programme for each multiplier using the shared \(\lambda\) value and stores per-point diagnostics (effective target after clipping, objective value, cone slack variables, solver warnings) in PortfolioFrontier.metadata.

Parameters:
  • num_portfolios – Number of grid points when target_multipliers is omitted. Must be positive; includes the upper endpoint max_multiplier.

  • max_multiplier – Upper bound for the auto-generated multiplier grid. Ignored if target_multipliers is supplied.

  • lambda_reg – Regulator coefficient \(\\lambda\). Applies to every relaxed point; the optional RP anchor always uses \(\\lambda = 0\).

  • target_multipliers – Explicit iterable of multipliers. When provided, the method skips automatic grid generation and uses the supplied values verbatim.

  • include_risk_parity – If True (default) the frontier prepends the pure risk parity solution so downstream plots can intercept the anchor directly.

  • constraints – Optional Constraints or dict overriding the instance constraints.

  • costs – Optional TransactionCosts overriding instance costs.

Returns:

PortfolioFrontier – Object containing weights (n, k), realised returns, volatility proxy (standard deviation), and diagnostic metadata for each node.

Parameters:
  • num_portfolios (int)

  • max_multiplier (float)

  • lambda_reg (float)

  • target_multipliers (Optional[Sequence[float]])

  • include_risk_parity (bool)

  • risk_budgets (Optional[npt.NDArray[np.floating]])

Return type:

PortfolioFrontier

relaxed_risk_parity_portfolio(*, lambda_reg=0.2, target_multiplier=1.2, return_target=None, risk_budgets=None, constraints=None, costs=None)[source]

Compute a relaxed risk parity allocation.

Returns the same triple as other portfolio methods: (weights, return, risk). For full diagnostics, use relaxed_risk_parity_portfolio_with_diagnostics().

Args/Returns: See relaxed_risk_parity_portfolio_with_diagnostics().

Parameters:
  • lambda_reg (float)

  • target_multiplier (Optional[float])

  • return_target (Optional[float])

  • risk_budgets (Optional[npt.NDArray[np.floating]])

Return type:

Tuple[pd.Series, float, float]

relaxed_risk_parity_portfolio_with_diagnostics(*, lambda_reg=0.2, target_multiplier=1.2, return_target=None, risk_budgets=None, constraints=None, costs=None)[source]

Compute a single relaxed risk parity allocation and expose solver diagnostics.

The routine first solves the baseline risk parity programme (lambda = 0) to obtain the benchmark return \(r_{RP} = \mu^{\top}x^{RP}\). Unless an explicit return_target is supplied, the relaxed model is then solved with the adaptive target \(R = m \cdot \max(r_{RP}, 0)\) where m is target_multiplier. Infeasible targets are clipped via a backtracking line-search toward the RP return before falling back to the unconstrained problem if necessary.

Parameters:
  • lambda_reg – Non-negative regulator coefficient \(\\lambda\). Setting 0 recovers the pure risk parity allocation. Defaults to 0.2.

  • target_multiplier – Optional multiplier \(m\) governing the adaptive target-return rule. Ignored when return_target is provided. Must be None when pairing with lambda_reg == 0. Defaults to 1.2.

  • return_target – Explicit target return \(R\). When supplied, overrides the adaptive rule. The method clips \(R\) down to the feasible region if necessary. Defaults to None.

  • constraints – Optional Constraints or dict overriding the instance constraints.

  • costs – Optional TransactionCosts overriding instance costs.

Returns:

Tuple[pd.Series, float, float, Dict[str, Any]] – Optimal portfolio weights (Series), achieved return, portfolio volatility, and rich diagnostics including variance, marginal risks, risk contributions, target information, and any solver warning emitted during target clipping.

Parameters:
  • lambda_reg (float)

  • target_multiplier (Optional[float])

  • return_target (Optional[float])

  • risk_budgets (Optional[npt.NDArray[np.floating]])

Return type:

Tuple[pd.Series, float, float, Dict[str, Any]]

robust_lambda_frontier(num_portfolios=10, max_lambda=2.0, *, lambdas=None, return_cov=None, constraints=None, costs=None)[source]

Computes a robust frontier based on uncertainty in expected returns.

Assumptions & Design Choices:

This method follows Meucci (2005, Eq. 9.158). The mu and cov on the AssetsDistribution must be:

  • mu — the posterior mean \(\\mu_1\) (or any point estimate).

  • cov — the mean-uncertainty scatter \(S_\\mu\), not the posterior covariance \(\\Sigma_1\). For an NIW posterior use RobustBayesPosterior.s_mu. If you want to supply \(\\Sigma_1\) directly, scale it: \(S_\\mu = \\frac{1}{T_1}\\frac{\\nu_1}{\\nu_1-2}\\Sigma_1\).

Parameters:
  • num_portfolios – The number of portfolios to compute. Defaults to 10.

  • max_lambda – Maximum penalty weight \(\\lambda\). Higher values shrink toward the minimum-uncertainty portfolio. Defaults to 2.0.

  • lambdas – Optional explicit \(\\lambda\) grid. Overrides num_portfolios/max_lambda.

  • return_cov – Optional return covariance (for a volatility overlay). This is distinct from dist.cov (the mean-uncertainty scatter).

  • constraints – Optional Constraints or dict overriding the instance constraints.

  • costs – Optional TransactionCosts overriding instance costs.

Returns:

A PortfolioFrontier object.

Parameters:
  • num_portfolios (int)

  • max_lambda (float)

  • lambdas (Optional[Sequence[float]])

  • return_cov (Optional[Union[npt.NDArray[np.floating], pd.DataFrame]])

Return type:

PortfolioFrontier

solve_robust_gamma_portfolio(gamma_mu, gamma_sigma_sq, *, constraints=None, costs=None)[source]

Solve for a single robust portfolio with explicit uncertainty constraints.

Uses \(\gamma_\mu\) (Meucci Eq. 9.156) as the penalty weight on mean-uncertainty radius, and caps the squared radius at \(\gamma_{\sigma}^2\).

Parameters:
  • gamma_mu – Penalty weight on \(\|S_\mu^{1/2}w\|_2\). Obtain from RobustBayesPosterior.cred_radius_mu(p_mu).

  • gamma_sigma_sq – Upper bound on \(\|S_\mu^{1/2}w\|_2^2\).

  • constraints – Optional Constraints or dict overriding the instance constraints.

  • costs – Optional TransactionCosts overriding instance costs.

Returns:

A tuple containing the portfolio weights, nominal return, and squared uncertainty radius.

Parameters:
  • gamma_mu (float)

  • gamma_sigma_sq (float)

Return type:

Tuple[pandas.Series, float, float]

variance_frontier(num_portfolios=10, *, constraints=None, costs=None)[source]

Compute the classical mean-variance efficient frontier.

Parameters:
  • num_portfolios – The number of portfolios to compute. Defaults to 10.

  • constraints – Optional Constraints or dict overriding the instance constraints.

  • costs – Optional TransactionCosts overriding instance costs.

Returns:

A PortfolioFrontier object. When scenarios are available on the distribution, a CVaR overlay is added to alternate_risks to allow CVaR-based selection on the same weights.

Parameters:

num_portfolios (int)

Return type:

PortfolioFrontier

class pyvallocation.portfolioapi.TransactionCosts(initial_weights, market_impact_costs=None, proportional_costs=None)[source]

Bases: object

Immutable container for transaction cost parameters.

Parameters:
  • initial_weights (Union[npt.NDArray[np.floating], pd.Series])

  • market_impact_costs (Optional[Union[npt.NDArray[np.floating], pd.Series]])

  • proportional_costs (Optional[Union[npt.NDArray[np.floating], pd.Series]])

initial_weights

Current portfolio weights (required).

Type:

numpy.typing.NDArray.numpy.floating | pandas.Series

market_impact_costs

Quadratic cost coefficients for mean-variance.

Type:

numpy.typing.NDArray.numpy.floating | pandas.Series | None

proportional_costs

Linear cost coefficients for CVaR and robust models.

Type:

numpy.typing.NDArray.numpy.floating | pandas.Series | None

initial_weights: numpy.typing.NDArray.numpy.floating | pandas.Series
market_impact_costs: numpy.typing.NDArray.numpy.floating | pandas.Series | None = None
proportional_costs: numpy.typing.NDArray.numpy.floating | pandas.Series | None = None