portfolio_optimization package

This package provides Python code that converts the multi-objective portfolio optimization problem into a QUBO problem. The transformed problem can then be solved using quantum annealing techniques.

The following objectives can be considered

Additionally, we allow for capital growth factor and arbitrary emission reduction constraints to be considered.

The Pareto front, the set of solutions where one objective can’t be improved without worsening the other objective, can be computed for return on capital and diversification.

The codebase is based on the following paper:

class portfolio_optimization.PortfolioOptimizer(portfolio_data, k=2, columns_rename=None)[source]

Bases: object

The PortfolioOptimizer class is used to convert multi-objective portfolio optimization problems into QUBO problems which can then be solved using QUBO solving techniques such as simulated or quantum annealing.

The following objectives can be considered

The following constraints can be added

  • capital growth, demand a minimum increase in outstanding assets.

  • emission reduction, demand a minimum reduction for an arbitrary emission type.

Usage example:

import numpy as np
from dwave.samplers import SimulatedAnnealingSampler

from tno.quantum.problems.portfolio_optimization import PortfolioOptimizer

# Choose sampler for solving qubo
sampler = SimulatedAnnealingSampler()
sampler_kwargs = {"num_reads": 20, "num_sweeps": 200}

# Set up penalty coefficients for the constraints
lambdas1 = np.logspace(-16, 1, 25, endpoint=False, base=10.0)
lambdas2 = np.logspace(-16, 1, 25, endpoint=False, base=10.0)
lambdas3 = np.array([1])

# Create portfolio optimization problem
portfolio_optimizer = PortfolioOptimizer("benchmark_dataset")
portfolio_optimizer.add_minimize_hhi(weights=lambdas1)
portfolio_optimizer.add_maximize_roc(formulation=1, weights_roc=lambdas1)
portfolio_optimizer.add_emission_constraint(
    weights=lambdas3,
    emission_now="emis_intens_now",
    emission_future="emis_intens_future",
    name="emission",
)

# Solve the portfolio optimization problem
results = portfolio_optimizer.run(sampler, sampler_kwargs)
print(results.head())
__init__(portfolio_data, k=2, columns_rename=None)[source]

Init PortfolioOptimizer.

Parameters:
  • portfolio_data (PortfolioData | DataFrame | str | Path) – Portfolio data represented by a PortfolioData object, a pandas DataFrame or a path to where portfolio data is stored. See the docstring of PortfolioData for data input conventions.

  • k (int) – The number of bits that are used to represent the outstanding amount for each asset. A fixed point representation is used to represent \(2^k\) different equidistant values in the range \([LB_i, UB_i]\) for asset i.

  • column_rename – can be used to rename data columns. See the docstring of PortfolioData for example.

Raises:

TypeError – If the provided portfolio_data input has the wrong type.

add_emission_constraint(emission_now, emission_future=None, reduction_percentage_target=0.7, name=None, weights=None)[source]

Adds emission constraint to the portfolio optimization problem.

The constraint is given by

\[\frac{\sum_{i=1}^Nf_i \cdot x_i}{\sum_{i=1}^N x_i} = g_e \frac{\sum_{i=1}^Ne_i \cdot y_i}{\sum_{i=1}^N y_i},\]

where:

  • \(N\) is the total number of assets,

  • \(x_i\) is the future outstanding amount for asset \(i\),

  • \(y_i\) is the current outstanding amount for asset \(i\),

  • \(e_i\) is the current emission intensity for asset \(i\),

  • \(f_i\) is the expected emission intensity at the future for asset \(i\),

  • \(g_e\) is the target value for the relative emission reduction.

Usage example:

>>> from tno.quantum.problems.portfolio_optimization import PortfolioOptimizer
>>> import numpy as np
>>> portfolio_optimizer = PortfolioOptimizer(filename="benchmark_dataset")
>>> lambdas = np.logspace(-16, 1, 25, endpoint=False, base=10.0)
>>> portfolio_optimizer.add_emission_constraint(
...   emission_now="emis_intens_now", weights=lambdas
... )

For the QUBO formulation, see the docs of QuboFactory. calc_emission_constraint().

Parameters:
  • emission_now (str) – Name of the column in the portfolio dataset corresponding to the variables emission intensity at current time.

  • emission_future (Optional[str]) – Name of the column in the portfolio dataset corresponding to the variables emission intensity at future time. If no value is provided, it is assumed that the emission intensity is constant over time, i.e., the variable emission_now will be used.

  • reduction_percentage_target (float) – target value for reduction percentage amount.

  • name (Optional[str]) – Name that will be used for emission constraint in the results df.

  • weights (Optional[ArrayLike]) – The coefficients that are considered as penalty parameter.

Return type:

None

add_growth_factor_constraint(growth_target, weights=None)[source]

Adds an outstanding amount growth factor constraint to the portfolio optimization problem.

The constraint is given by

