From 4f2d76e3e7c4b7af9e6c80d0ed6fdc6c1de268c8 Mon Sep 17 00:00:00 2001 From: robertmartin8 Date: Tue, 14 Apr 2020 22:28:13 +0800 Subject: [PATCH] improved plotting --- pypfopt/base_optimizer.py | 4 ++-- pypfopt/cla.py | 16 ---------------- pypfopt/efficient_frontier.py | 3 ++- pypfopt/expected_returns.py | 8 ++++---- pypfopt/hierarchical_portfolio.py | 16 ---------------- pypfopt/plotting.py | 12 ++++++++++-- pypfopt/risk_models.py | 2 +- 7 files changed, 19 insertions(+), 42 deletions(-) diff --git a/pypfopt/base_optimizer.py b/pypfopt/base_optimizer.py index e821047..fa69a7c 100644 --- a/pypfopt/base_optimizer.py +++ b/pypfopt/base_optimizer.py @@ -252,8 +252,8 @@ class BaseConvexOptimizer(BaseOptimizer): w = ef.convex_objective(logarithmic_barrier, cov_matrix=ef.cov_matrix) :param custom_objective: an objective function to be MINIMISED. This should be written using - cvxpy atoms Should map (w, **kwargs) -> float. - :type custom_objective: function with signature (cp.Variable, **kwargs) -> cp.Expression + cvxpy atoms Should map (w, `**kwargs`) -> float. + :type custom_objective: function with signature (cp.Variable, `**kwargs`) -> cp.Expression :param weights_sum_to_one: whether to add the default objective, defaults to True :type weights_sum_to_one: bool, optional :raises OptimizationError: if the objective is nonconvex or constraints nonlinear. diff --git a/pypfopt/cla.py b/pypfopt/cla.py index 5166290..1f14d0a 100644 --- a/pypfopt/cla.py +++ b/pypfopt/cla.py @@ -43,7 +43,6 @@ class CLA(base_optimizer.BaseOptimizer): - ``max_sharpe()`` optimises for maximal Sharpe ratio (a.k.a the tangency portfolio) - ``min_volatility()`` optimises for minimum volatility - ``efficient_frontier()`` computes the entire efficient frontier - - ``plot_efficient_frontier()`` to plot the efficient frontier. - ``portfolio_performance()`` calculates the expected return, volatility and Sharpe ratio for the optimised portfolio. - ``clean_weights()`` rounds the weights and clips near-zeros. @@ -445,21 +444,6 @@ class CLA(base_optimizer.BaseOptimizer): def plot_efficient_frontier( self, points=100, show_assets=True, filename=None, showfig=True ): - """ - Plot the efficient frontier - - :param points: number of points to plot, defaults to 100 - :type points: int, optional - :param show_assets: whether we should plot the asset risks/returns also, defaults to True - :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 - :type showfig: bool, optional - :raises ImportError: if matplotlib is not installed - :return: matplotlib axis - :rtype: matplotlib.axes object - """ try: import matplotlib.pyplot as plt except (ModuleNotFoundError, ImportError): diff --git a/pypfopt/efficient_frontier.py b/pypfopt/efficient_frontier.py index f351832..18e8dc7 100644 --- a/pypfopt/efficient_frontier.py +++ b/pypfopt/efficient_frontier.py @@ -58,7 +58,8 @@ class EfficientFrontier(base_optimizer.BaseConvexOptimizer): :param expected_returns: expected returns for each asset. Can be None if optimising for volatility only (but not recommended). :type expected_returns: pd.Series, list, np.ndarray - :param cov_matrix: covariance of returns for each asset + :param cov_matrix: covariance of returns for each asset. This **must** be + positive semidefinite, otherwise optimisation 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) diff --git a/pypfopt/expected_returns.py b/pypfopt/expected_returns.py index 1e7e718..4b5b9fe 100644 --- a/pypfopt/expected_returns.py +++ b/pypfopt/expected_returns.py @@ -82,7 +82,7 @@ def return_model(prices, method="mean_historical_return", **kwargs): - ``mean_historical_return`` - ``ema_historical_return`` - ``james_stein_shrinkage`` - - ``capm_returns`` + - ``capm_return`` :type method: str, optional :raises NotImplementedError: if the supplied method is not recognised @@ -95,8 +95,8 @@ def return_model(prices, method="mean_historical_return", **kwargs): return ema_historical_return(prices, **kwargs) elif method == "james_stein_shrinkage": return james_stein_shrinkage(prices, **kwargs) - elif method == "capm_returns": - return capm_returns(prices, **kwargs) + elif method == "capm_return": + return capm_return(prices, **kwargs) else: raise NotImplementedError("Return model {} not implemented".format(method)) @@ -214,7 +214,7 @@ def james_stein_shrinkage(prices, returns_data=False, compounding=False, frequen return theta_js * frequency -def capm_returns( +def capm_return( prices, market_prices=None, returns_data=False, diff --git a/pypfopt/hierarchical_portfolio.py b/pypfopt/hierarchical_portfolio.py index 79c527f..3e17e48 100644 --- a/pypfopt/hierarchical_portfolio.py +++ b/pypfopt/hierarchical_portfolio.py @@ -42,7 +42,6 @@ class HRPOpt(base_optimizer.BaseOptimizer): Public methods: - ``optimize()`` calculates weights using HRP - - ``plot_dendrogram()`` plots the clusters - ``portfolio_performance()`` calculates the expected return, volatility and Sharpe ratio for the optimised portfolio. - ``set_weights()`` creates self.weights (np.ndarray) from a weights dict @@ -169,21 +168,6 @@ class HRPOpt(base_optimizer.BaseOptimizer): return weights def plot_dendrogram(self, show_tickers=True, filename=None, showfig=True): - """ - Plot the clusters in the form of a dendrogram. - - :param show_tickers: whether to use tickers as labels (not recommended for large portfolios), - defaults to True - :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 - :type showfig: bool, optional - :raises ImportError: if matplotlib is not installed - :return: matplotlib axis - :rtype: matplotlib.axes object - """ - try: import matplotlib.pyplot as plt except (ModuleNotFoundError, ImportError): diff --git a/pypfopt/plotting.py b/pypfopt/plotting.py index 18ae132..e89b95b 100644 --- a/pypfopt/plotting.py +++ b/pypfopt/plotting.py @@ -1,5 +1,5 @@ """ -The ``cla`` module houses all the functions to generate various plots. +The ``plotting`` module houses all the functions to generate various plots. Currently implemented: @@ -111,7 +111,7 @@ class Plotting: @staticmethod def plot_efficient_frontier(cla, points=100, show_assets=True, **kwargs): """ - Plot the efficient frontier based on a cla object + Plot the efficient frontier based on a CLA object :param points: number of points to plot, defaults to 100 :type points: int, optional @@ -157,6 +157,14 @@ class Plotting: @staticmethod def plot_weights(weights, **kwargs): + """ + Plot the portfolio weights as a horizontal bar chart + + :param weights: the weights outputted by any PyPortfolioOpt optimiser + :type weights: {ticker: weight} dict + :return: matplotlib axis + :rtype: matplotlib.axes object + """ 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] diff --git a/pypfopt/risk_models.py b/pypfopt/risk_models.py index 21fc19c..0874998 100644 --- a/pypfopt/risk_models.py +++ b/pypfopt/risk_models.py @@ -596,7 +596,7 @@ def correlation_plot(cov_matrix, show_tickers=True, filename=None, showfig=True) warnings.warn( "This method is deprecated and will be removed in v1.2.0. " - "Please use pypfopt.plotting instead" + "Please use Plotting.plot_covariance(cov) instead" ) corr = cov_to_corr(cov_matrix)