Portfolio API

class pyvallocation.portfolioapi.AssetsDistribution(mu: npt.NDArray[np.floating] | pd.Series | None = None, cov: npt.NDArray[np.floating] | pd.DataFrame | None = None, scenarios: npt.NDArray[np.floating] | pd.DataFrame | None = None, probabilities: npt.NDArray[np.floating] | pd.Series | None = None, asset_names: List[str] | None = 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.

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: npt.NDArray[np.floating], returns: npt.NDArray[np.floating], risks: npt.NDArray[np.floating], risk_measure: str, asset_names: List[str] | None = None)[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.

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

A 1D NumPy array of shape (M,) containing the risk values for each portfolio on the frontier. The specific risk measure (e.g., volatility, CVaR, uncertainty budget) is indicated by risk_measure.

Type:

npt.NDArray[np.floating]

risk_measure

A string describing the risk measure used to construct this efficient frontier (e.g., ‘Volatility’, ‘CVaR (alpha=0.05)’, ‘Estimation Risk (‖Σ’¹/²w‖₂)’).

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]]

asset_names: List[str] | None = None
get_max_return_portfolio() Tuple[pandas.Series, float, float][source]

Finds the portfolio with the maximum expected return on the efficient frontier.

Returns:

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

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

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

Return type:

Tuple[pd.Series, float, float]

get_min_risk_portfolio() Tuple[pandas.Series, float, float][source]

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

Returns:

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.

Return type:

Tuple[pd.Series, float, float]

get_tangency_portfolio(risk_free_rate: float) Tuple[pandas.Series, float, float][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:

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.

Return type:

Tuple[pd.Series, float, float]

portfolio_at_return_target(min_return: float) Tuple[pandas.Series, float, float][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.

Returns:

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.

Return type:

Tuple[pd.Series, float, float]

portfolio_at_risk_target(max_risk: float) Tuple[pandas.Series, float, float][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.

Returns:

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.

Return type:

Tuple[pd.Series, float, float]

returns: numpy.typing.NDArray.numpy.floating
risk_measure: str
risks: numpy.typing.NDArray.numpy.floating
weights: numpy.typing.NDArray.numpy.floating
class pyvallocation.portfolioapi.PortfolioWrapper(distribution: AssetsDistribution)[source]

Bases: object

A high-level interface for portfolio construction and optimization.

This class serves as the main entry point for performing portfolio optimization. It simplifies the process by managing asset data, constraints, transaction costs, and the underlying optimization models.

Typical Workflow:

  1. Initialize: port = PortfolioWrapper(AssetsDistribution(...))

  2. Set Constraints: port.set_constraints(...)

  3. (Optional) Set Costs: port.set_transaction_costs(...)

  4. Compute: frontier = port.mean_variance_frontier() or portfolio = port.mean_variance_portfolio_at_return(0.10)

  5. Analyze: Use the returned PortfolioFrontier or portfolio objects.

mean_cvar_frontier(num_portfolios: int = 10, alpha: float = 0.05) PortfolioFrontier[source]

Computes 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 20.

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

Returns:

A PortfolioFrontier object.

mean_cvar_portfolio_at_return(return_target: float, alpha: float = 0.05) Tuple[pandas.Series, float, float][source]

Solves 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.

Returns:

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.

Return type:

Tuple[pd.Series, float, float]

Raises:

ValueError – If scenarios cannot be used or generated.

mean_variance_frontier(num_portfolios: int = 10) PortfolioFrontier[source]

Computes the classical Mean-Variance efficient frontier.

Parameters:

num_portfolios – The number of portfolios to compute. Defaults to 20.

Returns:

A PortfolioFrontier object.

mean_variance_portfolio_at_return(return_target: float) Tuple[pandas.Series, float, float][source]

Solves for the minimum variance 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. This is more accurate and efficient if only a single portfolio is of interest.

Parameters:

return_target (float) – The desired minimum expected return.

Returns:

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.

Return type:

Tuple[pd.Series, float, float]

Raises:

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

robust_lambda_frontier(num_portfolios: int = 10, max_lambda: float = 2.0) PortfolioFrontier[source]

Computes a robust frontier based on uncertainty in expected returns.

Assumptions & Design Choices:
  • This method follows Meucci’s robust framework. It assumes that the mu and cov from AssetsDistribution represent the posterior mean and the posterior scale matrix (for uncertainty), respectively.

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

  • max_lambda – The maximum value for the risk aversion parameter lambda, which controls the trade-off between nominal return and robustness.

Returns:

A PortfolioFrontier object.

set_constraints(params: Dict[str, Any])[source]

Builds and sets linear constraints for the portfolio.

This method uses the build_G_h_A_b utility to construct the constraint matrices and vectors based on a dictionary of parameters. These constraints are then stored internally and applied during optimization.

Parameters:

params (Dict[str, Any]) –

A dictionary of constraint parameters. Expected keys and their types/meanings include:

  • "long_only" (bool): If True, enforces non-negative weights (w >= 0).

  • "total_weight" (float): Sets the sum of weights (sum(w) = value).

  • "box_constraints" (Tuple[np.ndarray, np.ndarray]): A tuple (lower_bounds, upper_bounds)

    for individual asset weights.

  • "group_constraints" (List[Dict[str, Any]]): A list of dictionaries,

    each defining a group constraint (e.g., min/max weight for a subset of assets).

  • Any other parameters supported by pyvallocation.utils.constraints.build_G_h_A_b.

Raises:

RuntimeError – If constraint building fails due to invalid parameters or other issues.

set_transaction_costs(initial_weights: 'pd.Series' | npt.NDArray[np.floating], market_impact_costs: 'pd.Series' | npt.NDArray[np.floating] | None = None, proportional_costs: 'pd.Series' | npt.NDArray[np.floating] | None = None)[source]

Sets transaction cost parameters for rebalancing optimizations.

This method allows specifying initial portfolio weights and associated transaction costs (either quadratic market impact or linear proportional costs). These costs are incorporated into the optimization problem when applicable.

Assumptions & Design Choices:
  • If pandas.Series are provided for cost parameters, they are aligned to the official asset list of the portfolio (self.dist.asset_names). Assets present in the portfolio but missing from the input Series are assumed to have a cost of zero.

  • initial_weights that do not sum to 1.0 imply a starting position that includes cash (if sum < 1) or leverage (if sum > 1).

Parameters:
  • initial_weights (Union[pd.Series, npt.NDArray[np.floating]]) – A 1D array or pandas.Series of current portfolio weights. This is required if any transaction costs are to be applied.

  • market_impact_costs (Optional[Union[pd.Series, npt.NDArray[np.floating]]]) – For Mean-Variance optimization, a 1D array or pandas.Series of quadratic market impact cost coefficients. Defaults to None.

  • proportional_costs (Optional[Union[pd.Series, npt.NDArray[np.floating]]]) – For Mean-CVaR and Robust optimization, a 1D array or pandas.Series of linear proportional cost coefficients. Defaults to None.

Raises:

ValueError – If the shape of any provided cost parameter array does not match the number of assets (N).

solve_robust_gamma_portfolio(gamma_mu: float, gamma_sigma_sq: float) Tuple[pandas.Series, float, float][source]

Solves for a single robust portfolio with explicit uncertainty constraints.

Parameters:
  • gamma_mu – The penalty for estimation error in the mean.

  • gamma_sigma_sq – The squared upper bound for the total portfolio risk.

Returns:

A tuple containing the portfolio weights, return, and risk.