\[\frac{\sum_{i=1}^N x_i}{\sum_{i=1}^N y_i} = g_c,\]

where

  • \(N\) is the total number of assets,

  • \(x_i\) is the future outstanding amount for asset \(i\),

  • \(y_i\) is the current outstanding amount for asset \(i\),

  • \(g_c\) is the target value for the total growth factor.

This constraint can only be added once.

Usage example:

>>> from tno.quantum.problems.portfolio_optimization import PortfolioOptimizer
>>> import numpy as np
>>> portfolio_optimizer = PortfolioOptimizer(filename="benchmark_dataset")
>>> lambdas = np.logspace(-16, 1, 25, endpoint=False, base=10.0)
>>> portfolio_optimizer.add_emission_constraint(growth_target=1.2, weights=lambdas)

For the QUBO formulation, see the docs of QuboFactory. calc_growth_factor_constraint().

Parameters:
  • growth_target (float) – target value for growth factor total outstanding amount.

  • weights (Optional[ArrayLike]) – The coefficients that are considered as penalty parameter.

Raises:

ValueError – If constraint has been added before.

Return type:

None

add_maximize_roc(formulation, weights_roc=None, ancilla_variables=0, weights_stabilize=None)[source]

Adds the maximize ROC objective to the portfolio optimization problem.

The ROC objective is given by

\[ROC(x) = \frac{\sum_{i=1}^N \frac{x_i \cdot r_i}{y_i}} {\sum_{i=1}^N \frac{x_i \cdot c_i}{y_i}},\]

where

  • \(N\) is the total number of assets,

  • \(x_i\) is the future outstanding amount for asset \(i\),

  • \(y_i\) is the current outstanding amount for asset \(i\),

  • \(r_i\) is the return for asset \(i\),

  • \(c_i\) is the regulatory capital for asset \(i\).

As the ROC is not a quadratic function, it is approximated using two different formulations:

formulation 1:

\[ROC_1(x)=\sum_{i=1}^N\frac{x_i\cdot r_i}{c_i\cdot y_i}\]

Adds 1 qubo term, use weights_roc to scale.

formulation 2:

\[ROC_2(x)=\frac{1}{G_C \cdot C_{21}}\sum_{i=1}^N x_i\frac{r_i}{y_i}\]

In this formulation, \(G_C \cdot C_{21}\) approximates a fixed regulatory capital growth which is equal for all assets, where

  • \(1≤G_C<2\) is a growth factor to be estimated using ancilla variables,

  • \(C_{21} = \sum_{i=1}^N c_{i}\) is the sum of all assets’ regulatory capital.

This formulation adds 2 qubo terms, one for the ROC term, and one to stabilize the capital growth. The stabilize qubo requires an extra argument ancilla_variables. Use weights_roc and weights_stabilize to scale both qubo’s accordingly.

For the different QUBO formulations, see the docs of QuboFactory.

Usage example:

>>> from tno.quantum.problems.portfolio_optimization import PortfolioOptimizer
>>> import numpy as np
>>> portfolio_optimizer = PortfolioOptimizer(filename="benchmark_dataset")
>>> lambdas = np.logspace(-16, 1, 25, endpoint=False, base=10.0)
>>> portfolio_optimizer.add_maximize_roc(...)
Parameters:
  • formulation (int) – the ROC QUBO formulation that is being used. Possible options are: [1, 2].

  • weights_roc (Optional[ArrayLike]) – The coefficients that are considered as penalty parameter for maximizing the roc objective.

  • ancilla_variables (int) – The number of ancillary variables that are used to represent G_C using fixed point representation. Only relevant for roc formulation 2.

  • weights_stabilize (Optional[ArrayLike]) – The coefficients that are considered as penalty parameter for the stabilizing constraint. Only relevant for roc formulation 2.

Raises:

ValueError – If invalid formulation is provided.

Return type:

None

add_minimize_hhi(weights=None)[source]

Adds the minimize HHI objective to the portfolio optimization problem.

The HHI objective is given by

\[HHI(x) = \sum_{i=1}^N\left(\frac{x_i}{\sum_{j=1}^N x_j}\right)^2,\]

where

  • \(N\) is the total number of assets,

  • \(x_i\) is the future outstanding amount for asset \(i\).

As the objective contains non-quadratic terms, a QUBO formulation requires approximations. For the QUBO formulation, see the docs of QuboFactory. calc_minimize_hhi().

Usage example:

>>> from tno.quantum.problems.portfolio_optimization import PortfolioOptimizer
>>> import numpy as np
>>> portfolio_optimizer = PortfolioOptimizer(filename="benchmark_dataset")
>>> lambdas = np.logspace(-16, 1, 25, endpoint=False, base=10.0)
>>> portfolio_optimizer.add_minimize_hhi(weights=lambdas)
Parameters:

weights (Optional[ArrayLike]) – The coefficients that are considered as penalty parameter.

