Skip to content

Portfolio Optimization with Investment Bands API Reference

Data

Data model for Portfolio Optimization with Investment Bands use case.

PoIbtvData

Bases: UcData

Data for the Portfolio Optimization with Investment Bands use case.

Models a Markowitz-style mean-variance portfolio optimization where each asset has a minimum and maximum investment band. Continuous allocations are discretised using binary variables so the problem is suitable for QUBO/quantum solvers.

Attributes:

  • name (Literal['portfolio_optimization_with_investment_bands']) –

    Identifier for this data type.

  • log_returns (list[float]) –

    Expected log-return per asset. Positive values indicate expected gains, negative values indicate expected losses.

  • covariance_matrix (NumPyArray) –

    Symmetric positive semi-definite covariance matrix of shape (n_assets, n_assets). Entry [i,j] is the covariance between asset i and asset j. Diagonal entries are variances.

  • investment_bands (list[tuple[float, float]]) –

    (lower, upper) investment bounds per asset. Each allocation is constrained to lie within [lower, upper].

  • risk_aversion (float) –

    Risk aversion parameter lambda in the Markowitz objective: Higher values penalise variance more strongly. Default: 1.0.

  • max_budget (float) –

    Maximum total budget that can be allocated across all assets: sum_i investment[i] <= max_budget.

  • n_bits (int) –

    Number of binary bits used to discretise each asset's allocation within its investment band. Higher values give finer resolution but increase the number of binary variables (n_assets * n_bits).

plot(*, ax: Axes | None = None) -> Axes

Plot investment bands and expected returns per asset.

Shows a horizontal bar for each asset's investment band, colored per asset. The expected log-return is annotated to the right of each bar.

Parameters:

  • ax (Axes | None, default: None ) –

    Matplotlib axes to draw on. Creates a new figure if None.

Returns:

  • Axes

    The axes with the plot.

to_string() -> str

Return a human-readable description of the problem data.

Returns:

  • str

    String representation of the data including all parameters and per-asset investment bands and expected returns.

from_values(log_returns: list[float], covariance_matrix: np.ndarray, investment_bands: list[tuple[float, float]], max_budget: float, n_bits: int = 3, risk_aversion: float = 1.0) -> PoIbtvData staticmethod

Create a PoIbtvData instance from explicit problem parameters.

Parameters:

  • log_returns (list[float]) –

    Expected log-return per asset. Positive values indicate expected gains, negative values indicate expected losses.

  • covariance_matrix (ndarray) –

    Symmetric positive semi-definite covariance matrix of shape (n_assets, n_assets). Entry [i,j] represents the covariance between asset i and asset j. Diagonal entries are variances.

  • investment_bands (list[tuple[float, float]]) –

    (lower, upper) investment bounds per asset. Each allocation is constrained to [lower, upper]. Must have length n_assets.

  • max_budget (float) –

    Maximum total budget across all assets: sum_i investment[i] <= max_budget.

  • n_bits (int, default: 3 ) –

    Number of binary bits used to discretise each asset's allocation within its band. Higher values give finer resolution but increase the number of variables exponentially. Default: 3.

  • risk_aversion (float, default: 1.0 ) –

    Risk aversion parameter lambda in the Markowitz objective: Higher values penalise variance more strongly. Default: 1.0.

Returns:

  • PoIbtvData

    A validated data instance ready for optimisation.

Raises:

  • ValueError

    If any of the following conditions are violated:

    • covariance_matrix is not square
    • covariance_matrix shape does not match len(log_returns)
    • investment_bands length does not match len(log_returns)
    • Any lower bound exceeds its upper bound
    • max_budget is not positive
    • n_bits is not positive
    • risk_aversion is not positive

Examples:

>>> import numpy as np
>>> data = PoIbtvData.from_values(
...     log_returns=[0.05, 0.08, 0.03],
...     covariance_matrix=np.array(
...         [
...             [0.04, 0.01, 0.005],
...             [0.01, 0.09, 0.02],
...             [0.005, 0.02, 0.03],
...         ]
...     ),
...     investment_bands=[(0.1, 0.5), (0.1, 0.4), (0.1, 0.6)],
...     max_budget=1.0,
...     n_bits=3,
...     risk_aversion=1.0,
... )

generate_random(n_assets: int = 3, n_bits: int = 3, risk_aversion: float = 1.0, seed: int | None = None) -> PoIbtvData staticmethod

Generate a random PO-IBTV instance.

Creates a random portfolio problem with plausible return and covariance structure. Investment bands are sampled uniformly, and the covariance matrix is constructed to be positive semi-definite.

Parameters:

  • n_assets (int, default: 3 ) –

    Number of assets, by default 3.

  • n_bits (int, default: 3 ) –

    Number of binary bits per asset for discretisation, by default 3.

  • risk_aversion (float, default: 1.0 ) –

    Risk aversion parameter lambda controlling the trade-off between expected return and variance. Default: 1.0.

  • seed (int | None, default: None ) –

    Random seed for reproducibility, by default None.

Returns:

  • PoIbtvData

    A randomly generated data instance.

Examples:

>>> data = PoIbtvData.generate_random(n_assets=4, n_bits=3, seed=42)

Formulation

Formulation for Portfolio Optimization with Investment Bands use case.

PoIbtvFormulation

Bases: UcFormulation[PoIbtvData, PoIbtvSolution]

Formulation for Portfolio Optimization with Investment Bands.

Each asset's allocation is discretised within its investment band using n_bits binary variables. The objective is the Markowitz mean-variance formulation: maximise expected return minus risk-aversion-weighted variance.

