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
return on capital, indicated by ROC,
diversification, indicated by the Herfindahl-Hirschman Index HHI.
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
return on capital, indicated by ROC,
diversification, indicated by the Herfindahl-Hirschman Index HHI.
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 aPortfolioData
object, a pandasDataFrame
or a path to where portfolio data is stored. See the docstring ofPortfolioData
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 variableemission_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
. Useweights_roc
andweights_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 representG_C
using fixed point representation. Only relevant for roc formulation2
.weights_stabilize (
Optional[ArrayLike]
) – The coefficients that are considered as penalty parameter for the stabilizing constraint. Only relevant for roc formulation2
.
- 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 theSimulatedAnnealingSampler
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-DArrayLike
containing the x values of the plot.roc_values (
ArrayLike
) – 1-DArrayLike
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. IfNone
is given, a default color will be assigned bymatplotlib
. Default isNone
.label (
Optional
[str
]) – Label to use in the legend. IfNone
is given, no label will be used. Default isNone
.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 bymatplotlib
.vmin (
Optional
[float
]) – min value of data range that colormap covers as used bymatplotlib
.vmax (
Optional
[float
]) – max value of data range that colormap covers as used bymatplotlib
.alpha (
Optional
[float
]) – The alpha blending value as used bymatplotlib
.cmap (
UnionType
[str
,Colormap
,None
]) – The Colormap instance or registered colormap name as used bymatplotlib
.ax (
Optional
[Axes
]) –Axes
to plot on. IfNone
, a new figure with oneAxes
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-DArrayLike
containing the x values of the plot.roc_values (
ArrayLike
) – 1-DArrayLike
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. IfNone
is given, a default color will be assigned bymatplotlib
. Default isNone
.label (
Optional
[str
]) – Label to use in the legend. IfNone
is given, no label will be used. Default isNone
.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 bymatplotlib
.vmin (
Optional
[float
]) – min value of data range that colormap covers as used bymatplotlib
.vmax (
Optional
[float
]) – max value of data range that colormap covers as used bymatplotlib
.alpha (
Optional
[float
]) – The alpha blending value as used bymatplotlib
.cmap (
UnionType
[str
,Colormap
,None
]) – The Colormap instance or registered colormap name as used bymatplotlib
.ax (
Optional
[Axes
]) –Axes
to plot on. IfNone
, a new figure with oneAxes
will be created.
- Return type:
PatchCollection
|Any
- Returns:
The
matplotlib
PathCollection object created by scatter.
Subpackages
- portfolio_optimization.components package
Decoder
PortfolioData
PortfolioData.__contains__()
PortfolioData.__init__()
PortfolioData.__len__()
PortfolioData.__repr__()
PortfolioData.__str__()
PortfolioData.from_file()
PortfolioData.get_capital()
PortfolioData.get_column()
PortfolioData.get_income()
PortfolioData.get_l_bound()
PortfolioData.get_outstanding_now()
PortfolioData.get_returns()
PortfolioData.get_u_bound()
PortfolioData.print_portfolio_info()
QuboCompiler
QuboFactory
Results
pareto_front()
plot_front()
plot_points()
- Subpackages