Return type:

None

run(sampler=None, sampler_kwargs=None, verbose=True)[source]

Optimizes a portfolio given the set of provided constraints.

Usage example:

>>> from tno.quantum.problems.portfolio_optimization import PortfolioOptimizer
>>> from dwave.samplers import SimulatedAnnealingSampler
>>> portfolio_optimizer = PortfolioOptimizer(filename="benchmark_dataset")
>>> portfolio_optimizer.add_minimize_HHI()
>>> portfolio_optimizer.run(sampler=SimulatedAnnealingSampler(), verbose=False)
Parameters:
  • sampler (Optional[Sampler]) – Instance of a D-Wave Sampler that can be used to solve the QUBO. More information can be found in the D-Wave Ocean Documentation. By default the SimulatedAnnealingSampler is being used.

  • sampler_kwargs (Optional[dict[str, Any]]) – The sampler specific key-word arguments.

  • verbose (bool) – If True, print detailed information during execution

Return type:

Results

Returns:

results

Raises:

ValueError – if constraints are not set

portfolio_optimization.plot_front(diversification_values, roc_values, color=None, label=None, c=None, vmin=None, vmax=None, alpha=None, cmap=None, ax=None)[source]

Plots a pareto front of the given data-points in a Diversification-ROC plot.

Parameters:
  • diversification_values (ArrayLike) – 1-D ArrayLike containing the x values of the plot.

  • roc_values (ArrayLike) – 1-D ArrayLike containing the y values of the plot.

  • color (Optional[str]) – Optional color to use for the points. For an overview of allowed colors see the Matplotlib Documentation. If None is given, a default color will be assigned by matplotlib. Default is None.

  • label (Optional[str]) – Label to use in the legend. If None is given, no label will be used. Default is None.

  • c (Union[_SupportsArray[dtype[Any]], _NestedSequence[_SupportsArray[dtype[Any]]], bool, int, float, complex, str, bytes, _NestedSequence[Union[bool, int, float, complex, str, bytes]], Sequence[Union[tuple[float, float, float], str, tuple[float, float, float, float], tuple[Union[tuple[float, float, float], str], float], tuple[tuple[float, float, float, float], float]]], tuple[float, float, float], tuple[float, float, float, float], tuple[Union[tuple[float, float, float], str], float], tuple[tuple[float, float, float, float], float], None]) – The marker colors as used by matplotlib.

  • vmin (Optional[float]) – min value of data range that colormap covers as used by matplotlib.

  • vmax (Optional[float]) – max value of data range that colormap covers as used by matplotlib.

  • alpha (Optional[float]) – The alpha blending value as used by matplotlib.

  • cmap (UnionType[str, Colormap, None]) – The Colormap instance or registered colormap name as used by matplotlib.

  • ax (Optional[Axes]) – Axes to plot on. If None, a new figure with one Axes will be created.

Return type:

PatchCollection

Returns:

The matplotlib PathCollection object created by scatter.

portfolio_optimization.plot_points(diversification_values, roc_values, color=None, label=None, c=None, vmin=None, vmax=None, alpha=None, cmap=None, ax=None)[source]

Plots the given data-points in a Diversification-ROC plot.

Parameters:
  • diversification_values (ArrayLike) – 1-D ArrayLike containing the x values of the plot.

  • roc_values (ArrayLike) – 1-D ArrayLike containing the y values of the plot.

  • color (Optional[str]) – Optional color to use for the points. For an overview of allowed colors see the Matplotlib Documentation. If None is given, a default color will be assigned by matplotlib. Default is None.

  • label (Optional[str]) – Label to use in the legend. If None is given, no label will be used. Default is None.

  • c (Union[_SupportsArray[dtype[Any]], _NestedSequence[_SupportsArray[dtype[Any]]], bool, int, float, complex, str, bytes, _NestedSequence[Union[bool, int, float, complex, str, bytes]], Sequence[Union[tuple[float, float, float], str, tuple[float, float, float, float], tuple[Union[tuple[float, float, float], str], float], tuple[tuple[float, float, float, float], float]]], tuple[float, float, float], tuple[float, float, float, float], tuple[Union[tuple[float, float, float], str], float], tuple[tuple[float, float, float, float], float], None]) – The marker colors as used by matplotlib.

  • vmin (Optional[float]) – min value of data range that colormap covers as used by matplotlib.

  • vmax (Optional[float]) – max value of data range that colormap covers as used by matplotlib.

  • alpha (Optional[float]) – The alpha blending value as used by matplotlib.

  • cmap (UnionType[str, Colormap, None]) – The Colormap instance or registered colormap name as used by matplotlib.

  • ax (Optional[Axes]) – Axes to plot on. If None, a new figure with one Axes will be created.

Return type:

PatchCollection | Any

Returns:

The matplotlib PathCollection object created by scatter.

Subpackages