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 Type Description
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:

Name Type Description Default
ax Axes | None

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

None

Returns:

Type Description
Axes

The axes with the plot.

to_string() -> str

Return a human-readable description of the problem data.

Returns:

Type Description
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:

Name Type Description Default
log_returns list[float]

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

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

required
investment_bands list[tuple[float, float]]

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

required
max_budget float

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

required
n_bits int

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.

3
risk_aversion float

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

1.0

Returns:

Type Description
PoIbtvData

A validated data instance ready for optimisation.

Raises:

Type Description
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:

Name Type Description Default
n_assets int

Number of assets, by default 3.

3
n_bits int

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

3
risk_aversion float

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

1.0
seed int | None

Random seed for reproducibility, by default None.

None

Returns:

Type Description
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:

Name Type Description Default
data PoIbtvData

The problem data.

required

Returns:

Type Description
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:

Name Type Description Default
data PoIbtvData

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

required

Returns:

Type Description
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:

Name Type Description Default
solution Solution

The solution containing variable assignments.

required
data PoIbtvData

The original problem data.

required

Returns:

Type Description
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:

Type Description
NoSolutionFoundError

If the solver did not find a solution.

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 Type Description
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:

Name Type Description Default
data PoIbtvData

The original problem data containing investment bands.

required
ax Axes | None

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

None

Returns:

Type Description
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:

Name Type Description Default
min_assets int | None

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

None
max_assets int | None

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

None
n_bits int

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

3
num_instances int

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

1
seed int | None

Random seed for reproducibility (default: None).

None
sizes Sequence[int] | None

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

None

Returns:

Type Description
PoIbtvCollection

Collection containing generated instances.

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:

Name Type Description Default
max_runtime float

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

3600
quiet bool

Suppress the SCIP solver output.

True

Returns:

Type Description
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:

Type Description
ValueError

If max_runtime is not positive.