Skip to content

Commit 7a17a3d

Browse files
Merge pull request #279 from robertmartin8/v1.4.0
v1.4.0
2 parents aae7153 + 4fe281a commit 7a17a3d

27 files changed

+1432
-341
lines changed

.github/workflows/main.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,12 @@ jobs:
1010
runs-on: ubuntu-latest
1111
strategy:
1212
matrix:
13+
os: [ubuntu-latest]
1314
python-version: [3.6, 3.7, 3.8]
15+
include:
16+
- os: [windows-latest, macos-latest]
17+
python-version: 3.8
18+
1419
steps:
1520
- uses: actions/checkout@v2
1621
- name: Set up Python ${{ matrix.python-version }}

Dockerfile

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
FROM python:3.7.7-slim-stretch as builder
22

3-
# File Author / Maintainer
4-
# MAINTAINER
5-
63
# this will be user root regardless whether home/beakerx is not
74
COPY . /tmp/pypfopt
85

README.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<img src="https://img.shields.io/badge/python-v3-brightgreen.svg"
99
alt="python"></a> &nbsp;
1010
<a href="https://pypi.org/project/PyPortfolioOpt/">
11-
<img src="https://img.shields.io/badge/pypi-v1.3.1-brightgreen.svg"
11+
<img src="https://img.shields.io/badge/pypi-v1.4.0-brightgreen.svg"
1212
alt="pypi"></a> &nbsp;
1313
<a href="https://opensource.org/licenses/MIT">
1414
<img src="https://img.shields.io/badge/license-MIT-brightgreen.svg"
@@ -68,6 +68,8 @@ Head over to the [documentation on ReadTheDocs](https://pyportfolioopt.readthedo
6868
If you would like to play with PyPortfolioOpt interactively in your browser, you may launch Binder [here](https://mybinder.org/v2/gh/robertmartin8/pyportfolioopt/master). It takes a
6969
while to set up, but it lets you try out the cookbook recipes without having to deal with all of the requirements.
7070

71+
*Note: macOS users will need to install [Command Line Tools](https://osxdaily.com/2014/02/12/install-command-line-tools-mac-os-x/).*
72+
7173
*Note: if you are on windows, you first need to installl C++. ([download](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=BuildTools&rel=16), [install instructions](https://drive.google.com/file/d/0B4GsMXCRaSSIOWpYQkstajlYZ0tPVkNQSElmTWh1dXFaYkJr/view))*
7274

7375
This project is available on PyPI, meaning that you can just:
@@ -395,9 +397,12 @@ Special shout-outs to:
395397
- Aditya Bhutra
396398
- Thomas Schmelzer
397399
- Rich Caputo
400+
- Nicolas Knudde
398401

399402
## Getting in touch
400403

401-
If you are having a problem with PyPortfolioOpt, please raise an issue.
404+
If you are having a problem with PyPortfolioOpt, please raise a GitHub issue. For anything else, you can reach me at:
402405

403-
For anything else, you can contact me via the [form](https://reasonabledeviations.com/about/) on my website.
406+
<center>
407+
<img src="https://github.com/robertmartin8/ReasonableDeviations/blob/gh-pages/assets/images/contact.png" style="width:75%;"/>
408+
</center>

docs/BlackLitterman.rst

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Black-Litterman Allocation
55
##########################
66

77
The Black-Litterman (BL) model [1]_ takes a Bayesian approach to asset allocation.
8-
Specifically, it combines a **prior** estimate of returns (canonically, the market-implied
8+
Specifically, it combines a **prior** estimate of returns (for example, the market-implied
99
returns) with **views** on certain assets, to produce a **posterior** estimate of expected
1010
returns. The advantages of this are:
1111

@@ -52,11 +52,10 @@ I'd like to thank `Felipe Schneider <https://github.com/schneiderfelipe>`_ for
5252
contributions to the Black-Litterman implementation. A full example of its usage, including the acquistion
5353
of market cap data for free, please refer to the `cookbook recipe <https://github.com/robertmartin8/PyPortfolioOpt/blob/master/cookbook/4-Black-Litterman-Allocation.ipynb>`_.
5454

55-
.. caution::
55+
.. tip::
5656

57-
Our implementation of Black-Litterman makes frequent use of the fact that python 3.6+ dictionaries
58-
remain ordered. It is still possible to use python 3.5 but you will have to construct the BL inputs
59-
explicitly (``Q``, ``P``, ``omega``).
57+
Thomas Kirschenmann has built a neat interactive `Black-Litterman tool <https://github.com/thk3421-models/cardiel>`_
58+
on top of PyPortfolioOpt, which allows you to visualise BL outputs and compare optimisation objectives.
6059

6160
Priors
6261
======

docs/EfficientFrontier.rst

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class of problems, which happen to be incredibly useful for finance. A convex pr
2020
2121
where :math:`\mathbf{x} \in \mathbb{R}^n`, and :math:`f(\mathbf{x}), g_i(\mathbf{x})` are convex functions. [1]_
2222

23-
Fortunately, portfolio optimisation problems (with standard and objective constraints) are convex. This
23+
Fortunately, portfolio optimisation problems (with standard objectives and constraints) are convex. This
2424
allows us to immediately apply the vast body of theory as well as the refined solving routines -- accordingly,
2525
the main difficulty is inputting our specific problem into a solver.
2626

@@ -239,7 +239,7 @@ frontier. Note that some of the parent methods, like :py:func:`max_sharpe` and
239239
are not applicable to mean-semivariance portfolios, so calling them returns an error.
240240

241241
:py:class:`EfficientSemivariance` has a slightly different API to :py:class:`EfficientFrontier`. Instead of passing
242-
in a covariance matrix, you should past in a dataframe of historical returns (this can be constructed
242+
in a covariance matrix, you should past in a dataframe of historical/simulated returns (this can be constructed
243243
from your price dataframe using the helper method :py:func:`expected_returns.returns_from_prices`). Here
244244
is a full example, in which we seek the portfolio that minimises the semivariance for a target
245245
annual return of 20%::
@@ -279,6 +279,59 @@ implementation is based on Markowitz et al (2019) [3]_.
279279
:members:
280280
:exclude-members: max_sharpe, min_volatility
281281

282+
Efficient CVaR
283+
==============
284+
285+
The **conditional value-at-risk** (a.k.a **expected shortfall**) is a popular measure of tail risk. The CVaR can be
286+
thought of as the average of losses that occur on "very bad days", where "very bad" is quantified by the parameter
287+
:math:`\beta`.
288+
289+
For example, if we calculate the CVaR to be 10% for :math:`\beta = 0.95`, we can be 95% confident that the worst-case
290+
average daily loss will be 3%. Put differently, the CVaR is the average of all losses so severe that they only occur
291+
:math:`(1-\beta)\%` of the time.
292+
293+
While CVaR is quite an intuitive concept, a lot of new notation is required to formulate it mathematically (see
294+
the `wiki page <https://en.wikipedia.org/wiki/Expected_shortfall>`_ for more details). We will adopt the following
295+
notation:
296+
297+
- *w* for the vector of portfolio weights
298+
- *r* for a vector of asset returns (daily), with probability distribution :math:`p(r)`.
299+
- :math:`L(w, r) = - w^T r` for the loss of the portfolio
300+
- :math:`\alpha` for the portfolio value-at-risk (VaR) with confidence :math:`\beta`.
301+
302+
The CVaR can then be written as:
303+
304+
.. math::
305+
CVaR(w, \beta) = \frac{1}{1-\beta} \int_{L(w, r) \geq \alpha (w)} L(w, r) p(r)dr.
306+
307+
This is a nasty expression to optimise because we are essentially integrating over VaR values. The key insight
308+
of Rockafellar and Uryasev (2001) [4]_ is that we can can equivalently optimise the following convex function:
309+
310+
.. math::
311+
F_\beta (w, \alpha) = \alpha + \frac{1}{1-\beta} \int [-w^T r - \alpha]^+ p(r) dr,
312+
313+
where :math:`[x]^+ = \max(x, 0)`. The authors prove that minimising :math:`F_\beta(w, \alpha)` over all
314+
:math:`w, \alpha` minimises the CVaR. Suppose we have a sample of *T* daily returns (these
315+
can either be historical or simulated). The integral in the expression becomes a sum, so the CVaR optimisation
316+
problem reduces to a linear program:
317+
318+
.. math::
319+
320+
\begin{equation*}
321+
\begin{aligned}
322+
& \underset{w, \alpha}{\text{minimise}} & & \alpha + \frac{1}{1-\beta} \frac 1 T \sum_{i=1}^T u_i \\
323+
& \text{subject to} & & u_i \geq 0 \\
324+
&&& u_i \geq -w^T r_i - \alpha. \\
325+
\end{aligned}
326+
\end{equation*}
327+
328+
This formulation introduces a new variable for each datapoint (similar to Efficient Semivariance), so
329+
you may run into performance issues for long returns dataframes. At the same time, you should aim to
330+
provide a sample of data that is large enough to include tail events.
331+
332+
.. autoclass:: pypfopt.efficient_frontier.EfficientCVaR
333+
:members:
334+
:exclude-members: max_sharpe, min_volatility, max_quadratic_utility
282335

283336

284337
.. _custom-optimisation:
@@ -295,7 +348,7 @@ The :py:class:`EfficientFrontier` class inherits from the ``BaseConvexOptimizer`
295348
define your own optimisation problem. You can either optimise some generic ``convex_objective``
296349
(which *must* be built using ``cvxpy`` atomic functions -- see `here <https://www.cvxpy.org/tutorial/functions/index.html>`_)
297350
or a ``nonconvex_objective``, which uses ``scipy.optimize`` as the backend and thus has a completely
298-
different API. For examples, check out this `cookbook recipe
351+
different API. For more examples, check out this `cookbook recipe
299352
<https://github.com/robertmartin8/PyPortfolioOpt/blob/master/cookbook/3-Advanced-Mean-Variance-Optimisation.ipynb>`_.
300353

301354
.. class:: pypfopt.base_optimizer.BaseConvexOptimizer
@@ -311,3 +364,4 @@ References
311364
.. [1] Boyd, S.; Vandenberghe, L. (2004). `Convex Optimization <https://web.stanford.edu/~boyd/cvxbook/>`_.
312365
.. [2] Estrada, J (2007). `Mean-Semivariance Optimization: A Heuristic Approach <https://papers.ssrn.com/sol3/papers.cfm?abstract_id=1028206>`_.
313366
.. [3] Markowitz, H.; Starer, D.; Fram, H.; Gerber, S. (2019). `Avoiding the Downside <https://www.hudsonbaycapital.com/documents/FG/hudsonbay/research/599440_paper.pdf>`_.
367+
.. [4] Rockafellar, R.; Uryasev, D. (2001). `Optimization of conditional value-at-risk <https://www.ise.ufl.edu/uryasev/files/2011/11/CVaR1_JOR.pdf>`_

docs/Plotting.rst

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,60 @@ and add constraints like you normally would, but *before* calling an optimisatio
2222
ef.add_constraint(lambda w: w[2] == 0.15)
2323
ef.add_constraint(lambda w: w[3] + w[4] <= 0.10)
2424

25-
# 100 portfolios with risks between 0.10 and 0.30
26-
risk_range = np.linspace(0.10, 0.40, 100)
27-
ax = plotting.plot_efficient_frontier(ef, ef_param="risk", ef_param_range=risk_range,
28-
show_assets=True, showfig=True)
25+
fig, ax = plt.subplots()
26+
plotting.plot_efficient_frontier(ef, ax=ax, show_assets=True)
27+
plt.show()
2928

30-
This produces the following plot -- you can set attributes using the returned ``ax`` object:
29+
This produces the following plot:
3130

3231
.. image:: ../media/ef_plot.png
3332
:width: 80%
3433
:align: center
3534
:alt: the Efficient Frontier
3635

36+
You can explicitly pass a range of parameters (risk, utility, or returns) to generate a frontier::
37+
38+
# 100 portfolios with risks between 0.10 and 0.30
39+
risk_range = np.linspace(0.10, 0.40, 100)
40+
plotting.plot_efficient_frontier(ef, ef_param="risk", ef_param_range=risk_range,
41+
show_assets=True, showfig=True)
42+
43+
44+
We can easily generate more complex plots. The following script plots both the efficient frontier and
45+
randomly generated (suboptimal) portfolios, coloured by the Sharpe ratio::
46+
47+
fig, ax = plt.subplots()
48+
plotting.plot_efficient_frontier(ef, ax=ax, show_assets=False)
49+
50+
# Find the tangency portfolio
51+
ef.max_sharpe()
52+
ret_tangent, std_tangent, _ = ef.portfolio_performance()
53+
ax.scatter(std_tangent, ret_tangent, marker="*", s=100, c="r", label="Max Sharpe")
54+
55+
# Generate random portfolios
56+
n_samples = 10000
57+
w = np.random.dirichlet(np.ones(len(mu)), n_samples)
58+
rets = w.dot(mu)
59+
stds = np.sqrt(np.diag(w @ S @ w.T))
60+
sharpes = rets / stds
61+
ax.scatter(stds, rets, marker=".", c=sharpes, cmap="viridis_r")
62+
63+
# Output
64+
ax.set_title("Efficient Frontier with random portfolios")
65+
ax.legend()
66+
plt.tight_layout()
67+
plt.savefig("ef_scatter.png", dpi=200)
68+
plt.show()
69+
70+
This is the result:
71+
72+
.. image:: ../media/ef_scatter.png
73+
:width: 80%
74+
:align: center
75+
:alt: the Efficient Frontier with random portfolios
76+
77+
Documentation reference
78+
=======================
3779

3880
.. automodule:: pypfopt.plotting
3981

docs/Roadmap.rst

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ are interested in implementing one of these, raise an issue or send me an email
1313
discuss. If you have any other feature requests, please raise them using GitHub
1414
`issues <https://github.com/robertmartin8/PyPortfolioOpt/issues>`_
1515

16-
- Improved plotting
1716
- Open-source backtests using either `Backtrader <https://www.backtrader.com/>`_ or
1817
`Zipline <https://github.com/quantopian/zipline>`_.
1918
- Optimising for higher moments (i.e skew and kurtosis)
@@ -22,6 +21,17 @@ discuss. If you have any other feature requests, please raise them using GitHub
2221
- Monte Carlo optimisation with custom distributions
2322
- Further support for different risk/return models
2423

24+
1.4.0
25+
=====
26+
27+
- Finally implemented CVaR optimisation! This has been one of the most requested features. Many thanks
28+
to `Nicolas Knudde <https://github.com/nknudde>`_ for the initial draft.
29+
- Re-architected plotting so users can pass an ax, allowing for complex plots (see cookbook).
30+
- Helper method to compute the max-return portfolio (thanks to `Philipp Schiele <https://github.com/phschiele>`_)
31+
for the suggestion).
32+
- Several bug fixes and test improvements (thanks to `Carl Peasnell <https://github.com/SeaPea1>`_).
33+
34+
2535
1.3.0
2636
=====
2737

docs/conf.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,9 @@
5656
# built documents.
5757
#
5858
# The short X.Y version.
59-
version = "1.3"
59+
version = "1.4"
6060
# The full version, including alpha/beta/rc tags.
61-
release = "1.3.1"
61+
release = "1.4.0"
6262

6363
# The language for content autogenerated by Sphinx. Refer to documentation
6464
# for a list of supported languages.

docs/index.rst

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
<img src="https://img.shields.io/badge/python-v3-brightgreen.svg"
1616
alt="python"></a> &nbsp;
1717
<a href="https://pypi.org/project/PyPortfolioOpt/">
18-
<img src="https://img.shields.io/badge/pypi-v1.3.1-brightgreen.svg"
18+
<img src="https://img.shields.io/badge/pypi-v1.4.0-brightgreen.svg"
1919
alt="python"></a> &nbsp;
2020
<a href="https://opensource.org/licenses/MIT">
2121
<img src="https://img.shields.io/badge/license-MIT-brightgreen.svg"
@@ -43,14 +43,16 @@ in a risk-efficient way.
4343
Installation
4444
============
4545

46-
Installation on macOS or linux is as simple as::
46+
Prior to installing PyPortfolioOpt, you need to install C++. On macOS, this means that you need
47+
to install XCode Command Line Tools (see `here <https://osxdaily.com/2014/02/12/install-command-line-tools-mac-os-x/>`__).
4748

48-
pip install PyPortfolioOpt
49-
50-
Windows users need to go through the additional step of downloading C++ (for ``cvxpy``). You can
51-
download this `here <https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=BuildTools&rel=16>`__,
49+
For Windows users, download Visual Studio `here <https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=BuildTools&rel=16>`__,
5250
with additional instructions `here <https://drive.google.com/file/d/0B4GsMXCRaSSIOWpYQkstajlYZ0tPVkNQSElmTWh1dXFaYkJr/view>`__.
5351

52+
Installation can then be done via pip::
53+
54+
pip install PyPortfolioOpt
55+
5456
For the sake of best practice, it is good to do this with a dependency manager. I suggest you
5557
set yourself up with `poetry <https://github.com/sdispater/poetry>`_, then within a new poetry project
5658
run:
@@ -73,23 +75,22 @@ Thanks to Thomas Schmelzer, PyPortfolioOpt now supports Docker (requires
7375

7476
.. note::
7577
If any of these methods don't work, please `raise an issue
76-
<https://github.com/robertmartin8/PyPortfolioOpt/issues>`_ on GitHub
77-
78+
<https://github.com/robertmartin8/PyPortfolioOpt/issues>`_ with the 'packaging' label on GitHub
7879

7980

8081

8182
For developers
8283
--------------
8384

8485
If you are planning on using PyPortfolioOpt as a starting template for significant
85-
modifications, it probably makes sense to clone this repository and to just use the
86+
modifications, it probably makes sense to clone the repository and to just use the
8687
source code
8788

8889
.. code-block:: text
8990
9091
git clone https://github.com/robertmartin8/PyPortfolioOpt
9192
92-
Alternatively, if you still want the convenience of ``from pypfopt import x``,
93+
Alternatively, if you still want the convenience of a global ``from pypfopt import x``,
9394
you should try
9495

9596
.. code-block:: text
@@ -158,25 +159,13 @@ Contents
158159
Plotting
159160

160161
.. toctree::
162+
:maxdepth: 1
161163
:caption: Other information
162164

163165
Roadmap
164166
Contributing
165167
About
166168

167-
Advantages over existing implementations
168-
========================================
169-
170-
- Includes both classical methods (Markowitz 1952 and Black-Litterman), suggested best practices
171-
(e.g covariance shrinkage), along with many recent developments and novel
172-
features, like L2 regularisation, shrunk covariance, hierarchical risk parity.
173-
- Native support for pandas dataframes: easily input your daily prices data.
174-
- Extensive practical tests, which use real-life data.
175-
- Easy to combine with your proprietary strategies and models.
176-
- Robust to missing data, and price-series of different lengths (e.g FB data
177-
only goes back to 2012 whereas AAPL data goes back to 1980).
178-
179-
180169
Project principles and design decisions
181170
=======================================
182171

@@ -193,10 +182,24 @@ Project principles and design decisions
193182
<https://github.com/ambv/black>`_.
194183

195184

185+
Advantages over existing implementations
186+
========================================
187+
188+
- Includes both classical methods (Markowitz 1952 and Black-Litterman), suggested best practices
189+
(e.g covariance shrinkage), along with many recent developments and novel
190+
features, like L2 regularisation, exponential covariance, hierarchical risk parity.
191+
- Native support for pandas dataframes: easily input your daily prices data.
192+
- Extensive practical tests, which use real-life data.
193+
- Easy to combine with your proprietary strategies and models.
194+
- Robust to missing data, and price-series of different lengths (e.g FB data
195+
only goes back to 2012 whereas AAPL data goes back to 1980).
196+
197+
196198
Contributors
197199
=============
198200

199-
This is a non-exhaustive unordered list of contributors:
201+
This is a non-exhaustive unordered list of contributors. I am sincerely grateful for all
202+
of your efforts!
200203

201204
- Philipp Schiele
202205
- Carl Peasnell
@@ -206,6 +209,7 @@ This is a non-exhaustive unordered list of contributors:
206209
- Aditya Bhutra
207210
- Thomas Schmelzer
208211
- Rich Caputo
212+
- Nicolas Knudde
209213

210214

211215
Indices and tables

media/ef_plot.png

22.9 KB
Loading

0 commit comments

Comments
 (0)