Skip to content

Commit 78dafb4

Browse files
Merge pull request #266 from robertmartin8/v1.3.1
v1.3.1
2 parents 8eb2a16 + 66ce4bd commit 78dafb4

File tree

14 files changed

+253
-73
lines changed

14 files changed

+253
-73
lines changed

README.md

Lines changed: 1 addition & 1 deletion
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.0-brightgreen.svg"
11+
<img src="https://img.shields.io/badge/pypi-v1.3.1-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"

docs/Plotting.rst

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,46 @@
44
Plotting
55
########
66

7+
All of the optimisation functions in :py:class:`EfficientFrontier` produce a single optimal portfolio.
8+
However, you may want to plot the entire efficient frontier. This efficient frontier can be thought
9+
of in several different ways:
710

8-
.. automodule:: pypfopt.plotting
11+
1. The set of all :py:func:`efficient_risk` portfolios for a range of target risks
12+
2. The set of all :py:func:`efficient_return` portfolios for a range of target returns
13+
3. The set of all :py:func:`max_quadratic_utility` portfolios for a range of risk aversions.
914

10-
.. autofunction:: _plot_io
15+
The :py:mod:`plotting` module provides support for all three of these approaches. To produce
16+
a plot of the efficient frontier, you should instantiate your :py:class:`EfficientFrontier` object
17+
and add constraints like you normally would, but *before* calling an optimisation function (e.g with
18+
:py:func:`ef.max_sharpe`), you should pass this the instantiated object into :py:func:`plot.plot_efficient_frontier`::
19+
20+
ef = EfficientFrontier(mu, S, weight_bounds=(None, None))
21+
ef.add_constraint(lambda w: w[0] >= 0.2)
22+
ef.add_constraint(lambda w: w[2] == 0.15)
23+
ef.add_constraint(lambda w: w[3] + w[4] <= 0.10)
24+
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)
29+
30+
This produces the following plot -- you can set attributes using the returned ``ax`` object:
31+
32+
.. image:: ../media/ef_plot.png
33+
:width: 80%
34+
:align: center
35+
:alt: the Efficient Frontier
36+
37+
38+
.. automodule:: pypfopt.plotting
1139

1240
.. tip::
1341

1442
To save the plot, pass ``filename="somefile.png"`` as a keyword argument to any of
15-
the methods below.
43+
the plotting functions. This (along with some other kwargs) get passed through
44+
:py:func:`_plot_io` before being returned.
45+
46+
.. autofunction:: _plot_io
1647

1748
.. autofunction:: plot_covariance
1849

docs/Roadmap.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,19 @@ discuss. If you have any other feature requests, please raise them using GitHub
2525
1.3.0
2626
=====
2727

28+
- Significantly improved plotting functionality: can now plot constrained efficient frontier!
2829
- Efficient semivariance portfolios (thanks to `Philipp Schiele <https://github.com/phschiele>`_)
2930
- Improved functionality for portfolios with short positions (thanks to `Rich Caputo <https://github.com/arcaputo3>`_).
3031
- Significant improvement in test coverage (thanks to `Carl Peasnell <https://github.com/SeaPea1>`_).
3132
- Several bug fixes and usability improvements.
3233
- Migrated from TravisCI to Github Actions.
3334

35+
1.3.1
36+
-----
37+
38+
- Minor cleanup (forgotten commits from v1.3.0).
39+
40+
3441
1.2.0
3542
=====
3643

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
# The short X.Y version.
5959
version = "1.3"
6060
# The full version, including alpha/beta/rc tags.
61-
release = "1.3.0"
61+
release = "1.3.1"
6262

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

docs/index.rst

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,16 @@
1212
<embed>
1313
<p align="center">
1414
<a href="https://www.python.org/">
15-
<img src="https://img.shields.io/badge/python-v3-brightgreen.svg?style=flat-square"
15+
<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.0-brightgreen.svg?style=flat-square"
18+
<img src="https://img.shields.io/badge/pypi-v1.3.1-brightgreen.svg"
1919
alt="python"></a> &nbsp;
2020
<a href="https://opensource.org/licenses/MIT">
21-
<img src="https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square"
21+
<img src="https://img.shields.io/badge/license-MIT-brightgreen.svg"
2222
alt="MIT license"></a> &nbsp;
2323
<a href="https://github.com/robertmartin8/PyPortfolioOpt/graphs/commit-activity">
24-
<img src="https://img.shields.io/badge/Maintained%3F-yes-brightgreen.svg?style=flat-square"
24+
<img src="https://img.shields.io/badge/Maintained%3F-yes-brightgreen.svg"
2525
alt="MIT license"></a> &nbsp;
2626
</p>
2727
</embed>

media/ef_plot.png

52.5 KB
Loading

poetry.lock

Lines changed: 30 additions & 30 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pypfopt/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from .risk_models import CovarianceShrinkage
1111

1212

13-
__version__ = "1.3.0"
13+
__version__ = "1.3.1"
1414

1515
__all__ = [
1616
"market_implied_prior_returns",

pypfopt/base_optimizer.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -120,16 +120,17 @@ class BaseConvexOptimizer(BaseOptimizer):
120120
"""
121121
The BaseConvexOptimizer contains many private variables for use by
122122
``cvxpy``. For example, the immutable optimisation variable for weights
123-
is stored as self._w. Interacting directly with these variables is highly
124-
discouraged.
123+
is stored as self._w. Interacting directly with these variables directly
124+
is discouraged.
125125
126126
Instance variables:
127127
128128
- ``n_assets`` - int
129129
- ``tickers`` - str list
130130
- ``weights`` - np.ndarray
131-
- ``solver`` - str
132-
- ``solver_options`` - {str: str} dict
131+
- ``_opt`` - cp.Problem
132+
- ``_solver`` - str
133+
- ``_solver_options`` - {str: str} dict
133134
134135
Public methods:
135136
@@ -175,6 +176,7 @@ def __init__(
175176
self._upper_bounds = None
176177
self._map_bounds_to_constraints(weight_bounds)
177178

179+
self._opt = None
178180
self._solver = solver
179181
self._verbose = verbose
180182
self._solver_options = solver_options if solver_options else {}
@@ -227,19 +229,21 @@ def _solve_cvxpy_opt_problem(self):
227229
:raises exceptions.OptimizationError: if problem is not solvable by cvxpy
228230
"""
229231
try:
230-
opt = cp.Problem(cp.Minimize(self._objective), self._constraints)
232+
self._opt = cp.Problem(cp.Minimize(self._objective), self._constraints)
231233

232234
if self._solver is not None:
233-
opt.solve(
235+
self._opt.solve(
234236
solver=self._solver, verbose=self._verbose, **self._solver_options
235237
)
236238
else:
237-
opt.solve(verbose=self._verbose, **self._solver_options)
239+
self._opt.solve(verbose=self._verbose, **self._solver_options)
238240
except (TypeError, cp.DCPError) as e:
239241
raise exceptions.OptimizationError from e
240242

241-
if opt.status not in {"optimal", "optimal_inaccurate"}:
242-
raise exceptions.OptimizationError("Solver status: {}".format(opt.status))
243+
if self._opt.status not in {"optimal", "optimal_inaccurate"}:
244+
raise exceptions.OptimizationError(
245+
"Solver status: {}".format(self._opt.status)
246+
)
243247
self.weights = self._w.value.round(16) + 0.0 # +0.0 removes signed zero
244248
return self._make_output_weights()
245249

0 commit comments

Comments
 (0)