consistent use of "optimization"

pull/298/head
robertmartin8 2021-02-19 14:10:06 +08:00
parent b34179b02d
commit 9f4aef1013
37 changed files with 209 additions and 211 deletions

View File

@ -4,7 +4,7 @@ Please refer to the roadmap for a list of areas that I think PyPortfolioOpt coul
from. In addition, the following is always welcome::
- Improve performance of existing code (but not at the cost of readability) are there any nice numpy tricks I've missed?
- Add new optimisation objective functions. For example, if you think that the best performance metric has not been included, write it into a function (or suggest it in [Issues](https://github.com/robertmartin8/PyPortfolioOpt/issues) and I will have a go).
- Add new optimization objective functions. For example, if you think that the best performance metric has not been included, write it into a function (or suggest it in [Issues](https://github.com/robertmartin8/PyPortfolioOpt/issues) and I will have a go).
- Help me write more tests! If you are someone learning about quant finance and/or unit testing in python, what better way to practice than to write some tests on an open-source project! Feel free to check for edge cases, or test performance on a dataset with more stocks.
## Guidelines
@ -37,7 +37,7 @@ If you have questions unrelated to the project, drop me an email contact det
## Bugs/issues
If you find any bugs or the portfolio optimisation is not working as expected, feel free to [raise an issue](https://github.com/robertmartin8/PyPortfolioOpt/issues). I would ask that you provide the following information in the issue:
If you find any bugs or the portfolio optimization is not working as expected, feel free to [raise an issue](https://github.com/robertmartin8/PyPortfolioOpt/issues). I would ask that you provide the following information in the issue:
- Descriptive title so that other users can see the existing issues
- Operating system, python version, and python distribution (optional).

View File

@ -26,8 +26,8 @@
<!-- content -->
PyPortfolioOpt is a library that implements portfolio optimisation methods, including
classical mean-variance optimisation techniques and Black-Litterman allocation, as well as more
PyPortfolioOpt is a library that implements portfolio optimization methods, including
classical mean-variance optimization techniques and Black-Litterman allocation, as well as more
recent developments in the field like shrinkage and Hierarchical Risk Parity, along with
some novel experimental features like exponentially-weighted covariance matrices.
@ -49,14 +49,14 @@ Head over to the [documentation on ReadTheDocs](https://pyportfolioopt.readthedo
- [For development](#for-development)
- [A quick example](#a-quick-example)
- [What's new](#whats-new)
- [An overview of classical portfolio optimisation methods](#an-overview-of-classical-portfolio-optimisation-methods)
- [An overview of classical portfolio optimization methods](#an-overview-of-classical-portfolio-optimization-methods)
- [Features](#features)
- [Expected returns](#expected-returns)
- [Risk models (covariance)](#risk-models-covariance)
- [Objective functions](#objective-functions)
- [Adding constraints or different objectives](#adding-constraints-or-different-objectives)
- [Black-Litterman allocation](#black-litterman-allocation)
- [Other optimisers](#other-optimisers)
- [Other optimizers](#other-optimizers)
- [Advantages over existing implementations](#advantages-over-existing-implementations)
- [Project principles and design decisions](#project-principles-and-design-decisions)
- [Testing](#testing)
@ -124,7 +124,7 @@ df = pd.read_csv("tests/resources/stock_prices.csv", parse_dates=True, index_col
mu = expected_returns.mean_historical_return(df)
S = risk_models.sample_cov(df)
# Optimise for maximal Sharpe ratio
# Optimize for maximal Sharpe ratio
ef = EfficientFrontier(mu, S)
raw_weights = ef.max_sharpe()
cleaned_weights = ef.clean_weights()
@ -206,23 +206,23 @@ As of v1.2.0:
new plots. All other plotting functions (scattered in different classes) have been retained,
but are now deprecated.
## An overview of classical portfolio optimisation methods
## An overview of classical portfolio optimization methods
Harry Markowitz's 1952 paper is the undeniable classic, which turned portfolio optimisation from an art into a science. The key insight is that by combining assets with different expected returns and volatilities, one can decide on a mathematically optimal allocation which minimises the risk for a target return the set of all such optimal portfolios is referred to as the **efficient frontier**.
Harry Markowitz's 1952 paper is the undeniable classic, which turned portfolio optimization from an art into a science. The key insight is that by combining assets with different expected returns and volatilities, one can decide on a mathematically optimal allocation which minimises the risk for a target return the set of all such optimal portfolios is referred to as the **efficient frontier**.
<center>
<img src="https://github.com/robertmartin8/PyPortfolioOpt/blob/master/media/efficient_frontier_white.png" style="width:60%;"/>
</center>
Although much development has been made in the subject, more than half a century later, Markowitz's core ideas are still fundamentally important and see daily use in many portfolio management firms.
The main drawback of mean-variance optimisation is that the theoretical treatment requires knowledge of the expected returns and the future risk-characteristics (covariance) of the assets. Obviously, if we knew the expected returns of a stock life would be much easier, but the whole game is that stock returns are notoriously hard to forecast. As a substitute, we can derive estimates of the expected return and covariance based on historical data though we do lose the theoretical guarantees provided by Markowitz, the closer our estimates are to the real values, the better our portfolio will be.
The main drawback of mean-variance optimization is that the theoretical treatment requires knowledge of the expected returns and the future risk-characteristics (covariance) of the assets. Obviously, if we knew the expected returns of a stock life would be much easier, but the whole game is that stock returns are notoriously hard to forecast. As a substitute, we can derive estimates of the expected return and covariance based on historical data though we do lose the theoretical guarantees provided by Markowitz, the closer our estimates are to the real values, the better our portfolio will be.
Thus this project provides four major sets of functionality (though of course they are intimately related)
- Estimates of expected returns
- Estimates of risk (i.e covariance of asset returns)
- Objective functions to be optimised
- Optimisers.
- Objective functions to be optimized
- Optimizers.
A key design goal of PyPortfolioOpt is **modularity** the user should be able to swap in their
components while still making use of the framework that PyPortfolioOpt provides.
@ -253,7 +253,7 @@ The covariance matrix encodes not just the volatility of an asset, but also how
- an unbiased estimate of the covariance matrix
- relatively easy to compute
- the de facto standard for many years
- however, it has a high estimation error, which is particularly dangerous in mean-variance optimisation because the optimiser is likely to give excess weight to these erroneous estimates.
- however, it has a high estimation error, which is particularly dangerous in mean-variance optimization because the optimizer is likely to give excess weight to these erroneous estimates.
- Semicovariance: a measure of risk that focuses on downside variation.
- Exponential covariance: an improvement over sample covariance that gives more weight to recent data
- Covariance shrinkage: techniques that involve combining the sample covariance matrix with a structured estimator, to reduce the effect of erroneous weights. PyPortfolioOpt provides wrappers around the efficient vectorised implementations provided by `sklearn.covariance`.
@ -280,7 +280,7 @@ The covariance matrix encodes not just the volatility of an asset, but also how
### Adding constraints or different objectives
- Long/short: by default all of the mean-variance optimisation methods in PyPortfolioOpt are long-only, but they can be initialised to allow for short positions by changing the weight bounds:
- Long/short: by default all of the mean-variance optimization methods in PyPortfolioOpt are long-only, but they can be initialised to allow for short positions by changing the weight bounds:
```python
ef = EfficientFrontier(mu, S, weight_bounds=(-1, 1))
@ -299,7 +299,7 @@ ef.efficient_return(target_return=0.2, market_neutral=True)
ef = EfficientFrontier(mu, S, weight_bounds=(0, 0.1))
```
One issue with mean-variance optimisation is that it leads to many zero-weights. While these are
One issue with mean-variance optimization is that it leads to many zero-weights. While these are
"optimal" in-sample, there is a large body of research showing that this characteristic leads
mean-variance portfolios to underperform out-of-sample. To that end, I have introduced an
objective function that can reduce the number of negligible weights for any of the objective functions. Essentially, it adds a penalty (parameterised by `gamma`) on small weights, with a term that looks just like L2 regularisation in machine learning. It may be necessary to try several `gamma` values to achieve the desired number of non-negligible weights. For the test portfolio of 20 securities, `gamma ~ 1` is sufficient
@ -328,16 +328,16 @@ ef = EfficientFrontier(rets, S)
ef.max_sharpe()
```
### Other optimisers
### Other optimizers
The features above mostly pertain to solving mean-variance optimisation problems via quadratic programming (though this is taken care of by `cvxpy`). However, we offer different optimisers as well:
The features above mostly pertain to solving mean-variance optimization problems via quadratic programming (though this is taken care of by `cvxpy`). However, we offer different optimizers as well:
- Mean-semivariance optimisation
- Mean-CVaR optimisation
- Mean-semivariance optimization
- Mean-CVaR optimization
- Hierarchical Risk Parity, using clustering algorithms to choose uncorrelated assets
- Markowitz's critical line algorithm (CLA)
Please refer to the [documentation](https://pyportfolioopt.readthedocs.io/en/latest/OtherOptimisers.html) for more.
Please refer to the [documentation](https://pyportfolioopt.readthedocs.io/en/latest/OtherOptimizers.html) for more.
## Advantages over existing implementations
@ -352,10 +352,10 @@ Please refer to the [documentation](https://pyportfolioopt.readthedocs.io/en/lat
## Project principles and design decisions
- It should be easy to swap out individual components of the optimisation process
- It should be easy to swap out individual components of the optimization process
with the user's proprietary improvements.
- Usability is everything: it is better to be self-explanatory than consistent.
- There is no point in portfolio optimisation unless it can be practically
- There is no point in portfolio optimization unless it can be practically
applied to real asset prices.
- Everything that has been implemented should be tested.
- Inline documentation is good: dedicated (separate) documentation is better.

View File

@ -240,7 +240,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"The exponential moving average is marginally better than the others, but the improvement is almost unnoticeable. We also note that the mean absolute deviations are above 25%, meaning that if your expected annual returns are 10%, on average the realised annual return could be anywhere from a 15% loss to a 35% gain. This is a massive range, and gives some context to the advice in the docs suggesting that you optimise without providing an estimate of returns."
"The exponential moving average is marginally better than the others, but the improvement is almost unnoticeable. We also note that the mean absolute deviations are above 25%, meaning that if your expected annual returns are 10%, on average the realised annual return could be anywhere from a 15% loss to a 35% gain. This is a massive range, and gives some context to the advice in the docs suggesting that you optimize without providing an estimate of returns."
]
},
{

View File

@ -4,11 +4,11 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"# Mean-variance optimisation\n",
"# Mean-variance optimization\n",
"\n",
"In this cookbook recipe, we work on several examples demonstrating PyPortfolioOpt's mean-variance capabilities. I will discuss what I think should be your \"default\" options, based on my experience in optimising portfolios.\n",
"\n",
"To start, you need a list of tickers. Some people just provide the whole universe of stocks, but I don't think this is a good idea - portfolio optimisation is quite different from asset selection. I would suggest anywhere from 10-50 stocks as a starting point.\n",
"To start, you need a list of tickers. Some people just provide the whole universe of stocks, but I don't think this is a good idea - portfolio optimization is quite different from asset selection. I would suggest anywhere from 10-50 stocks as a starting point.\n",
"\n",
"Some of the things we cover:\n",
"\n",
@ -827,7 +827,7 @@
"source": [
"## Long/short min variance\n",
"\n",
"In this section, we construct a long/short portfolio with the objective of minimising variance. There is a good deal of research that demonstrates that these global-minimum variance (GMV) portfolios outperform mean-variance optimised portfolios."
"In this section, we construct a long/short portfolio with the objective of minimising variance. There is a good deal of research that demonstrates that these global-minimum variance (GMV) portfolios outperform mean-variance optimized portfolios."
]
},
{
@ -1353,7 +1353,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"We then set up the optimiser and add our constraints. We can use `ef.add_objective()` to add other constraints. For example, let's say that in addition to the above sector constraints, I specifically want:\n",
"We then set up the optimizer and add our constraints. We can use `ef.add_objective()` to add other constraints. For example, let's say that in addition to the above sector constraints, I specifically want:\n",
"\n",
"- 10% of the portfolio in AMZN\n",
"- Less than 5% of my portfolio in TSLA"
@ -1564,7 +1564,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"While this portfolio seems like it meets our objectives, we might be worried by the fact that a lot of the tickers have been assigned zero weight. In effect, the optimiser is \"overfitting\" to the data you have provided -- you are much more likely to get better results by enforcing some level of diversification. One way of doing this is to use **L2 regularisation** essentially, adding a penalty on the number of near-zero weights."
"While this portfolio seems like it meets our objectives, we might be worried by the fact that a lot of the tickers have been assigned zero weight. In effect, the optimizer is \"overfitting\" to the data you have provided -- you are much more likely to get better results by enforcing some level of diversification. One way of doing this is to use **L2 regularisation** essentially, adding a penalty on the number of near-zero weights."
]
},
{
@ -1837,7 +1837,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"## Efficient semi-variance optimisation\n",
"## Efficient semi-variance optimization\n",
"\n",
"In this example, we will minimise the portfolio semivariance (i.e downside volatility) subject to a return constraint (target 20%).\n",
"\n",
@ -1927,7 +1927,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"However, this solution is not truly optimal in mean-semivariance space. To do the optimisation properly, we must use the `EfficientSemivariance` class. This requires us to first compute the returns and drop NaNs."
"However, this solution is not truly optimal in mean-semivariance space. To do the optimization properly, we must use the `EfficientSemivariance` class. This requires us to first compute the returns and drop NaNs."
]
},
{
@ -2001,7 +2001,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"## Efficient CVaR optimisation\n",
"## Efficient CVaR optimization\n",
"\n",
"In this example, we will find the portfolio that maximises return subject to a CVaR constraint.\n",
"\n",
@ -2423,7 +2423,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"As per the docs, *before* we call any optimisation function, we should pass this to the plotting module:"
"As per the docs, *before* we call any optimization function, we should pass this to the plotting module:"
]
},
{

View File

@ -344,7 +344,7 @@
"source": [
"## Min volatility with a transaction cost objective\n",
"\n",
"Let's say that you already have a portfolio, and want to now optimise it. It could be quite expensive to completely reallocate, so you may want to take into account transaction costs. PyPortfolioOpt provides a simple objective to account for this.\n",
"Let's say that you already have a portfolio, and want to now optimize it. It could be quite expensive to completely reallocate, so you may want to take into account transaction costs. PyPortfolioOpt provides a simple objective to account for this.\n",
"\n",
"Note: this objective will not play nicely with `max_sharpe`."
]
@ -450,7 +450,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"The optimiser seems to really like JD. The reason for this is that it is highly anticorrelated to other assets (notice the dark column in the covariance plot). Hence, historically, it adds a lot of diversification. But it is dangerous to place too much emphasis on what happened in the past, so we may want to limit the asset weights. \n",
"The optimizer seems to really like JD. The reason for this is that it is highly anticorrelated to other assets (notice the dark column in the covariance plot). Hence, historically, it adds a lot of diversification. But it is dangerous to place too much emphasis on what happened in the past, so we may want to limit the asset weights. \n",
"\n",
"In addition, we notice that 4 stocks have now been allocated zero weight, which may be undesirable. Both of these problems can be fixed by adding an [L2 regularisation objective](https://pyportfolioopt.readthedocs.io/en/latest/EfficientFrontier.html#more-on-l2-regularisation). "
]
@ -601,13 +601,13 @@
"- Quadratic utility\n",
"- Transaction cost model (a simple one)\n",
"\n",
"However, you may want have a different objective. If this new objective is **convex**, you can optimise a portfolio with the full benefit of PyPortfolioOpt's modular syntax, for example adding other constraints and objectives.\n",
"However, you may want have a different objective. If this new objective is **convex**, you can optimize a portfolio with the full benefit of PyPortfolioOpt's modular syntax, for example adding other constraints and objectives.\n",
"\n",
"To demonstrate this, we will minimise the **logarithmic-barrier** function suggested in the paper 60 Years of Portfolio Optimisation, by Kolm et al (2014):\n",
"To demonstrate this, we will minimise the **logarithmic-barrier** function suggested in the paper 60 Years of Portfolio Optimization, by Kolm et al (2014):\n",
"\n",
"$$f(w, S, k) = w^T S w - k \\sum_{i=1}^N \\ln w$$\n",
"\n",
"We must first convert this mathematical objective into the language of cvxpy. Cvxpy is a powerful modelling language for convex optimisation problems. It is clean and easy to use, the only caveat is that objectives must be expressed with `cvxpy` functions, a list of which can be found [here](https://www.cvxpy.org/tutorial/functions/index.html)."
"We must first convert this mathematical objective into the language of cvxpy. Cvxpy is a powerful modelling language for convex optimization problems. It is clean and easy to use, the only caveat is that objectives must be expressed with `cvxpy` functions, a list of which can be found [here](https://www.cvxpy.org/tutorial/functions/index.html)."
]
},
{
@ -740,9 +740,9 @@
"source": [
"## Custom nonconvex objectives\n",
"\n",
"In some cases, you may be trying to optimise for nonconvex objectives. Optimisation in general is a very hard problem, so please be aware that you may have mixed results in that case. Convex problems, on the other hand, are well understood and can be solved with nice theoretical guarantees.\n",
"In some cases, you may be trying to optimize for nonconvex objectives. Optimization in general is a very hard problem, so please be aware that you may have mixed results in that case. Convex problems, on the other hand, are well understood and can be solved with nice theoretical guarantees.\n",
"\n",
"PyPortfolioOpt does offer some functionality for nonconvex optimisation, but it is not really encouraged. In particular, nonconvex optimisation is not compatible with PyPortfolioOpt's modular constraints API.\n",
"PyPortfolioOpt does offer some functionality for nonconvex optimization, but it is not really encouraged. In particular, nonconvex optimization is not compatible with PyPortfolioOpt's modular constraints API.\n",
"\n",
"As an example, we will use the Deviation Risk Parity objective from Kolm et al (2014). Because we are not using a convex solver, we don't have to define it using `cvxpy` functions."
]

View File

@ -15,7 +15,7 @@
"- Downloading data for the Black-Litterman method\n",
"- Constructing the prior return vector based on market equilibrium\n",
"- Two ways of constructing the uncertainty matrix\n",
"- Combining Black-Litterman with mean-variance optimisation\n",
"- Combining Black-Litterman with mean-variance optimization\n",
"\n",
"## Downloading data\n",
"\n",
@ -818,7 +818,7 @@
"source": [
"## Portfolio allocation\n",
"\n",
"Now that we have constructed our Black-Litterman posterior estimate, we can proceed to use any of the optimisers discussed in previous recipes."
"Now that we have constructed our Black-Litterman posterior estimate, we can proceed to use any of the optimizers discussed in previous recipes."
]
},
{
@ -839,7 +839,7 @@
"name": "stderr",
"output_type": "stream",
"text": [
"/Users/Robert/github/PyPortfolioOpt/pypfopt/efficient_frontier.py:196: UserWarning: max_sharpe transforms the optimisation problem so additional objectives may not work as expected.\n",
"/Users/Robert/github/PyPortfolioOpt/pypfopt/efficient_frontier.py:196: UserWarning: max_sharpe transforms the optimization problem so additional objectives may not work as expected.\n",
" warnings.warn(\n"
]
},

View File

@ -7,7 +7,7 @@
"source": [
"# Hierarchical Risk Parity\n",
"\n",
"HRP is a modern portfolio optimisation method inspired by machine learning.\n",
"HRP is a modern portfolio optimization method inspired by machine learning.\n",
"\n",
"The idea is that by examining the hierarchical structure of the market, we can better diversify. \n",
"\n",
@ -461,7 +461,7 @@
"id": "answering-tamil",
"metadata": {},
"source": [
"## HRP optimisation\n",
"## HRP optimization\n",
"\n",
"HRP uses a completely different backend, so it is currently not possible to pass constraints or specify an objective function."
]

View File

@ -9,10 +9,10 @@ over to my `website <https://reasonabledeviations.com>`_.
I learn fastest when making real projects. In early 2018 I began seriously trying
to self-educate on certain topics in quantitative finance, and mean-variance
optimisation is one of the cornerstones of this field. I read quite a few journal
optimization is one of the cornerstones of this field. I read quite a few journal
articles and explanations but ultimately felt that a real proof of understanding would
lie in the implementation. At the same time, I realised that existing open-source
(python) portfolio optimisation libraries (there are one or two), were unsatisfactory
(python) portfolio optimization libraries (there are one or two), were unsatisfactory
for several reasons, and that people 'out there' might benefit from a
well-documented and intuitive API. This is what motivated the development of
PyPortfolioOpt.

View File

@ -45,7 +45,7 @@ Similarly, we can calculate a posterior estimate of the covariance matrix:
Though the algorithm is relatively simple, BL proved to be a challenge from a software
engineering perspective because it's not quite clear how best to fit it into PyPortfolioOpt's
API. The full discussion can be found on a `Github issue thread <https://github.com/robertmartin8/PyPortfolioOpt/issues/48>`_,
but I ultimately decided that though BL is not technically an optimiser, it didn't make sense to
but I ultimately decided that though BL is not technically an optimizer, it didn't make sense to
split up its methods into `expected_returns` or `risk_models`. I have thus made it an independent
module and owing to the comparatively extensive theory, have given it a dedicated documentation page.
I'd like to thank `Felipe Schneider <https://github.com/schneiderfelipe>`_ for his multiple
@ -55,7 +55,7 @@ of market cap data for free, please refer to the `cookbook recipe <https://githu
.. tip::
Thomas Kirschenmann has built a neat interactive `Black-Litterman tool <https://github.com/thk3421-models/cardiel>`_
on top of PyPortfolioOpt, which allows you to visualise BL outputs and compare optimisation objectives.
on top of PyPortfolioOpt, which allows you to visualise BL outputs and compare optimization objectives.
Priors
======
@ -192,7 +192,7 @@ Output of the BL model
======================
The BL model outputs posterior estimates of the returns and covariance matrix. The default suggestion in the literature is to
then input these into an optimiser (see :ref:`efficient-frontier`). A quick alternative, which is quite useful for debugging, is
then input these into an optimizer (see :ref:`efficient-frontier`). A quick alternative, which is quite useful for debugging, is
to calculate the weights implied by the returns vector [4]_. It is actually the reverse of the procedure we used to calculate the
returns implied by the market weights.

View File

@ -5,8 +5,8 @@ Contributing
Some of the things that I'd love for people to help with:
- Improve performance of existing code (but not at the cost of readability)
- Add new optimisation objectives. For example, if you would like to use something other
than the Sharpe ratio, write an optimiser! (or suggest it in
- Add new optimization objectives. For example, if you would like to use something other
than the Sharpe ratio, write an optimizer! (or suggest it in
`Issues <https://github.com/robertmartin8/PyPortfolioOpt/issues>`_ and I will have a go).
- Help me write more tests! If you are someone learning about quant finance and/or unit
testing in python, what better way to practice than to write some tests on an
@ -71,7 +71,7 @@ details can be found on my `website <https://reasonabledeviations.com/about/>`_.
Bugs/issues
===========
If you find any bugs or the portfolio optimisation is not working as expected,
If you find any bugs or the portfolio optimization is not working as expected,
feel free to `raise an issue <https://github.com/robertmartin8/PyPortfolioOpt/issues>`_.
I would ask that you provide the following information in the issue:

View File

@ -4,13 +4,13 @@
Expected Returns
################
Mean-variance optimisation requires knowledge of the expected returns. In practice,
Mean-variance optimization requires knowledge of the expected returns. In practice,
these are rather difficult to know with any certainty. Thus the best we can do is to
come up with estimates, for example by extrapolating historical data, This is the
main flaw in mean-variance optimisation the optimisation procedure is sound, and provides
main flaw in mean-variance optimization the optimization procedure is sound, and provides
strong mathematical guarantees, *given the correct inputs*. This is one of the reasons
why I have emphasised modularity: users should be able to come up with their own
superior models and feed them into the optimiser.
superior models and feed them into the optimizer.
.. caution::
@ -33,7 +33,7 @@ superior models and feed them into the optimiser.
This is probably the default textbook approach. It is intuitive and easily interpretable,
however the estimates are subject to large uncertainty. This is a problem especially in the
context of a mean-variance optimiser, which will maximise the erroneous inputs.
context of a mean-variance optimizer, which will maximise the erroneous inputs.
.. autofunction:: ema_historical_return

View File

@ -4,28 +4,28 @@
General Efficient Frontier
##########################
The mean-variance optimisation methods described previously can be used whenever you have a vector
The mean-variance optimization methods described previously can be used whenever you have a vector
of expected returns and a covariance matrix. The objective and constraints will be some combination
of the portfolio return and portfolio volatility.
However, you may want to construct the efficient frontier for an entirely different type of risk model
(one that doesn't depend on covariance matrices), or optimise an objective unrelated to portfolio
(one that doesn't depend on covariance matrices), or optimize an objective unrelated to portfolio
return (e.g tracking error). PyPortfolioOpt comes with several popular alternatives and provides support
for custom optimisation problems.
for custom optimization problems.
Efficient Semivariance
======================
Instead of penalising volatility, mean-semivariance optimisation seeks to only penalise
Instead of penalising volatility, mean-semivariance optimization seeks to only penalise
downside volatility, since upside volatility may be desirable.
There are two approaches to the mean-semivariance optimisation problem. The first is to use a
There are two approaches to the mean-semivariance optimization problem. The first is to use a
heuristic (i.e "quick and dirty") solution: pretending that the semicovariance matrix
(implemented in :py:mod:`risk_models`) is a typical covariance matrix and doing standard
mean-variance optimisation. It can be shown that this *does not* yield a portfolio that
mean-variance optimization. It can be shown that this *does not* yield a portfolio that
is efficient in mean-semivariance space (though it might be a good-enough approximation).
Fortunately, it is possible to write mean-semivariance optimisation as a convex problem
Fortunately, it is possible to write mean-semivariance optimization as a convex problem
(albeit one with many variables), that can be solved to give an "exact" solution.
For example, to maximise return for a target semivariance
:math:`s^*` (long-only), we would solve the following problem:
@ -47,7 +47,7 @@ Here, **B** is the :math:`T \times N` (scaled) matrix of excess returns:
``B = (returns - benchmark) / sqrt(T)``. Additional linear equality constraints and
convex inequality constraints can be added.
PyPortfolioOpt allows users to optimise along the efficient semivariance frontier
PyPortfolioOpt allows users to optimize along the efficient semivariance frontier
via the :py:class:`EfficientSemivariance` class. :py:class:`EfficientSemivariance` inherits from
:py:class:`EfficientFrontier`, so it has the same utility methods
(e.g :py:func:`add_constraint`, :py:func:`portfolio_performance`), but finds portfolios on the mean-semivariance
@ -85,7 +85,7 @@ implementation is based on Markowitz et al (2019) [2]_.
.. caution::
Finding portfolios on the mean-semivariance frontier is computationally harder
than standard mean-variance optimisation: our implementation uses ``2T + N`` optimisation variables,
than standard mean-variance optimization: our implementation uses ``2T + N`` optimization variables,
meaning that for 50 assets and 3 years of data, there are about 1500 variables.
While :py:class:`EfficientSemivariance` allows for additional constraints/objectives in principle,
you are much more likely to run into solver errors. I suggest that you keep :py:class:`EfficientSemivariance`
@ -120,15 +120,15 @@ The CVaR can then be written as:
.. math::
CVaR(w, \beta) = \frac{1}{1-\beta} \int_{L(w, r) \geq \alpha (w)} L(w, r) p(r)dr.
This is a nasty expression to optimise because we are essentially integrating over VaR values. The key insight
of Rockafellar and Uryasev (2001) [3]_ is that we can can equivalently optimise the following convex function:
This is a nasty expression to optimize because we are essentially integrating over VaR values. The key insight
of Rockafellar and Uryasev (2001) [3]_ is that we can can equivalently optimize the following convex function:
.. math::
F_\beta (w, \alpha) = \alpha + \frac{1}{1-\beta} \int [-w^T r - \alpha]^+ p(r) dr,
where :math:`[x]^+ = \max(x, 0)`. The authors prove that minimising :math:`F_\beta(w, \alpha)` over all
:math:`w, \alpha` minimises the CVaR. Suppose we have a sample of *T* daily returns (these
can either be historical or simulated). The integral in the expression becomes a sum, so the CVaR optimisation
can either be historical or simulated). The integral in the expression becomes a sum, so the CVaR optimization
problem reduces to a linear program:
.. math::
@ -154,19 +154,19 @@ The implementation is based on Rockafellar and Uryasev (2001) [3]_.
:exclude-members: max_sharpe, min_volatility, max_quadratic_utility
.. _custom-optimisation:
.. _custom-optimization:
Custom optimisation problems
Custom optimization problems
============================
We have seen previously that it is easy to add constraints to ``EfficientFrontier`` objects (and
by extension, other general efficient frontier objects like ``EfficientSemivariance``). However, what if you aren't interested
in anything related to ``max_sharpe()``, ``min_volatility()``, ``efficient_risk()`` etc and want to
set up a completely new problem to optimise for some custom objective?
set up a completely new problem to optimize for some custom objective?
For example, perhaps our objective is to construct a basket of assets that best replicates a
particular index, in otherwords, to minimise the **tracking error**. This does not fit within
a mean-variance optimisation paradigm, but we can still implement it in PyPortfolioOpt::
a mean-variance optimization paradigm, but we can still implement it in PyPortfolioOpt::
from pypfopt.base_optimizer import BaseConvexOptimizer
from pypfopt.objective_functions import ex_post_tracking error
@ -190,11 +190,11 @@ The ``EfficientFrontier`` class inherits from ``BaseConvexOptimizer``. It may be
to call ``convex_objective`` from an ``EfficientFrontier`` instance than from ``BaseConvexOptimizer``,
particularly if your objective depends on the mean returns or covariance matrix.
You can either optimise some generic ``convex_objective``
You can either optimize some generic ``convex_objective``
(which *must* be built using ``cvxpy`` atomic functions -- see `here <https://www.cvxpy.org/tutorial/functions/index.html>`_)
or a ``nonconvex_objective``, which uses ``scipy.optimize`` as the backend and thus has a completely
different API. For more examples, check out this `cookbook recipe
<https://github.com/robertmartin8/PyPortfolioOpt/blob/master/cookbook/3-Advanced-Mean-Variance-Optimisation.ipynb>`_.
<https://github.com/robertmartin8/PyPortfolioOpt/blob/master/cookbook/3-Advanced-Mean-Variance-Optimization.ipynb>`_.
.. class:: pypfopt.base_optimizer.BaseConvexOptimizer

View File

@ -1,9 +1,9 @@
##########################
Mean-Variance Optimisation
Mean-Variance Optimization
##########################
Mathematical optimisation is a very difficult problem in general, particularly when we are dealing
with complex objectives and constraints. However, **convex optimisation** problems are a well-understood
Mathematical optimization is a very difficult problem in general, particularly when we are dealing
with complex objectives and constraints. However, **convex optimization** problems are a well-understood
class of problems, which happen to be incredibly useful for finance. A convex problem has the following form:
.. math::
@ -18,7 +18,7 @@ class of problems, which happen to be incredibly useful for finance. A convex pr
where :math:`\mathbf{x} \in \mathbb{R}^n`, and :math:`f(\mathbf{x}), g_i(\mathbf{x})` are convex functions. [1]_
Fortunately, portfolio optimisation problems (with standard objectives and constraints) are convex. This
Fortunately, portfolio optimization problems (with standard objectives and constraints) are convex. This
allows us to immediately apply the vast body of theory as well as the refined solving routines -- accordingly,
the main difficulty is inputting our specific problem into a solver.
@ -26,25 +26,25 @@ PyPortfolioOpt aims to do the hard work for you, allowing for one-liners like ``
to generate a portfolio that minimises the volatility, while at the same time allowing for more
complex problems to be built up from modular units. This is all possible thanks to
`cvxpy <https://www.cvxpy.org/>`_, the *fantastic* python-embedded modelling
language for convex optimisation upon which PyPortfolioOpt's efficient frontier functionality lies.
language for convex optimization upon which PyPortfolioOpt's efficient frontier functionality lies.
.. tip::
You can find complete examples in the relevant cookbook `recipe <https://github.com/robertmartin8/PyPortfolioOpt/blob/master/cookbook/2-Mean-Variance-Optimisation.ipynb>`_.
You can find complete examples in the relevant cookbook `recipe <https://github.com/robertmartin8/PyPortfolioOpt/blob/master/cookbook/2-Mean-Variance-Optimization.ipynb>`_.
Structure
=========
As shown in the definition of a convex problem, there are essentially two things we need to specify:
the optimisation objective, and the optimisation constraints. For example, the classic portfolio
optimisation problem is to **minimise risk** subject to a **return constraint** (i.e the portfolio
the optimization objective, and the optimization constraints. For example, the classic portfolio
optimization problem is to **minimise risk** subject to a **return constraint** (i.e the portfolio
must return more than a certain amount). From an implementation perspective, however, there is
not much difference between an objective and a constraint. Consider a similar problem, which is to
**maximize return** subject to a **risk constraint** -- now, the role of risk and return have swapped.
To that end, PyPortfolioOpt defines an :py:mod:`objective_functions` module that contains objective functions
(which can also act as constraints, as we have just seen). The actual optimisation occurs in the :py:class:`efficient_frontier.EfficientFrontier` class.
(which can also act as constraints, as we have just seen). The actual optimization occurs in the :py:class:`efficient_frontier.EfficientFrontier` class.
This class provides straightforward methods for optimising different objectives (all documented below).
However, PyPortfolioOpt was designed so that you can easily add new constraints or objective terms to an existing problem.
@ -75,7 +75,7 @@ Basic Usage
.. tip::
If you want to generate short-only portfolios, there is a quick hack. Multiply
your expected returns by -1, then optimise a long-only portfolio.
your expected returns by -1, then optimize a long-only portfolio.
.. automethod:: min_volatility
@ -100,7 +100,7 @@ Basic Usage
.. caution::
If you pass an unreasonable target into :py:meth:`efficient_risk` or
:py:meth:`efficient_return`, the optimiser will fail silently and return
:py:meth:`efficient_return`, the optimizer will fail silently and return
weird weights. *Caveat emptor* applies!
.. automethod:: efficient_return
@ -110,7 +110,7 @@ Basic Usage
.. tip::
If you would like to use the ``portfolio_performance`` function independently of any
optimiser (e.g for debugging purposes), you can use::
optimizer (e.g for debugging purposes), you can use::
from pypfopt import base_optimizer
@ -156,12 +156,12 @@ Objective functions
More on L2 Regularisation
=========================
As has been discussed in the :ref:`user-guide`, mean-variance optimisation often
As has been discussed in the :ref:`user-guide`, mean-variance optimization often
results in many weights being negligible, i.e the efficient portfolio does not end up
including most of the assets. This is expected behaviour, but it may be undesirable
if you need a certain number of assets in your portfolio.
In order to coerce the mean-variance optimiser to produce more non-negligible
In order to coerce the mean-variance optimizer to produce more non-negligible
weights, we add what can be thought of as a "small weights penalty" to all
of the objective functions, parameterised by :math:`\gamma` (``gamma``). Considering
the minimum variance objective for instance, we have:

View File

@ -1,16 +1,16 @@
.. _other-optimisers:
.. _other-optimizers:
################
Other Optimisers
Other Optimizers
################
Efficient frontier methods involve the direct optimisation of an objective subject to constraints.
However, there are some portfolio optimisation schemes that are completely different in character.
Efficient frontier methods involve the direct optimization of an objective subject to constraints.
However, there are some portfolio optimization schemes that are completely different in character.
PyPortfolioOpt provides support for these alternatives, while still giving you access to the same
pre and post-processing API.
.. note::
As of v0.4, these other optimisers now inherit from ``BaseOptimizer`` or
As of v0.4, these other optimizers now inherit from ``BaseOptimizer`` or
``BaseConvexOptimizer``, so you no longer have to implement pre-processing and
post-processing methods on your own. You can thus easily swap out, say,
``EfficientFrontier`` for ``HRPOpt``.
@ -18,7 +18,7 @@ pre and post-processing API.
Hierarchical Risk Parity (HRP)
==============================
Hierarchical Risk Parity is a novel portfolio optimisation method developed by
Hierarchical Risk Parity is a novel portfolio optimization method developed by
Marcos Lopez de Prado [1]_. Though a detailed explanation can be found in the
linked paper, here is a rough overview of how HRP works:
@ -33,7 +33,7 @@ linked paper, here is a rough overview of how HRP works:
The advantages of this are that it does not require the inversion of the covariance
matrix as with traditional mean-variance optimisation, and seems to produce diverse
matrix as with traditional mean-variance optimization, and seems to produce diverse
portfolios that perform well out of sample.
.. image:: ../media/dendrogram.png
@ -57,8 +57,8 @@ The Critical Line Algorithm
===========================
This is a robust alternative to the quadratic solver used to find mean-variance optimal portfolios,
that is especially advantageous when we apply linear inequalities. Unlike generic convex optimisation routines,
the CLA is specially designed for portfolio optimisation. It is guaranteed to converge after a certain
that is especially advantageous when we apply linear inequalities. Unlike generic convex optimization routines,
the CLA is specially designed for portfolio optimization. It is guaranteed to converge after a certain
number of iterations, and can efficiently derive the entire efficient frontier.
.. image:: ../media/cla_plot.png
@ -69,7 +69,7 @@ number of iterations, and can efficiently derive the entire efficient frontier.
.. tip::
In general, unless you have specific requirements e.g you would like to efficiently compute the entire
efficient frontier for plotting, I would go with the standard ``EfficientFrontier`` optimiser.
efficient frontier for plotting, I would go with the standard ``EfficientFrontier`` optimizer.
I am most grateful to Marcos López de Prado and David Bailey for providing the implementation [2]_.
Permission for its distribution has been received by email. It has been modified such that it has
@ -85,15 +85,15 @@ the same API, though as of v0.5.0 we only support ``max_sharpe()`` and ``min_vol
.. automethod:: __init__
Implementing your own optimiser
Implementing your own optimizer
===============================
Please note that this is quite different to implementing :ref:`custom-optimisation`, because in
that case we are still using the same convex optimisation structure. However, HRP and CLA optimisation
have a fundamentally different optimisation method. In general, these are much more difficult
Please note that this is quite different to implementing :ref:`custom-optimization`, because in
that case we are still using the same convex optimization structure. However, HRP and CLA optimization
have a fundamentally different optimization method. In general, these are much more difficult
to code up compared to custom objective functions.
To implement a custom optimiser that is compatible with the rest of PyPortfolioOpt, just
To implement a custom optimizer that is compatible with the rest of PyPortfolioOpt, just
extend ``BaseOptimizer`` (or ``BaseConvexOptimizer`` if you want to use ``cvxpy``),
both of which can be found in ``base_optimizer.py``. This gives you access to utility
methods like ``clean_weights()``, as well as making sure that any output is compatible

View File

@ -4,7 +4,7 @@
Plotting
########
All of the optimisation functions in :py:class:`EfficientFrontier` produce a single optimal portfolio.
All of the optimization functions in :py:class:`EfficientFrontier` produce a single optimal portfolio.
However, you may want to plot the entire efficient frontier. This efficient frontier can be thought
of in several different ways:
@ -14,7 +14,7 @@ of in several different ways:
The :py:mod:`plotting` module provides support for all three of these approaches. To produce
a plot of the efficient frontier, you should instantiate your :py:class:`EfficientFrontier` object
and add constraints like you normally would, but *before* calling an optimisation function (e.g with
and add constraints like you normally would, but *before* calling an optimization function (e.g with
:py:func:`ef.max_sharpe`), you should pass this the instantiated object into :py:func:`plot.plot_efficient_frontier`::
ef = EfficientFrontier(mu, S, weight_bounds=(None, None))

View File

@ -6,12 +6,12 @@ Post-processing weights
After optimal weights have been generated, it is often necessary to do some
post-processing before they can be used practically. In particular, you are
likely using portfolio optimisation techniques to generate a
likely using portfolio optimization techniques to generate a
**portfolio allocation** a list of tickers and corresponding integer quantities
that you could go and purchase at a broker.
However, it is not trivial to convert the continuous weights (output by any of our
optimisation methods) into an actionable allocation. For example, let us say that we
optimization methods) into an actionable allocation. For example, let us say that we
have $10,000 that we would like to allocate. If we multiply the weights by this total
portfolio value, the result will be dollar amounts of each asset. So if the optimal weight
for Apple is 0.15, we need $1500 worth of Apple stock. However, Apple shares come
@ -74,7 +74,7 @@ closest to our desired weights. We will use the following notation:
- :math:`x \in \mathbb{Z}^n` is the integer allocation (i.e the result)
- :math:`r \in \mathbb{R}` is the remaining unallocated value, i.e :math:`r = T - x \cdot p`.
The optimisation problem is then given by:
The optimization problem is then given by:
.. math::

View File

@ -4,7 +4,7 @@
Risk Models
###########
In addition to the expected returns, mean-variance optimisation requires a
In addition to the expected returns, mean-variance optimization requires a
**risk model**, some way of quantifying asset risk. The most commonly-used risk model
is the covariance matrix, which describes asset volatilities and their co-dependence. This is
important because one of the principles of diversification is that risk can be
@ -63,7 +63,7 @@ covariance.
variances). Although the sample covariance matrix is an unbiased estimator of the
covariance matrix, i.e :math:`E(S) = \Sigma`, in practice it suffers from
misspecification error and a lack of robustness. This is particularly problematic
in mean-variance optimisation, because the optimiser may give extra credence to
in mean-variance optimization, because the optimizer may give extra credence to
the erroneous values.
.. note::

View File

@ -18,13 +18,13 @@ discuss. If you have any other feature requests, please raise them using GitHub
- Risk parity
- Optimising for higher moments (i.e skew and kurtosis)
- Factor modelling - this is conceptually doable, but a lot of thought needs to be put into the API.
- Monte Carlo optimisation with custom distributions
- Monte Carlo optimization with custom distributions
- Further support for different risk/return models
1.4.0
=====
- Finally implemented CVaR optimisation! This has been one of the most requested features. Many thanks
- Finally implemented CVaR optimization! This has been one of the most requested features. Many thanks
to `Nicolas Knudde <https://github.com/nknudde>`_ for the initial draft.
- Re-architected plotting so users can pass an ax, allowing for complex plots (see cookbook).
- Helper method to compute the max-return portfolio (thanks to `Philipp Schiele <https://github.com/phschiele>`_)
@ -145,7 +145,7 @@ Matplotlib now required dependency; support for pandas 1.0.
- Replaced ``BaseScipyOptimizer`` with ``BaseConvexOptimizer``
- ``hierarchical_risk_parity`` was replaced by ``hierarchical_portfolios`` to leave the door open for other hierarchical methods.
- Sadly, removed CVaR optimisation for the time being until I can properly fix it.
- Sadly, removed CVaR optimization for the time being until I can properly fix it.
1.0.1
-----
@ -156,7 +156,7 @@ Fixed minor issues in CLA: weight bound bug, ``efficient_frontier`` needed weigh
-----
Fixed small but important bug where passing ``expected_returns=None`` fails. According to the docs, users
should be able to only pass covariance if they want to only optimise min volatility.
should be able to only pass covariance if they want to only optimize min volatility.
0.5.0
@ -166,7 +166,7 @@ should be able to only pass covariance if they want to only optimise min volatil
- Custom bounds per asset
- Improved ``BaseOptimizer``, adding a method that writes weights
to text and fixing a bug in ``set_weights``.
- Unconstrained quadratic utility optimisation (analytic)
- Unconstrained quadratic utility optimization (analytic)
- Revamped docs, with information on types of attributes and
more examples.
@ -184,9 +184,9 @@ experience.
0.5.3
-----
- Fixed an optimisation bug in ``EfficientFrontier.efficient_risk``. An error is now
thrown if optimisation fails.
- Added a hidden API to change the scipy optimiser method.
- Fixed an optimization bug in ``EfficientFrontier.efficient_risk``. An error is now
thrown if optimization fails.
- Added a hidden API to change the scipy optimizer method.
0.5.4
-----
@ -209,7 +209,7 @@ Began migration to cvxpy by changing the discrete allocation backend from PuLP t
modified the linear programming method suggested by `Dingyuan Wang <https://github.com/gumblex>`_;
added postprocessing section to User Guide.
- Further refactoring and docs for ``HRPOpt``.
- Major documentation update, e.g to support custom optimisers
- Major documentation update, e.g to support custom optimizers
0.4.1
-----
@ -265,13 +265,13 @@ Refactored shrinkage models, including single factor and constant correlation.
0.2.0
=====
- Hierarchical Risk Parity optimisation
- Hierarchical Risk Parity optimization
- Semicovariance matrix
- Exponential covariance matrix
- CVaR optimisation
- CVaR optimization
- Better support for custom objective functions
- Multiple bug fixes (including minimum volatility vs minimum variance)
- Refactored so all optimisers inherit from a ``BaseOptimizer``.
- Refactored so all optimizers inherit from a ``BaseOptimizer``.
0.2.1
-----

View File

@ -7,14 +7,14 @@ User Guide
This is designed to be a practical guide, mostly aimed at users who are interested in a
quick way of optimally combining some assets (most likely stocks). However, when
necessary I do introduce the required theory and also point out areas that may be
suitable springboards for more advanced optimisation techniques. Details about the
suitable springboards for more advanced optimization techniques. Details about the
parameters can be found in the respective documentation pages (please see the sidebar).
For this guide, we will be focusing on mean-variance optimisation (MVO), which is what
most people think of when they hear "portfolio optimisation". MVO forms the core of
For this guide, we will be focusing on mean-variance optimization (MVO), which is what
most people think of when they hear "portfolio optimization". MVO forms the core of
PyPortfolioOpt's offering, though it should be noted that MVO comes in many flavours,
which can have very different performance characteristics. Please refer to the sidebar
to get a feeling for the possibilities, as well as the other optimisation methods
to get a feeling for the possibilities, as well as the other optimization methods
offered. But for now, we will continue with the standard Efficient Frontier.
PyPortfolioOpt is designed with modularity in mind; the below flowchart sums up the
@ -26,7 +26,7 @@ current functionality and overall layout of PyPortfolioOpt.
Processing historical prices
============================
Mean-variance optimisation requires two things: the expected returns of the assets,
Mean-variance optimization requires two things: the expected returns of the assets,
and the covariance matrix (or more generally, a *risk model* quantifying asset risk).
PyPortfolioOpt provides methods for estimating both (located in
:py:mod:`expected_returns` and :py:mod:`risk_models` respectively), but also supports
@ -81,13 +81,13 @@ and ``S`` will be the estimated covariance matrix (part of it is shown below)::
Now that we have expected returns and a risk model, we are ready to move on to the
actual portfolio optimisation.
actual portfolio optimization.
Mean-variance optimisation
Mean-variance optimization
===============================
Mean-variance optimisation is based on Harry Markowitz's 1952 classic paper [1]_, which
Mean-variance optimization is based on Harry Markowitz's 1952 classic paper [1]_, which
spearheaded the transformation of portfolio management from an art into a science. The key insight is that by
combining assets with different expected returns and volatilities, one can decide on a
mathematically optimal allocation.
@ -95,10 +95,10 @@ mathematically optimal allocation.
If :math:`w` is the weight vector of stocks with expected returns :math:`\mu`, then the
portfolio return is equal to each stock's weight multiplied by its return, i.e
:math:`w^T \mu`. The portfolio risk in terms of the covariance matrix :math:`\Sigma`
is given by :math:`w^T \Sigma w`. Portfolio optimisation can then be regarded as a
convex optimisation problem, and a solution can be found using quadratic programming.
is given by :math:`w^T \Sigma w`. Portfolio optimization can then be regarded as a
convex optimization problem, and a solution can be found using quadratic programming.
If we denote the target return as :math:`\mu^*`, the precise statement of the long-only
portfolio optimisation problem is as follows:
portfolio optimization problem is as follows:
.. math::
@ -122,7 +122,7 @@ portfolio) the set of all these optimal portfolios is referred to as the
Each dot on this diagram represents a different possible portfolio, with darker blue
corresponding to 'better' portfolios (in terms of the Sharpe Ratio). The dotted
black line is the efficient frontier itself. The triangular markers represent the
best portfolios for different optimisation objectives.
best portfolios for different optimization objectives.
The Sharpe ratio is the portfolio's return in excess of the risk-free rate, per unit risk
(volatility).
@ -143,7 +143,7 @@ dataframe ``S`` from before::
weights = ef.max_sharpe()
If you print these weights, you will get quite an ugly result, because they will
be the raw output from the optimiser. As such, it is recommended that you use
be the raw output from the optimizer. As such, it is recommended that you use
the :py:meth:`clean_weights` method, which truncates tiny weights to zero
and rounds the rest::
@ -186,7 +186,7 @@ weights ``w``, we can use the :py:meth:`portfolio_performance` method::
Annual volatility: 21.7%
Sharpe Ratio: 1.43
A detailed discussion of optimisation parameters is presented in
A detailed discussion of optimization parameters is presented in
:ref:`efficient-frontier`. However, there are two main variations which
are discussed below.
@ -201,7 +201,7 @@ with bounds that allow negative weights, for example::
This can be extended to generate **market neutral portfolios** (with weights
summing to zero), but these are only available for the :py:meth:`efficient_risk`
and :py:meth:`efficient_return` optimisation methods for mathematical reasons.
and :py:meth:`efficient_return` optimization methods for mathematical reasons.
If you want a market neutral portfolio, pass ``market_neutral=True`` as shown below::
ef.efficient_return(target_return=0.2, market_neutral=True)
@ -209,13 +209,13 @@ If you want a market neutral portfolio, pass ``market_neutral=True`` as shown be
Dealing with many negligible weights
------------------------------------
From experience, I have found that mean-variance optimisation often sets many
From experience, I have found that mean-variance optimization often sets many
of the asset weights to be zero. This may not be ideal if you need to have a certain
number of positions in your portfolio, for diversification purposes or otherwise.
To combat this, I have introduced an objective function which borrows the idea of
regularisation from machine learning. Essentially, by adding an additional cost
function to the objective, you can 'encourage' the optimiser to choose different
function to the objective, you can 'encourage' the optimizer to choose different
weights (mathematical details are provided in the :ref:`L2-Regularisation` section).
To use this feature, change the ``gamma`` parameter::
@ -284,8 +284,8 @@ Improving performance
Let's say you have conducted backtests and the results aren't spectacular. What
should you try?
- Try the Hierarchical Risk Parity model (see :ref:`other-optimisers`) which seems
to robustly outperform mean-variance optimisation out of sample.
- Try the Hierarchical Risk Parity model (see :ref:`other-optimizers`) which seems
to robustly outperform mean-variance optimization out of sample.
- Use the Black-Litterman model to construct a more stable model of expected returns.
Alternatively, just drop the expected returns altogether! There is a large body of research
that suggests that minimum variance portfolios (``ef.min_volatility()``) consistently outperform
@ -299,7 +299,7 @@ in the sidebar to learn more about the parameters and theoretical details of the
different models offered by PyPortfolioOpt. If you have any questions, please
raise an issue on GitHub and I will try to respond promptly.
If you'd like even more examples, check out the cookbook `recipe <https://github.com/robertmartin8/PyPortfolioOpt/blob/master/cookbook/2-Mean-Variance-Optimisation.ipynb>`_.
If you'd like even more examples, check out the cookbook `recipe <https://github.com/robertmartin8/PyPortfolioOpt/blob/master/cookbook/2-Mean-Variance-Optimization.ipynb>`_.
References

View File

@ -6,7 +6,7 @@
.. raw:: html
<meta prefix="og: http://ogp.me/ns#" property="og:title" content="PyPortfolioOpt" />
<meta prefix="og: http://ogp.me/ns#" property="og:description" content="Portfolio optimisation in python" />
<meta prefix="og: http://ogp.me/ns#" property="og:description" content="Portfolio optimization in python" />
<meta prefix="og: http://ogp.me/ns#" property="og:image" content="https://github.com/robertmartin8/PyPortfolioOpt/blob/master/media/logo_v1.png"/>
<embed>
@ -27,7 +27,7 @@
</embed>
PyPortfolioOpt is a library that implements portfolio optimisation methods, including
PyPortfolioOpt is a library that implements portfolio optimization methods, including
classical efficient frontier techniques and Black-Litterman allocation, as well as more
recent developments in the field like shrinkage and Hierarchical Risk Parity, along with
some novel experimental features like exponentially-weighted covariance matrices.
@ -133,7 +133,7 @@ that's fine too::
mu = expected_returns.mean_historical_return(df)
S = risk_models.sample_cov(df)
# Optimise for maximal Sharpe ratio
# Optimize for maximal Sharpe ratio
ef = EfficientFrontier(mu, S)
weights = ef.max_sharpe()
ef.portfolio_performance(verbose=True)
@ -159,7 +159,7 @@ Contents
MeanVariance
GeneralEfficientFrontier
BlackLitterman
OtherOptimisers
OtherOptimizers
Postprocessing
Plotting
@ -174,10 +174,10 @@ Contents
Project principles and design decisions
=======================================
- It should be easy to swap out individual components of the optimisation process
- It should be easy to swap out individual components of the optimization process
with the user's proprietary improvements.
- Usability is everything: it is better to be self-explanatory than consistent.
- There is no point in portfolio optimisation unless it can be practically
- There is no point in portfolio optimization unless it can be practically
applied to real asset prices.
- Everything that has been implemented should be tested.
- Inline documentation is good: dedicated (separate) documentation is better.

View File

@ -1,7 +1,7 @@
"""
The ``base_optimizer`` module houses the parent classes ``BaseOptimizer`` from which all
optimisers will inherit. ``BaseConvexOptimizer`` is the base class for all ``cvxpy`` (and ``scipy``)
optimisation.
optimizers will inherit. ``BaseConvexOptimizer`` is the base class for all ``cvxpy`` (and ``scipy``)
optimization.
Additionally, we define a general utility function ``portfolio_performance`` to
evaluate return and risk for a given set of portfolio weights.
@ -119,7 +119,7 @@ class BaseConvexOptimizer(BaseOptimizer):
"""
The BaseConvexOptimizer contains many private variables for use by
``cvxpy``. For example, the immutable optimisation variable for weights
``cvxpy``. For example, the immutable optimization variable for weights
is stored as self._w. Interacting directly with these variables directly
is discouraged.
@ -134,8 +134,8 @@ class BaseConvexOptimizer(BaseOptimizer):
Public methods:
- ``add_objective()`` adds a (convex) objective to the optimisation problem
- ``add_constraint()`` adds a constraint to the optimisation problem
- ``add_objective()`` adds a (convex) objective to the optimization problem
- ``add_constraint()`` adds a constraint to the optimization problem
- ``convex_objective()`` solves for a generic convex objective with linear constraints
- ``nonconvex_objective()`` solves for a generic nonconvex objective using the scipy backend.
This is prone to getting stuck in local minima and is generally *not* recommended.
@ -167,7 +167,7 @@ class BaseConvexOptimizer(BaseOptimizer):
"""
super().__init__(n_assets, tickers)
# Optimisation variables
# Optimization variables
self._w = cp.Variable(n_assets)
self._objective = None
self._additional_objectives = []
@ -266,7 +266,7 @@ class BaseConvexOptimizer(BaseOptimizer):
def add_constraint(self, new_constraint):
"""
Add a new constraint to the optimisation problem. This constraint must satisfy DCP rules,
Add a new constraint to the optimization problem. This constraint must satisfy DCP rules,
i.e be either a linear equality constraint or convex inequality constraint.
Examples::
@ -323,12 +323,12 @@ class BaseConvexOptimizer(BaseOptimizer):
def convex_objective(self, custom_objective, weights_sum_to_one=True, **kwargs):
"""
Optimise a custom convex objective function. Constraints should be added with
``ef.add_constraint()``. Optimiser arguments must be passed as keyword-args. Example::
Optimize a custom convex objective function. Constraints should be added with
``ef.add_constraint()``. Optimizer arguments must be passed as keyword-args. Example::
# Could define as a lambda function instead
def logarithmic_barrier(w, cov_matrix, k=0.1):
# 60 Years of Portfolio Optimisation, Kolm et al (2014)
# 60 Years of Portfolio Optimization, Kolm et al (2014)
return cp.quad_form(w, cov_matrix) - k * cp.sum(cp.log(w))
w = ef.convex_objective(logarithmic_barrier, cov_matrix=ef.cov_matrix)
@ -363,7 +363,7 @@ class BaseConvexOptimizer(BaseOptimizer):
initial_guess=None,
):
"""
Optimise some objective function using the scipy backend. This can
Optimize some objective function using the scipy backend. This can
support nonconvex objectives and nonlinear constraints, but may get stuck
at local minima. Example::
@ -392,11 +392,11 @@ class BaseConvexOptimizer(BaseOptimizer):
:param constraints: list of constraints in the scipy format (i.e dicts)
:type constraints: dict list
:param solver: which SCIPY solver to use, e.g "SLSQP", "COBYLA", "BFGS".
User beware: different optimisers require different inputs.
User beware: different optimizers require different inputs.
:type solver: string
:param initial_guess: the initial guess for the weights, shape (n,) or (n, 1)
:type initial_guess: np.ndarray
:return: asset weights that optimise the custom objective
:return: asset weights that optimize the custom objective
:rtype: OrderedDict
"""
# Sanitise inputs

View File

@ -176,7 +176,7 @@ class BlackLittermanModel(base_optimizer.BaseOptimizer):
# Keep raw dataframes
self._raw_cov_matrix = cov_matrix
#  Initialise base optimiser
#  Initialise base optimizer
if isinstance(cov_matrix, np.ndarray):
self.cov_matrix = cov_matrix
super().__init__(len(cov_matrix), list(range(len(cov_matrix))))
@ -430,7 +430,7 @@ class BlackLittermanModel(base_optimizer.BaseOptimizer):
Compute the weights implied by the posterior returns, given the
market price of risk. Technically this can be applied to any
estimate of the expected returns, and is in fact a special case
of mean-variance optimisation
of mean-variance optimization
.. math::

View File

@ -24,7 +24,7 @@ class CLA(base_optimizer.BaseOptimizer):
- ``lb`` - np.ndarray
- ``ub`` - np.ndarray
- Optimisation parameters:
- Optimization parameters:
- ``w`` - np.ndarray list
- ``ls`` - float list
@ -38,11 +38,11 @@ class CLA(base_optimizer.BaseOptimizer):
Public methods:
- ``max_sharpe()`` optimises for maximal Sharpe ratio (a.k.a the tangency portfolio)
- ``min_volatility()`` optimises for minimum volatility
- ``max_sharpe()`` optimizes for maximal Sharpe ratio (a.k.a the tangency portfolio)
- ``min_volatility()`` optimizes for minimum volatility
- ``efficient_frontier()`` computes the entire efficient frontier
- ``portfolio_performance()`` calculates the expected return, volatility and Sharpe ratio for
the optimised portfolio.
the optimized portfolio.
- ``clean_weights()`` rounds the weights and clips near-zeros.
- ``save_weights_to_file()`` saves the weights to csv, json, or txt.
"""

View File

@ -13,7 +13,7 @@ from .efficient_frontier import EfficientFrontier
class EfficientCVaR(EfficientFrontier):
"""
The EfficientCVaR class allows for optimisation along the mean-CVaR frontier, using the
The EfficientCVaR class allows for optimization along the mean-CVaR frontier, using the
formulation of Rockafellar and Ursayev (2001).
Instance variables:
@ -36,8 +36,8 @@ class EfficientCVaR(EfficientFrontier):
- ``min_cvar()`` minimises the CVaR
- ``efficient_risk()`` maximises return for a given CVaR
- ``efficient_return()`` minimises CVaR for a given target return
- ``add_objective()`` adds a (convex) objective to the optimisation problem
- ``add_constraint()`` adds a constraint to the optimisation problem
- ``add_objective()`` adds a (convex) objective to the optimization problem
- ``add_constraint()`` adds a constraint to the optimization problem
- ``portfolio_performance()`` calculates the expected return and CVaR of the portfolio
- ``set_weights()`` creates self.weights (np.ndarray) from a weights dict

View File

@ -15,7 +15,7 @@ class EfficientFrontier(base_optimizer.BaseConvexOptimizer):
"""
An EfficientFrontier object (inheriting from BaseConvexOptimizer) contains multiple
optimisation methods that can be called (corresponding to different objective
optimization methods that can be called (corresponding to different objective
functions) with various parameters. Note: a new EfficientFrontier object should
be instantiated if you want to make any change to objectives/constraints/bounds/parameters.
@ -35,18 +35,18 @@ class EfficientFrontier(base_optimizer.BaseConvexOptimizer):
Public methods:
- ``min_volatility()`` optimises for minimum volatility
- ``max_sharpe()`` optimises for maximal Sharpe ratio (a.k.a the tangency portfolio)
- ``min_volatility()`` optimizes for minimum volatility
- ``max_sharpe()`` optimizes for maximal Sharpe ratio (a.k.a the tangency portfolio)
- ``max_quadratic_utility()`` maximises the quadratic utility, given some risk aversion.
- ``efficient_risk()`` maximises return for a given target risk
- ``efficient_return()`` minimises risk for a given target return
- ``add_objective()`` adds a (convex) objective to the optimisation problem
- ``add_constraint()`` adds a constraint to the optimisation problem
- ``add_objective()`` adds a (convex) objective to the optimization problem
- ``add_constraint()`` adds a constraint to the optimization problem
- ``convex_objective()`` solves for a generic convex objective with linear constraints
- ``portfolio_performance()`` calculates the expected return, volatility and Sharpe ratio for
the optimised portfolio.
the optimized portfolio.
- ``set_weights()`` creates self.weights (np.ndarray) from a weights dict
- ``clean_weights()`` rounds the weights and clips near-zeros.
- ``save_weights_to_file()`` saves the weights to csv, json, or txt.
@ -66,7 +66,7 @@ class EfficientFrontier(base_optimizer.BaseConvexOptimizer):
optimising for volatility only (but not recommended).
:type expected_returns: pd.Series, list, np.ndarray
:param cov_matrix: covariance of returns for each asset. This **must** be
positive semidefinite, otherwise optimisation will fail.
positive semidefinite, otherwise optimization will fail.
:type cov_matrix: pd.DataFrame or np.array
:param weight_bounds: minimum and maximum weight of each asset OR single min/max pair
if all identical, defaults to (0, 1). Must be changed to (-1, 1)
@ -195,7 +195,7 @@ class EfficientFrontier(base_optimizer.BaseConvexOptimizer):
def _max_return(self, return_value=True):
"""
Helper method to maximise return. This should not be used to optimise a portfolio.
Helper method to maximise return. This should not be used to optimize a portfolio.
:return: asset weights for the return-minimising portfolio
:rtype: OrderedDict
@ -221,7 +221,7 @@ class EfficientFrontier(base_optimizer.BaseConvexOptimizer):
Maximise the Sharpe Ratio. The result is also referred to as the tangency portfolio,
as it is the portfolio for which the capital market line is tangent to the efficient frontier.
This is a convex optimisation problem after making a certain variable substitution. See
This is a convex optimization problem after making a certain variable substitution. See
`Cornuejols and Tutuncu (2006) <http://web.math.ku.dk/~rolf/CT_FinOpt.pdf>`_ for more.
:param risk_free_rate: risk-free rate of borrowing/lending, defaults to 0.02.
@ -245,7 +245,7 @@ class EfficientFrontier(base_optimizer.BaseConvexOptimizer):
# between how these objectives work for max_sharpe vs min_volatility
if len(self._additional_objectives) > 0:
warnings.warn(
"max_sharpe transforms the optimisation problem so additional objectives may not work as expected."
"max_sharpe transforms the optimization problem so additional objectives may not work as expected."
)
for obj in self._additional_objectives:
self._objective += obj

View File

@ -13,7 +13,7 @@ from .efficient_frontier import EfficientFrontier
class EfficientSemivariance(EfficientFrontier):
"""
EfficientSemivariance objects allow for optimisation along the mean-semivariance frontier.
EfficientSemivariance objects allow for optimization along the mean-semivariance frontier.
This may be relevant for users who are more concerned about downside deviation.
Instance variables:
@ -37,12 +37,12 @@ class EfficientSemivariance(EfficientFrontier):
- ``max_quadratic_utility()`` maximises the "downside quadratic utility", given some risk aversion.
- ``efficient_risk()`` maximises return for a given target semideviation
- ``efficient_return()`` minimises semideviation for a given target return
- ``add_objective()`` adds a (convex) objective to the optimisation problem
- ``add_constraint()`` adds a constraint to the optimisation problem
- ``add_objective()`` adds a (convex) objective to the optimization problem
- ``add_constraint()`` adds a constraint to the optimization problem
- ``convex_objective()`` solves for a generic convex objective with linear constraints
- ``portfolio_performance()`` calculates the expected return, semideviation and Sortino ratio for
the optimised portfolio.
the optimized portfolio.
- ``set_weights()`` creates self.weights (np.ndarray) from a weights dict
- ``clean_weights()`` rounds the weights and clips near-zeros.
- ``save_weights_to_file()`` saves the weights to csv, json, or txt.

View File

@ -1,6 +1,6 @@
"""
The ``expected_returns`` module provides functions for estimating the expected returns of
the assets, which is a required input in mean-variance optimisation.
the assets, which is a required input in mean-variance optimization.
By convention, the output of these methods is expected *annual* returns. It is assumed that
*daily* prices are provided, though in reality the functions are agnostic

View File

@ -1,6 +1,6 @@
"""
The ``hierarchical_portfolio`` module seeks to implement one of the recent advances in
portfolio optimisation the application of hierarchical clustering models in allocation.
portfolio optimization the application of hierarchical clustering models in allocation.
All of the hierarchical classes have a similar API to ``EfficientFrontier``, though since
many hierarchical models currently don't support different objectives, the actual allocation
@ -43,7 +43,7 @@ class HRPOpt(base_optimizer.BaseOptimizer):
- ``optimize()`` calculates weights using HRP
- ``portfolio_performance()`` calculates the expected return, volatility and Sharpe ratio for
the optimised portfolio.
the optimized portfolio.
- ``set_weights()`` creates self.weights (np.ndarray) from a weights dict
- ``clean_weights()`` rounds the weights and clips near-zeros.
- ``save_weights_to_file()`` saves the weights to csv, json, or txt.
@ -126,7 +126,7 @@ class HRPOpt(base_optimizer.BaseOptimizer):
for j, k in ((0, len(i) // 2), (len(i) // 2, len(i)))
if len(i) > 1
] # bi-section
# For each pair, optimise locally.
# For each pair, optimize locally.
for i in range(0, len(cluster_items), 2):
first_cluster = cluster_items[i]
second_cluster = cluster_items[i + 1]

View File

@ -1,14 +1,14 @@
"""
The ``objective_functions`` module provides optimisation objectives, including the actual
objective functions called by the ``EfficientFrontier`` object's optimisation methods.
These methods are primarily designed for internal use during optimisation and each requires
The ``objective_functions`` module provides optimization objectives, including the actual
objective functions called by the ``EfficientFrontier`` object's optimization methods.
These methods are primarily designed for internal use during optimization and each requires
a different signature (which is why they have not been factored into a class).
For obvious reasons, any objective function must accept ``weights``
as an argument, and must also have at least one of ``expected_returns`` or ``cov_matrix``.
The objective functions either compute the objective given a numpy array of weights, or they
return a cvxpy *expression* when weights are a ``cp.Variable``. In this way, the same objective
function can be used both internally for optimisation and externally for computing the objective
function can be used both internally for optimization and externally for computing the objective
given weights. ``_objective_value()`` automatically chooses between the two behaviours.
``objective_functions`` defaults to objectives for minimisation. In the cases of objectives

View File

@ -213,7 +213,7 @@ def plot_efficient_frontier(
"""
Plot the efficient frontier based on either a CLA or EfficientFrontier object.
:param opt: an instantiated optimiser object BEFORE optimising an objective
:param opt: an instantiated optimizer object BEFORE optimising an objective
:type opt: EfficientFrontier or CLA
:param ef_param: [EfficientFrontier] whether to use a range over utility, risk, or return.
Defaults to "return".
@ -257,7 +257,7 @@ def plot_weights(weights, ax=None, **kwargs):
"""
Plot the portfolio weights as a horizontal bar chart
:param weights: the weights outputted by any PyPortfolioOpt optimiser
:param weights: the weights outputted by any PyPortfolioOpt optimizer
:type weights: {ticker: weight} dict
:param ax: ax to plot to, optional
:type ax: matplotlib.axes

View File

@ -1,7 +1,7 @@
[tool.poetry]
name = "PyPortfolioOpt"
version = "1.4.0"
description = "Financial portfolio optimisation in python"
description = "Financial portfolio optimization in python"
license = "MIT"
authors = ["Robert Andrew Martin <martin.robertandrew@gmail.com>"]
readme = "README.md"

View File

@ -10,7 +10,7 @@ with open("README.md", "r") as f:
setup(
name="PyPortfolioOpt",
version="1.4.0",
description="Financial portfolio optimisation in python",
description="Financial portfolio optimization in python",
long_description=desc,
long_description_content_type="text/markdown",
url="https://github.com/robertmartin8/PyPortfolioOpt",

View File

@ -93,7 +93,7 @@ def test_bound_input_types():
def test_bound_failure():
# Ensure optimisation fails when lower bound is too high or upper bound is too low
# Ensure optimization fails when lower bound is too high or upper bound is too low
ef = EfficientFrontier(
*setup_efficient_frontier(data_only=True), weight_bounds=(0.06, 0.13)
)

View File

@ -70,7 +70,7 @@ def test_custom_convex_objective_market_neutral_efficient_risk():
ef.efficient_risk(target_risk, market_neutral=True)
built_in = ef.weights
# Recreate the market-neutral efficient_risk optimiser using this API
# Recreate the market-neutral efficient_risk optimizer using this API
ef = EfficientFrontier(
*setup_efficient_frontier(data_only=True), weight_bounds=(-1, 1)
)
@ -124,7 +124,7 @@ def test_custom_tracking_error():
def test_custom_convex_logarithmic_barrier():
# 60 Years of Portfolio Optimisation, Kolm et al (2014)
# 60 Years of Portfolio Optimization, Kolm et al (2014)
ef = setup_efficient_frontier()
def logarithmic_barrier(w, cov_matrix, k=0.1):
@ -144,7 +144,7 @@ def test_custom_convex_logarithmic_barrier():
def test_custom_convex_deviation_risk_parity_error():
# 60 Years of Portfolio Optimisation, Kolm et al (2014)
# 60 Years of Portfolio Optimization, Kolm et al (2014)
ef = setup_efficient_frontier()
def deviation_risk_parity(w, cov_matrix):
@ -195,7 +195,7 @@ def test_custom_nonconvex_min_var():
def test_custom_nonconvex_logarithmic_barrier():
# 60 Years of Portfolio Optimisation, Kolm et al (2014)
# 60 Years of Portfolio Optimization, Kolm et al (2014)
ef = setup_efficient_frontier()
def logarithmic_barrier(weights, cov_matrix, k=0.1):
@ -210,7 +210,7 @@ def test_custom_nonconvex_logarithmic_barrier():
def test_custom_nonconvex_deviation_risk_parity_1():
# 60 Years of Portfolio Optimisation, Kolm et al (2014) - first definition
# 60 Years of Portfolio Optimization, Kolm et al (2014) - first definition
ef = setup_efficient_frontier()
def deviation_risk_parity(w, cov_matrix):
@ -224,7 +224,7 @@ def test_custom_nonconvex_deviation_risk_parity_1():
def test_custom_nonconvex_deviation_risk_parity_2():
# 60 Years of Portfolio Optimisation, Kolm et al (2014) - second definition
# 60 Years of Portfolio Optimization, Kolm et al (2014) - second definition
ef = setup_efficient_frontier()
def deviation_risk_parity(w, cov_matrix):
@ -314,7 +314,7 @@ def test_custom_nonconvex_utility_objective():
def test_custom_nonconvex_objective_market_neutral_efficient_risk():
# Recreate the market-neutral efficient_risk optimiser using this API
# Recreate the market-neutral efficient_risk optimizer using this API
target_risk = 0.19
ef = EfficientFrontier(
*setup_efficient_frontier(data_only=True), weight_bounds=(-1, 1)

View File

@ -31,9 +31,7 @@ def test_hrp_portfolio():
# pd.Series(w).to_csv(resource("weights_hrp.csv"))
x = pd.read_csv(resource("weights_hrp.csv"), squeeze=True, index_col=0)
pd.testing.assert_series_equal(
x, pd.Series(w), check_names=False, check_less_precise=True
)
pd.testing.assert_series_equal(x, pd.Series(w), check_names=False, rtol=1e-2)
assert isinstance(w, dict)
assert set(w.keys()) == set(df.columns)

View File

@ -77,9 +77,9 @@ def test_sample_cov_npd():
np.testing.assert_equal(S2_df.to_numpy(), S2)
assert S2_df.index.equals(S_df.index)
assert S2_df.columns.equals(S_df.columns)
with pytest.raises(NotImplementedError):
risk_models.fix_nonpositive_semidefinite(S, fix_method="blah")
with pytest.warns(UserWarning):
with pytest.raises(NotImplementedError):
risk_models.fix_nonpositive_semidefinite(S, fix_method="blah")
def test_fix_npd_different_method():

View File

@ -119,7 +119,7 @@ def simple_ef_weights(expected_returns, cov_matrix, target_return, weights_sum):
:type cov_matrix: np.ndarray
:param target_return: the target return for the portfolio to achieve.
:type target_return: float
:param weights_sum: the sum of the returned weights, optimisation constraint.
:param weights_sum: the sum of the returned weights, optimization constraint.
:type weights_sum: float
:return: weight for each asset, which sum to 1.0
:rtype: np.ndarray