redesigned plot API (can pass ax)
parent
6bb799d823
commit
3e2308ed27
|
@ -22,18 +22,60 @@ and add constraints like you normally would, but *before* calling an optimisatio
|
|||
ef.add_constraint(lambda w: w[2] == 0.15)
|
||||
ef.add_constraint(lambda w: w[3] + w[4] <= 0.10)
|
||||
|
||||
# 100 portfolios with risks between 0.10 and 0.30
|
||||
risk_range = np.linspace(0.10, 0.40, 100)
|
||||
ax = plotting.plot_efficient_frontier(ef, ef_param="risk", ef_param_range=risk_range,
|
||||
show_assets=True, showfig=True)
|
||||
fig, ax = plt.subplots()
|
||||
plotting.plot_efficient_frontier(ef, ax=ax, show_assets=True)
|
||||
plt.show()
|
||||
|
||||
This produces the following plot -- you can set attributes using the returned ``ax`` object:
|
||||
This produces the following plot:
|
||||
|
||||
.. image:: ../media/ef_plot.png
|
||||
:width: 80%
|
||||
:align: center
|
||||
:alt: the Efficient Frontier
|
||||
|
||||
You can explicitly pass a range of parameters (risk, utility, or returns) to generate a frontier::
|
||||
|
||||
# 100 portfolios with risks between 0.10 and 0.30
|
||||
risk_range = np.linspace(0.10, 0.40, 100)
|
||||
plotting.plot_efficient_frontier(ef, ef_param="risk", ef_param_range=risk_range,
|
||||
show_assets=True, showfig=True)
|
||||
|
||||
|
||||
We can easily generate more complex plots. The following script plots both the efficient frontier and
|
||||
randomly generated (suboptimal) portfolios, coloured by the Sharpe ratio::
|
||||
|
||||
fig, ax = plt.subplots()
|
||||
plotting.plot_efficient_frontier(ef, ax=ax, show_assets=False)
|
||||
|
||||
# Find the tangency portfolio
|
||||
ef.max_sharpe()
|
||||
ret_tangent, std_tangent, _ = ef.portfolio_performance()
|
||||
ax.scatter(std_tangent, ret_tangent, marker="*", s=100, c="r", label="Max Sharpe")
|
||||
|
||||
# Generate random portfolios
|
||||
n_samples = 10000
|
||||
w = np.random.dirichlet(np.ones(len(mu)), n_samples)
|
||||
rets = w.dot(mu)
|
||||
stds = np.sqrt(np.diag(w @ S @ w.T))
|
||||
sharpes = rets / stds
|
||||
ax.scatter(stds, rets, marker=".", c=sharpes, cmap="viridis_r")
|
||||
|
||||
# Output
|
||||
ax.set_title("Efficient Frontier with random portfolios")
|
||||
ax.legend()
|
||||
plt.tight_layout()
|
||||
plt.savefig("ef_scatter.png", dpi=200)
|
||||
plt.show()
|
||||
|
||||
This is the result:
|
||||
|
||||
.. image:: ../media/ef_scatter.png
|
||||
:width: 80%
|
||||
:align: center
|
||||
:alt: the Efficient Frontier with random portfolios
|
||||
|
||||
Documentation reference
|
||||
=======================
|
||||
|
||||
.. automodule:: pypfopt.plotting
|
||||
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 75 KiB |
Binary file not shown.
After Width: | Height: | Size: 210 KiB |
|
@ -30,11 +30,11 @@ def _plot_io(**kwargs):
|
|||
:type filename: str, optional
|
||||
:param dpi: dpi of figure to save or plot, defaults to 300
|
||||
:type dpi: int (between 50-500)
|
||||
:param showfig: whether to plt.show() the figure, defaults to True
|
||||
:param showfig: whether to plt.show() the figure, defaults to False
|
||||
:type showfig: bool, optional
|
||||
"""
|
||||
filename = kwargs.get("filename", None)
|
||||
showfig = kwargs.get("showfig", True)
|
||||
showfig = kwargs.get("showfig", False)
|
||||
dpi = kwargs.get("dpi", 300)
|
||||
|
||||
plt.tight_layout()
|
||||
|
@ -81,7 +81,7 @@ def plot_covariance(cov_matrix, plot_correlation=False, show_tickers=True, **kwa
|
|||
return ax
|
||||
|
||||
|
||||
def plot_dendrogram(hrp, show_tickers=True, **kwargs):
|
||||
def plot_dendrogram(hrp, ax=None, show_tickers=True, **kwargs):
|
||||
"""
|
||||
Plot the clusters in the form of a dendrogram.
|
||||
|
||||
|
@ -92,18 +92,19 @@ def plot_dendrogram(hrp, show_tickers=True, **kwargs):
|
|||
:type show_tickers: bool, optional
|
||||
:param filename: name of the file to save to, defaults to None (doesn't save)
|
||||
:type filename: str, optional
|
||||
:param showfig: whether to plt.show() the figure, defaults to True
|
||||
:param showfig: whether to plt.show() the figure, defaults to False
|
||||
:type showfig: bool, optional
|
||||
:return: matplotlib axis
|
||||
:rtype: matplotlib.axes object
|
||||
"""
|
||||
ax = ax or plt.gca()
|
||||
|
||||
if hrp.clusters is None:
|
||||
hrp.optimize()
|
||||
|
||||
fig, ax = plt.subplots()
|
||||
if show_tickers:
|
||||
sch.dendrogram(hrp.clusters, labels=hrp.tickers, ax=ax, orientation="top")
|
||||
plt.xticks(rotation=90)
|
||||
ax.tick_params(axis="x", rotation=90)
|
||||
plt.tight_layout()
|
||||
else:
|
||||
sch.dendrogram(hrp.clusters, no_labels=True, ax=ax)
|
||||
|
@ -113,7 +114,7 @@ def plot_dendrogram(hrp, show_tickers=True, **kwargs):
|
|||
return ax
|
||||
|
||||
|
||||
def _plot_cla(cla, points, show_assets, **kwargs):
|
||||
def _plot_cla(cla, points, ax, show_assets):
|
||||
"""
|
||||
Helper function to plot the efficient frontier from a CLA object
|
||||
"""
|
||||
|
@ -126,7 +127,6 @@ def _plot_cla(cla, points, show_assets, **kwargs):
|
|||
|
||||
mus, sigmas, _ = cla.frontier_values
|
||||
|
||||
fig, ax = plt.subplots()
|
||||
ax.plot(sigmas, mus, label="Efficient frontier")
|
||||
ax.scatter(optimal_risk, optimal_ret, marker="x", s=100, color="r", label="optimal")
|
||||
|
||||
|
@ -155,7 +155,7 @@ def _ef_default_returns_range(ef, points):
|
|||
return np.linspace(min_ret, max_ret, points)
|
||||
|
||||
|
||||
def _plot_ef(ef, ef_param, ef_param_range, show_assets):
|
||||
def _plot_ef(ef, ef_param, ef_param_range, ax, show_assets):
|
||||
"""
|
||||
Helper function to plot the efficient frontier from an EfficientFrontier object
|
||||
"""
|
||||
|
@ -183,7 +183,6 @@ def _plot_ef(ef, ef_param, ef_param_range, show_assets):
|
|||
mus.append(ret)
|
||||
sigmas.append(sigma)
|
||||
|
||||
fig, ax = plt.subplots()
|
||||
ax.plot(sigmas, mus, label="Efficient frontier")
|
||||
|
||||
if show_assets:
|
||||
|
@ -198,7 +197,13 @@ def _plot_ef(ef, ef_param, ef_param_range, show_assets):
|
|||
|
||||
|
||||
def plot_efficient_frontier(
|
||||
opt, ef_param="return", ef_param_range=None, points=100, show_assets=True, **kwargs
|
||||
opt,
|
||||
ef_param="return",
|
||||
ef_param_range=None,
|
||||
points=100,
|
||||
ax=None,
|
||||
show_assets=True,
|
||||
**kwargs
|
||||
):
|
||||
"""
|
||||
Plot the efficient frontier based on either a CLA or EfficientFrontier object.
|
||||
|
@ -218,18 +223,20 @@ def plot_efficient_frontier(
|
|||
:type show_assets: bool, optional
|
||||
:param filename: name of the file to save to, defaults to None (doesn't save)
|
||||
:type filename: str, optional
|
||||
:param showfig: whether to plt.show() the figure, defaults to True
|
||||
:param showfig: whether to plt.show() the figure, defaults to False
|
||||
:type showfig: bool, optional
|
||||
:return: matplotlib axis
|
||||
:rtype: matplotlib.axes object
|
||||
"""
|
||||
ax = ax or plt.gca()
|
||||
|
||||
if isinstance(opt, CLA):
|
||||
ax = _plot_cla(opt, points, show_assets=show_assets)
|
||||
ax = _plot_cla(opt, points, ax=ax, show_assets=show_assets)
|
||||
elif isinstance(opt, EfficientFrontier):
|
||||
if ef_param_range is None:
|
||||
ef_param_range = _ef_default_returns_range(opt, points)
|
||||
|
||||
ax = _plot_ef(opt, ef_param, ef_param_range, show_assets=show_assets)
|
||||
ax = _plot_ef(opt, ef_param, ef_param_range, ax=ax, show_assets=show_assets)
|
||||
else:
|
||||
raise NotImplementedError("Please pass EfficientFrontier or CLA object")
|
||||
|
||||
|
@ -241,22 +248,25 @@ def plot_efficient_frontier(
|
|||
return ax
|
||||
|
||||
|
||||
def plot_weights(weights, **kwargs):
|
||||
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
|
||||
:type weights: {ticker: weight} dict
|
||||
:param ax: ax to plot to, optional
|
||||
:type ax: matplotlib.axes
|
||||
:return: matplotlib axis
|
||||
:rtype: matplotlib.axes object
|
||||
:rtype: matplotlib.axes
|
||||
"""
|
||||
ax = ax or plt.gca()
|
||||
|
||||
desc = sorted(weights.items(), key=lambda x: x[1], reverse=True)
|
||||
labels = [i[0] for i in desc]
|
||||
vals = [i[1] for i in desc]
|
||||
|
||||
y_pos = np.arange(len(labels))
|
||||
|
||||
fig, ax = plt.subplots()
|
||||
ax.barh(y_pos, vals)
|
||||
ax.set_xlabel("Weight")
|
||||
ax.set_yticks(y_pos)
|
||||
|
|
|
@ -1,21 +1,27 @@
|
|||
import numpy as np
|
||||
import matplotlib
|
||||
import matplotlib.pyplot as plt
|
||||
from tests.utilities_for_tests import get_data, setup_efficient_frontier
|
||||
from pypfopt import plotting, risk_models, expected_returns
|
||||
from pypfopt import HRPOpt, CLA, EfficientFrontier
|
||||
|
||||
|
||||
def test_correlation_plot():
|
||||
plt.figure()
|
||||
df = get_data()
|
||||
|
||||
S = risk_models.CovarianceShrinkage(df).ledoit_wolf()
|
||||
ax = plotting.plot_covariance(S, showfig=False)
|
||||
assert len(ax.findobj()) == 256
|
||||
plt.clf()
|
||||
ax = plotting.plot_covariance(S, show_tickers=False, showfig=False)
|
||||
assert len(ax.findobj()) == 136
|
||||
plt.clf()
|
||||
plt.close()
|
||||
|
||||
|
||||
def test_dendrogram_plot():
|
||||
plt.figure()
|
||||
df = get_data()
|
||||
returns = df.pct_change().dropna(how="all")
|
||||
hrp = HRPOpt(returns)
|
||||
|
@ -24,13 +30,17 @@ def test_dendrogram_plot():
|
|||
ax = plotting.plot_dendrogram(hrp, showfig=False)
|
||||
assert len(ax.findobj()) == 185
|
||||
assert type(ax.findobj()[0]) == matplotlib.collections.LineCollection
|
||||
plt.clf()
|
||||
|
||||
ax = plotting.plot_dendrogram(hrp, show_tickers=False, showfig=False)
|
||||
assert len(ax.findobj()) == 65
|
||||
assert type(ax.findobj()[0]) == matplotlib.collections.LineCollection
|
||||
plt.clf()
|
||||
plt.close()
|
||||
|
||||
|
||||
def test_cla_plot():
|
||||
plt.figure()
|
||||
df = get_data()
|
||||
rets = expected_returns.mean_historical_return(df)
|
||||
S = risk_models.exp_cov(df)
|
||||
|
@ -38,14 +48,34 @@ def test_cla_plot():
|
|||
|
||||
ax = plotting.plot_efficient_frontier(cla, showfig=False)
|
||||
assert len(ax.findobj()) == 143
|
||||
plt.clf()
|
||||
|
||||
ax = plotting.plot_efficient_frontier(cla, show_assets=False, showfig=False)
|
||||
assert len(ax.findobj()) == 161
|
||||
plt.clf()
|
||||
plt.close()
|
||||
|
||||
|
||||
def test_cla_plot_ax():
|
||||
plt.figure()
|
||||
df = get_data()
|
||||
rets = expected_returns.mean_historical_return(df)
|
||||
S = risk_models.exp_cov(df)
|
||||
cla = CLA(rets, S)
|
||||
|
||||
fig, ax = plt.subplots(figsize=(12, 10))
|
||||
plotting.plot_efficient_frontier(cla, ax=ax)
|
||||
assert len(ax.findobj()) == 143
|
||||
plt.close()
|
||||
plt.close()
|
||||
|
||||
|
||||
def test_default_ef_plot():
|
||||
plt.figure()
|
||||
ef = setup_efficient_frontier()
|
||||
ax = plotting.plot_efficient_frontier(ef)
|
||||
ax = plotting.plot_efficient_frontier(ef, show_assets=True)
|
||||
assert len(ax.findobj()) == 125
|
||||
plt.clf()
|
||||
|
||||
# with constraints
|
||||
ef = setup_efficient_frontier()
|
||||
|
@ -53,18 +83,24 @@ def test_default_ef_plot():
|
|||
ef.add_constraint(lambda x: x[0] == 0.05)
|
||||
ax = plotting.plot_efficient_frontier(ef)
|
||||
assert len(ax.findobj()) == 125
|
||||
plt.clf()
|
||||
plt.close()
|
||||
|
||||
|
||||
def test_ef_plot_utility():
|
||||
plt.figure()
|
||||
ef = setup_efficient_frontier()
|
||||
delta_range = np.arange(0.001, 100, 1)
|
||||
ax = plotting.plot_efficient_frontier(
|
||||
ef, ef_param="utility", ef_param_range=delta_range, showfig=False
|
||||
)
|
||||
assert len(ax.findobj()) == 125
|
||||
plt.clf()
|
||||
plt.close()
|
||||
|
||||
|
||||
def test_ef_plot_risk():
|
||||
plt.figure()
|
||||
ef = setup_efficient_frontier()
|
||||
ef.min_volatility()
|
||||
min_risk = ef.portfolio_performance()[1]
|
||||
|
@ -75,18 +111,24 @@ def test_ef_plot_risk():
|
|||
ef, ef_param="risk", ef_param_range=risk_range, showfig=False
|
||||
)
|
||||
assert len(ax.findobj()) == 125
|
||||
plt.clf()
|
||||
plt.close()
|
||||
|
||||
|
||||
def ef_plot_return():
|
||||
plt.figure()
|
||||
ef = setup_efficient_frontier()
|
||||
return_range = np.linspace(0, ef.expected_returns.max(), 50)
|
||||
ax = plotting.plot_efficient_frontier(
|
||||
ef, ef_param="return", ef_param_range=return_range, showfig=False
|
||||
)
|
||||
assert len(ax.findobj()) == 125
|
||||
plt.clf()
|
||||
plt.close()
|
||||
|
||||
|
||||
def test_ef_plot_utility_short():
|
||||
plt.figure()
|
||||
ef = EfficientFrontier(
|
||||
*setup_efficient_frontier(data_only=True), weight_bounds=(None, None)
|
||||
)
|
||||
|
@ -95,9 +137,12 @@ def test_ef_plot_utility_short():
|
|||
ef, ef_param="utility", ef_param_range=delta_range, showfig=False
|
||||
)
|
||||
assert len(ax.findobj()) == 161
|
||||
plt.clf()
|
||||
plt.close()
|
||||
|
||||
|
||||
def test_constrained_ef_plot_utility():
|
||||
plt.figure()
|
||||
ef = setup_efficient_frontier()
|
||||
ef.add_constraint(lambda w: w[0] >= 0.2)
|
||||
ef.add_constraint(lambda w: w[2] == 0.15)
|
||||
|
@ -108,9 +153,12 @@ def test_constrained_ef_plot_utility():
|
|||
ef, ef_param="utility", ef_param_range=delta_range, showfig=False
|
||||
)
|
||||
assert len(ax.findobj()) == 125
|
||||
plt.clf()
|
||||
plt.close()
|
||||
|
||||
|
||||
def test_constrained_ef_plot_risk():
|
||||
plt.figure()
|
||||
ef = EfficientFrontier(
|
||||
*setup_efficient_frontier(data_only=True), weight_bounds=(None, None)
|
||||
)
|
||||
|
@ -125,9 +173,12 @@ def test_constrained_ef_plot_risk():
|
|||
ef, ef_param="risk", ef_param_range=risk_range, show_assets=True, showfig=False
|
||||
)
|
||||
assert len(ax.findobj()) == 137
|
||||
plt.clf()
|
||||
plt.close()
|
||||
|
||||
|
||||
def test_weight_plot():
|
||||
plt.figure()
|
||||
df = get_data()
|
||||
returns = df.pct_change().dropna(how="all")
|
||||
hrp = HRPOpt(returns)
|
||||
|
@ -135,3 +186,32 @@ def test_weight_plot():
|
|||
|
||||
ax = plotting.plot_weights(w, showfig=False)
|
||||
assert len(ax.findobj()) == 197
|
||||
plt.clf()
|
||||
plt.close()
|
||||
|
||||
|
||||
def test_weight_plot_multi():
|
||||
ef = setup_efficient_frontier()
|
||||
w1 = ef.min_volatility()
|
||||
ef = setup_efficient_frontier()
|
||||
w2 = ef.max_sharpe()
|
||||
|
||||
fig, (ax1, ax2) = plt.subplots(2)
|
||||
plotting.plot_weights(w1, ax1, showfig=False)
|
||||
plotting.plot_weights(w2, ax2, showfig=False)
|
||||
|
||||
assert len(fig.axes) == 2
|
||||
assert len(fig.axes[0].findobj()) == 209
|
||||
assert len(fig.axes[1].findobj()) == 209
|
||||
plt.close()
|
||||
|
||||
|
||||
def test_weight_plot_add_attribute():
|
||||
plt.figure()
|
||||
|
||||
ef = setup_efficient_frontier()
|
||||
w = ef.min_volatility()
|
||||
ax = plotting.plot_weights(w)
|
||||
ax.set_title("Test")
|
||||
assert len(ax.findobj()) == 209
|
||||
plt.close()
|
||||
|
|
Loading…
Reference in New Issue