The continuous allocation per asset is encoded as: investment[i] = lower[i] + step[i] * sum_q (2^q * x[i,q]) where step[i] = (upper[i] - lower[i]) / (2^n_bits - 1)

This allows a quantum solver to search over discretised allocations within the investment bands without requiring continuous variables.

Mathematical Formulation
Decision Variables:
    x[i,q] in {0,1} for asset i, bit q in 0..n_bits-1

Derived investment:
    investment[i] = lower[i] + step[i] * sum_q (2^q * x[i,q])
    where step[i] = (upper[i] - lower[i]) / (2^n_bits - 1)

Objective (maximize):
    sum_i log_returns[i] * investment[i]
    ``- risk_aversion * sum_{i,j} sigma[i][j] * investment[i] * investment[j]``
    where risk_aversion = 1 / (2 * target_volatility^2)

Constraints:
    1. Budget: sum_i investment[i] <= max_budget

to_string(data: PoIbtvData) -> str staticmethod

Format the formulation as a string.

Parameters:

Returns:

  • str

    String representation of the formulation.

formulate(data: PoIbtvData) -> Model staticmethod

Formulate PO-IBTV as an optimization model.

Expands the continuous Markowitz objective fully into binary variables by substituting the discretised investment expression. The resulting objective contains linear and quadratic terms in x[i,q].

Parameters:

  • data (PoIbtvData) –

    The problem data containing returns, covariance, bands, and parameters.

Returns:

  • Model

    A Luna optimization model representing the investment band portfolio problem.

interpret(solution: Solution, data: PoIbtvData) -> PoIbtvSolution staticmethod

Extract solution from solver result.

Reconstructs the continuous allocation per asset from the binary variables and computes portfolio return, volatility, and validity.

Parameters:

  • solution (Solution) –

    The solution containing variable assignments.

  • data (PoIbtvData) –

    The original problem data.

Returns:

  • PoIbtvSolution

    A structured solution object with: - allocations: continuous allocation per asset - portfolio_return: expected return of the portfolio - portfolio_volatility: standard deviation of the portfolio - is_valid: whether investment bands and budget are satisfied

Raises:

Solution

Solution model for Portfolio Optimization with Investment Bands use case.

PoIbtvSolution

Bases: UcSolution

Solution for the Portfolio Optimization with Investment Bands use case.

Attributes:

  • name (Literal['portfolio_optimization_with_investment_bands']) –

    Identifier for this solution type.

  • allocations (list[float]) –

    Allocation fraction per asset (discretised).

  • portfolio_return (float) –

    Expected portfolio return.

  • portfolio_volatility (float) –

    Portfolio volatility (standard deviation).

  • is_valid (bool) –

    Whether the solution satisfies all constraints (allocations within bands, budget met).

plot(data: PoIbtvData, *, ax: Axes | None = None) -> Axes

Plot the portfolio allocation against investment bands.

Mirrors the data plot layout: horizontal bars show the investment bands (background) with the selected allocation overlaid as a marker, making it easy to see where each allocation falls within its band.

Parameters:

  • data (PoIbtvData) –

    The original problem data containing investment bands.

  • ax (Axes | None, default: None ) –

    Matplotlib axes to draw on. Creates a new figure if None.

Returns:

  • Axes

    The axes with the plot.

to_string() -> str

Return a string describing the PO-IBTV solution.

Instance

Instance model for PoIbtv use case.

PoIbtvInstance

Bases: UcInstance[PoIbtvData, PoIbtvFormulation, PoIbtvSolution]

Instance combining data and formulation for PoIbtv.

Collection

Collection of PO Investment Bands instances.

PoIbtvCollection

Bases: UcInstanceCollection[PoIbtvInstance]

Collection of PO Investment Bands instances.

This collection provides methods to generate benchmark instances with various characteristics for testing and evaluation.

from_random(min_assets: int | None = None, max_assets: int | None = None, n_bits: int = 3, num_instances: int = 1, *, sizes: Sequence[int] | None = None, seed: int | None = None) -> PoIbtvCollection classmethod

Generate random PO-IBTV instances.

Parameters:

  • min_assets (int | None, default: None ) –

    Minimum number of assets per instance (default: 2).

  • max_assets (int | None, default: None ) –

    Maximum number of assets per instance (default: 5).

  • n_bits (int, default: 3 ) –

    Number of binary bits per asset (default: 3).

  • num_instances (int, default: 1 ) –

    Number of instances per asset count (default: 1).

  • seed (int | None, default: None ) –

    Random seed for reproducibility (default: None).

  • sizes (Sequence[int] | None, default: None ) –

    Explicit sizes to generate, e.g. [10, 50, 100], instead of a range. Mutually exclusive with min_assets/max_assets, by default None.

Returns:

filter_infeasible(max_runtime: float = 3600, *, quiet: bool = True) -> list[bool]

Drop the instances of this collection that have no feasible solution.

Every instance is formulated and handed to SCIP, which stops as soon as it finds the first feasible solution. An instance is removed from the collection when SCIP proves the model infeasible, when no solution turns up within max_runtime, or when formulating it fails altogether. This keeps randomly generated instances from breaking a downstream pipeline.

Parameters:

  • max_runtime (float, default: 3600 ) –

    SCIP time limit per instance in seconds. Must be positive. Defaults to 3600 seconds.

  • quiet (bool, default: True ) –

    Suppress the SCIP solver output.

Returns:

  • list[bool]

    Feasibility mask over the instances as they were before filtering, in that order: True where the instance was kept, False where it was removed.

Raises: